3165 字
16 分钟

JumpServer 升级到 v5:Lion 合并与 Chen 探针踩坑

2026-09-24 凌晨,把 Kubernetes 上的 JumpServer 社区版从 v4.10.16-ce 升到了 v5.0.0-ce。先升级到 v4.10.19 LTS,完成备份和验收,再继续 v5。最后运行的是五个组件、每个两副本,Helm revision 8

中间遇到两个与 v5 直接相关的问题:官方发布的 Helm Chart 仍然引用不存在的 lion:v5.0.0-ce;Chen 沿用旧健康检查路径,导致容器反复重启。

这篇记录使用的来源、实际执行顺序,以及最后留下的两项 values 覆盖。文中的版本和状态以这次维护窗口为准,命令中的配置文件名已泛化,不包含生产凭据。

起点:先确认运行版本#

原来有六个 Deployment:Core、Celery、Chen、KoKo、Lion、Web,每个都是 2/2 Ready。Core 和 Celery 使用同一个 Core 镜像,其他组件使用各自的镜像,标签统一为 v4.10.16-ce

数据库是外置 PostgreSQL,缓存是 Redis;应用数据卷使用 CephFS。这次只升级 JumpServer,没有同时升级数据库、Redis 或存储集群。

我先交叉检查了三处信息:

Terminal window
helm list -n jumpserver -o json
kubectl -n jumpserver get deployments \
-o custom-columns='NAME:.metadata.name,READY:.status.readyReplicas,DESIRED:.spec.replicas,IMAGES:.spec.template.spec.containers[*].image'
kubectl -n jumpserver get pods \
-o custom-columns='NAME:.metadata.name,IMAGES:.spec.containers[*].image,IMAGE_IDS:.status.containerStatuses[*].imageID'

本地 Chart 的 versionappVersion,Helm release 元数据,以及实际 Pod 的镜像都指向 v4.10.16。只读 Chart.yaml 不足以确认线上版本:覆盖文件、上一次发布失败或未完成的滚动更新,都可能让它与运行状态不同。

这次选择先升 LTS,还有一个明确原因:官方 9 月 9 日的安全公告指出,v4.0.0 至 v4.10.19 之前的版本受到 Access Key 越权泄露问题影响,社区版也在范围内,v4.10.19 是修复版本。官方安全公告

先 LTS、再 v5 是本次维护安排,并不是所有 v4 升级都必须经过这一站。官方的 v5 升级说明支持从 v4 迁移到 v5。

官方发布包与 main 有差异#

Chart 来自 jumpserver/helm-charts,但实际部署使用的是 jumpserver-v5.0.0 固定发布包

当时核对到的来源关系如下:

来源对应版本或提交本次用途
v4.10.19 Chart 发布包jumpserver-v4.10.19第一阶段升级
v5.0.0 Chart 发布包ef64070cca6ba77e72f49ec915060b3f5311cbf0实际部署的 v5 模板
复盘时核对的 main6b4f34ecbdb92247e17214f4cd2f712e38165cfc比较尚未进入上述发布包的修复

v5 包的 SHA256 与官方 Helm 仓库索引一致:

c6bc889c21642c05542952d69660b416badf456c724dc036aae29e8851c00866

这个包相对 v4.10.19 只修改了 Chart 版本和应用版本。升级完成后再检查 main,发现它早在 09-20 就多了一个 适配 KoKo 图形会话的提交:删除 Lion 模板及旧代理路由,把字体平滑配置移到 KoKo,并调整 video PVC 模板。

因此,浏览 main 看到的内容,不能直接当成下载到的 v5.0.0 发布包内容。本次没有切换到该 main 快照,而是保留发布包的官方模板,通过独立 values 文件修正实际遇到的问题。

停机前,把镜像和恢复点准备好#

升级前重新检查了 Kubernetes API、节点、etcd、Ceph、PostgreSQL 复制和 Redis 主从状态,也确认没有活跃 JumpServer 会话。

新镜像先拉到三个节点,再逐项比较运行时缓存中的 digest 与官方镜像 digest。这个步骤在本次确实起了作用:一个节点访问 Harbor 时出现过 TLS handshake timeout,重试后成功。如果把下载留到停机之后,维护窗口就会包含这段不确定的等待。

备份按用途分开保存:

备份保护的内容本次校验
etcd 快照Kubernetes API 状态快照状态、大小、revision、双份 SHA256
原 Chart 和生产覆盖文件版本及部署配置两份归档 SHA256 一致
PostgreSQL custom dumpJumpServer 数据库目录检查、完整解析、双份 SHA256
PostgreSQL globals角色等全局对象使用 --no-role-passwords,不导出角色密码
Redis RDB缓存及运行状态快照原生导出、文件头检查、双份 SHA256
五个应用卷归档Core、Chen、KoKo、Lion 数据及 Web 日志完整读取归档、双份 SHA256

