从代码 Push 到 K8s 滚动更新 —— 前端 CI/CD 全链路拆解
一、引子:前端部署为什么链路这么长?
很多开发者在初次接触前端部署时,看到 Jenkins 日志里冒出一堆 docker build、helm upgrade、kubectl rollout,难免一头雾水。明明后端部署就是打个 jar 包扔上去 java -jar 就完事了,前端怎么拐了这么多弯?
其实前后端在 K8s 这套体系下跑的是完全相同的流水线逻辑,只是"集装箱"里装的东西不一样。本文以前端项目的真实 Jenkins 日志为线索,把每一步拆开讲清楚。
二、完整链路一览
Git 代码 Push
│
▼
[GitLab Runner] npm install → npm run build → 产出 dist.zip
│
▼
[Jenkins] 下载 zip → 解压 → docker build → docker push 到 Harbor
│
▼
[Jenkins] 调 Helm 通知 K8s:"用新镜像升级"
│
▼
[K8s] 拉新镜像 → 启动新 Pod → 健康检查通过 → 旧 Pod 下线 → 完成
下面逐步展开。
三、GitLab Runner 构建产物
代码 push 到 GitLab 后,第一棒是 GitLab Runner。它做的是纯粹的"体力活"——拉代码、装依赖、跑构建:
git clone <仓库>
npm install
npm run build # 产出 dist/ 文件夹
zip -r dist.zip dist/ # 压缩
# 上传至文件服务器,生成下载链接
最终产出是一个 zip 文件和它的下载地址。为什么不是 Jenkins 来构建? 因为 Jenkins 的 Agent 节点是通用机器,不一定装了 Node 环境。让 GitLab Runner 来构建,环境与代码仓库绑定,职责更清晰——这就是关注点分离。Jenkins 只管编排,不干具体的构建活儿。
四、Jenkins 打包 Docker 镜像
Jenkins 拿到 zip 下载链接后:
wget -O dist.zip "https://s.longhu.net/.../purchse-evaluate-web-UAT.zip"
unzip dist.zip -d ./dist
docker build -t purchse-evaluate-web-uat:263526 -f ./h5/dockerfile ./
实际 Dockerfile 长这样:
FROM nginx:latest # 基础镜像:自带 nginx
COPY ./h5/nginx.conf /etc/nginx/nginx.conf # 换 nginx 配置
COPY ./dist /usr/share/nginx/dist # 把前端静态文件塞进去
EXPOSE 80
这个镜像本质上就是 “nginx 程序 + 前端静态文件” 打包成的一个标准单元。K8s 只认镜像,不认裸文件——没有镜像,它根本不知道该怎么"启动你"。
五、推送到 Harbor 镜像仓库
docker push harbor.longfor.com/longhu-devops-k8s-sit/purchse-evaluate-web-uat:263526
镜像仓库是必需品。 Jenkins 构建镜像的机器和 K8s 集群的机器不是同一台,镜像必须上传到 Harbor(企业级 Docker Registry),K8s 各节点才能通过 docker pull 拉下来运行。
类比:GitLab Runner 产出 zip(原材料),Jenkins 把它装进"保温箱"(Docker 镜像),Harbor 就是中央仓库货架,K8s 各节点按需取货。
六、Helm 通知 K8s 升级
helm upgrade --set image.tag=263526 purchse-evaluate-web-uat-39856 /Docker/helm/h5
Helm 是 K8s 的包管理器(类似 yum / npm)。它用 chart 模板把部署配置(镜像名、副本数、域名、端口)打包,一条命令完成升级。
这条命令翻译成人话:“用第 263526 号版本的新镜像,替换掉现在跑着的旧版本的 purchse-evaluate-web-uat 这个服务”。
七、K8s 滚动更新机制
这是整个链路最精华的部分。Helm 下发指令后,K8s 执行滚动更新(Rolling Update):
时间线 ────────────────────────────────────────→
阶段 1:旧 Pod 正常运行
┌──────────┐
│ 旧版本 │ ← 正在接收用户请求
└──────────┘
阶段 2:K8s 拉新镜像,启动新 Pod
┌──────────┐
│ 旧版本 │ ← 仍在接请求(用户流量没断)
└──────────┘
┌──────────┐
│ 新版本 │ ← 启动中…拉镜像 → nginx 进程跑起来
└──────────┘
阶段 3:健康检查(Readiness Probe)
K8s 访问 http://新Pod:80/ → 返回 200 → ✅ Ready
阶段 4:切换流量,淘汰旧 Pod
旧 Pod 收到 SIGTERM → 不再接收新请求 → 处理完手头连接 → 退出
┌──────────┐
│ 新版本 │ ← 所有流量切过来了
└──────────┘
阶段 5:旧 Pod 删除,滚动更新完成 ✅
核心原则:整个过程中用户请求不中断。 K8s 保证至少有一个 Pod 在接客,这就是"零停机部署"。
八、常见问题:部署超时
真实场景中的典型错误日志:
Waiting for deployment rollout to finish:
1 old replicas are pending termination...
timeout has been exceeded: ABORTED
这不是代码问题,而是 K8s 集群层面的问题,常见原因有三个:
- 新 Pod 没 Ready。 健康检查一直不过(比如 nginx 配置错了、镜像拉不下来),K8s 不敢关旧的——关了服务就断了。
- 旧 Pod 僵死。 SIGTERM 发过去但进程不响应,一直卡在 Terminating 状态。
- 节点资源不足。 集群没有余力拉新镜像或启动新容器。
排查方式:
kubectl -n devops get pods | grep purchse-evaluate-web-uat
kubectl -n devops describe pod <pod名> # 重点看 Events 字段
大多数情况下,部署最终是成功的,只是 Jenkins 的 3 分钟超时等不及了。
九、前后端流程对比
把前后端放在同一个框架下看,逻辑完全一致:
| 步骤 | 后端 jar | 前端 |
|---|---|---|
| 拉代码构建 | Maven/Gradle compile | npm run build |
| 产出物 | app.jar | dist.zip |
| 打包 | Dockerfile + java 基础镜像 | Dockerfile + nginx 基础镜像 |
| 部署 | Helm → K8s | Helm → K8s |
| 运行时 | JVM 启动 Spring Boot | nginx 提供静态文件 |
唯一的区别是"集装箱里谁当服务员"——后端自带 Tomcat,前端得配 nginx。这一点在第二篇详细展开。
更多推荐
所有评论(0)