背景与目标
一个 Go 编写的 Web 应用,原来靠手工 docker build 和 kubectl apply 发布:步骤多、容易漏,出了问题也说不清线上跑的到底是哪个版本。
目标:每次发布都能追溯到具体的代码提交;发布失败不影响用户;回滚不靠登录服务器,而是一个可审计的操作。
架构
- 提交 PR
- GitHub Actionsgo vet · go test
- 合并 main分支保护
- Jenkins Pipeline轮询代码仓库
- docker build构建时再跑测试
- 私有 Registrytag:构建号-提交号
- 更新 GitOps 仓库只改镜像 tag
- Argo CDauto-sync · selfHeal
- Kubernetes滚动更新
关键做法
- 01
代码与部署配置分仓
应用代码放在 labapp,"线上应该是什么样"放在 labapp-gitops。部署历史就是 Git 历史,谁在什么时候改了什么一目了然。
- 02
PR 阶段自动检查
GitHub Actions 在 PR 上跑 go vet 和单元测试,配合分支保护,检查不过不能合并。为了能测,先把路由抽成独立函数并补上测试。
- 03
Jenkins 构建并推送镜像
自建带 Docker CLI 的 Jenkins 镜像;镜像 tag 采用"构建号-提交号 7 位",从任意一个线上镜像都能反查到对应的代码提交。
- 04
最小权限更新部署仓库
Jenkins 使用 fine-grained token,只能写 labapp-gitops 一个仓库,每次只改镜像 tag 一行;Jenkins 本身不持有集群凭据。
- 05
Argo CD 自动同步
开启自动同步、prune 和 selfHeal:有人手动改了集群,也会被改回 Git 里的状态,线上与配置始终一致。
- 06
坏版本演练
故意发布一个监听端口写错的版本:单元测试发现不了,但就绪探针失败,新 Pod 始终不 Ready;maxUnavailable 设为 0,旧 Pod 不会被删除,服务不中断。在部署仓库 git revert 后自动恢复。
遇到的问题
Argo CD 的 Redis 一直拉不下镜像
- 原因
- chart 默认镜像来自 ECR Public,国内网络访问失败
- 解决
- 在 values 中改用 docker.io 镜像,并固定 chart 版本
检查失败的 PR 仍然可以合并
- 原因
- 首次配置分支保护时检查还没运行过,没勾上;管理员默认可绕过规则
- 解决
- 补选必需检查,并开启"不允许绕过规则"
节点拉私有镜像时报 HTTP / HTTPS 不匹配
- 原因
- containerd 的 hosts.toml 多写了 skip_verify,被当作 TLS 配置,优先走 HTTPS
- 解决
- 删除该配置,通过 containerd 日志确认按 HTTP 拉取
两个节点关机后,上面的 Pod 一直显示 Running
- 原因
- 3 个节点挂了 2 个,节点控制器判定为大面积故障,小集群会停止驱逐
- 解决
- cordon 故障节点并强制删除其上的 Pod,由控制器在健康节点重建
结果
- 从合并代码到新版本上线全程自动,无需登录服务器
- 每个线上镜像都能对应到一个 Git 提交
- 坏版本被就绪探针拦截,旧版本持续服务,用户无感知
- 回滚就是一次 git revert,几分钟内完成且有记录可查