etcd 快照不能代替数据库和文件备份。备份文件保存在两台节点的受限目录中,正文只保留方法;原始 values、渲染结果、日志和归档没有放进博客仓库。

最后一轮数据库和文件备份是在停止业务组件之后进行的。原部署的 Core、Celery selector 存在重叠,停止和验收按明确的 Deployment 名称、Pod 所属关系处理,不能把一个宽泛标签选择器当成组件边界。

pg_restore 结束了,客户端却还在等#

第一阶段备份校验时,还碰到一次容易误判为数据库卡住的问题:

Terminal window
kubectl -n jumpserver exec -i "$postgres_pod" -c postgres -- \
pg_restore --list < jumpserver.dump > archive-list.txt

容器里的 pg_restore 已经结束,目录清单也已生成,但本地 kubectl exec -i 没有退出。检查发现,--list 只读取归档目录,输入流还有剩余内容。这次把剩余 stdin 消费完后,命令正常返回:

Terminal window
# postgres_pod 指向已核对的 PostgreSQL 实例
kubectl -n jumpserver exec -i "$postgres_pod" -c postgres -- \
sh -c 'pg_restore --list; result=$?; cat >/dev/null; exit "$result"' \
< jumpserver.dump > archive-list.txt
kubectl -n jumpserver exec -i "$postgres_pod" -c postgres -- \
pg_restore --file=/dev/null < jumpserver.dump

第一条保留了 pg_restore 的退出状态,第二条用于完整解析归档。这两项都不是数据库恢复演练。

这次等待发生在迁移前,保护流程先恢复了原组件的副本数。修正校验方式后,再停机生成最终备份,没有在状态不明时继续执行迁移。

第一阶段:升级到 v4.10.19 LTS#

第一阶段使用官方 v4.10.19 Chart 和原生产覆盖文件。新 Chart 渲染结果与现网比较后,确认副本数、selector、环境变量、卷挂载和 PVC 规格保持一致。

正式发布前做过 helm lint、使用占位凭据的离线渲染和 Helm server dry-run。这里的 server dry-run 仍不会运行数据库迁移 hook,也不能证明新镜像里的每个 HTTP 路径都可用。

对生产配置渲染时还要注意:--hide-secret 隐藏的是 Secret 对象,Deployment 环境变量等位置仍可能含敏感值。这次完整结果留在受限文件中,只输出字段比较结果。

LTS 阶段新增 24 项 Django 迁移,迁移记录从 138 增到 162。Helm revision 6 部署成功,六个组件恢复 2/2 Ready,随后完成入口和稳定性检查。

这个恢复点保留下来。进入 v5 阶段时又重新备份了一次,避免把升级前的 v4.10.16 数据当成 v4.10.19 的恢复基线。

v5 的第一个问题:Lion 镜像不存在#

预拉 v5 镜像时,Core、Chen、KoKo、Web 都能找到对应标签,唯独 jumpserver/lion:v5.0.0-ce 不存在。

继续核对官方安装器、镜像列表和项目 issue,维护者已经明确说明:Lion 的代码合并进了 KoKo,v5 不再有独立 Lion 组件官方 issue #17554

这也能在 v5 KoKo 源码中交叉验证:图形连接路由位于 /koko/lion/;容器内还包含 guacd,监听本机 4822 端口。

因此没有继续尝试拉取一个不存在的镜像,也没有把旧 Lion 镜像重新打成 v5 标签。针对这个发布包,先加了一个覆盖:

lion:
enabled: false

渲染比较确认,独立 Lion 的 Deployment、Service 和 PVC 声明被移除,其余五个 Deployment 仍各两副本。旧 Lion PVC 已有 helm.sh/resource-policy: keep,升级后保留下来,没有删除数据卷。

停机前也检查了旧卷的数据目录:sessions、drive、replays、ftp_files、certs 均没有文件,所以这次没有录制或上传文件需要搬迁。这个结论只适用于本次现场;已有数据的环境还需要单独处理迁移,不能只保留旧卷就宣称数据路径完全兼容。

组件数量从六个变为五个,是 Lion 合并的结果。Windows 图形连接的验收重点,也随之变成了 KoKo 内置的图形服务。

v5 的第二个问题:Chen 被旧探针反复重启#

第一轮 v5 发布后,Helm revision 7 显示 deployed28 项数据库迁移完成,迁移记录从 162 增到 190。

但逐个请求 Pod 的健康路径时,Chen 出现了这样的响应链:

GET /chen → 302,Location: /chen/
GET /chen/ → 404

