- Python 87.4%
- Shell 8.3%
- Jinja 4.3%
这一条不来自 0430→0716 升级文档,是补差异:ansible-x-p 部署模板已带上这两个 key(f9e29ee),所以新装环境自带;存量环境的 nacos 里没有,不补前端岗位职责选项 是空的。 走 nacos-patch-lines.yml 而不是 nacos-patch.yml 的 set —— set 会把 config.duty 按点拆成父/子去现网找 config 块,现网没有该块时直接 error。部署模板用的是扁平 key 写法,所以这里用 lineinfile + insertafter EOF,与模板字面一致。 另加前置断言:现网若已存在嵌套式 config 块则停下让人工看 —— 扁平与嵌套并存时 YAML 合法不报错,但 Spring 绑定谁生效不确定,事后极难定位。 幂等已验证(复现 lineinfile 语义连跑三遍):第1遍 changed 追加两行,第2/3遍 skipped 走"无变化则不发布"分支,内容与第1遍完全一致,每条 regexp 恰好命中一行。 regexp 交叉隔离已验:config.duty 不误伤 config.dutyEn / 注释行 / dutyExtra。 两个值与 ansible-x-p 模板逐字节一致(sha256 相同),新装与升级环境无差异。 |
||
|---|---|---|
| callback_plugins | ||
| docs/history | ||
| group_vars | ||
| inventory | ||
| lib | ||
| versions | ||
| .gitignore | ||
| ansible.cfg | ||
| config.yaml.edge.example | ||
| config.yaml.example | ||
| README.md | ||
| run-all.sh | ||
| run-dtx-migration.yml | ||
| show-nacos-config.yml | ||
| upgrade.yaml | ||
x-cloud-upgrade
X 平台已部署环境的版本升级仓库。
ansible-x-p / ansible-edge-p 负责把一个环境装出来,本仓库负责把一个
已经在跑的环境从旧版本升到新版本 —— nacos 配置、configmap、数据库脚本、
redis 缓存、业务接口调用、云边同步任务,按升级文档一条条落成可重复执行的
playbook。
一、它解决什么问题
每次发版都会来三份文档(《统一集成文档》《配置更新-云》《配置更新-边》), 里面是几十条散落的操作:改哪个 nacos 配置的哪一行、往哪个库跑哪个 SQL、 调哪个接口传哪个 excel、哪些步骤只有 Saas 环境要做。
人工照着做的问题不是"做不到",而是:
- 顺序错了不报错。镜像后的 SQL 在镜像更新前跑掉了,服务照样起, 过几天才从奇怪现象反推回来。
- 漏做一步不报错。h5 包的 downloadUrl 忘了改、水印开关忘了打开、 某个镜像忘了更新 —— 全绿,但结果不对。
- 文档里的值是文档作者环境的值。里面的域名、项目 ID、各种 token, 照抄进去就是把本环境接到别人的站点上。
所以本仓库的做法是:先 check 只统计影响面,人工核对数字,再 apply; 凡是"漏做不报错"的步骤一律做成显式的人工确认关卡;凡是环境相关的值 一律从部署仓库读,不在这里写第二份。
二、怎么跑
前提
- 在服务器上执行,不在本地跑。云端那台已装
ansible-x-p, 边缘盒子上已装ansible-edge-p(本仓库两边各放一份)。 - 部署仓库的路径不用填:运行时自动探测(找带
config.yaml的x-cloud/x-edge目录)。探到多个会停下来让你指定,不会瞎猜。 探测失败时它会把找过的位置一起打出来,照着提示补x_cloud_root/x_edge_root即可。
环境信息:config.yaml(可选,但强烈建议放一份)
环境信息(数据库账号密码、域名、集团编码等)默认全部沿用部署仓库的
config.yaml,不用重复维护。只有当本环境某个值和部署仓库里的不一样时,
才在本仓库根目录放一份 config.yaml 覆盖它:
cp config.yaml.example config.yaml # 云端那台
cp config.yaml.edge.example config.yaml # 边缘盒子
vi config.yaml # 只填要覆盖的行,其余保持注释
规则很简单:
- 只填要改的。填了的字段覆盖部署仓库同名字段,没填的照旧 ——
逐字段深合并,不会因为你只写了一个
password就把同段其他字段清空。 - 不填就等于没这个文件,行为和以前完全一致。
- 优先级:部署仓库
.default.yaml→ 部署仓库config.yaml→ 本仓库config.yaml→ 命令行-e(-e最高)。 - ★ 这个文件已被
.gitignore挡住,绝不入库(里面是本环境的密码)。 两份.example模板照常入库。
第一步:空跑,只看影响面
ansible-playbook upgrade.yaml -e "from=0430 to=0716 stage=all mode=check"
mode=check 不改任何数据,只统计"这条 SQL 会影响多少行""这个配置现在
是什么值""镜像清单里哪些还是旧 tag"。把这些数字和升级文档的预期值对一遍,
再往下走。
第二步:按阶段 apply
推荐直接用 run-all.sh 串跑(17 阶段依次 apply,失败即停):
./run-all.sh 0430 0716 # 全量串跑(每段前有确认)
./run-all.sh 0430 0716 -y # 跳过确认
./run-all.sh 0430 0716 -r cloud-sql-post-image # 从某阶段续跑(修完错用这个)
./run-all.sh 0430 0716 -s cloud-kafka-topic # 只跑一个阶段
./run-all.sh 0430 0716 -l # 列出 17 个阶段
要手工一段一段跑的话,一次只跑一个阶段,上一个没确认完不要跑下一个:
# ── 云端 01..11 ──
ansible-playbook upgrade.yaml -e "from=0430 to=0716 stage=cloud-nacos mode=apply"
ansible-playbook upgrade.yaml -e "from=0430 to=0716 stage=cloud-configmap mode=apply"
ansible-playbook upgrade.yaml -e "from=0430 to=0716 stage=cloud-sql-pre-image mode=apply"
ansible-playbook upgrade.yaml -e "from=0430 to=0716 stage=cloud-kafka-topic mode=apply"
ansible-playbook upgrade.yaml -e "from=0430 to=0716 stage=cloud-minio-h5 mode=apply"
ansible-playbook upgrade.yaml -e "from=0430 to=0716 stage=cloud-image mode=apply"
ansible-playbook upgrade.yaml -e "from=0430 to=0716 stage=cloud-sql-post-image mode=apply"
ansible-playbook upgrade.yaml -e "from=0430 to=0716 stage=cloud-redis mode=apply"
ansible-playbook upgrade.yaml -e "from=0430 to=0716 stage=cloud-saas-migration mode=apply"
ansible-playbook upgrade.yaml -e "from=0430 to=0716 stage=cloud-api-calls mode=apply"
ansible-playbook upgrade.yaml -e "from=0430 to=0716 stage=cloud-xxljob mode=apply"
# ── 边缘 12..15 ★ 在盒子上执行,不是云端那台 ──
ansible-playbook upgrade.yaml -e "from=0430 to=0716 stage=edge-nacos mode=apply"
ansible-playbook upgrade.yaml -e "from=0430 to=0716 stage=edge-configmap mode=apply"
ansible-playbook upgrade.yaml -e "from=0430 to=0716 stage=edge-image mode=apply"
ansible-playbook upgrade.yaml -e "from=0430 to=0716 stage=edge-sql-post-image mode=apply"
# ── 收尾 16(实际跑在云端,但要求边缘已就绪)+ 云边同步 17 ──
ansible-playbook upgrade.yaml -e "from=0430 to=0716 stage=edge-api-calls mode=apply"
ansible-playbook upgrade.yaml -e "from=0430 to=0716 stage=sync-flink mode=apply"
stage 只有这 17 个名字可填,另有一个 all(仅 mode=check,用来一次看全貌)。
旧的 5 段 alias(cloud-pre / cloud-post / edge / final / sync)已删除 ——
一个 stage 对应一段、一个文件,不再有「一个名字展开成好几段」的间接层。
要连着跑多段就用 run-all.sh(逐段独立调用,失败即停、能续跑)。
填了已删除的旧名字不会静默跑空,守卫会报错并打印新旧对应关系。
镜像不需要人工换。cloud-image(06)和 edge-image(14)就是换镜像那两段:
按版本清单 kubectl set image 只换 tag → 等 rollout →
确认服务完全 Running/Ready(没起来就停) → 把新 tag 回写部署仓库。
要准备的只有一件事:新镜像得能拉到 —— 通网环境节点自己拉;离网环境
先导入节点(云端 ctr -n k8s.io images import,边缘 k3s ctr images import)。
没导也不会静默跑过去:表现是 ImagePullBackOff,就绪检查会连原因一起报出来。
带密钥执行(密钥文件不在本仓库,见 group_vars/all.yml 第五节):
ansible-playbook upgrade.yaml -e "from=0430 to=0716 stage=edge-api-calls mode=apply" \
-e "@/data/application/.upgrade-secrets.yml"
SQL 报错后只重跑 SQL
SQL 报错是最常见的中断点,改完往往只需要把那一段 SQL 重跑一遍 —— 17 段 体系下直接跑对应阶段就行,不会连带重跑 nacos/configmap/镜像:
# 镜像前 SQL
ansible-playbook upgrade.yaml -e "from=0430 to=0716 stage=cloud-sql-pre-image mode=apply"
# 镜像后 SQL
ansible-playbook upgrade.yaml -e "from=0430 to=0716 stage=cloud-sql-post-image mode=apply"
# 边缘镜像后 SQL ★ 在盒子上执行
ansible-playbook upgrade.yaml -e "from=0430 to=0716 stage=edge-sql-post-image mode=apply"
# 或者用 run-all.sh 从失败那段续跑后面全部
./run-all.sh 0430 0716 -r cloud-sql-post-image
| stage | 跑 |
|---|---|
cloud-sql-pre-image |
cloud/03-sql-pre-image.yml |
cloud-sql-post-image |
cloud/07-sql-post-image.yml |
edge-sql-post-image |
edge/15-sql-post-image.yml |
- 旧的
sql-pre/sql-post/sql-edge三个 alias 已删除 —— 上面的阶段名已经 等价,两套名字并存只会记混。 - 不做库备份(整个仓库都不做,见「不做 mysqldump 备份」一节)。跑之前 自己确认有回滚点。
cloud-saas-migration(09,DTX 标准配置数据迁移)也是跑 SQL,但它要求镜像 已换、服务已就绪 —— 所以是独立一段,不要跟上面几段混在一起理解。
几个守卫
守卫 = playbook 正式干活之前的一串 fail 任务,条件命中就立刻中止,
一个真实操作都不执行 —— 把「命令敲错 → 误改生产数据」拦在开跑前。
| 守卫 | 原因 |
|---|---|
不给 mode= 直接报错 |
裸跑会直接改生产数据 |
from / to / stage 必须给 |
少写参数会拼出空路径,报一堆看不懂的错 |
stage 取值必须合法 |
拼错一个字母就变成"什么都没跑还全绿"(包括填已删除的旧 alias) |
stage=all 不许配 mode=apply |
一次跑完中间没人工确认点,失败时难判断跑到哪了 |
| 版本目录不存在时明确报错 | 而不是跑了个空 playbook |
执行日志:每跑一次留一份,按时间顺序
每执行一次,自动生成一份独立的、给人看的日志:
logs/<from>-to-<to>/<stage>-<mode>-<时间戳>.log
logs/<from>-to-<to>/latest.log 软链接,指向最近一次
路径不用自己记 —— 开头的「升级概览」和结束时的收尾提示都会把它打出来。
里面是整个执行过程按时间顺序的记录:进入了哪个步骤文件、每个任务的结果、
完整的 msg / stdout / stderr(不像终端那样被折叠)。结尾再按四类汇总一次:
| 汇总段 | 看它干什么 |
|---|---|
| ✗ 失败 | 哪一步中止了,修完重跑同一个 stage |
| ⚠ 需要人工关注 / 条件性跳过 | ★ 查漏补缺主要看这段 |
| ✎ 实际发生变更 | mode=check 时这段应该几乎是空的 |
| ○ 跳过 | 确认跳过的都是「本该跳过」的 |
第二段是这份日志存在的主要理由。附件没放、开关没开、需要人工去调的接口 ——
这类事不会让升级失败,在终端里一闪而过就被后面几百行刷走了,最容易漏。
插件按输出里的 ★ / ⚠ / 「人工」标记把它们收集到一起,跑完 tail 一下就能看全:
tail -n 80 logs/0430-to-0716/latest.log # 只看结尾四段汇总
less logs/0430-to-0716/latest.log # 看完整过程
实现是 callback_plugins/upgrade_log.py,靠 ansible.cfg 的
callback_plugins = ./callback_plugins 自动加载,不需要改执行命令。
两个可调项只能用 -e 传(callback 读不到 group_vars,只能读 extra_vars):
| 参数 | 默认 | 说明 |
|---|---|---|
upgrade_log_dir |
<仓库>/logs |
日志根目录 |
upgrade_log_maxlen |
20000 |
单个字段最多记多少字符,超出截断并标注 |
还有一份 logs/ansible-raw.log 是 ansible 自己的原始日志(所有运行追加到同一个
文件,格式给机器看)。排查插件本身或者要看被截断掉的超长输出时才用它。
两个坑
logs/目录必须存在(仓库里用logs/.gitkeep保住了)。ansible 不会创建log_path的父目录,目录不在就只有启动时一句 warning,然后整个过程一个字不记。- 必须在仓库根目录执行
ansible-playbook。不在根目录 → 读不到ansible.cfg→ 插件不加载 → 没有这份日志。真发生了的话,pre_tasks里的 「日志 · 插件没生效时明确说一声」会当场警告,不会让你以为记着其实没记。
为什么用 -e stage= 而不是 --tags
- tag 是 OR 语义。
--tags apply,cloud-nacos会把所有带apply的任务 都跑一遍(含镜像后 SQL 的),而不是取交集。 - 本仓库全靠
include_tasks(动态 include)。动态 include 的子任务无法被--tags选择,而 include 语句上的 tag 会应用到它包含的全部任务。
用 tag 分模式在这个结构下根本不成立,所以阶段和模式都是变量,tag 一个不用。
三、目录结构
upgrade.yaml 入口。守卫 + 加载环境变量 + 概览 + 调用版本编排
group_vars/all.yml 升级过程专属变量(★ 环境变量不在这里)
inventory/hosts 只有 local 一台
ansible.cfg ★ 在这里启用执行日志插件(必须在仓库根目录执行才生效)
config.yaml.example 环境信息模板(云端)→ 复制成 config.yaml 再改
config.yaml.edge.example 环境信息模板(边缘)
config.yaml ★ 你自己放的覆盖值,已被 .gitignore 挡住,不入库
callback_plugins/
upgrade_log.py 统一执行日志:每跑一次一份,按时间顺序 + 结尾分类汇总
logs/ 日志落地处(.gitkeep 保目录,日志本身不入库)
lib/ 可复用能力,跨版本共用
load-x-cloud-vars.yml 从 ansible-x-p 读环境变量 ← 唯一耦合点
load-x-edge-vars.yml 从 ansible-edge-p 读环境变量
load-local-config.yml 读本仓库 config.yaml(没有就跳过)
merge-local-config.yml 把它逐字段深合并进上面读来的环境变量
sql-run.yml 执行 SQL(选库 → 执行 → 失败就停;无 --force、不吞错)
mysql-query.yml 只读查询
nacos-get.yml 拉取 nacos 配置
nacos-publish.yml 发布 nacos 配置
nacos-patch.yml 整段替换某个 key
nacos-patch-lines.yml 行级增改
configmap-patch.yml 改 configmap 里的 JS/配置文件内容 + 重启对应应用
redis-del-key.yml 删缓存 key
pod-http-call.yml ★ 进容器调接口(不走网关,避开 token)
image-manifest-check.yml 核对集群里的镜像 tag 是否已按清单更新
image-update.yml ★ 换镜像:set image 只换 tag → 等 rollout →
确认服务完全 Running/Ready → 回写 app_info
record-version.yml 记录升级历史
scripts/ 上面那些 yml 用到的 python 脚本
versions/
manifest.yml 有哪些升级区间
0430-to-0716/
RELEASE-NOTES.md ★ 版本说明:逐条对照升级文档,审查用
meta.yml 本次元信息:影响范围 / 不可逆操作 / 附件状态
main.yml 编排:17 个阶段各自 include 哪个文件
cloud/01..11,16 云端各步 + 收尾(文件名数字 = 阶段执行序号)
edge/12..15 边缘端各步
sync/17-flink.yml 云边同步
files/ 附件(SQL / excel / 镜像清单 / h5 包)
docs/history/ 开发过程归档(执行升级用不到,只在想改框架时查)
加一个新版本区间要做什么
versions/manifest.yml里登记from/to- 建
versions/<from>-to-<to>/,写meta.yml+main.yml - 写
RELEASE-NOTES.md—— 逐条列「升级文档要求什么 → 本仓库怎么做的」, 尤其是没照文档字面做的地方及原因。这一步不能省:升级脚本里大量判断 都是核对部署模板之后做的,不写下来则下一个人(包括几个月后的自己) 只能看到代码和文档不一致,无法判断那是 bug 还是有意的 - 各步骤复用
lib/下的能力,不要在版本目录里写新的底层实现
lib/ 是跨版本共用的。发现某个版本需要 lib/ 改动时,改法必须向后兼容
(比如 configmap-patch.yml 的 keep_existing 就是新增一个可选语义,
不影响已有调用)。
四、几条硬约束
1. 环境变量绝不维护两份
mysql 连接、库名映射、命名空间、nacos 地址、域名、minio 地址 —— 基准值一律
运行时从部署仓库的 config.yaml 读(lib/load-x-*-vars.yml),本仓库不留副本。
本仓库的 config.yaml 是覆盖层,不是第二份台账:只填与部署仓库不一致的
那几行,没填的字段照旧从部署仓库取。所以「不维护两份」这条依然成立。
把整套值抄一份到本仓库的后果不是"重复",是两边会不一致:部署时改了域名, 升级时按旧的那份把配置改回去,而且看起来一切正常。
2. 仓库里不出现 IP / 域名 / 账号 / 密码 / token / 集团编码 / 项目 ID
升级文档里的这些值都是文档作者环境的。本仓库的处理是:
- 能从部署仓库推导的 → 推导(三个跳转域名就是这么来的)
- 推导不出来的 → 声明变量名,不给默认值,不给就跳过并提示
不给空默认值的原因:接口拿到 projectId='' 可能直接空指针,报错会把人
引到别处,比"这一步被跳过了"难查得多。
3. 云边是两台机器、两个集群
云端 k8s(kubeadm)和边缘 k3s 之间 kubeconfig 不通。edge-* 阶段必须在
盒子上跑。在云端跑 stage=all mode=check 时,边缘部分只打印清单、
不去连边缘集群 —— 而不是拿云端的库去比。
4. 所有业务接口都进容器调
lib/pod-http-call.yml:kubectl exec 进 pod 直接打 127.0.0.1:8080,
不走网关,也就不需要网关那层的 token。
5. "漏做不报错"的步骤一律做成显式关卡
用变量而不是 pause —— pause 在 upgrade_assume_yes=true 时会被跳过,
那就变成"升级全绿、这一步根本没做"。当前的关卡:
| 变量 | 卡的是什么 | 漏做的表现 |
|---|---|---|
upgrade_flink_done / _skip |
云边同步任务切没切到新版 | 接口调用成功,但拿到旧结构数据 |
upgrade_edge_hostmaps_done |
多域名配置(本次不执行,已收进开关后面) | 边缘端登录页打不开 |
6. 破坏性操作先确认(★ 备份不由本仓库做)
删表重建、删库重建这类在 meta.yml 的 irreversible_operations 里列着。
mode=check 会先把影响行数打出来 —— 但回滚点要你自己提前准备,
原因见下一节。
7. ★ 本仓库不做 mysqldump 备份
整个仓库没有库级备份环节,也没有「跳过备份」这个开关。原因:
- 库体量大(
dtx_server单库 20G 上下,dtx_digital还是分库成批), 而升级调试期反复重跑是常态 —— 每跑一遍就多一份全量 dump。 - 磁盘写满的后果不是「备份失败」而是把平台备挂:节点被打
disk-pressure:NoSchedule污点 → nacos 副本全部 Pending → 平台 502。
所以现在的约定是:
回滚点由运维自己提前准备(自己
mysqldump/ 存储快照 / 从既有备份 系统取)。playbook 不帮你做,也不输出「备份已完成」这种让人放心的话。
仍然保留的是三类小文件备份(_backup_dir 下,各几 KB):
| 目录 | 内容 | 谁写的 |
|---|---|---|
nacos/ |
改之前该 key 的当前值 | lib/nacos-get.yml |
configmap/ |
改之前 configmap 的原内容 | lib/configmap-patch.yml |
minio/ |
覆盖之前的旧 json(minio 没开版本控制) | cloud/05-minio-h5.yml |
images/ |
部署 vars 原文件 + rollback-<stage>.sh |
lib/image-update.yml |
这几类是「改哪个 key 就先把那个 key 的当前值存下来」,不占空间,改错了 只能靠它恢复 —— 删掉反而危险。
五、写 configmap 补丁时的坑(★ 容易错,记在这里)
缩进要按 configmap 里的真实缩进,不是模板里看到的
部署模板里 JS 内容是 YAML 块标量:
data:
microservice.env.js: | # ← 2 空格
window.SystemConfig = { # ← 4 空格,这 4 空格会被整体剥掉
DELIVERY_WEB_URL: "...", # ← 模板里 6 空格 → configmap 里实际 2 空格
WATERMARK_CONFIG: {
SHOW: false, # ← 模板里 8 空格 → configmap 里实际 4 空格
lineinfile 的 line 必须按 configmap 里的真实缩进(模板缩进 − 4) 写。
按模板写的后果:每次跑都产生纯空格 diff → 白重启一次前端 pod,
还在 diff 里留一堆噪音干扰复核。
文档说"新增",实际可能已经存在
升级文档里好几处写的"新增配置",在部署模板里其实已经有了而且值是对的。
这时用 keep_existing: true:缺了才插,已经在了原样保留并把现值打出来供核对。
JS 对象里同名 key 后者胜
按文档字面"新增一段 WATERMARK_CONFIG"插到 window.SystemConfig 后面,
而模板里靠末尾本来就有一个 SHOW: false —— 插在前面的 true 会被后面那个
false 覆盖。表现是:diff 看着改了、pod 也重启了、页面上水印还是没有。
所以这类"打开一个开关"的变更必须改已有的那一行,不是新增。
六、当前状态(0430 → 0716)
★ 本次升级做了什么、哪些是自动的、哪些要人工、我在哪些地方没照文档字面做 ——
全部整理在 versions/0430-to-0716/RELEASE-NOTES.md。
那份是按「文档节号 → 实现位置 → 状态」逐条列的,用来审查有没有遗漏、理解有没有偏差。
已实现的步骤见 versions/0430-to-0716/main.yml。
还缺的信息(见 meta.yml 的 pending_attachments):
- 集成文档 6.1.4 第 3 步要导入的 3 张 meos_control 编码配置表 SQL(从 SaaS 导出)
→ 放到
versions/0430-to-0716/files/sql/saas-export/,cloud/07会自动导入 - 集成文档第 5 节 DTX 标准数据迁移:按导出日期下载解压后的 SQL
→ 放到
versions/0430-to-0716/files/sql/saas-migration/,cloud/09会自动导入 dtx-server + 调 clearCache
明确不做的:
- 《mid-frame-web-edge 边缘端单域名版本配置变更.md》及云端对应那份 —— 本次不执行
- Flink 脚本变更 —— 私有化走 BCD 提单,不由本仓库执行
(
sync/17-flink.yml只输出指引 + 卡人工确认)
七、执行前后各看一眼
执行前
mode=check跑过,影响行数和文档预期对得上upgrade_is_saas填对了(私有化环境 =false)- 回滚点自己准备好了 —— 本仓库不做库备份(见四、7)
- 需要的密钥/参数都通过
-e或 vault 文件给了
执行后
tail -n 80 logs/<区间>/latest.log—— 「需要人工关注」那段逐条过完- 各阶段的镜像核对报告没有"清单要求更新、集群里还是旧 tag"
- 人工关卡那几项确实做了,不是靠
_skip蒙过去的 - 抽查一两条业务数据,确认新字段真的有值
- 日志文件留档(和小文件备份目录一起归档,出问题时是唯一的过程证据)