NicholasTao / NicholasTao.github.io

✨ Build a beautiful and simple website in literally minutes. Demo at https://beautifuljekyll.com
https://beautifuljekyll.com
MIT License
0 stars 0 forks source link

模块设计 #8

Open NicholasTao opened 2 years ago

NicholasTao commented 2 years ago

主要改动:升级粒度缩小至instance,task系统,数据整理

次要改动:升级框架重新部署,脚本易用性,统一数据接口,简化回退,简化upg-server数据处理,模块解耦

概况:继承现有流程,去除历史问题,开放未来接口

目标:

​ 大规模性能提高5倍

​ 现网升级Bug减少至1/2

​ 升级框架新需求开发工作量减少至1/3

​ 升级时长减少

​ 版本交付周期缩短,其它组件升级开发测试时间减少,开发单次测试从2天->2小时

数据接口

升级框架内部调用Biz类, Biz类调用Api类, Api类调用cps接口

实现升级框架内部逻辑与外部Api解耦

统一基类BaseApi实现底层网络处理、安全检查

# BaseApi -> CpsBaseApi -> CpsHostInfoApi
class CpsHostInfoApi(CpsBaseApi):
    URL = "/v1/hosts_info"
    def GET(json_body): pass
    def POST(json_body): pass

class CpsBiz(object):
    def get_host_info(uuid):
        return CpsHostInfo.GET({"uuid": uuid})

前后脚本

脚本涉及到各域适配,将在后期实现。先期将适配旧脚本方案。

注册脚本

注册方式更灵活直观

可注册阶段

任何task前后:client端某组件分发、关闭、启动,server

注册方法

升级包中特定文件夹内继承基类BaseScript即可注册

class BackupDBScript(BaseScript):
    @client_run_when("before", "gauss", "stop") # host_filter=filter_func
    def backup(): pass
    @server_run_when("after", "gauss", "commit")
    def count_backup_size(): pass

运行脚本

S/C运行工步前后时会查看是否存在对应脚本,如果有就会自动执行。

脚本重试

新升级框架中任何task可以单独回退重试,所以脚本也可以反复测试。

脚本测试

root权限可以在server和client端,创建和修改脚本,并在注册记录内手动注册,即可执行。

新升级框架中任何task可以单独回退重试,所以脚本也可以反复测试。

可以灵活测试和添加现网自动化规避方案

Task系统

task是升级最小单元,client侧一般是对某个instance的操作(如A节点Nova的关闭)

task可以重试回退

server侧task较为简单,下面主要说明client侧task

主要task为组件检查、启停、安装rpm、安装hostos rpm,清理(commit)

Client生成Task

client在信息收集和分发后,本地已有所有升级相关数据。所以了解本地所有template情况,可以生成本地升级task。

可以通过client端命令行、调用client api等执行相应task。升级

所有工步执行, 自底向上都是一致的,可以多种方法查询和执行,结果都一致

多个task执行:可传参"parallel"或"serial"指定

{
    "method": "serial",
    "subs": [J1, J2]
}
# 这个文档

升级编排生成计划书

​ 编排逻辑 -> 编排器 -> 计划书 <- 执行器

[task1, task2, task3...], <-已完成

task_x2, <- 执行中

task_x3

升级收集信息和分发后,server测已获得所有升级数据。此时可以生成升级计划书(如上图)。所有task和相关执行顺序都记录在计划书中。后续upg-server仅通过计划书顺序执行

​ 计划书细节

  1. 滚动执行、热迁移等小部分特殊的“并行”工步会在计划书中特别标明,在执行时动态生成详细排序。

task属性

每一条task在server侧和client都有记录,client侧保存在本地sqlite,server侧保存在gaussdb。通过client侧上报保持一致。少数server未成功client成功的情况,server可以再次下发任务。

Server执行计划书

server需要实现以下功能:

1. 根据接收的命令(如进行管理面升级)
2. 从计划书截取当前计划书(如进行管理面升级部分)
3. 从数据库读取当前计划书各个task当前执行情况
4. 从下一个待执行或重试task(s)开始执行,client端仅异步下发,server端独立线程执行
5. 定期监控task执行情况(task执行情况会由client端或server端工作线程上报,由承接保存在gaussdb)

新升级编排优点

  1. 通过计划书解耦复杂动态的升级逻辑和静态的执行操作,便于代码开发维护
  2. 便于测试和规避,可以手动生成和修改计划书,对升级进行管理
  3. 各局点可以采用不同的编排器, 自选功能(网络紧急回退、滚动、多版本等)
  4. 未来可以将升级后调测、升级检查等无关联都纳入升级编排,提高升级工步的并行度,缩短升级时间。

升级工步

升级工步继承了原有工步,主要改动是

  1. 更详尽的信息收集
  2. 升级框架重新部署

升级框架部署

升级框架不再常驻在环境中,仅在升级前由FCCI通过cps部署

信息收集

client测自主调用cps接口、读取本地文件等方式收集节点信息并缓存。如有信息变动(主要是template变更),需要手动触发再次收集。一切可以在升级前收集的信息都需要在此时收集。

分发

//类似,与旧分发不同的是,新分发不再分template进行分发

检查

未来会提供SDK方便各组件开发。

管理面升级

未来会梳理进件Batch(管理面组件启停批次),缩短启停时间

提交(清理环境)

新增升级框架自身清理和本次升级数据收集。

Server结构

线程

  1. 执行器:下发和监控task执行,唯一。监控采用增量监控,已成功\失败的不再监控,减少计算量。
  2. task承接上报:接收task执行结果和数据,并发(未来可分布到多upg-server)
  3. 下发:并发下发到client,超大规模下有瓶颈
  4. 查询接口:从server本地数据中组织信息返回(未来可分布到多upg-server)

模块

  1. 逻辑书:每个环境每个环境可以定制编排逻辑
  2. 编排器:由逻辑书和编排器生成计划书
  3. 执行器:根据接收的命令截取计划书,生成当前计划书,执行task下发和监控
  4. 数据库:主要使用gaussdb,小部分本地文件保存。动态数据仅task状态。
  5. API handle:
  6. DAO: 各外部组件数据接口,zookeeper、swift、cps...

Client结构

升级工步逻辑:分发、执行...所有升级书据在client本地

模块

  1. 执行器:根据接收的计划书(server下发,命令行下发)截取计划书,执行task并上报结果
  2. 数据库:主要使用sqlite,小部分本地文件保存。动态数据仅task状态。
  3. API handle:
  4. DAO: 各外部组件数据接口,zookeeper、swift、cps...

数据

升级是数据和状态的管理。

静态数据

升级前环境信息,升级后环境信息,这两者都可以在升级前信息收集阶段收集(后者可以通过前者推导得出)

由于是静态信息可以在数据库、S\C本地保存多个副本,方便升级框架使用

动态数据

task状态:task状态以执行端(一般是client)为准,success fail均上报server,server再保存到gaussdb。超时或通讯中断情况下,server会记录为相应fail。fail工步可以重试。即使特殊情况下,已success节点被错误标记为fail,导致重新下发task,执行端也可以区分,不执行并立即上报success

热迁移分组、当前计划书等: 这部分数据只能在升级中生成,异常情况(如fail或节点)可以随时再次生成,即使与原来不一致也不影响升级。因为升级底层是task的执行。

开发测试

单步反复回退升级

仅必要参数,其余参数client会从本地信息自己收集

可以脱离server进行升级

FCCI -> (updatetool) -> upg-server -> upg-client 行为保持一致,避免端到端测试发现的问题

新特性:提供实时脚本功能,能在OpenStack发布后对代码进行修改,修复bug补丁

验收

Client端

client是相对独立开发的,按照信息收集、分发、执行。。。顺序开发。测试时也针对单个工步进行反复测试(用回退task功能)。初期可以在单节点上用某个template测试;中期可以用client测编排功能生成client侧计划书,实现一键式节点升级;后期与server侧联调。

编排

信息收集和分发后,可以执行编排,生成json格式的计划书。可以通过各个升级场景的手动测试用例进行测试。

与旧升级框架一起测试

复用旧升级框架的升级后调测等功能进行验证

回退

鉴于回退功能现网使用不多,所以提供宏观全局一键回退和微观task回退功能,这样可以覆盖绝大部分现网情况。

其余回退情况可以在开发指导下通过组合微观task回退功能实现

NicholasTao commented 2 years ago

网络升级 中间 调测 灵活 编排

cps适配 配置变更

instance task 个数多

job task

可视化

进度慢解释

大规模解释

编排demo

升级中扩容

NicholasTao commented 2 years ago

网络灵活 思考:常用特殊编排,通过脚本化实现。 new 大阶段结束后,遍历task状态查看是否完成。 new client上传?? instance多job: server一个job对应多个task状态,如batch1启动对应client多个组件启停。无法同时满足精细显示和少数据??少数据生成多精细需要负责逻辑。压缩:同样流程节点压缩,成功状态压缩。 升级中扩容:针对未来升级中扩容,是一个临时状态,通过

可视化 进度慢解释 大规模解释 编排demo

NicholasTao commented 2 years ago

下周新升级框架分工,7/19将开会共同整理讨论输出的分析结果 @所有人 

  1. 线上升级能力分析 付炫丞 7/14 针对大规模性能(Server逻辑重、数据读取通讯慢、Swift\Zookeeper瓶颈)、开发测试困难(instance task细粒度无法回退重试)等问题,了解比较线上解决方案

  2. 已有升级特性梳理 郭家发 7/14 梳理升级框架现有所有特性,各种场景、升级回退、忽略等

  3. 现网需求和解决方案 余德元 7/19 梳理不受已有升级框架限制,现网需要改进或新增的需求

  4. 新升级框架设计 陶羽翔 7/19 如何解决现有问题 如何对外兼容旧框架 task系统针对具体业务(管理面执行)如何运行

  5. 升级框架已有问题和解决方案 ALL 7/19

NicholasTao commented 2 years ago
class Mission(object):
    @classmethod
    def json2obj(t4):
        self._t4 = [
            [
                Job.json2obj(t2)
                for t2 in t3
            ] 
            for t3 in t4
        ]

class Job(object):
    @classmethod
    def json2obj(t2):
        self._t2 = [
            [
                Task.str2obj(t)
                for t in t1
            ] 
            for t1 in t2
        ]
        self.target = self._t2[0][0].target

class Task(object):
    tid
    target
    action
    pass
NicholasTao commented 2 years ago

为什么做新框架?

新框架目标

  1. 大规模性能提高至10k以上 原因: 优化了升级框架内部瓶颈点
  2. 现网Bug减少至1/2 原因: 代码可信整改
  3. 升级框架新需求开发工作量减少至1/3 原因: 代码架构优化
  4. 周边组件测试时间缩短至1/4 原因: 新特性,细粒度上下文无关的升级回退

The Header