原 livenessProbe 仍检查 /chen。两个 Chen 实例随后各发生了至少两次重启。Chart 渲染正确、Helm 已完成,都没有覆盖到这个运行时变化。

查阅 v5 官方安装器的 Chen 配置,实际使用的检查地址是:

http://localhost:8082/chen/healthy

直接请求两个 Pod 的该路径,均返回 200。于是把修正加入同一份覆盖文件,重新渲染后确认只有 Chen 的 liveness 路径发生变化,再发布 revision 8

这次补发使用了 --no-hooks:数据库已经完成迁移,差异也限定在探针,不需要再次执行 pre-upgrade 数据库 Job。首次 v5 升级仍正常执行 hook,不能把这个参数当成通用升级选项。

Chen 还有一个验收细节:当前模板没有给它配置 readinessProbe。新 Pod 刚变成 Kubernetes Ready 时,HTTP 服务可能尚未监听。补发后的第一次 HTTP 检查就遇到过短暂的连接拒绝,等待启动后才通过。因此后续以真实健康接口和持续观察为准,没有把 Deployment Ready 当成全部业务验收。

最后保留的覆盖文件#

官方模板没有直接修改。最终的 values-v5-upgrade.yaml 如下:

# v5 的图形连接已合并到 KoKo
lion:
enabled: false
# v5 Chen 的实际健康检查路径
chen:
livenessProbe:
httpGet:
path: /chen/healthy

生产覆盖文件继续保存原有数据库、Redis、凭据、镜像仓库和存储设置。这份非敏感覆盖单独保存,两层 values 一起使用:

Terminal window
# ./jumpserver 为校验过的官方 v5.0.0 Chart 解包目录
# values-prod.yaml 为自己环境的生产配置,不使用上游默认值代替
helm upgrade jumpserver ./jumpserver \
-n jumpserver \
--reset-values \
-f ./values-prod.yaml \
-f ./values-v5-upgrade.yaml \
--wait --wait-for-jobs --timeout 15m

这条命令展示的是完成备份、差异核对之后的正式发布方式。上面两项覆盖针对本次固定发布包;以后换到已修复的 Chart,要重新比较渲染结果,再决定哪些覆盖可以移除。

本次没有使用自动 Helm rollback。数据库迁移已经改变持久数据时,启动旧镜像不等于恢复旧状态:需要结合升级前数据库、文件和配置恢复,而不是只看 Helm revision 是否退回去了。

最终状态与验证范围#

两阶段的关键记录如下,时间均为 2026-09-24,UTC+8

阶段时间Helm revision数据库迁移结果
v4.10.16 → v4.10.19 LTS00:45:216138 → 162,新增 24 项六组件各 2/2
v4.10.19 → v5.0.001:11:497162 → 190,新增 28 项随后发现 Chen 探针问题
修正 Chen liveness01:15:11 发起发布8未重复执行迁移 hook新 Chen 副本恢复
最终稳定性观察01:16:10–01:19:138无新增迁移约 183 秒、7 次采样通过

最终检查到了这些结果:

  • Core、Celery、Chen、KoKo、Web 五个 Deployment 均为 2/2 Ready,十个应用 Pod 的镜像 digest 与官方 v5 镜像一致;Core 实际代码版本为 v5.0.0
  • 八个 HTTP 组件实例健康检查通过。两只 KoKo 的 /koko/lion/health/ 返回 200,guacd 均监听 4822。
  • 登录页、/api/health/ 和图形服务健康入口持续返回 200;管理页 /ui/、终端页 /luna/ 及抽查的静态资源可正常加载。
  • 原九个 PVC 的 UID 和 PV 绑定保持,原凭据 Secret 的 UID/resourceVersion 不变。
  • 普通用户、资产、资产账号、资产授权数量保持。组件启动会新增服务账号,因此总用户数有增长,没有把它写成“所有表行数完全不变”。
  • PostgreSQL 两实例健康,复制追平;Redis 主从正常。Kubernetes API、etcd、Ceph 与维护前基线一致。
  • 一个 Web 容器启动时重启过一次;Chen 修复后的观察窗口内没有新增重启,近 90 秒日志抽查没有 ERROR、FATAL、CRITICAL 或 Traceback。

这里仍有明确的验证边界:没有执行完整数据库隔离恢复演练,也没有用真实授权账号逐一完成 SSH、RDP 和数据库交互登录。三分钟观察证明的是这段窗口内的运行状态,不能替代长时间负载观察或完整业务回归。

这次最有价值的差异,都出现在 Chart 以外:镜像是否实际发布、组件是否合并,以及新进程究竟提供了哪个健康接口。把这些与发布包、运行镜像和真实请求放在一起核对,才能解释为什么 Helm 已经 deployed,维护工作却还没有结束。