Open NicholasTao opened 2 years ago
网络升级 中间 调测 灵活 编排
cps适配 配置变更
instance task 个数多
job task
可视化
进度慢解释
大规模解释
编排demo
升级中扩容
网络灵活 思考:常用特殊编排,通过脚本化实现。 new 大阶段结束后,遍历task状态查看是否完成。 new client上传?? instance多job: server一个job对应多个task状态,如batch1启动对应client多个组件启停。无法同时满足精细显示和少数据??少数据生成多精细需要负责逻辑。压缩:同样流程节点压缩,成功状态压缩。 升级中扩容:针对未来升级中扩容,是一个临时状态,通过
可视化 进度慢解释 大规模解释 编排demo
下周新升级框架分工,7/19将开会共同整理讨论输出的分析结果 @所有人
线上升级能力分析 付炫丞 7/14 针对大规模性能(Server逻辑重、数据读取通讯慢、Swift\Zookeeper瓶颈)、开发测试困难(instance task细粒度无法回退重试)等问题,了解比较线上解决方案
已有升级特性梳理 郭家发 7/14 梳理升级框架现有所有特性,各种场景、升级回退、忽略等
现网需求和解决方案 余德元 7/19 梳理不受已有升级框架限制,现网需要改进或新增的需求
新升级框架设计 陶羽翔 7/19 如何解决现有问题 如何对外兼容旧框架 task系统针对具体业务(管理面执行)如何运行
升级框架已有问题和解决方案 ALL 7/19
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
灵活升级 细粒度任务独立回退升级, 任意小工步升级失败后可以独立回退。并重试或添加规避代码后重试
单工步调试:开发过程中可以单工步调试,且无需依赖前后工步。如单独调试升级配置转换(这是一线人员重点要求添加的功能)
影响: 开发中的测试效率, 生产中的规避效率
并行升级 工程一升级AB组件的同时,工程二可以并行升级无依赖的CD组件
影响: 升级效率,升级灵活性
多版本升级 支持一个版本的组件升级到不同版本,如控制节点升级OS,计算节点不升级OS。控制节点升级到X版本Nova,计算节点升级到Y版本Nova
N-X升级 支持N-X升级的升级框架支持 a) 提供管理面备区升级能力,管理组件随hostos在备区升级后重启生效;同时保留管理面重启单独组件升级方式
可视化升级(未来特性) 升级前可以生成整个升级计划书,供用户和技术人员检视,并可以根据需要灵活修改。升级过程不再是盲盒
并行化 编排、任务下发、任务上报接收、任务执行、查询进度都可以用独立进程执行,其中任务下发、任务上报接收、任务执行、查询进度各自可以用并发多进程
大规模 可以支持10k节点升级,升级框架本身不再是性能瓶颈。提供相关性能分析接口,在开发过程中就能自动分析各升级组件和外部接口性能。
升级脚本易用性提升
统一升级脚本,提供可读的升级SDK(整理常用接口, 隔离内外接口,减少参数),提高开发效率。
同时提供升级脚本单独调试功能。
这是各组件升级开发人员和升级接口人重点要求改善的特性。
todo
详细见下文“新旧框架对比”章节
好的架构逻辑清晰,坏的架构一言难尽
本文可以详细描述新框架的架构逻辑。但是无法详细说明旧框架的架构逻辑,原因就是旧框架是混乱的无法简单梳理清楚的。
既然旧框架无法说明清楚,如何保证新框架和旧框架的一致?
升级框架是一个服务,只要对外接口和对外特性保持一致即可。内部实现的变更是不需要一一对应验证的。
与旧框架对比见下文[旧框架图]
与旧框架相同, 按照以下顺序
分发 -> 检查 -> 管理面升级 -> 网络升级 -> 数据面升级-> 提交
这部分逻辑写在新框架的[[[逻辑书]](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新增模块
以最典型的升级大任务--管理面升级为例
接收外部升级命令"管理面升级执行"
根据命令[[[任务截取器]](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维数组, 内层数组表示里面的任务是同一批并行执行的,外部数组表示每批任务是串行执行的
任务下发器下发批节点任务。首先下发的是"关闭第1批组件"
[节点A关闭第1批组件, 节点B关闭第1批组件, ...]
既将"节点A关闭第1批组件"下发给节点A,"节点B关闭第1批组件"下发给节点B...
任务执行器接收下发的节点任务。以节点A为例,此时节点任务"节点A关闭第1批组件"的内容是
[
[节点A关闭nova-api, 节点A关闭swift-ngnix, ...]
[节点A关闭nova-console, ... ]
...
]
# 注意到这里是2维数组, 内层数组表示里面的任务是同一批并行执行的,外部数组表示每批任务是串行执行的
任务执行器会按照顺序和串并行情况依次调用相应的任务脚本。
# 如节点A关闭nova-api任务
调用stop_nova_api.py脚本
任务执行器完成所有任务脚本后,上报任务执行结果给任务状态执行器。
# 全部成功时的上报
{
节点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表示这个任务还未执行时,前置任务已经失败了
任务状态执行器接受上报后,直接将消息保存在数据库中
此处是旧框架的一个性能瓶颈,主要是保存前后做了很多数据处理
新框架在此处只有保存动作,可以提升性能
工步3下发任务后,任务下发器会定期查询数据库,是否所有下发任务已经完成。当查询到所有任务执行成功后,则继续下发下一批节点任务(存在失败则中断升级)。重复工步3-8,直到当前计划书中所有任务执行完成。
查询: 升级过程中前端需要查询升级进度时,查询器会直接从数据库查询,从而与升级执行逻辑解耦。详细见[查询器详细设计]
升级工程是由一个个小粒度的任务按照顺序组成的。只要做到任意单任务的升级回退,就可以完成任意组合的升级回退。
场景二: A节点升级后调测失败
旧框架:
场景一:
现象:现网升级中,某个组件在某个节点数据割接出现了问题,导致组件启动后异常。需要先恢复环境,再规避继续升级。
旧框架:
方法1: 整体回退,整个环境回退到升级前。再修改配置转换逻辑代码,再次从头走升级后逻辑。时间太长,现网基本不会采用。
预期规避时间: 10小时以上
方法2: 人工处理割接后的数据,将数据逐一改成正确数据。规避时间长,人工操作易出错。
预期规避时间: 30分钟
新框架:
单独回退该组件该节点的数据割接任务。修改配置转换逻辑代码。重试数据割接任务。
由于只需要回退重试单个小任务,规避时间缩短。
第一次修改配置转换逻辑代码时间30分钟,规避时间5分钟
以后
升级(或回退)过程中,随时可以添加新组件,进行并行升级。
这里框架只提供底层支持,具体是否可以并行还要看任务或组件之间的依赖关系。
新框架中,升级的数据分为静态和动态数据两种。静态数据如升级信息、部署信息,这部分信息是升级前收集的,升级中保持不变,所以没有并发问题。动态数据是任务状态,由于划分了细粒度任务,新添加组件的升级任务与旧组件的升级任务是分开的所以互不影响。
todo 图:A工程,B工程共享数据,分别状态
应用场景:
A组件升级过程中,创建新工程升级B组件。参考公有云并行升级。
新框架的任务的粒度是基于instance的,所以每个instance都可以指定是否升级,升级的目标版本是什么。
如下文场景1,实现方法
应用场景:
控制节点和计算节点的hostos版本是r8,用户想将某几个计算节点升级到r9,其它节点保留在r8以节省升级时间
同一组件两个版本归一
同一组件一个版本分化为两个
支持N-X升级的升级框架支持
管理组件随hostos在备区升级后重启生效;同时保留管理面重启单独组件升级方式
待补充
升级逻辑应该是简单的,实现是过分复杂的。
新升级框架整体架构、模块划分、核心模块交互、主要数据结构等设计 模块开发顺序、测试方案等设计
设计书内部评审
按模块分工给具体开发人员,主要有以下四个模块
与各个模块开发人员对齐模块设计方案和接口
四大模块并行开发 -> 联调 -> 对接ci\端到端环境验证
最小的原子化升级任务,一般是指对某个instance进行某个操作。如“节点1的nova-api升级rpm包”
构成元素是host_target_action:
host:
一般指执行的节点, 如“host1”。全局任务下host为特殊节点“upg-server”。
target:
任务对象,一般为某个template,如“nova-api”。
action:
任务动作,如“stop”,“up-rpm”,“start”。
上文“节点1的nova-api升级rpm包”可表示为“host1_nova-api_up-rpm”
action是原子化细粒度不可分割的。
旧框架:
任务是粗粒度、非原子性的。如“升级执行”任务,包括了多批组件的启停、rpm升级、修改配置等多个小任务。
分批关闭组件 -> 修改配置 -> rpm升级 -> 分批开启组件
假如rpm升级工步失败,
系统只显示“升级执行失败”,需要排除日志才能定位出错点
重试时,需要先重试分批关闭组件、修改配置两个前置工步,才能重试rpm升级
重试时,pkg内组件无法区分。例如控制节点FusionPlatFrom包内20个组件, 只有一个组件失败了,必须20个组件一起重试
新框架:
任务是细粒度、原子性的。任务要么全部成功,要么全部失败。
假如rpm升级工步失败,
1. 系统直接显示“A节点B组件rpm升级失败” 2. 重试时,可以直接重试“A节点B组件rpm升级失败”,无需冗余重试前置工步和相关组件
原子性的其它优点这里不赘述。
某个节点一次执行的任务。结构上是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个组件。
某个节点一次执行的任务。结构上是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的内容和执行顺序
灵活升级 细粒度任务独立回退升级, 任意小工步升级失败后可以独立回退。并重试或添加规避代码后重试
单工步调试:开发过程中可以单工步调试,且无需依赖前后工步。如单独调试升级配置转换(这是一线人员重点要求添加的功能)
影响: 开发中的测试效率, 生产中的规避效率
并行升级 工程一升级AB组件的同时,工程二可以并行升级无依赖的CD组件
影响: 升级效率,升级灵活性
多版本升级 支持一个版本的组件升级到不同版本,如控制节点升级OS,计算节点不升级OS。控制节点升级到X版本Nova,计算节点升级到Y版本Nova
N-X升级 支持N-X升级的升级框架支持 a) 提供管理面备区升级能力,管理组件随hostos在备区升级后重启生效;同时保留管理面重启单独组件升级方式
可视化升级(未来特性) 升级前可以生成整个升级计划书,供用户和技术人员检视,并可以根据需要灵活修改。升级过程不再是盲盒
并行化 编排、任务下发、任务上报接收、任务执行、查询进度都可以用独立进程执行,其中任务下发、任务上报接收、任务执行、查询进度各自可以用并发多进程
大规模 可以支持10k节点升级,升级框架本身不再是性能瓶颈。提供相关性能分析接口,在开发过程中就能自动分析各升级组件和外部接口性能。
升级脚本易用性提升
统一升级脚本,提供可读的升级SDK(整理常用接口, 隔离内外接口,减少参数),提高开发效率。
同时提供升级脚本单独调试功能。
这是各组件升级开发人员和升级接口人重点要求改善的特性。
todo
详细见下文“新旧框架对比”章节
好的架构逻辑清晰,坏的架构一言难尽
本文可以详细描述新框架的架构逻辑。但是无法详细说明旧框架的架构逻辑,原因就是旧框架是混乱的无法简单梳理清楚的。
既然旧框架无法说明清楚,如何保证新框架和旧框架的一致?
升级框架是一个服务,只要对外接口和对外特性保持一致即可。内部实现的变更是不需要一一对应验证的。
与旧框架对比见下文[旧框架图]
与旧框架相同, 按照以下顺序
分发 -> 检查 -> 管理面升级 -> 网络升级 -> 数据面升级-> 提交
这部分逻辑写在新框架的[[逻辑书]](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新增模块
以最典型的升级大任务--管理面升级为例
接收外部升级命令"管理面升级执行"
根据命令[[[[任务截取器]](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维数组, 内层数组表示里面的任务是同一批并行执行的,外部数组表示每批任务是串行执行的
任务下发器下发批节点任务。首先下发的是"关闭第1批组件"
[节点A关闭第1批组件, 节点B关闭第1批组件, ...]
既将"节点A关闭第1批组件"下发给节点A,"节点B关闭第1批组件"下发给节点B...
任务执行器接收下发的节点任务。以节点A为例,此时节点任务"节点A关闭第1批组件"的内容是
[
[节点A关闭nova-api, 节点A关闭swift-ngnix, ...]
[节点A关闭nova-console, ... ]
...
]
# 注意到这里是2维数组, 内层数组表示里面的任务是同一批并行执行的,外部数组表示每批任务是串行执行的
任务执行器会按照顺序和串并行情况依次调用相应的任务脚本。
# 如节点A关闭nova-api任务
调用stop_nova_api.py脚本
任务执行器完成所有任务脚本后,上报任务执行结果给任务状态执行器。
# 全部成功时的上报
{
节点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表示这个任务还未执行时,前置任务已经失败了
任务状态执行器接受上报后,直接将消息保存在数据库中
此处是旧框架的一个性能瓶颈,主要是保存前后做了很多数据处理
新框架在此处只有保存动作,可以提升性能
工步3下发任务后,任务下发器会定期查询数据库,是否所有下发任务已经完成。当查询到所有任务执行成功后,则继续下发下一批节点任务(存在失败则中断升级)。重复工步3-8,直到当前计划书中所有任务执行完成。
查询: 升级过程中前端需要查询升级进度时,查询器会直接从数据库查询,从而与升级执行逻辑解耦。详细见[查询器详细设计]
升级工程是由一个个小粒度的任务按照顺序组成的。只要做到任意单任务的升级回退,就可以完成任意组合的升级回退。
场景二: A节点升级后调测失败
旧框架:
场景一:
现象:现网升级中,某个组件在某个节点数据割接出现了问题,导致组件启动后异常。需要先恢复环境,再规避继续升级。
旧框架:
方法1: 整体回退,整个环境回退到升级前。再修改配置转换逻辑代码,再次从头走升级后逻辑。时间太长,现网基本不会采用。
预期规避时间: 10小时以上
方法2: 人工处理割接后的数据,将数据逐一改成正确数据。规避时间长,人工操作易出错。
预期规避时间: 30分钟
新框架:
单独回退该组件该节点的数据割接任务。修改配置转换逻辑代码。重试数据割接任务。
由于只需要回退重试单个小任务,规避时间缩短。
第一次修改配置转换逻辑代码时间30分钟,规避时间5分钟
以后
升级(或回退)过程中,随时可以添加新组件,进行并行升级。
这里框架只提供底层支持,具体是否可以并行还要看任务或组件之间的依赖关系。
新框架中,升级的数据分为静态和动态数据两种。静态数据如升级信息、部署信息,这部分信息是升级前收集的,升级中保持不变,所以没有并发问题。动态数据是任务状态,由于划分了细粒度任务,新添加组件的升级任务与旧组件的升级任务是分开的所以互不影响。
todo 图:A工程,B工程共享数据,分别状态
应用场景:
A组件升级过程中,创建新工程升级B组件。参考公有云并行升级。
新框架的任务的粒度是基于instance的,所以每个instance都可以指定是否升级,升级的目标版本是什么。
如下文场景1,实现方法
应用场景:
控制节点和计算节点的hostos版本是r8,用户想将某几个计算节点升级到r9,其它节点保留在r8以节省升级时间
同一组件两个版本归一
同一组件一个版本分化为两个
支持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)直接访问数据库,执行写-任务状态操作。
新框架在升级前新增了升级前信息收集工步。主要收集两种数据:
升级逻辑应该是简单的,实现是过分复杂的。
新升级框架整体架构、模块划分、核心模块交互、主要数据结构等设计 模块开发顺序、测试方案等设计
设计书内部评审
按模块分工给具体开发人员,主要有以下四个模块
与各个模块开发人员对齐模块设计方案和接口
四大模块并行开发 -> 联调 -> 对接ci\端到端环境验证
最小的原子化升级任务,一般是指对某个instance进行某个操作。如“节点1的nova-api升级rpm包”
构成元素是host_target_action:
host:
一般指执行的节点, 如“host1”。全局任务下host为特殊节点“upg-server”。
target:
任务对象,一般为某个template,如“nova-api”。
action:
任务动作,如“stop”,“up-rpm”,“start”。
上文“节点1的nova-api升级rpm包”可表示为“host1_nova-api_up-rpm”
action是原子化细粒度不可分割的。
旧框架:
任务是粗粒度、非原子性的。如“升级执行”任务,包括了多批组件的启停、rpm升级、修改配置等多个小任务。
分批关闭组件 -> 修改配置 -> rpm升级 -> 分批开启组件
假如rpm升级工步失败,
系统只显示“升级执行失败”,需要排除日志才能定位出错点
重试时,需要先重试分批关闭组件、修改配置两个前置工步,才能重试rpm升级
重试时,pkg内组件无法区分。例如控制节点FusionPlatFrom包内20个组件, 只有一个组件失败了,必须20个组件一起重试
新框架:
任务是细粒度、原子性的。任务要么全部成功,要么全部失败。
假如rpm升级工步失败,
1. 系统直接显示“A节点B组件rpm升级失败” 2. 重试时,可以直接重试“A节点B组件rpm升级失败”,无需冗余重试前置工步和相关组件
原子性的其它优点这里不赘述。
某个节点一次执行的任务。结构上是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个组件。
某个节点一次执行的任务。结构上是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的内容和执行顺序
灵活升级 细粒度任务独立回退升级, 任意小工步升级失败后可以独立回退。并重试或添加规避代码后重试
单工步调试:开发过程中可以单工步调试,且无需依赖前后工步。如单独调试升级配置转换
详细设计: 灵活升级特性设计
旧框架实现对比: [旧框架设计]
影响: 开发中的测试效率, 生产中的规避效率
并行升级 工程一升级AB组件的同时,工程二可以并行升级无依赖的CD组件
详细设计: 并行升级特性设计
旧框架实现对比: [旧框架设计]
影响: 升级效率,升级灵活性
多版本升级 支持一个版本的组件升级到不同版本,如控制节点升级OS,计算节点不升级OS。控制节点升级到X版本Nova,计算节点升级到Y版本Nova
N-X升级 支持N-X升级的升级框架支持 a) 提供管理面备区升级能力,管理组件随hostos在备区升级后重启生效;同时保留管理面重启单独组件升级方式
可视化升级(未来特性) 升级前可以生成整个升级计划书,供用户和技术人员检视,并可以根据需要灵活修改。升级过程不再是盲盒
详细见下文“新旧框架对比”章节
好的架构逻辑清晰,坏的架构一言难尽
本文可以详细描述新框架的架构逻辑。但是无法详细说明旧框架的架构逻辑,原因就是旧框架是混乱的无法简单梳理清楚的。
既然旧框架无法说明清楚,如何保证新框架和旧框架的一致?
升级框架是一个服务,只要对外接口和对外特性保持一致即可。内部实现的变更是不需要一一对应验证的。
详细见下文[模块设计]章节
与旧框架对比见下文[旧框架图]
与旧框架相同, 按照以下顺序
分发 -> 检查 -> 管理面升级 -> 网络升级 -> 数据面升级-> 提交
这部分逻辑写在新框架的[[逻辑书]](# 逻辑书详细设计)中, 未来可以通过改变逻辑书来修改升级顺序。
与旧框架保持相同
[信息收集 -> 依次]
Note新增模块
以最典型的升级大任务--管理面升级为例
接收外部升级命令"管理面升级执行"
根据命令[任务截取器]从[全局计划书]截取[当前计划书],此时当前计划书的内容为
[
[节点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维数组, 内层数组表示里面的任务是同一批并行执行的,外部数组表示每批任务是串行执行的
任务下发器下发批节点任务。首先下发的是"关闭第1批组件"
[节点A关闭第1批组件, 节点B关闭第1批组件, ...]
既将"节点A关闭第1批组件"下发给节点A,"节点B关闭第1批组件"下发给节点B...
任务执行器接收下发的节点任务。以节点A为例,此时节点任务"节点A关闭第1批组件"的内容是
[
[节点A关闭nova-api, 节点A关闭swift-ngnix, ...]
[节点A关闭nova-console, ... ]
...
]
# 注意到这里是2维数组, 内层数组表示里面的任务是同一批并行执行的,外部数组表示每批任务是串行执行的
任务执行器会安装顺序和串并行情况依次调用相应的任务脚本
升级工程是由一个个小粒度的任务按照顺序组成的。只要做到任意单任务的升级回退,就可以完成任意组合的升级回退。
场景二: A节点升级后调测失败
旧框架:
场景一:
现象:现网升级中,某个组件在某个节点数据割接出现了问题,导致组件启动后异常。需要先恢复环境,再规避继续升级。
旧框架:
方法1: 整体回退,整个环境回退到升级前。再修改配置转换逻辑代码,再次从头走升级后逻辑。时间太长,现网基本不会采用。
预期规避时间: 10小时以上
方法2: 人工处理割接后的数据,将数据逐一改成正确数据。规避时间长,人工操作易出错。
预期规避时间: 30分钟
新框架:
单独回退该组件该节点的数据割接任务。修改配置转换逻辑代码。重试数据割接任务。
由于只需要回退重试单个小任务,规避时间缩短。
第一次修改配置转换逻辑代码时间30分钟,规避时间5分钟
以后
升级逻辑应该是简单的,实现是过分复杂的。
新升级框架整体架构、模块划分、核心模块交互、主要数据结构等设计 模块开发顺序、测试方案等设计
设计书内部评审
按模块分工给具体开发人员,主要有以下四个模块
与各个模块开发人员对齐模块设计方案和接口
四大模块并行开发 -> 联调 -> 对接ci\端到端环境验证
最小的原子化升级任务,一般是指对某个instance进行某个操作。如“节点1的nova-api升级rpm包”
构成元素是host_target_action:
host:
一般指执行的节点, 如“host1”。全局任务下host为特殊节点“upg-server”。
target:
任务对象,一般为某个template,如“nova-api”。
action:
任务动作,如“stop”,“up-rpm”,“start”。
上文“节点1的nova-api升级rpm包”可表示为“host1_nova-api_up-rpm”
action是原子化细粒度不可分割的。
旧框架:
任务是粗粒度、非原子性的。如“升级执行”任务,包括了多批组件的启停、rpm升级、修改配置等多个小任务。
分批关闭组件 -> 修改配置 -> rpm升级 -> 分批开启组件
假如rpm升级工步失败,
系统只显示“升级执行失败”,需要排除日志才能定位出错点
重试时,需要先重试分批关闭组件、修改配置两个前置工步,才能重试rpm升级
重试时,pkg内组件无法区分。例如控制节点FusionPlatFrom包内20个组件, 只有一个组件失败了,必须20个组件一起重试
新框架:
任务是细粒度、原子性的。任务要么全部成功,要么全部失败。
假如rpm升级工步失败,
1. 系统直接显示“A节点B组件rpm升级失败” 2. 重试时,可以直接重试“A节点B组件rpm升级失败”,无需冗余重试前置工步和相关组件
原子性的其它优点这里不赘述。
某个节点一次执行的任务。结构上是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个组件。
某个节点一次执行的任务。结构上是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的内容和执行顺序
灵活升级 细粒度任务独立回退升级, 任意小工步升级失败后可以独立回退。并重试或添加规避代码后重试
单工步调试:开发过程中可以单工步调试,且无需依赖前后工步。如单独调试升级配置转换(这是一线人员重点要求添加的功能)
影响: 开发中的测试效率, 生产中的规避效率
并行升级 工程一升级AB组件的同时,工程二可以并行升级无依赖的CD组件
影响: 升级效率,升级灵活性
多版本升级 支持一个版本的组件升级到不同版本,如控制节点升级OS,计算节点不升级OS。控制节点升级到X版本Nova,计算节点升级到Y版本Nova
N-X升级 支持N-X升级的升级框架支持 a) 提供管理面备区升级能力,管理组件随hostos在备区升级后重启生效;同时保留管理面重启单独组件升级方式
可视化升级(未来特性) 升级前可以生成整个升级计划书,供用户和技术人员检视,并可以根据需要灵活修改。升级过程不再是盲盒
并行化 编排、任务下发、任务上报接收、任务执行、查询进度都可以用独立进程执行,其中任务下发、任务上报接收、任务执行、查询进度各自可以用并发多进程
大规模 可以支持10k节点升级,升级框架本身不再是性能瓶颈。提供相关性能分析接口,在开发过程中就能自动分析各升级组件和外部接口性能。
升级脚本易用性提升
统一升级脚本,提供可读的升级SDK(整理常用接口, 隔离内外接口,减少参数),提高开发效率。
同时提供升级脚本单独调试功能。
这是各组件升级开发人员和升级接口人重点要求改善的特性。
todo
详细见下文“新旧框架对比”章节
好的架构逻辑清晰,坏的架构一言难尽
本文可以详细描述新框架的架构逻辑。但是无法详细说明旧框架的架构逻辑,原因就是旧框架是混乱的无法简单梳理清楚的。
既然旧框架无法说明清楚,如何保证新框架和旧框架的一致?
升级框架是一个服务,只要对外接口和对外特性保持一致即可。内部实现的变更是不需要一一对应验证的。
与旧框架对比见下文[旧框架图]
与旧框架相同, 按照以下顺序
分发 -> 检查 -> 管理面升级 -> 网络升级 -> 数据面升级-> 提交
这部分逻辑写在新框架的[[逻辑书]](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新增模块
以最典型的升级大任务--管理面升级为例
接收外部升级命令"管理面升级执行"
根据命令[[任务截取器]](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维数组, 内层数组表示里面的任务是同一批并行执行的,外部数组表示每批任务是串行执行的
任务下发器下发批节点任务。首先下发的是"关闭第1批组件"
[节点A关闭第1批组件, 节点B关闭第1批组件, ...]
既将"节点A关闭第1批组件"下发给节点A,"节点B关闭第1批组件"下发给节点B...
任务执行器接收下发的节点任务。以节点A为例,此时节点任务"节点A关闭第1批组件"的内容是
[
[节点A关闭nova-api, 节点A关闭swift-ngnix, ...]
[节点A关闭nova-console, ... ]
...
]
# 注意到这里是2维数组, 内层数组表示里面的任务是同一批并行执行的,外部数组表示每批任务是串行执行的
任务执行器会按照顺序和串并行情况依次调用相应的任务脚本。
# 如节点A关闭nova-api任务
调用stop_nova_api.py脚本
任务执行器完成所有任务脚本后,上报任务执行结果给任务状态执行器。
# 全部成功时的上报
{
节点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表示这个任务还未执行时,前置任务已经失败了
任务状态执行器接受上报后,直接将消息保存在数据库中
此处是旧框架的一个性能瓶颈,主要是保存前后做了很多数据处理
新框架在此处只有保存动作,可以提升性能
工步3下发任务后,任务下发器会定期查询数据库,是否所有下发任务已经完成。当查询到所有任务执行成功后,则继续下发下一批节点任务(存在失败则中断升级)。重复工步3-8,直到当前计划书中所有任务执行完成。
查询: 升级过程中前端需要查询升级进度时,查询器会直接从数据库查询,从而与升级执行逻辑解耦。详细见[查询器详细设计]
升级工程是由一个个小粒度的任务按照顺序组成的。只要做到任意单任务的升级回退,就可以完成任意组合的升级回退。
场景二: A节点升级后调测失败
旧框架:
场景一:
现象:现网升级中,某个组件在某个节点数据割接出现了问题,导致组件启动后异常。需要先恢复环境,再规避继续升级。
旧框架:
方法1: 整体回退,整个环境回退到升级前。再修改配置转换逻辑代码,再次从头走升级后逻辑。时间太长,现网基本不会采用。
预期规避时间: 10小时以上
方法2: 人工处理割接后的数据,将数据逐一改成正确数据。规避时间长,人工操作易出错。
预期规避时间: 30分钟
新框架:
单独回退该组件该节点的数据割接任务。修改配置转换逻辑代码。重试数据割接任务。
由于只需要回退重试单个小任务,规避时间缩短。
第一次修改配置转换逻辑代码时间30分钟,规避时间5分钟
以后
升级(或回退)过程中,随时可以添加新组件,进行并行升级。
这里框架只提供底层支持,具体是否可以并行还要看任务或组件之间的依赖关系。
新框架中,升级的数据分为静态和动态数据两种。静态数据如升级信息、部署信息,这部分信息是升级前收集的,升级中保持不变,所以没有并发问题。动态数据是任务状态,由于划分了细粒度任务,新添加组件的升级任务与旧组件的升级任务是分开的所以互不影响。
todo 图:A工程,B工程共享数据,分别状态
应用场景:
A组件升级过程中,创建新工程升级B组件。参考公有云并行升级。
新框架的任务的粒度是基于instance的,所以每个instance都可以指定是否升级,升级的目标版本是什么。
如下文场景1,实现方法
应用场景:
控制节点和计算节点的hostos版本是r8,用户想将某几个计算节点升级到r9,其它节点保留在r8以节省升级时间
同一组件两个版本归一
同一组件一个版本分化为两个
支持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)直接访问数据库,执行写-任务状态操作。
新框架在升级前新增了升级前信息收集工步。主要收集两种数据:
部署数据从cps接口获取,升级数据通过升级描述文件获取。
升级过程中其它模块通过统一低代码接口调用。
这个新模块的优点是,避免了每个工步单独实现数据处理。详见[旧框架设计-内部数据管理]
这些数据都是静态数据,避免了
输入: Mission
执行:
输出: Mission执行结果
# 情况1,所有Task成功。输出如下
{
done: [t1, t2...]
}
# 情况1,执行中出现Task失败。输出如下
{
done: [t1, t2...], # 已执行,且成功
fail: [t3, t4], # 已执行,但失败
undo: [t5, t6, ...] # 未执行
}
从当前计划书获取当前计划,称为Mission。将当前计划书传给Mission执行器。
Mission执行器依次将Mission中的子BatchJob传给BatchJob执行器。
Mission是BatchJob的1维数组,其中的BatchJob顺序执行
BatchJob执行器将BatchJob的子Job发送给对应节点(通过Job发送器)
下发任务完成后,BatchJob执行器将BatchJob的子Task传给Task监控器监控。
Task监控器会定时查询数据库,检查是否所有Task完成。完成后通知BatchJob执行器。
BatchJob执行器将BatchJob执行结果上报给Mission执行器。
Mission执行器重复2-6,直到Mission执行完成
根据升级信息和升级逻辑书,生成升级工程的所有升级任务。示例如下:
# 输入
部署数据:节点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,生成任务计划书。示例如下:
# 输入
部署数据:节点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]]],
]
# 计划书数据结构设计见附录
便于开发:编排和执行逻辑解耦,有关执行顺序的升级新特性只需要修改逻辑书。新特性包括但不限于:管理面备区升级,网络单独升级
便于测试:
可以手动或脚本生成任务计划书,进行特化的升级测试,如:
# 手动编写计划书,跳过检查阶段,只升级节点A的X组件
[[[
[A_X_dispatch],
[A_X_stop],
[A_X_uprpm],
[A_X_start]
]]]
可以临时添加任务,或调整任务顺序。如添加新任务,任务中构造异常,以自动化测试异常情况下的升级。
便于检视
可以通过检视逻辑书或计划书的方式,详细了解升级顺序。如:
当前升级实际操作中,升级不是自动化一次性跑完的。需要根据前端命令,一次执行部分任务。
截取器是根据前端命令和全局计划书,截取当前计划书的模块。
主要操作是根据前端命令从全局计划书中找到相应的任务,再保留任务间的顺序生成当前计划书。
# 前端命令升级节点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_
依赖
大部分业务逻辑依赖于多个数据接口实现,造成了开发、测试、定位困难
#
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()
新升级框架整体架构、模块划分、核心模块交互、主要数据结构等设计 模块开发顺序、测试方案等设计
设计书内部评审
按模块分工给具体开发人员,主要有以下四个模块
与各个模块开发人员对齐模块设计方案和接口
四大模块并行开发 -> 联调 -> 对接ci\端到端环境验证
可以,因为
最小的原子化升级任务,一般是指对某个instance进行某个操作。如“节点1的nova-api升级rpm包”
构成元素是host_target_action:
host:
一般指执行的节点, 如“host1”。全局任务下host为特殊节点“upg-server”。
target:
任务对象,一般为某个template,如“nova-api”。
action:
任务动作,如“stop”,“up-rpm”,“start”。
上文“节点1的nova-api升级rpm包”可表示为“host1_nova-api_up-rpm”
action是原子化细粒度不可分割的。
旧框架:
任务是粗粒度、非原子性的。如“升级执行”任务,包括了多批组件的启停、rpm升级、修改配置等多个小任务。
分批关闭组件 -> 修改配置 -> rpm升级 -> 分批开启组件
假如rpm升级工步失败,
系统只显示“升级执行失败”,需要排除日志才能定位出错点
重试时,需要先重试分批关闭组件、修改配置两个前置工步,才能重试rpm升级
重试时,pkg内组件无法区分。例如控制节点FusionPlatFrom包内20个组件, 只有一个组件失败了,必须20个组件一起重试
新框架:
任务是细粒度、原子性的。任务要么全部成功,要么全部失败。
假如rpm升级工步失败,
1. 系统直接显示“A节点B组件rpm升级失败” 2. 重试时,可以直接重试“A节点B组件rpm升级失败”,无需冗余重试前置工步和相关组件
原子性的其它优点这里不赘述。
某个节点一次执行的任务。结构上是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个组件。
某个节点一次执行的任务。结构上是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的内容和执行顺序
灵活升级 细粒度任务独立回退升级, 任意小工步升级失败后可以独立回退。并重试或添加规避代码后重试
单工步调试:开发过程中可以单工步调试,且无需依赖前后工步。如单独调试升级配置转换
详细设计: 灵活升级特性设计
旧框架实现对比: [旧框架设计]
影响: 开发中的测试效率, 生产中的规避效率
并行升级 工程一升级AB组件的同时,工程二可以并行升级无依赖的CD组件
详细设计: 并行升级特性设计
旧框架实现对比: [旧框架设计]
影响: 升级效率,升级灵活性
多版本升级 支持一个版本的组件升级到不同版本,如控制节点升级OS,计算节点不升级OS。控制节点升级到X版本Nova,计算节点升级到Y版本Nova
N-X升级 支持N-X升级的升级框架支持 a) 提供管理面备区升级能力,管理组件随hostos在备区升级后重启生效;同时保留管理面重启单独组件升级方式
可视化升级(未来特性) 升级前可以生成整个升级计划书,供用户和技术人员检视,并可以根据需要灵活修改。升级过程不再是盲盒
详细见下文“新旧框架对比”章节
好的架构逻辑清晰,坏的架构一言难尽
本文可以详细描述新框架的架构逻辑。但是无法详细说明旧框架的架构逻辑,原因就是旧框架是混乱的无法简单梳理清楚的。
既然旧框架无法说明清楚,如何保证新框架和旧框架的一致?
升级框架是一个服务,只要对外接口和对外特性保持一致即可。内部实现的变更是不需要一一对应验证的。
详细见下文[模块设计]章节
与旧框架对比见下文[旧框架图]
与旧框架相同, 按照以下顺序
分发 -> 检查 -> 管理面升级 -> 网络升级 -> 数据面升级-> 提交
这部分逻辑写在新框架的[[逻辑书]](# 逻辑书详细设计)中, 未来可以通过改变逻辑书来修改升级顺序。
与旧框架保持相同
[信息收集 -> 依次]
Note新增模块
以最典型的升级大任务--管理面升级为例
接收外部升级命令"管理面升级执行"
根据命令[任务截取器]从[全局计划书]截取[当前计划书],此时当前计划书的内容为
[
[节点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维数组, 内层数组表示里面的任务是同一批并行执行的,外部数组表示每批任务是串行执行的
任务下发器下发批节点任务。首先下发的是"关闭第1批组件"
[节点A关闭第1批组件, 节点B关闭第1批组件, ...]
既将"节点A关闭第1批组件"下发给节点A,"节点B关闭第1批组件"下发给节点B...
任务执行器接收下发的节点任务。以节点A为例,此时节点任务"节点A关闭第1批组件"的内容是
[
[节点A关闭nova-api, 节点A关闭swift-ngnix, ...]
[节点A关闭nova-console, ... ]
...
]
# 注意到这里是2维数组, 内层数组表示里面的任务是同一批并行执行的,外部数组表示每批任务是串行执行的
任务执行器会安装顺序和串并行情况依次调用相应的任务脚本
升级工程是由一个个小粒度的任务按照顺序组成的。只要做到任意单任务的升级回退,就可以完成任意组合的升级回退。
场景二: A节点升级后调测失败
旧框架:
场景一:
现象:现网升级中,某个组件在某个节点数据割接出现了问题,导致组件启动后异常。需要先恢复环境,再规避继续升级。
旧框架:
方法1: 整体回退,整个环境回退到升级前。再修改配置转换逻辑代码,再次从头走升级后逻辑。时间太长,现网基本不会采用。
预期规避时间: 10小时以上
方法2: 人工处理割接后的数据,将数据逐一改成正确数据。规避时间长,人工操作易出错。
预期规避时间: 30分钟
新框架:
单独回退该组件该节点的数据割接任务。修改配置转换逻辑代码。重试数据割接任务。
由于只需要回退重试单个小任务,规避时间缩短。
第一次修改配置转换逻辑代码时间30分钟,规避时间5分钟
以后
升级逻辑应该是简单的,实现是过分复杂的。
新升级框架整体架构、模块划分、核心模块交互、主要数据结构等设计 模块开发顺序、测试方案等设计
设计书内部评审
按模块分工给具体开发人员,主要有以下四个模块
与各个模块开发人员对齐模块设计方案和接口
四大模块并行开发 -> 联调 -> 对接ci\端到端环境验证
最小的原子化升级任务,一般是指对某个instance进行某个操作。如“节点1的nova-api升级rpm包”
构成元素是host_target_action:
host:
一般指执行的节点, 如“host1”。全局任务下host为特殊节点“upg-server”。
target:
任务对象,一般为某个template,如“nova-api”。
action:
任务动作,如“stop”,“up-rpm”,“start”。
上文“节点1的nova-api升级rpm包”可表示为“host1_nova-api_up-rpm”
action是原子化细粒度不可分割的。
旧框架:
任务是粗粒度、非原子性的。如“升级执行”任务,包括了多批组件的启停、rpm升级、修改配置等多个小任务。
分批关闭组件 -> 修改配置 -> rpm升级 -> 分批开启组件
假如rpm升级工步失败,
系统只显示“升级执行失败”,需要排除日志才能定位出错点
重试时,需要先重试分批关闭组件、修改配置两个前置工步,才能重试rpm升级
重试时,pkg内组件无法区分。例如控制节点FusionPlatFrom包内20个组件, 只有一个组件失败了,必须20个组件一起重试
新框架:
任务是细粒度、原子性的。任务要么全部成功,要么全部失败。
假如rpm升级工步失败,
1. 系统直接显示“A节点B组件rpm升级失败” 2. 重试时,可以直接重试“A节点B组件rpm升级失败”,无需冗余重试前置工步和相关组件
原子性的其它优点这里不赘述。
某个节点一次执行的任务。结构上是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个组件。
某个节点一次执行的任务。结构上是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的内容和执行顺序
外部诉求
内部诉求
历史问题
开发需求
框架设计
风险
验证
保证继承
计划
50k / 2k = 25
模块划分
阶段
主要改动:升级粒度缩小至instance,task系统,数据整理
次要改动:升级框架重新部署,脚本易用性,统一数据接口,简化回退,简化upg-server数据处理,模块解耦
概况:继承现有流程,去除历史问题,开放未来接口
目标:
大规模性能提高5倍
现网升级Bug减少至1/2
升级框架新需求开发工作量减少至1/3
升级时长减少
版本交付周期缩短,其它组件升级开发测试时间减少,开发单次测试从2天->2小时
数据接口
升级框架内部调用Biz类, Biz类调用Api类, Api类调用cps接口
实现升级框架内部逻辑与外部Api解耦
统一基类BaseApi实现底层网络处理、安全检查
前后脚本
脚本涉及到各域适配,将在后期实现。先期将适配旧脚本方案。
注册脚本
注册方式更灵活直观
可注册阶段
任何task前后:client端某组件分发、关闭、启动,server
注册方法
升级包中特定文件夹内继承基类BaseScript即可注册
运行脚本
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。升级
升级编排生成计划书
升级收集信息和分发后,server测已获得所有升级数据。此时可以生成升级计划书(如上图)。所有task和相关执行顺序都记录在计划书中。后续upg-server仅通过计划书顺序执行
task属性
每一条task在server侧和client都有记录,client侧保存在本地sqlite,server侧保存在gaussdb。通过client侧上报保持一致。少数server未成功client成功的情况,server可以再次下发任务。
Server执行计划书
server需要实现以下功能:
新升级编排优点
升级工步
升级工步继承了原有工步,主要改动是
升级框架部署
升级框架不再常驻在环境中,仅在升级前由FCCI通过cps部署
信息收集
client测自主调用cps接口、读取本地文件等方式收集节点信息并缓存。如有信息变动(主要是template变更),需要手动触发再次收集。一切可以在升级前收集的信息都需要在此时收集。
分发
//类似,与旧分发不同的是,新分发不再分template进行分发
检查
未来会提供SDK方便各组件开发。
管理面升级
未来会梳理进件Batch(管理面组件启停批次),缩短启停时间
提交(清理环境)
新增升级框架自身清理和本次升级数据收集。
Server结构
线程
模块
Client结构
升级工步逻辑:分发、执行...所有升级书据在client本地
模块
数据
升级是数据和状态的管理。
静态数据
升级前环境信息,升级后环境信息,这两者都可以在升级前信息收集阶段收集(后者可以通过前者推导得出)
由于是静态信息可以在数据库、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回退功能实现