[[Link to Header](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#the-header)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#the-header)

新增的特性

  1. 灵活升级 细粒度任务独立回退升级, 任意小工步升级失败后可以独立回退。并重试或添加规避代码后重试

    单工步调试:开发过程中可以单工步调试,且无需依赖前后工步。如单独调试升级配置转换(这是一线人员重点要求添加的功能)

    详细设计: [[灵活升级特性设计](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E7%81%B5%E6%B4%BB%E5%8D%87%E7%BA%A7%E7%89%B9%E6%80%A7%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E7%81%B5%E6%B4%BB%E5%8D%87%E7%BA%A7%E7%89%B9%E6%80%A7%E8%AE%BE%E8%AE%A1)

    旧框架实现对比: [[[旧框架设计]](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E6%97%A7%E6%A1%86%E6%9E%B6%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E6%97%A7%E6%A1%86%E6%9E%B6%E8%AE%BE%E8%AE%A1)

    影响: 开发中的测试效率, 生产中的规避效率

  2. 并行升级 工程一升级AB组件的同时,工程二可以并行升级无依赖的CD组件

    详细设计: [[并行升级特性设计](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%B9%B6%E8%A1%8C%E5%8D%87%E7%BA%A7%E7%89%B9%E6%80%A7%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%B9%B6%E8%A1%8C%E5%8D%87%E7%BA%A7%E7%89%B9%E6%80%A7%E8%AE%BE%E8%AE%A1)

    旧框架实现对比: [[[旧框架设计]](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E6%97%A7%E6%A1%86%E6%9E%B6%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E6%97%A7%E6%A1%86%E6%9E%B6%E8%AE%BE%E8%AE%A1)

    影响: 升级效率,升级灵活性

  3. 多版本升级 支持一个版本的组件升级到不同版本,如控制节点升级OS,计算节点不升级OS。控制节点升级到X版本Nova,计算节点升级到Y版本Nova

    [[多版本升级特性设计]](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%A4%9A%E7%89%88%E6%9C%AC%E5%8D%87%E7%BA%A7%E7%89%B9%E6%80%A7%E8%AE%BE%E8%AE%A1)

  4. N-X升级 支持N-X升级的升级框架支持 a) 提供管理面备区升级能力,管理组件随hostos在备区升级后重启生效;同时保留管理面重启单独组件升级方式

  5. 可视化升级(未来特性) 升级前可以生成整个升级计划书,供用户和技术人员检视,并可以根据需要灵活修改。升级过程不再是盲盒

改善的特性

  1. 并行化 编排、任务下发、任务上报接收、任务执行、查询进度都可以用独立进程执行,其中任务下发、任务上报接收、任务执行、查询进度各自可以用并发多进程

  2. 大规模 可以支持10k节点升级,升级框架本身不再是性能瓶颈。提供相关性能分析接口,在开发过程中就能自动分析各升级组件和外部接口性能。

  3. 升级脚本易用性提升

统一升级脚本,提供可读的升级SDK(整理常用接口, 隔离内外接口,减少参数),提高开发效率。

同时提供升级脚本单独调试功能。

这是各组件升级开发人员和升级接口人重点要求改善的特性。

详细设计见: [[升级脚本特性设计]](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%8D%87%E7%BA%A7%E8%84%9A%E6%9C%AC%E7%89%B9%E6%80%A7%E8%AE%BE%E8%AE%A1)

主要改动点

  1. 统一数据收集
  2. 细粒度升级
  3. 解耦

对"一跳升级"和"可控升级"的意义

todo

新框架对比

详细见下文“新旧框架对比”章节

好的架构逻辑清晰,坏的架构一言难尽

本文可以详细描述新框架的架构逻辑。但是无法详细说明旧框架的架构逻辑,原因就是旧框架是混乱的无法简单梳理清楚的。

既然旧框架无法说明清楚,如何保证新框架和旧框架的一致?

升级框架是一个服务,只要对外接口和对外特性保持一致即可。内部实现的变更是不需要一一对应验证的。

新框架设计原理

  1. 升级工程是由多个小任务组成的。
  2. 升级就是单个任务的执行内容和多个任务的执行顺序组成。
  3. 升级是简单的。(代码量15k,核心逻辑"启停、升级rpm")

新框架是怎么样的?

模块交互

升级模块详细流程图

  1. 升级前编排生成全局计划书,包含了所有升级任务的内容、执行顺序和串并行信息。
  2. 升级时根据外部升级命令截取相应的升级任务生成当前计划书。
  3. 通过(upg-server)任务下发器下发当前计划书给对应的(upg-client)任务执行器。
  4. 任务执行器根据任务内容调用相应任务脚本。
  5. 任务脚本执行完成后,任务执行器上报任务给任务状态接收器,接收器固化结果在数据库中,下发器监控到任务完成后继续执行下一批任务。
  6. 循环3-5直到当前计划书中全部任务完成。

详细见下文[[[模块设计]](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E6%A8%A1%E5%9D%97%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E6%A8%A1%E5%9D%97%E8%AE%BE%E8%AE%A1)章节

与旧框架对比见下文[旧框架图]

升级顺序

与旧框架相同, 按照以下顺序

分发 -> 检查 -> 管理面升级 -> 网络升级 -> 数据面升级-> 提交

这部分逻辑写在新框架的[[[逻辑书]](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%20%E9%80%BB%E8%BE%91%E4%B9%A6%E8%AF%A6%E7%BB%86%E8%AE%BE%E8%AE%A1)](# 逻辑书详细设计)中, 未来可以通过改变逻辑书来修改升级顺序。

对外接口/升级脚本

与旧框架保持相同

新框架如何完成升级任务?

整体流程

[信息收集 -> 依次]

Note新增模块

端到端流程

以最典型的升级大任务--管理面升级为例

image-20221101163005340

  1. 接收外部升级命令"管理面升级执行"

  2. 根据命令[[[任务截取器]](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E4%BB%BB%E5%8A%A1%E6%88%AA%E5%8F%96%E5%99%A8%E8%AF%A6%E7%BB%86%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E4%BB%BB%E5%8A%A1%E6%88%AA%E5%8F%96%E5%99%A8%E8%AF%A6%E7%BB%86%E8%AE%BE%E8%AE%A1)[[[全局计划书]](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%85%A8%E5%B1%80%E8%AE%A1%E5%88%92%E4%B9%A6%E8%AF%A6%E7%BB%86%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%85%A8%E5%B1%80%E8%AE%A1%E5%88%92%E4%B9%A6%E8%AF%A6%E7%BB%86%E8%AE%BE%E8%AE%A1)截取[[[当前计划书]](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%BD%93%E5%89%8D%E8%AE%A1%E5%88%92%E4%B9%A6%E8%AF%A6%E7%BB%86%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%BD%93%E5%89%8D%E8%AE%A1%E5%88%92%E4%B9%A6%E8%AF%A6%E7%BB%86%E8%AE%BE%E8%AE%A1),此时当前计划书的内容为

    [
    [节点A关闭第1批组件, 节点B关闭第1批组件, ...],
    [节点A关闭第2批组件, 节点B关闭第2批组件, ...],
    ...
    [组件X修改配置, 组件Y修改配置, ...],
    [节点A升级(XY...)组件rpm, 节点B升级(YZ...)组件rpm...]
    [节点A开启第1批组件, 节点B开启第1批组件, ...],
    [节点A开启第2批组件, 节点B开启第2批组件, ...],
    ...
    ]
    # 注意到这里是2维数组, 内层数组表示里面的任务是同一批并行执行的,外部数组表示每批任务是串行执行的
  3. 任务下发器下发批节点任务。首先下发的是"关闭第1批组件"

    [节点A关闭第1批组件, 节点B关闭第1批组件, ...]

    既将"节点A关闭第1批组件"下发给节点A,"节点B关闭第1批组件"下发给节点B...

  4. 任务执行器接收下发的节点任务。以节点A为例,此时节点任务"节点A关闭第1批组件"的内容是

    [
    [节点A关闭nova-api, 节点A关闭swift-ngnix, ...]
    [节点A关闭nova-console, ... ]
    ...
    ]
    # 注意到这里是2维数组, 内层数组表示里面的任务是同一批并行执行的,外部数组表示每批任务是串行执行的
  5. 任务执行器会按照顺序和串并行情况依次调用相应的任务脚本。

    # 如节点A关闭nova-api任务
    调用stop_nova_api.py脚本
  6. 任务执行器完成所有任务脚本后,上报任务执行结果给任务状态执行器。

    # 全部成功时的上报
    {
    节点A关闭nova-api: done,
    节点A关闭swift-ngnix: done,
    节点A关闭nova-console: done,
    ...
    }
    
    # 部分失败时的上报
    {
    节点A关闭nova-api: done,
    节点A关闭swift-ngnix: fail,
    节点A关闭nova-console: undo,
    ...
    }
    # 节点A关闭nova-console: undo表示这个任务还未执行时,前置任务已经失败了
  7. 任务状态执行器接受上报后,直接将消息保存在数据库中

    此处是旧框架的一个性能瓶颈,主要是保存前后做了很多数据处理

    新框架在此处只有保存动作,可以提升性能

  8. 工步3下发任务后,任务下发器会定期查询数据库,是否所有下发任务已经完成。当查询到所有任务执行成功后,则继续下发下一批节点任务(存在失败则中断升级)。重复工步3-8,直到当前计划书中所有任务执行完成。

查询: 升级过程中前端需要查询升级进度时,查询器会直接从数据库查询,从而与升级执行逻辑解耦。详细见[查询器详细设计]

新旧框架对比

详细设计

特性设计

灵活升级特性设计

升级工程是由一个个小粒度的任务按照顺序组成的。只要做到任意单任务的升级回退,就可以完成任意组合的升级回退。

场景二: A节点升级后调测失败

    旧框架:

场景一:

现象:现网升级中,某个组件在某个节点数据割接出现了问题,导致组件启动后异常。需要先恢复环境,再规避继续升级。

旧框架: 

    方法1: 整体回退,整个环境回退到升级前。再修改配置转换逻辑代码,再次从头走升级后逻辑。时间太长,现网基本不会采用。

    预期规避时间: 10小时以上

    方法2: 人工处理割接后的数据,将数据逐一改成正确数据。规避时间长,人工操作易出错。

    预期规避时间: 30分钟                 

新框架:

    单独回退该组件该节点的数据割接任务。修改配置转换逻辑代码。重试数据割接任务。

    由于只需要回退重试单个小任务,规避时间缩短。

    第一次修改配置转换逻辑代码时间30分钟,规避时间5分钟

    以后

并行升级特性设计

升级(或回退)过程中,随时可以添加新组件,进行并行升级。

这里框架只提供底层支持,具体是否可以并行还要看任务或组件之间的依赖关系。

新框架中,升级的数据分为静态和动态数据两种。静态数据如升级信息、部署信息,这部分信息是升级前收集的,升级中保持不变,所以没有并发问题。动态数据是任务状态,由于划分了细粒度任务,新添加组件的升级任务与旧组件的升级任务是分开的所以互不影响。

todo 图:A工程,B工程共享数据,分别状态

应用场景:

​ A组件升级过程中,创建新工程升级B组件。参考公有云并行升级。

多版本升级特性设计

新框架的任务的粒度是基于instance的,所以每个instance都可以指定是否升级,升级的目标版本是什么。

如下文场景1,实现方法

  1. 升级前通过外部命令获取需要升级hostos的计算节点列表,称为待升节点。
  2. 升级编排第一步(即生成所有升级任务, 详见[编排详细设计])时,只生成待升节点的hostos升级任务。
  3. 正常编排。此时生成的全局任务书里,已经没有了其它节点的hostos升级任务。
  4. 按照全局计划书,执行升级工程。只升级待升节点的hostos升级任务。

应用场景:

  1. 控制节点和计算节点的hostos版本是r8,用户想将某几个计算节点升级到r9,其它节点保留在r8以节省升级时间

  2. 同一组件两个版本归一

  3. 同一组件一个版本分化为两个

N-X升级特性设计

支持N-X升级的升级框架支持

提供管理面备区升级能力

管理组件随hostos在备区升级后重启生效;同时保留管理面重启单独组件升级方式

其它支持

待补充

大规模特性设计

模块设计

旧框架设计

升级逻辑应该是简单的,实现是过分复杂的。

工程进度

详细设计 已完成(2周)

新升级框架整体架构、模块划分、核心模块交互、主要数据结构等设计 模块开发顺序、测试方案等设计

设计书评审 进行中(6周)

设计书内部评审

模块分工

按模块分工给具体开发人员,主要有以下四个模块

  1. 升级脚本(执行具体升级任务: 下载rpm, 启停组件, 升级rpm等)
  2. server任务系统开发(升级任务的顺序编排和下发)
  3. client任务执行器开发(接收server下发的命令, 调用相关组件)
  4. 外部数据接口(对cps\nova\swift\zookeeper\gaussdb), 内部数据接口(server/client通信)

分模块设计 (1周)

与各个模块开发人员对齐模块设计方案和接口

工程开发 (15人月)

四大模块并行开发 -> 联调 -> 对接ci\端到端环境验证

FAQ

新框架能否完成升级任务?

为什么重写不是重构?

旧框架有什么问题?

代码问题

架构问题

附录

主要数据结构

任务(Task)

最小的原子化升级任务,一般是指对某个instance进行某个操作。如“节点1的nova-api升级rpm包”

构成元素是host_target_action:

  1. host:

    一般指执行的节点, 如“host1”。全局任务下host为特殊节点“upg-server”。

  2. target:

    任务对象,一般为某个template,如“nova-api”。

  3. action:

    任务动作,如“stop”,“up-rpm”,“start”。

    上文“节点1的nova-api升级rpm包”可表示为“host1_nova-api_up-rpm”

    action是原子化细粒度不可分割的。

    旧框架:

    任务是粗粒度、非原子性的。如“升级执行”任务,包括了多批组件的启停、rpm升级、修改配置等多个小任务。

    分批关闭组件 -> 修改配置 -> rpm升级 -> 分批开启组件

    假如rpm升级工步失败,

    1. 系统只显示“升级执行失败”,需要排除日志才能定位出错点

    2. 重试时,需要先重试分批关闭组件、修改配置两个前置工步,才能重试rpm升级

    3. 重试时,pkg内组件无法区分。例如控制节点FusionPlatFrom包内20个组件, 只有一个组件失败了,必须20个组件一起重试

    新框架:

    任务是细粒度、原子性的。任务要么全部成功,要么全部失败。

    假如rpm升级工步失败,

       1.  系统直接显示“A节点B组件rpm升级失败”
                   2.  重试时,可以直接重试“A节点B组件rpm升级失败”,无需冗余重试前置工步和相关组件

    原子性的其它优点这里不赘述。

节点任务(Job)

某个节点一次执行的任务。结构上是Task的2维数组。Job描述了子任务内容、执行顺序和串并行情况。

为了性能考虑,upg-server会对一个upg-client批量下发任务。

"""节点任务Job数据结构示例"""
[
    [h1_stop_nova-api, h1_stop_swift-store],
    [h1_uprpm_nova-api, h1_uprpm_swift-store],
    [h1_start_nova-api, h1_start_swift-store]
]

如上图,节点h1升级nova-api, swift-store共有6个任务。需要先并行执行关闭2个组件,完成后再并行升级2个组件rpm,完成后再并行打开2个组件。

计划书(Mission)

某个节点一次执行的任务。结构上是Task的4维数组, Job的2维数组。Mission描述了子Job内容、执行顺序和串并行情况。

"""节点任务Job数据结构示例"""
[
    [h1_execute_manage_job, h2_execute_manage_job],
    [h1_execute_hostos, h2_execute_hostos],
    [h1_effect_hostos, h2_effect_hostos]
]

全局计划书

所有包含升级所有Task的内容和执行顺序

当前计划书

根据外部命令从全局计划书截取的,当前需要执行的Task的内容和执行顺序

NicholasTao commented 2 years ago

为什么做新框架?

新框架目标

  1. 大规模性能提高至10k以上 原因: 优化了升级框架内部瓶颈点
  2. 现网Bug减少至1/2 原因: 代码可信整改
  3. 升级框架新需求开发工作量减少至1/3 原因: 代码架构优化
  4. 周边组件测试时间缩短至1/4 原因: 新特性,细粒度上下文无关的升级回退

The Header

[[[Link to Header](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#the-header)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#the-header)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#the-header)

新增的特性

  1. 灵活升级 细粒度任务独立回退升级, 任意小工步升级失败后可以独立回退。并重试或添加规避代码后重试

    单工步调试:开发过程中可以单工步调试,且无需依赖前后工步。如单独调试升级配置转换(这是一线人员重点要求添加的功能)

    详细设计: [[灵活升级特性设计]](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E7%81%B5%E6%B4%BB%E5%8D%87%E7%BA%A7%E7%89%B9%E6%80%A7%E8%AE%BE%E8%AE%A1)

    旧框架实现对比: [[旧框架设计]](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E6%97%A7%E6%A1%86%E6%9E%B6%E8%AE%BE%E8%AE%A1)

    影响: 开发中的测试效率, 生产中的规避效率

  2. 并行升级 工程一升级AB组件的同时,工程二可以并行升级无依赖的CD组件

    详细设计: [[[并行升级特性设计](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%B9%B6%E8%A1%8C%E5%8D%87%E7%BA%A7%E7%89%B9%E6%80%A7%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%B9%B6%E8%A1%8C%E5%8D%87%E7%BA%A7%E7%89%B9%E6%80%A7%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%B9%B6%E8%A1%8C%E5%8D%87%E7%BA%A7%E7%89%B9%E6%80%A7%E8%AE%BE%E8%AE%A1)

    旧框架实现对比: [[[[旧框架设计]](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E6%97%A7%E6%A1%86%E6%9E%B6%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E6%97%A7%E6%A1%86%E6%9E%B6%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E6%97%A7%E6%A1%86%E6%9E%B6%E8%AE%BE%E8%AE%A1)

    影响: 升级效率,升级灵活性

  3. 多版本升级 支持一个版本的组件升级到不同版本,如控制节点升级OS,计算节点不升级OS。控制节点升级到X版本Nova,计算节点升级到Y版本Nova

    [[[多版本升级特性设计]](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%A4%9A%E7%89%88%E6%9C%AC%E5%8D%87%E7%BA%A7%E7%89%B9%E6%80%A7%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%A4%9A%E7%89%88%E6%9C%AC%E5%8D%87%E7%BA%A7%E7%89%B9%E6%80%A7%E8%AE%BE%E8%AE%A1)

  4. N-X升级 支持N-X升级的升级框架支持 a) 提供管理面备区升级能力,管理组件随hostos在备区升级后重启生效;同时保留管理面重启单独组件升级方式

  5. 可视化升级(未来特性) 升级前可以生成整个升级计划书,供用户和技术人员检视,并可以根据需要灵活修改。升级过程不再是盲盒

改善的特性

  1. 并行化 编排、任务下发、任务上报接收、任务执行、查询进度都可以用独立进程执行,其中任务下发、任务上报接收、任务执行、查询进度各自可以用并发多进程

  2. 大规模 可以支持10k节点升级,升级框架本身不再是性能瓶颈。提供相关性能分析接口,在开发过程中就能自动分析各升级组件和外部接口性能。

  3. 升级脚本易用性提升

统一升级脚本,提供可读的升级SDK(整理常用接口, 隔离内外接口,减少参数),提高开发效率。

同时提供升级脚本单独调试功能。

这是各组件升级开发人员和升级接口人重点要求改善的特性。

详细设计见: [[[升级脚本特性设计]](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%8D%87%E7%BA%A7%E8%84%9A%E6%9C%AC%E7%89%B9%E6%80%A7%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%8D%87%E7%BA%A7%E8%84%9A%E6%9C%AC%E7%89%B9%E6%80%A7%E8%AE%BE%E8%AE%A1)

主要改动点

  1. 统一数据收集
  2. 细粒度升级
  3. 解耦

对"一跳升级"和"可控升级"的意义

todo

新框架对比

详细见下文“新旧框架对比”章节

好的架构逻辑清晰,坏的架构一言难尽

本文可以详细描述新框架的架构逻辑。但是无法详细说明旧框架的架构逻辑,原因就是旧框架是混乱的无法简单梳理清楚的。

既然旧框架无法说明清楚,如何保证新框架和旧框架的一致?

升级框架是一个服务,只要对外接口和对外特性保持一致即可。内部实现的变更是不需要一一对应验证的。

新框架设计原理

  1. 升级工程是由多个小任务组成的。
  2. 升级就是单个任务的执行内容和多个任务的执行顺序组成。
  3. 升级是简单的。(代码量15k,核心逻辑"启停、升级rpm")

新框架是怎么样的?

模块交互

升级模块详细流程图

  1. 升级前编排生成全局计划书,包含了所有升级任务的内容、执行顺序和串并行信息。
  2. 升级时根据外部升级命令截取相应的升级任务生成当前计划书。
  3. 通过(upg-server)任务下发器下发当前计划书给对应的(upg-client)任务执行器。
  4. 任务执行器根据任务内容调用相应任务脚本。
  5. 任务脚本执行完成后,任务执行器上报任务给任务状态接收器,接收器固化结果在数据库中,下发器监控到任务完成后继续执行下一批任务。
  6. 循环3-5直到当前计划书中全部任务完成。

详细见下文[[[[模块设计]](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E6%A8%A1%E5%9D%97%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E6%A8%A1%E5%9D%97%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E6%A8%A1%E5%9D%97%E8%AE%BE%E8%AE%A1)章节

与旧框架对比见下文[旧框架图]

升级顺序

与旧框架相同, 按照以下顺序

分发 -> 检查 -> 管理面升级 -> 网络升级 -> 数据面升级-> 提交

这部分逻辑写在新框架的[[逻辑书]](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E9%80%BB%E8%BE%91%E4%B9%A6%E8%AF%A6%E7%BB%86%E8%AE%BE%E8%AE%A1)中, 未来可以通过改变逻辑书来修改升级顺序。

对外接口/升级脚本

与旧框架保持相同

新框架如何完成升级任务?

整体流程

[信息收集 -> 依次]

Note新增模块

端到端流程

以最典型的升级大任务--管理面升级为例

image-20221101163005340

  1. 接收外部升级命令"管理面升级执行"

  2. 根据命令[[[[任务截取器]](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E4%BB%BB%E5%8A%A1%E6%88%AA%E5%8F%96%E5%99%A8%E8%AF%A6%E7%BB%86%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E4%BB%BB%E5%8A%A1%E6%88%AA%E5%8F%96%E5%99%A8%E8%AF%A6%E7%BB%86%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E4%BB%BB%E5%8A%A1%E6%88%AA%E5%8F%96%E5%99%A8%E8%AF%A6%E7%BB%86%E8%AE%BE%E8%AE%A1)[[[[全局计划书]](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%85%A8%E5%B1%80%E8%AE%A1%E5%88%92%E4%B9%A6%E8%AF%A6%E7%BB%86%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%85%A8%E5%B1%80%E8%AE%A1%E5%88%92%E4%B9%A6%E8%AF%A6%E7%BB%86%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%85%A8%E5%B1%80%E8%AE%A1%E5%88%92%E4%B9%A6%E8%AF%A6%E7%BB%86%E8%AE%BE%E8%AE%A1)截取[[[[当前计划书]](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%BD%93%E5%89%8D%E8%AE%A1%E5%88%92%E4%B9%A6%E8%AF%A6%E7%BB%86%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%BD%93%E5%89%8D%E8%AE%A1%E5%88%92%E4%B9%A6%E8%AF%A6%E7%BB%86%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%BD%93%E5%89%8D%E8%AE%A1%E5%88%92%E4%B9%A6%E8%AF%A6%E7%BB%86%E8%AE%BE%E8%AE%A1),此时当前计划书的内容为

    [
    [节点A关闭第1批组件, 节点B关闭第1批组件, ...],
    [节点A关闭第2批组件, 节点B关闭第2批组件, ...],
    ...
    [组件X修改配置, 组件Y修改配置, ...],
    [节点A升级(XY...)组件rpm, 节点B升级(YZ...)组件rpm...]
    [节点A开启第1批组件, 节点B开启第1批组件, ...],
    [节点A开启第2批组件, 节点B开启第2批组件, ...],
    ...
    ]
    # 注意到这里是2维数组, 内层数组表示里面的任务是同一批并行执行的,外部数组表示每批任务是串行执行的
  3. 任务下发器下发批节点任务。首先下发的是"关闭第1批组件"

    [节点A关闭第1批组件, 节点B关闭第1批组件, ...]

    既将"节点A关闭第1批组件"下发给节点A,"节点B关闭第1批组件"下发给节点B...

  4. 任务执行器接收下发的节点任务。以节点A为例,此时节点任务"节点A关闭第1批组件"的内容是

    [
    [节点A关闭nova-api, 节点A关闭swift-ngnix, ...]
    [节点A关闭nova-console, ... ]
    ...
    ]
    # 注意到这里是2维数组, 内层数组表示里面的任务是同一批并行执行的,外部数组表示每批任务是串行执行的
  5. 任务执行器会按照顺序和串并行情况依次调用相应的任务脚本。

    # 如节点A关闭nova-api任务
    调用stop_nova_api.py脚本
  6. 任务执行器完成所有任务脚本后,上报任务执行结果给任务状态执行器。

    # 全部成功时的上报
    {
    节点A关闭nova-api: done,
    节点A关闭swift-ngnix: done,
    节点A关闭nova-console: done,
    ...
    }
    
    # 部分失败时的上报
    {
    节点A关闭nova-api: done,
    节点A关闭swift-ngnix: fail,
    节点A关闭nova-console: undo,
    ...
    }
    # 节点A关闭nova-console: undo表示这个任务还未执行时,前置任务已经失败了
  7. 任务状态执行器接受上报后,直接将消息保存在数据库中

    此处是旧框架的一个性能瓶颈,主要是保存前后做了很多数据处理

    新框架在此处只有保存动作,可以提升性能

  8. 工步3下发任务后,任务下发器会定期查询数据库,是否所有下发任务已经完成。当查询到所有任务执行成功后,则继续下发下一批节点任务(存在失败则中断升级)。重复工步3-8,直到当前计划书中所有任务执行完成。

查询: 升级过程中前端需要查询升级进度时,查询器会直接从数据库查询,从而与升级执行逻辑解耦。详细见[查询器详细设计]

新旧框架对比

详细设计

特性设计

灵活升级特性设计

升级工程是由一个个小粒度的任务按照顺序组成的。只要做到任意单任务的升级回退,就可以完成任意组合的升级回退。

场景二: A节点升级后调测失败

    旧框架:

场景一:

现象:现网升级中,某个组件在某个节点数据割接出现了问题,导致组件启动后异常。需要先恢复环境,再规避继续升级。

旧框架: 

    方法1: 整体回退,整个环境回退到升级前。再修改配置转换逻辑代码,再次从头走升级后逻辑。时间太长,现网基本不会采用。

    预期规避时间: 10小时以上

    方法2: 人工处理割接后的数据,将数据逐一改成正确数据。规避时间长,人工操作易出错。

    预期规避时间: 30分钟                 

新框架:

    单独回退该组件该节点的数据割接任务。修改配置转换逻辑代码。重试数据割接任务。

    由于只需要回退重试单个小任务,规避时间缩短。

    第一次修改配置转换逻辑代码时间30分钟,规避时间5分钟

    以后

并行升级特性设计

升级(或回退)过程中,随时可以添加新组件,进行并行升级。

这里框架只提供底层支持,具体是否可以并行还要看任务或组件之间的依赖关系。

新框架中,升级的数据分为静态和动态数据两种。静态数据如升级信息、部署信息,这部分信息是升级前收集的,升级中保持不变,所以没有并发问题。动态数据是任务状态,由于划分了细粒度任务,新添加组件的升级任务与旧组件的升级任务是分开的所以互不影响。

todo 图:A工程,B工程共享数据,分别状态

应用场景:

A组件升级过程中,创建新工程升级B组件。参考公有云并行升级。

多版本升级特性设计

新框架的任务的粒度是基于instance的,所以每个instance都可以指定是否升级,升级的目标版本是什么。

如下文场景1,实现方法

  1. 升级前通过外部命令获取需要升级hostos的计算节点列表,称为待升节点。
  2. 升级编排第一步(即生成所有升级任务, 详见[编排详细设计])时,只生成待升节点的hostos升级任务。
  3. 正常编排。此时生成的全局任务书里,已经没有了其它节点的hostos升级任务。
  4. 按照全局计划书,执行升级工程。只升级待升节点的hostos升级任务。

应用场景:

  1. 控制节点和计算节点的hostos版本是r8,用户想将某几个计算节点升级到r9,其它节点保留在r8以节省升级时间

  2. 同一组件两个版本归一

  3. 同一组件一个版本分化为两个

N-X升级特性设计

支持N-X升级的升级框架支持

提供管理面备区升级能力

管理组件随hostos在备区升级后重启生效;同时保留管理面重启单独组件升级方式

其它支持

待补充

大规模特性设计

减少任务下发前后的数据处理

新框架中任务在升级前生成并保存在数据库中

# 数据库中的任务
h1_nova-api_stop: done
h2_swift-ngnix_stop: done
...
hn_cps-client_uprpm: undo
...

# 当前计划书中的任务
[
    [[[h1_stop_nova-api], [h1_stop_swift-store]], [[h2_stop_nova-api], [h2_stop_swift-store]]],
    ...
]

下发任务时,只需要

减少任务上报前后的数据处理

每个任务都有一个唯一id, 如"h1_stop_nova-api"。详见[附录-主要数据结构-任务(Task)]

执行器(upg-client)执行完节点任务后,会将子任务的结果上报给状态接收器

# 上报结构体
{
    h1_stop_nova-api: done,
    h1_stop_swift-store: fail,
    ...
}

状态接收器每隔1s钟将接收的数据批量存在数据库中, 详见[任务状态表设计]

# 保存数据库状态
h1_stop_nova-api: done
h1_stop_swift-store: fail
...

以10000节点同时执行任务为例, 每个节点每分钟上报一次。状态接收器相当于每秒执行一次写操作, 修改167条数据。远远小于数据库和python WSGI-server的性能。

由于状态接收器是解耦的

这里也可以使用如下设计

执行器(upg-client)直接访问数据库,执行写-任务状态操作。

模块设计

升级前信息收集/统一数据接口

新框架在升级前新增了升级前信息收集工步。主要收集两种数据:

  1. 部署数据:
    1. 每个节点部署了哪些组件,组件升级前的版本号是多少
    2. 节点是管理节点、计算节点、宿主机、网络节点还是SDI节点
    3. 环境的网络分组情况
  2. 升级数据:
    1. 本次升级工程涉及哪些升级包,以及相关的升级参数:对应的组件、版本号、rpm列表、是否需要重启...

旧框架设计

升级逻辑应该是简单的,实现是过分复杂的。

工程进度

详细设计 已完成(2周)

新升级框架整体架构、模块划分、核心模块交互、主要数据结构等设计 模块开发顺序、测试方案等设计

设计书评审 进行中(6周)

设计书内部评审

模块分工

按模块分工给具体开发人员,主要有以下四个模块

  1. 升级脚本(执行具体升级任务: 下载rpm, 启停组件, 升级rpm等)
  2. server任务系统开发(升级任务的顺序编排和下发)
  3. client任务执行器开发(接收server下发的命令, 调用相关组件)
  4. 外部数据接口(对cps\nova\swift\zookeeper\gaussdb), 内部数据接口(server/client通信)

分模块设计 (1周)

与各个模块开发人员对齐模块设计方案和接口

工程开发 (15人月)

四大模块并行开发 -> 联调 -> 对接ci\端到端环境验证

FAQ

新框架能否完成升级任务?

为什么重写不是重构?

旧框架有什么问题?

代码问题

架构问题

附录

主要数据结构

任务(Task)

最小的原子化升级任务,一般是指对某个instance进行某个操作。如“节点1的nova-api升级rpm包”

构成元素是host_target_action:

  1. host:

    一般指执行的节点, 如“host1”。全局任务下host为特殊节点“upg-server”。

  2. target:

    任务对象,一般为某个template,如“nova-api”。

  3. action:

    任务动作,如“stop”,“up-rpm”,“start”。

    上文“节点1的nova-api升级rpm包”可表示为“host1_nova-api_up-rpm”

    action是原子化细粒度不可分割的。

    旧框架:

    任务是粗粒度、非原子性的。如“升级执行”任务,包括了多批组件的启停、rpm升级、修改配置等多个小任务。

    分批关闭组件 -> 修改配置 -> rpm升级 -> 分批开启组件

    假如rpm升级工步失败,

    1. 系统只显示“升级执行失败”,需要排除日志才能定位出错点

    2. 重试时,需要先重试分批关闭组件、修改配置两个前置工步,才能重试rpm升级

    3. 重试时,pkg内组件无法区分。例如控制节点FusionPlatFrom包内20个组件, 只有一个组件失败了,必须20个组件一起重试

    新框架:

    任务是细粒度、原子性的。任务要么全部成功,要么全部失败。

    假如rpm升级工步失败,

       1.  系统直接显示“A节点B组件rpm升级失败”
                   2.  重试时,可以直接重试“A节点B组件rpm升级失败”,无需冗余重试前置工步和相关组件

    原子性的其它优点这里不赘述。

节点任务(Job)

某个节点一次执行的任务。结构上是Task的2维数组。Job描述了子任务内容、执行顺序和串并行情况。

为了性能考虑,upg-server会对一个upg-client批量下发任务。

"""节点任务Job数据结构示例"""
[
    [h1_stop_nova-api, h1_stop_swift-store],
    [h1_uprpm_nova-api, h1_uprpm_swift-store],
    [h1_start_nova-api, h1_start_swift-store]
]

如上图,节点h1升级nova-api, swift-store共有6个任务。需要先并行执行关闭2个组件,完成后再并行升级2个组件rpm,完成后再并行打开2个组件。

计划书(Mission)

某个节点一次执行的任务。结构上是Task的4维数组, Job的2维数组。Mission描述了子Job内容、执行顺序和串并行情况。

"""节点任务Job数据结构示例"""
[
    [h1_execute_manage_job, h2_execute_manage_job],
    [h1_execute_hostos, h2_execute_hostos],
    [h1_effect_hostos, h2_effect_hostos]
]

全局计划书

所有包含升级所有Task的内容和执行顺序

当前计划书

根据外部命令从全局计划书截取的,当前需要执行的Task的内容和执行顺序

为什么做新框架?

新框架目标

  1. 大规模性能提高至10k以上 原因: 优化了升级框架内部瓶颈点
  2. 现网Bug减少至1/2 原因: 代码可信整改
  3. 升级框架新需求开发工作量减少至1/3 原因: 代码架构优化
  4. 周边组件测试时间缩短至1/4 原因: 新特性,细粒度上下文无关的升级回退

The Header

Link to Header

新增的特性

  1. 灵活升级 细粒度任务独立回退升级, 任意小工步升级失败后可以独立回退。并重试或添加规避代码后重试

    单工步调试:开发过程中可以单工步调试,且无需依赖前后工步。如单独调试升级配置转换

    详细设计: 灵活升级特性设计

    旧框架实现对比: [旧框架设计]

    影响: 开发中的测试效率, 生产中的规避效率

  2. 并行升级 工程一升级AB组件的同时,工程二可以并行升级无依赖的CD组件

    详细设计: 并行升级特性设计

    旧框架实现对比: [旧框架设计]

    影响: 升级效率,升级灵活性

  3. 多版本升级 支持一个版本的组件升级到不同版本,如控制节点升级OS,计算节点不升级OS。控制节点升级到X版本Nova,计算节点升级到Y版本Nova

  4. N-X升级 支持N-X升级的升级框架支持 a) 提供管理面备区升级能力,管理组件随hostos在备区升级后重启生效;同时保留管理面重启单独组件升级方式

  5. 可视化升级(未来特性) 升级前可以生成整个升级计划书,供用户和技术人员检视,并可以根据需要灵活修改。升级过程不再是盲盒

改善的特性

  1. 并行化 编排、任务下发、任务上报接收、任务执行、查询进度都可以用独立进程执行,其中任务下发、任务上报接收、任务执行、查询进度各自可以用并发多进程
  2. 大规模 可以支持10k节点升级,升级框架本身不再是性能瓶颈。提供相关性能分析接口,在开发过程中就能自动分析各升级组件和外部接口性能。

主要改动点

  1. 统一数据收集
  2. 细粒度升级
  3. 解耦

新框架对比

详细见下文“新旧框架对比”章节

好的架构逻辑清晰,坏的架构一言难尽

本文可以详细描述新框架的架构逻辑。但是无法详细说明旧框架的架构逻辑,原因就是旧框架是混乱的无法简单梳理清楚的。

既然旧框架无法说明清楚,如何保证新框架和旧框架的一致?

升级框架是一个服务,只要对外接口和对外特性保持一致即可。内部实现的变更是不需要一一对应验证的。

新框架设计原理

  1. 升级工程是由多个小任务组成的。
  2. 升级就是单个任务的执行内容和多个任务的执行顺序组成。
  3. 升级是简单的。(代码量15k,核心逻辑"启停、升级rpm")

新框架是怎么样的?

模块交互

升级模块详细流程图

  1. 升级前编排生成全局计划书,包含了所有升级任务的内容、执行顺序和串并行信息。
  2. 升级时根据外部升级命令截取相应的升级任务生成当前计划书。
  3. 通过(upg-server)任务下发器下发当前计划书给对应的(upg-client)任务执行器。
  4. 任务执行器根据任务内容调用相应任务脚本。
  5. 任务脚本执行完成后,任务执行器上报任务给任务状态接收器,接收器固化结果在数据库中,下发器监控到任务完成后继续执行下一批任务。
  6. 循环3-5直到当前计划书中全部任务完成。

详细见下文[模块设计]章节

与旧框架对比见下文[旧框架图]

升级顺序

与旧框架相同, 按照以下顺序

分发 -> 检查 -> 管理面升级 -> 网络升级 -> 数据面升级-> 提交

这部分逻辑写在新框架的[[逻辑书]](# 逻辑书详细设计)中, 未来可以通过改变逻辑书来修改升级顺序。

对外接口/升级脚本

与旧框架保持相同

新框架如何完成升级任务?

整体流程

[信息收集 -> 依次]

Note新增模块

端到端流程

以最典型的升级大任务--管理面升级为例

image-20221101163005340

  1. 接收外部升级命令"管理面升级执行"

  2. 根据命令[任务截取器][全局计划书]截取[当前计划书],此时当前计划书的内容为

    [
    [节点A关闭第1批组件, 节点B关闭第1批组件, ...],
    [节点A关闭第2批组件, 节点B关闭第2批组件, ...],
    ...
    [组件X修改配置, 组件Y修改配置, ...],
    [节点A升级(XY...)组件rpm, 节点B升级(YZ...)组件rpm...]
    [节点A开启第1批组件, 节点B开启第1批组件, ...],
    [节点A开启第2批组件, 节点B开启第2批组件, ...],
    ...
    ]
    # 注意到这里是2维数组, 内层数组表示里面的任务是同一批并行执行的,外部数组表示每批任务是串行执行的
  3. 任务下发器下发批节点任务。首先下发的是"关闭第1批组件"

    [节点A关闭第1批组件, 节点B关闭第1批组件, ...]

    既将"节点A关闭第1批组件"下发给节点A,"节点B关闭第1批组件"下发给节点B...

  4. 任务执行器接收下发的节点任务。以节点A为例,此时节点任务"节点A关闭第1批组件"的内容是

    [
    [节点A关闭nova-api, 节点A关闭swift-ngnix, ...]
    [节点A关闭nova-console, ... ]
    ...
    ]
    # 注意到这里是2维数组, 内层数组表示里面的任务是同一批并行执行的,外部数组表示每批任务是串行执行的
  5. 任务执行器会安装顺序和串并行情况依次调用相应的任务脚本

新旧框架对比

详细设计

特性设计

灵活升级特性设计

升级工程是由一个个小粒度的任务按照顺序组成的。只要做到任意单任务的升级回退,就可以完成任意组合的升级回退。

场景二: A节点升级后调测失败

​ 旧框架:

场景一:

​ 现象:现网升级中,某个组件在某个节点数据割接出现了问题,导致组件启动后异常。需要先恢复环境,再规避继续升级。

​ 旧框架:

​ 方法1: 整体回退,整个环境回退到升级前。再修改配置转换逻辑代码,再次从头走升级后逻辑。时间太长,现网基本不会采用。

​ 预期规避时间: 10小时以上

​ 方法2: 人工处理割接后的数据,将数据逐一改成正确数据。规避时间长,人工操作易出错。

​ 预期规避时间: 30分钟

​ 新框架:

​ 单独回退该组件该节点的数据割接任务。修改配置转换逻辑代码。重试数据割接任务。

​ 由于只需要回退重试单个小任务,规避时间缩短。

​ 第一次修改配置转换逻辑代码时间30分钟,规避时间5分钟

​ 以后

并行升级特性设计

模块设计

旧框架设计

升级逻辑应该是简单的,实现是过分复杂的。

工程进度

详细设计 已完成(2周)

新升级框架整体架构、模块划分、核心模块交互、主要数据结构等设计 模块开发顺序、测试方案等设计

设计书评审 进行中(6周)

设计书内部评审

模块分工

按模块分工给具体开发人员,主要有以下四个模块

  1. 升级脚本(执行具体升级任务: 下载rpm, 启停组件, 升级rpm等)
  2. server任务系统开发(升级任务的顺序编排和下发)
  3. client任务执行器开发(接收server下发的命令, 调用相关组件)
  4. 外部数据接口(对cps\nova\swift\zookeeper\gaussdb), 内部数据接口(server/client通信)

分模块设计 (1周)

与各个模块开发人员对齐模块设计方案和接口

工程开发 (15人月)

四大模块并行开发 -> 联调 -> 对接ci\端到端环境验证

FAQ

新框架能否完成升级任务?

为什么重写不是重构?

旧框架有什么问题?

代码问题

架构问题

附录

重点问题

  1. N-X升级
  2. 可控升级
  3. 任意单任务回退
  4. 配置转换单独调试

主要数据结构

任务(Task)

最小的原子化升级任务,一般是指对某个instance进行某个操作。如“节点1的nova-api升级rpm包”

构成元素是host_target_action:

  1. host:

    一般指执行的节点, 如“host1”。全局任务下host为特殊节点“upg-server”。

  2. target:

    任务对象,一般为某个template,如“nova-api”。

  3. action:

    任务动作,如“stop”,“up-rpm”,“start”。

    上文“节点1的nova-api升级rpm包”可表示为“host1_nova-api_up-rpm”

    action是原子化细粒度不可分割的。

    旧框架:

    任务是粗粒度、非原子性的。如“升级执行”任务,包括了多批组件的启停、rpm升级、修改配置等多个小任务。

    分批关闭组件 -> 修改配置 -> rpm升级 -> 分批开启组件

    假如rpm升级工步失败,

    1. 系统只显示“升级执行失败”,需要排除日志才能定位出错点

    2. 重试时,需要先重试分批关闭组件、修改配置两个前置工步,才能重试rpm升级

    3. 重试时,pkg内组件无法区分。例如控制节点FusionPlatFrom包内20个组件, 只有一个组件失败了,必须20个组件一起重试

    新框架:

    任务是细粒度、原子性的。任务要么全部成功,要么全部失败。

    假如rpm升级工步失败,

       1.  系统直接显示“A节点B组件rpm升级失败”
                   2.  重试时,可以直接重试“A节点B组件rpm升级失败”,无需冗余重试前置工步和相关组件

    原子性的其它优点这里不赘述。

节点任务(Job)

某个节点一次执行的任务。结构上是Task的2维数组。Job描述了子任务内容、执行顺序和串并行情况。

为了性能考虑,upg-server会对一个upg-client批量下发任务。

"""节点任务Job数据结构示例"""
[
    [h1_stop_nova-api, h1_stop_swift-store],
    [h1_uprpm_nova-api, h1_uprpm_swift-store],
    [h1_start_nova-api, h1_start_swift-store]
]

如上图,节点h1升级nova-api, swift-store共有6个任务。需要先并行执行关闭2个组件,完成后再并行升级2个组件rpm,完成后再并行打开2个组件。

计划书(Mission)

某个节点一次执行的任务。结构上是Task的4维数组, Job的2维数组。Mission描述了子Job内容、执行顺序和串并行情况。

"""节点任务Job数据结构示例"""
[
    [h1_execute_manage_job, h2_execute_manage_job],
    [h1_execute_hostos, h2_execute_hostos],
    [h1_effect_hostos, h2_effect_hostos]
]

全局计划书

所有包含升级所有Task的内容和执行顺序

当前计划书

NicholasTao commented 2 years ago

为什么做新框架?

新框架目标

  1. 大规模性能提高至10k以上 原因: 优化了升级框架内部瓶颈点
  2. 现网Bug减少至1/2 原因: 代码可信整改
  3. 升级框架新需求开发工作量减少至1/3 原因: 代码架构优化
  4. 周边组件测试时间缩短至1/4 原因: 新特性,细粒度上下文无关的升级回退

The Header

[[[Link to Header](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#the-header)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#the-header)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#the-header)

新增的特性

  1. 灵活升级 细粒度任务独立回退升级, 任意小工步升级失败后可以独立回退。并重试或添加规避代码后重试

    单工步调试:开发过程中可以单工步调试,且无需依赖前后工步。如单独调试升级配置转换(这是一线人员重点要求添加的功能)

    详细设计: [[灵活升级特性设计]](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E7%81%B5%E6%B4%BB%E5%8D%87%E7%BA%A7%E7%89%B9%E6%80%A7%E8%AE%BE%E8%AE%A1)

    旧框架实现对比: [[旧框架设计]](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E6%97%A7%E6%A1%86%E6%9E%B6%E8%AE%BE%E8%AE%A1)

    影响: 开发中的测试效率, 生产中的规避效率

  2. 并行升级 工程一升级AB组件的同时,工程二可以并行升级无依赖的CD组件

    详细设计: [[[并行升级特性设计](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%B9%B6%E8%A1%8C%E5%8D%87%E7%BA%A7%E7%89%B9%E6%80%A7%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%B9%B6%E8%A1%8C%E5%8D%87%E7%BA%A7%E7%89%B9%E6%80%A7%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%B9%B6%E8%A1%8C%E5%8D%87%E7%BA%A7%E7%89%B9%E6%80%A7%E8%AE%BE%E8%AE%A1)

    旧框架实现对比: [[[[旧框架设计]](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E6%97%A7%E6%A1%86%E6%9E%B6%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E6%97%A7%E6%A1%86%E6%9E%B6%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E6%97%A7%E6%A1%86%E6%9E%B6%E8%AE%BE%E8%AE%A1)

    影响: 升级效率,升级灵活性

  3. 多版本升级 支持一个版本的组件升级到不同版本,如控制节点升级OS,计算节点不升级OS。控制节点升级到X版本Nova,计算节点升级到Y版本Nova

    [[[多版本升级特性设计]](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%A4%9A%E7%89%88%E6%9C%AC%E5%8D%87%E7%BA%A7%E7%89%B9%E6%80%A7%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%A4%9A%E7%89%88%E6%9C%AC%E5%8D%87%E7%BA%A7%E7%89%B9%E6%80%A7%E8%AE%BE%E8%AE%A1)

  4. N-X升级 支持N-X升级的升级框架支持 a) 提供管理面备区升级能力,管理组件随hostos在备区升级后重启生效;同时保留管理面重启单独组件升级方式

  5. 可视化升级(未来特性) 升级前可以生成整个升级计划书,供用户和技术人员检视,并可以根据需要灵活修改。升级过程不再是盲盒

改善的特性

  1. 并行化 编排、任务下发、任务上报接收、任务执行、查询进度都可以用独立进程执行,其中任务下发、任务上报接收、任务执行、查询进度各自可以用并发多进程

  2. 大规模 可以支持10k节点升级,升级框架本身不再是性能瓶颈。提供相关性能分析接口,在开发过程中就能自动分析各升级组件和外部接口性能。

  3. 升级脚本易用性提升

    统一升级脚本,提供可读的升级SDK(整理常用接口, 隔离内外接口,减少参数),提高开发效率。

    同时提供升级脚本单独调试功能。

    这是各组件升级开发人员和升级接口人重点要求改善的特性。

    详细设计见: [[[升级脚本特性设计]](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%8D%87%E7%BA%A7%E8%84%9A%E6%9C%AC%E7%89%B9%E6%80%A7%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%8D%87%E7%BA%A7%E8%84%9A%E6%9C%AC%E7%89%B9%E6%80%A7%E8%AE%BE%E8%AE%A1)

主要改动点

  1. 统一数据收集
  2. 细粒度升级
  3. 解耦

对"一跳升级"和"可控升级"的意义

todo

新框架对比

详细见下文“新旧框架对比”章节

好的架构逻辑清晰,坏的架构一言难尽

本文可以详细描述新框架的架构逻辑。但是无法详细说明旧框架的架构逻辑,原因就是旧框架是混乱的无法简单梳理清楚的。

既然旧框架无法说明清楚,如何保证新框架和旧框架的一致?

升级框架是一个服务,只要对外接口和对外特性保持一致即可。内部实现的变更是不需要一一对应验证的。

新框架设计原理

  1. 升级工程是由多个小任务组成的。
  2. 升级就是单个任务的执行内容和多个任务的执行顺序组成。
  3. 升级是简单的。(代码量15k,核心逻辑"启停、升级rpm")

新框架是怎么样的?

模块交互

升级模块详细流程图

  1. 升级前编排生成全局计划书,包含了所有升级任务的内容、执行顺序和串并行信息。
  2. 升级时根据外部升级命令截取相应的升级任务生成当前计划书。
  3. 通过(upg-server)任务下发器下发当前计划书给对应的(upg-client)任务执行器。
  4. 任务执行器根据任务内容调用相应任务脚本。
  5. 任务脚本执行完成后,任务执行器上报任务给任务状态接收器,接收器固化结果在数据库中,下发器监控到任务完成后继续执行下一批任务。
  6. 循环3-5直到当前计划书中全部任务完成。

详细见下文[[[[模块设计]](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E6%A8%A1%E5%9D%97%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E6%A8%A1%E5%9D%97%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E6%A8%A1%E5%9D%97%E8%AE%BE%E8%AE%A1)章节

与旧框架对比见下文[旧框架图]

升级顺序

与旧框架相同, 按照以下顺序

分发 -> 检查 -> 管理面升级 -> 网络升级 -> 数据面升级-> 提交

这部分逻辑写在新框架的[[逻辑书]](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E9%80%BB%E8%BE%91%E4%B9%A6%E8%AF%A6%E7%BB%86%E8%AE%BE%E8%AE%A1)中, 未来可以通过改变逻辑书来修改升级顺序。

对外接口/升级脚本

与旧框架保持相同

新框架如何完成升级任务?

整体流程

[信息收集 -> 依次]

Note新增模块

端到端流程

以最典型的升级大任务--管理面升级为例

image-20221101163005340

  1. 接收外部升级命令"管理面升级执行"

  2. 根据命令[[任务截取器]](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E4%BB%BB%E5%8A%A1%E6%88%AA%E5%8F%96%E5%99%A8%E8%AF%A6%E7%BB%86%E8%AE%BE%E8%AE%A1)[[[[全局计划书]](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%85%A8%E5%B1%80%E8%AE%A1%E5%88%92%E4%B9%A6%E8%AF%A6%E7%BB%86%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%85%A8%E5%B1%80%E8%AE%A1%E5%88%92%E4%B9%A6%E8%AF%A6%E7%BB%86%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%85%A8%E5%B1%80%E8%AE%A1%E5%88%92%E4%B9%A6%E8%AF%A6%E7%BB%86%E8%AE%BE%E8%AE%A1)截取[[[[当前计划书]](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%BD%93%E5%89%8D%E8%AE%A1%E5%88%92%E4%B9%A6%E8%AF%A6%E7%BB%86%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%BD%93%E5%89%8D%E8%AE%A1%E5%88%92%E4%B9%A6%E8%AF%A6%E7%BB%86%E8%AE%BE%E8%AE%A1)](https://github.com/NicholasTao/NicholasTao.github.io/issues/8#%E5%BD%93%E5%89%8D%E8%AE%A1%E5%88%92%E4%B9%A6%E8%AF%A6%E7%BB%86%E8%AE%BE%E8%AE%A1),此时当前计划书的内容为

    [
    [节点A关闭第1批组件, 节点B关闭第1批组件, ...],
    [节点A关闭第2批组件, 节点B关闭第2批组件, ...],
    ...
    [组件X修改配置, 组件Y修改配置, ...],
    [节点A升级(XY...)组件rpm, 节点B升级(YZ...)组件rpm...]
    [节点A开启第1批组件, 节点B开启第1批组件, ...],
    [节点A开启第2批组件, 节点B开启第2批组件, ...],
    ...
    ]
    # 注意到这里是2维数组, 内层数组表示里面的任务是同一批并行执行的,外部数组表示每批任务是串行执行的
  3. 任务下发器下发批节点任务。首先下发的是"关闭第1批组件"

    [节点A关闭第1批组件, 节点B关闭第1批组件, ...]

    既将"节点A关闭第1批组件"下发给节点A,"节点B关闭第1批组件"下发给节点B...

  4. 任务执行器接收下发的节点任务。以节点A为例,此时节点任务"节点A关闭第1批组件"的内容是

    [
    [节点A关闭nova-api, 节点A关闭swift-ngnix, ...]
    [节点A关闭nova-console, ... ]
    ...
    ]
    # 注意到这里是2维数组, 内层数组表示里面的任务是同一批并行执行的,外部数组表示每批任务是串行执行的
  5. 任务执行器会按照顺序和串并行情况依次调用相应的任务脚本。

    # 如节点A关闭nova-api任务
    调用stop_nova_api.py脚本
  6. 任务执行器完成所有任务脚本后,上报任务执行结果给任务状态执行器。

    # 全部成功时的上报
    {
    节点A关闭nova-api: done,
    节点A关闭swift-ngnix: done,
    节点A关闭nova-console: done,
    ...
    }
    
    # 部分失败时的上报
    {
    节点A关闭nova-api: done,
    节点A关闭swift-ngnix: fail,
    节点A关闭nova-console: undo,
    ...
    }
    # 节点A关闭nova-console: undo表示这个任务还未执行时,前置任务已经失败了
  7. 任务状态执行器接受上报后,直接将消息保存在数据库中

    此处是旧框架的一个性能瓶颈,主要是保存前后做了很多数据处理

    新框架在此处只有保存动作,可以提升性能

  8. 工步3下发任务后,任务下发器会定期查询数据库,是否所有下发任务已经完成。当查询到所有任务执行成功后,则继续下发下一批节点任务(存在失败则中断升级)。重复工步3-8,直到当前计划书中所有任务执行完成。

查询: 升级过程中前端需要查询升级进度时,查询器会直接从数据库查询,从而与升级执行逻辑解耦。详细见[查询器详细设计]

端到端流程时序图

image-20221103164418952

新旧框架对比

详细设计

特性设计

灵活升级特性设计

升级工程是由一个个小粒度的任务按照顺序组成的。只要做到任意单任务的升级回退,就可以完成任意组合的升级回退。

场景二: A节点升级后调测失败

    旧框架:

场景一:

现象:现网升级中,某个组件在某个节点数据割接出现了问题,导致组件启动后异常。需要先恢复环境,再规避继续升级。

旧框架: 

    方法1: 整体回退,整个环境回退到升级前。再修改配置转换逻辑代码,再次从头走升级后逻辑。时间太长,现网基本不会采用。

    预期规避时间: 10小时以上

    方法2: 人工处理割接后的数据,将数据逐一改成正确数据。规避时间长,人工操作易出错。

    预期规避时间: 30分钟                 

新框架:

    单独回退该组件该节点的数据割接任务。修改配置转换逻辑代码。重试数据割接任务。

    由于只需要回退重试单个小任务,规避时间缩短。

    第一次修改配置转换逻辑代码时间30分钟,规避时间5分钟

    以后

并行升级特性设计

升级(或回退)过程中,随时可以添加新组件,进行并行升级。

这里框架只提供底层支持,具体是否可以并行还要看任务或组件之间的依赖关系。

新框架中,升级的数据分为静态和动态数据两种。静态数据如升级信息、部署信息,这部分信息是升级前收集的,升级中保持不变,所以没有并发问题。动态数据是任务状态,由于划分了细粒度任务,新添加组件的升级任务与旧组件的升级任务是分开的所以互不影响。

todo 图:A工程,B工程共享数据,分别状态

应用场景:

A组件升级过程中,创建新工程升级B组件。参考公有云并行升级。

多版本升级特性设计

新框架的任务的粒度是基于instance的,所以每个instance都可以指定是否升级,升级的目标版本是什么。

如下文场景1,实现方法

  1. 升级前通过外部命令获取需要升级hostos的计算节点列表,称为待升节点。
  2. 升级编排第一步(即生成所有升级任务, 详见[编排详细设计])时,只生成待升节点的hostos升级任务。
  3. 正常编排。此时生成的全局任务书里,已经没有了其它节点的hostos升级任务。
  4. 按照全局计划书,执行升级工程。只升级待升节点的hostos升级任务。

应用场景:

  1. 控制节点和计算节点的hostos版本是r8,用户想将某几个计算节点升级到r9,其它节点保留在r8以节省升级时间

  2. 同一组件两个版本归一

  3. 同一组件一个版本分化为两个

N-X升级特性设计

支持N-X升级的升级框架支持

提供管理面备区升级能力

管理组件随hostos在备区升级后重启生效;同时保留管理面重启单独组件升级方式

其它支持

待补充

大规模特性设计

减少任务下发前后的数据处理

新框架中任务在升级前生成并保存在数据库中

# 数据库中的任务
h1_nova-api_stop: done
h2_swift-ngnix_stop: done
...
hn_cps-client_uprpm: undo
...

# 当前计划书中的任务
[
    [[[h1_stop_nova-api], [h1_stop_swift-store]], [[h2_stop_nova-api], [h2_stop_swift-store]]],
    ...
]

下发任务时,只需要

减少任务上报前后的数据处理

每个任务都有一个唯一id, 如"h1_stop_nova-api"。详见[附录-主要数据结构-任务(Task)]

执行器(upg-client)执行完节点任务后,会将子任务的结果上报给状态接收器

# 上报结构体
{
    h1_stop_nova-api: done,
    h1_stop_swift-store: fail,
    ...
}

状态接收器每隔1s钟将接收的数据批量存在数据库中, 详见[任务状态表设计]

# 保存数据库状态
h1_stop_nova-api: done
h1_stop_swift-store: fail
...

以10000节点同时执行任务为例, 每个节点每分钟上报一次。状态接收器相当于每秒执行一次写操作, 修改167条数据。远远小于数据库和python WSGI-server的性能。

由于状态接收器是解耦的

这里也可以使用如下设计

执行器(upg-client)直接访问数据库,执行写-任务状态操作。

模块设计

升级前信息收集/统一数据接口

新框架在升级前新增了升级前信息收集工步。主要收集两种数据:

  1. 部署数据:
    1. 每个节点部署了哪些组件,组件升级前的版本号是多少
    2. 节点是管理节点、计算节点、宿主机、网络节点还是SDI节点
    3. 环境的网络分组情况
  2. 升级数据:
    1. 本次升级工程涉及哪些升级包,以及相关的升级参数:对应的组件、版本号、rpm列表、是否需要重启...

部署数据从cps接口获取,升级数据通过升级描述文件获取。

升级过程中其它模块通过统一低代码接口调用。

这个新模块的优点是,避免了每个工步单独实现数据处理。详见[旧框架设计-内部数据管理]

这些数据都是静态数据,避免了

下发器模块设计

image-20221103141111707

输入: Mission

执行:

  1. 将Mission中的任务按顺序下发给执行器
  2. 获取所有任务执行结果并返回

输出: Mission执行结果

# 情况1,所有Task成功。输出如下
{
    done: [t1, t2...]
}

# 情况1,执行中出现Task失败。输出如下
{
    done: [t1, t2...], # 已执行,且成功
    fail: [t3, t4],     # 已执行,但失败
    undo: [t5, t6, ...] # 未执行
}

执行过程

  1. 从当前计划书获取当前计划,称为Mission。将当前计划书传给Mission执行器。

  2. Mission执行器依次将Mission中的子BatchJob传给BatchJob执行器。

    Mission是BatchJob的1维数组,其中的BatchJob顺序执行

  3. BatchJob执行器将BatchJob的子Job发送给对应节点(通过Job发送器)

  4. 下发任务完成后,BatchJob执行器将BatchJob的子Task传给Task监控器监控。

  5. Task监控器会定时查询数据库,检查是否所有Task完成。完成后通知BatchJob执行器。

  6. BatchJob执行器将BatchJob执行结果上报给Mission执行器。

  7. Mission执行器重复2-6,直到Mission执行完成

编排器设计

image-20221103152319570

Task生成器

根据升级信息和升级逻辑书,生成升级工程的所有升级任务。示例如下:

# 输入
部署数据:节点A, 上面部署了组件X, Y, Z; 节点B,上面部署了X
升级数据:X, Y需要重启升级
逻辑书: 重启升级的instance需要做5个动作dispatch, check, stop, uprpm, start

# 输出
A_X_dispatch, A_X_check, A_X_stop, A_X_uprpm, A_X_start
A_Y_dispatch, A_Y_check, A_Y_stop, A_Y_uprpm, A_Y_start
B_X_dispatch, B_X_check, B_X_stop, B_X_uprpm, B_X_start

Task编排器

根据升级信息和所有Task,生成任务计划书。示例如下:

# 输入
部署数据:节点A, 上面部署了组件X, Y, Z; 节点B,上面部署了X
升级数据:X, Y需要重启升级
逻辑书: 重启升级的instance需要做5个动作dispatch, check, stop, uprpm, start

# 输入
所有Task:
A_X_dispatch, A_X_check, A_X_stop, A_X_uprpm, A_X_start
A_Y_dispatch, A_Y_check, A_Y_stop, A_Y_uprpm, A_Y_start
B_X_dispatch, B_X_check, B_X_stop, B_X_uprpm, B_X_start
逻辑书:
1. 同时分发下载所有升级包
2. 同时检查所有组件
3. 先关闭X, 再关闭Y
4. 同时升级所有rpm包
5. 先开启Y,再开启X

# 输出
[
    [[[A_X_dispatch]], [[A_Y_dispatch]], [[B_X_dispatch]]],
    [[[A_X_check]], [[A_Y_check]], [[B_X_check]]],
    [[[A_X_stop]], [[B_X_stop]]],
    [[[A_Y_stop]]],
    [[[A_X_uprpm, A_Y_uprpm]], [[B_X_uprpm]]],
    [[[A_Y_start]]],
    [[[A_X_start]], [[B_X_start]]],
]
# 计划书数据结构设计见附录

编排器优点

  1. 便于开发:编排和执行逻辑解耦,有关执行顺序的升级新特性只需要修改逻辑书。新特性包括但不限于:管理面备区升级,网络单独升级

  2. 便于测试:

    可以手动或脚本生成任务计划书,进行特化的升级测试,如:

    # 手动编写计划书,跳过检查阶段,只升级节点A的X组件
    [[[
    [A_X_dispatch],
    [A_X_stop],
    [A_X_uprpm],
    [A_X_start]
    ]]]

    可以临时添加任务,或调整任务顺序。如添加新任务,任务中构造异常,以自动化测试异常情况下的升级。

  3. 便于检视

    可以通过检视逻辑书或计划书的方式,详细了解升级顺序。如:

    1. 组件开发人员,可自行确认升级两个组件关闭的先后顺序
    2. 了解网络节点的分批情况(网络节点必须分成几批升级)

截取器设计

当前升级实际操作中,升级不是自动化一次性跑完的。需要根据前端命令,一次执行部分任务。

截取器是根据前端命令和全局计划书,截取当前计划书的模块。

主要操作是根据前端命令从全局计划书中找到相应的任务,再保留任务间的顺序生成当前计划书。

# 前端命令升级节点A组件Y,节点B组件X
# 输入全局计划书
[
    [[[A_X_dispatch]], [[A_Y_dispatch]], [[B_X_dispatch]]],
    [[[A_X_check]], [[A_Y_check]], [[B_X_check]]],
    [[[A_X_stop]], [[B_X_stop]]],
    [[[A_Y_stop]]],
    [[[A_X_uprpm, A_Y_uprpm]], [[B_X_uprpm]]],
    [[[A_Y_start]]],
    [[[A_X_start]], [[B_X_start]]],
]

# 输出当前计划书(A_X, dispatch, check等无关任务都被剔除了)
[
    [[[B_X_stop]]],
    [[[A_Y_stop]]],
    [[[A_Y_uprpm]], [[B_X_uprpm]]],
    [[[A_Y_start]]],
    [[[B_X_start]]],
]

另外,必要情况下,截取时可以更改任务顺序。如全局计划书中先升级节点12再升级节点34,截取时外部命令要求先升级节点13再升级24,则截取器可以调整顺序(这项功能常用于网络自定义分批升级)。

截取器优点

当前计划书也具有可读、可更改的特点,可参考上文[编排器优点]关于全局计划书的描述。

旧框架设计

升级逻辑应该是简单的,实现是过分复杂的。

内部数据管理

动态数据多,导致开发测试定位困难

例子1: 现网出现问题,定位到函数function,但是无法确定哪一行出错函数func获取了。

# 函数function出错, 待定位
def function():
    a = cache_a.get_a()
    b = cache_b.get_b()
    c = cache_c.get_c()
    return a - b + c;

其中三个从内存获取数据的方法,每个在前文都有很多修改逻辑,都可能出错。

# 前文涉及到数据a的逻辑
cache_a.set_a(dict_x)
cache_a.update_a(dict_y)
cache_a.update_a(dict_z)
def function():
    a = dao.get_a()
    b = dao.get_b()
    c = dao.get_c()
    return a - b + c;

# 前文涉及到数据a的逻辑
dao.init() # 生成了raw_a, raw_b, raw_c
def init():
    generate_raw_a()
    generate_raw_b()
    generate_raw_c()

def get_a()
    return const_
  1. 依赖

image-20221103100528166

大部分业务逻辑依赖于多个数据接口实现,造成了开发、测试、定位困难

# 
def function():
    a = model_a.get_a()
    b = model_b.get_b()
    c = model_c.get_c()
    return a + b + c;

def test_function():
    model_a.set_a()
    model_b.set_b()
    model_c.set_c()
    function()

工程进度

详细设计 已完成(2周)

新升级框架整体架构、模块划分、核心模块交互、主要数据结构等设计 模块开发顺序、测试方案等设计

设计书评审 进行中(6周)

设计书内部评审

模块分工

按模块分工给具体开发人员,主要有以下四个模块

  1. 升级脚本(执行具体升级任务: 下载rpm, 启停组件, 升级rpm等)
  2. server任务系统开发(升级任务的顺序编排和下发)
  3. client任务执行器开发(接收server下发的命令, 调用相关组件)
  4. 外部数据接口(对cps\nova\swift\zookeeper\gaussdb), 内部数据接口(server/client通信)

分模块设计 (1周)

与各个模块开发人员对齐模块设计方案和接口

工程开发 (15人月)

四大模块并行开发 -> 联调 -> 对接ci\端到端环境验证

FAQ

新框架能否完成升级任务?

可以,因为

  1. 升级工程能被任务计划书完整描述。包括细粒度的升级任务、任务间的顺序和串并行关系。
  2. 任务计划书可以被任务下发执行器依序执行。

为什么重写不是重构?

旧框架有什么问题?

代码问题

架构问题

附录

主要数据结构

任务(Task)

最小的原子化升级任务,一般是指对某个instance进行某个操作。如“节点1的nova-api升级rpm包”

构成元素是host_target_action:

  1. host:

    一般指执行的节点, 如“host1”。全局任务下host为特殊节点“upg-server”。

  2. target:

    任务对象,一般为某个template,如“nova-api”。

  3. action:

    任务动作,如“stop”,“up-rpm”,“start”。

    上文“节点1的nova-api升级rpm包”可表示为“host1_nova-api_up-rpm”

    action是原子化细粒度不可分割的。

    旧框架:

    任务是粗粒度、非原子性的。如“升级执行”任务,包括了多批组件的启停、rpm升级、修改配置等多个小任务。

    分批关闭组件 -> 修改配置 -> rpm升级 -> 分批开启组件

    假如rpm升级工步失败,

    1. 系统只显示“升级执行失败”,需要排除日志才能定位出错点

    2. 重试时,需要先重试分批关闭组件、修改配置两个前置工步,才能重试rpm升级

    3. 重试时,pkg内组件无法区分。例如控制节点FusionPlatFrom包内20个组件, 只有一个组件失败了,必须20个组件一起重试

    新框架:

    任务是细粒度、原子性的。任务要么全部成功,要么全部失败。

    假如rpm升级工步失败,

       1.  系统直接显示“A节点B组件rpm升级失败”
                   2.  重试时,可以直接重试“A节点B组件rpm升级失败”,无需冗余重试前置工步和相关组件

    原子性的其它优点这里不赘述。

节点任务(Job)

某个节点一次执行的任务。结构上是Task的2维数组。Job描述了子任务内容、执行顺序和串并行情况。

为了性能考虑,upg-server会对一个upg-client批量下发任务。

"""节点任务Job数据结构示例"""
[
    [h1_stop_nova-api, h1_stop_swift-store],
    [h1_uprpm_nova-api, h1_uprpm_swift-store],
    [h1_start_nova-api, h1_start_swift-store]
]

如上图,节点h1升级nova-api, swift-store共有6个任务。需要先并行执行关闭2个组件,完成后再并行升级2个组件rpm,完成后再并行打开2个组件。

计划书(Mission)

某个节点一次执行的任务。结构上是Task的4维数组, Job的2维数组。Mission描述了子Job内容、执行顺序和串并行情况。

"""节点任务Job数据结构示例"""
[
    [h1_execute_manage_job, h2_execute_manage_job],
    [h1_execute_hostos, h2_execute_hostos],
    [h1_effect_hostos, h2_effect_hostos]
]

全局计划书

所有包含升级所有Task的内容和执行顺序

当前计划书

根据外部命令从全局计划书截取的,当前需要执行的Task的内容和执行顺序

为什么做新框架?

新框架目标

  1. 大规模性能提高至10k以上 原因: 优化了升级框架内部瓶颈点
  2. 现网Bug减少至1/2 原因: 代码可信整改
  3. 升级框架新需求开发工作量减少至1/3 原因: 代码架构优化
  4. 周边组件测试时间缩短至1/4 原因: 新特性,细粒度上下文无关的升级回退

The Header

Link to Header

新增的特性

  1. 灵活升级 细粒度任务独立回退升级, 任意小工步升级失败后可以独立回退。并重试或添加规避代码后重试

    单工步调试:开发过程中可以单工步调试,且无需依赖前后工步。如单独调试升级配置转换

    详细设计: 灵活升级特性设计

    旧框架实现对比: [旧框架设计]

    影响: 开发中的测试效率, 生产中的规避效率

  2. 并行升级 工程一升级AB组件的同时,工程二可以并行升级无依赖的CD组件

    详细设计: 并行升级特性设计

    旧框架实现对比: [旧框架设计]

    影响: 升级效率,升级灵活性

  3. 多版本升级 支持一个版本的组件升级到不同版本,如控制节点升级OS,计算节点不升级OS。控制节点升级到X版本Nova,计算节点升级到Y版本Nova

  4. N-X升级 支持N-X升级的升级框架支持 a) 提供管理面备区升级能力,管理组件随hostos在备区升级后重启生效;同时保留管理面重启单独组件升级方式

  5. 可视化升级(未来特性) 升级前可以生成整个升级计划书,供用户和技术人员检视,并可以根据需要灵活修改。升级过程不再是盲盒

改善的特性

  1. 并行化 编排、任务下发、任务上报接收、任务执行、查询进度都可以用独立进程执行,其中任务下发、任务上报接收、任务执行、查询进度各自可以用并发多进程
  2. 大规模 可以支持10k节点升级,升级框架本身不再是性能瓶颈。提供相关性能分析接口,在开发过程中就能自动分析各升级组件和外部接口性能。

主要改动点

  1. 统一数据收集
  2. 细粒度升级
  3. 解耦

新框架对比

详细见下文“新旧框架对比”章节

好的架构逻辑清晰,坏的架构一言难尽

本文可以详细描述新框架的架构逻辑。但是无法详细说明旧框架的架构逻辑,原因就是旧框架是混乱的无法简单梳理清楚的。

既然旧框架无法说明清楚,如何保证新框架和旧框架的一致?

升级框架是一个服务,只要对外接口和对外特性保持一致即可。内部实现的变更是不需要一一对应验证的。

新框架设计原理

  1. 升级工程是由多个小任务组成的。
  2. 升级就是单个任务的执行内容和多个任务的执行顺序组成。
  3. 升级是简单的。(代码量15k,核心逻辑"启停、升级rpm")

新框架是怎么样的?

模块交互

升级模块详细流程图

  1. 升级前编排生成全局计划书,包含了所有升级任务的内容、执行顺序和串并行信息。
  2. 升级时根据外部升级命令截取相应的升级任务生成当前计划书。
  3. 通过(upg-server)任务下发器下发当前计划书给对应的(upg-client)任务执行器。
  4. 任务执行器根据任务内容调用相应任务脚本。
  5. 任务脚本执行完成后,任务执行器上报任务给任务状态接收器,接收器固化结果在数据库中,下发器监控到任务完成后继续执行下一批任务。
  6. 循环3-5直到当前计划书中全部任务完成。

详细见下文[模块设计]章节

与旧框架对比见下文[旧框架图]

升级顺序

与旧框架相同, 按照以下顺序

分发 -> 检查 -> 管理面升级 -> 网络升级 -> 数据面升级-> 提交

这部分逻辑写在新框架的[[逻辑书]](# 逻辑书详细设计)中, 未来可以通过改变逻辑书来修改升级顺序。

对外接口/升级脚本

与旧框架保持相同

新框架如何完成升级任务?

整体流程

[信息收集 -> 依次]

Note新增模块

端到端流程

以最典型的升级大任务--管理面升级为例

image-20221101163005340

  1. 接收外部升级命令"管理面升级执行"

  2. 根据命令[任务截取器][全局计划书]截取[当前计划书],此时当前计划书的内容为

    [
    [节点A关闭第1批组件, 节点B关闭第1批组件, ...],
    [节点A关闭第2批组件, 节点B关闭第2批组件, ...],
    ...
    [组件X修改配置, 组件Y修改配置, ...],
    [节点A升级(XY...)组件rpm, 节点B升级(YZ...)组件rpm...]
    [节点A开启第1批组件, 节点B开启第1批组件, ...],
    [节点A开启第2批组件, 节点B开启第2批组件, ...],
    ...
    ]
    # 注意到这里是2维数组, 内层数组表示里面的任务是同一批并行执行的,外部数组表示每批任务是串行执行的
  3. 任务下发器下发批节点任务。首先下发的是"关闭第1批组件"

    [节点A关闭第1批组件, 节点B关闭第1批组件, ...]

    既将"节点A关闭第1批组件"下发给节点A,"节点B关闭第1批组件"下发给节点B...

  4. 任务执行器接收下发的节点任务。以节点A为例,此时节点任务"节点A关闭第1批组件"的内容是

    [
    [节点A关闭nova-api, 节点A关闭swift-ngnix, ...]
    [节点A关闭nova-console, ... ]
    ...
    ]
    # 注意到这里是2维数组, 内层数组表示里面的任务是同一批并行执行的,外部数组表示每批任务是串行执行的
  5. 任务执行器会安装顺序和串并行情况依次调用相应的任务脚本

新旧框架对比

详细设计

特性设计

灵活升级特性设计

升级工程是由一个个小粒度的任务按照顺序组成的。只要做到任意单任务的升级回退,就可以完成任意组合的升级回退。

场景二: A节点升级后调测失败

    旧框架:

场景一:

现象:现网升级中,某个组件在某个节点数据割接出现了问题,导致组件启动后异常。需要先恢复环境,再规避继续升级。

旧框架: 

    方法1: 整体回退,整个环境回退到升级前。再修改配置转换逻辑代码,再次从头走升级后逻辑。时间太长,现网基本不会采用。

    预期规避时间: 10小时以上

    方法2: 人工处理割接后的数据,将数据逐一改成正确数据。规避时间长,人工操作易出错。

    预期规避时间: 30分钟                 

新框架:

    单独回退该组件该节点的数据割接任务。修改配置转换逻辑代码。重试数据割接任务。

    由于只需要回退重试单个小任务,规避时间缩短。

    第一次修改配置转换逻辑代码时间30分钟,规避时间5分钟

    以后

并行升级特性设计

模块设计

旧框架设计

升级逻辑应该是简单的,实现是过分复杂的。

工程进度

详细设计 已完成(2周)

新升级框架整体架构、模块划分、核心模块交互、主要数据结构等设计 模块开发顺序、测试方案等设计

设计书评审 进行中(6周)

设计书内部评审

模块分工

按模块分工给具体开发人员,主要有以下四个模块

  1. 升级脚本(执行具体升级任务: 下载rpm, 启停组件, 升级rpm等)
  2. server任务系统开发(升级任务的顺序编排和下发)
  3. client任务执行器开发(接收server下发的命令, 调用相关组件)
  4. 外部数据接口(对cps\nova\swift\zookeeper\gaussdb), 内部数据接口(server/client通信)

分模块设计 (1周)

与各个模块开发人员对齐模块设计方案和接口

工程开发 (15人月)

四大模块并行开发 -> 联调 -> 对接ci\端到端环境验证

FAQ

新框架能否完成升级任务?

为什么重写不是重构?

旧框架有什么问题?

代码问题

架构问题

附录

重点问题

  1. N-X升级
  2. 可控升级
  3. 任意单任务回退
  4. 配置转换单独调试

主要数据结构

任务(Task)

最小的原子化升级任务,一般是指对某个instance进行某个操作。如“节点1的nova-api升级rpm包”

构成元素是host_target_action:

  1. host:

    一般指执行的节点, 如“host1”。全局任务下host为特殊节点“upg-server”。

  2. target:

    任务对象,一般为某个template,如“nova-api”。

  3. action:

    任务动作,如“stop”,“up-rpm”,“start”。

    上文“节点1的nova-api升级rpm包”可表示为“host1_nova-api_up-rpm”

    action是原子化细粒度不可分割的。

    旧框架:

    任务是粗粒度、非原子性的。如“升级执行”任务,包括了多批组件的启停、rpm升级、修改配置等多个小任务。

    分批关闭组件 -> 修改配置 -> rpm升级 -> 分批开启组件

    假如rpm升级工步失败,

    1. 系统只显示“升级执行失败”,需要排除日志才能定位出错点

    2. 重试时,需要先重试分批关闭组件、修改配置两个前置工步,才能重试rpm升级

    3. 重试时,pkg内组件无法区分。例如控制节点FusionPlatFrom包内20个组件, 只有一个组件失败了,必须20个组件一起重试

    新框架:

    任务是细粒度、原子性的。任务要么全部成功,要么全部失败。

    假如rpm升级工步失败,

       1.  系统直接显示“A节点B组件rpm升级失败”
                   2.  重试时,可以直接重试“A节点B组件rpm升级失败”,无需冗余重试前置工步和相关组件

    原子性的其它优点这里不赘述。

节点任务(Job)

某个节点一次执行的任务。结构上是Task的2维数组。Job描述了子任务内容、执行顺序和串并行情况。

为了性能考虑,upg-server会对一个upg-client批量下发任务。

"""节点任务Job数据结构示例"""
[
    [h1_stop_nova-api, h1_stop_swift-store],
    [h1_uprpm_nova-api, h1_uprpm_swift-store],
    [h1_start_nova-api, h1_start_swift-store]
]

如上图,节点h1升级nova-api, swift-store共有6个任务。需要先并行执行关闭2个组件,完成后再并行升级2个组件rpm,完成后再并行打开2个组件。

计划书(Mission)

某个节点一次执行的任务。结构上是Task的4维数组, Job的2维数组。Mission描述了子Job内容、执行顺序和串并行情况。

"""节点任务Job数据结构示例"""
[
    [h1_execute_manage_job, h2_execute_manage_job],
    [h1_execute_hostos, h2_execute_hostos],
    [h1_effect_hostos, h2_effect_hostos]
]

全局计划书

所有包含升级所有Task的内容和执行顺序

当前计划书

NicholasTao commented 2 years ago
  1. 外部诉求

  2. 内部诉求

  3. 历史问题

  4. 开发需求

  5. 框架设计

  6. 风险

    验证

  7. 保证继承

    1. 主要特性
    2. 测试
  8. 计划

    1. 50k / 2k = 25

    2. 模块划分

      阶段