一、引子:前端部署为什么链路这么长?
很多开发者在初次接触前端部署时,看到 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。它做的是纯粹的"体力活"——拉代码、装依赖、跑构建:
gitclone<仓库>npminstallnpmrun build# 产出 dist/ 文件夹zip-rdist.zip dist/# 压缩# 上传至文件服务器,生成下载链接最终产出是一个 zip 文件和它的下载地址。为什么不是 Jenkins 来构建?因为 Jenkins 的 Agent 节点是通用机器,不一定装了 Node 环境。让 GitLab Runner 来构建,环境与代码仓库绑定,职责更清晰——这就是关注点分离。Jenkins 只管编排,不干具体的构建活儿。
四、Jenkins 打包 Docker 镜像
Jenkins 拿到 zip 下载链接后:
wget-Odist.zip"https://s.longhu.net/.../purchse-evaluate-web-UAT.zip"unzipdist.zip-d./distdockerbuild-tpurchse-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 镜像仓库
dockerpush 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--setimage.tag=263526purchse-evaluate-web-uat-39856 /Docker/helm/h5Helm 是 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-ndevops get pods|greppurchse-evaluate-web-uat kubectl-ndevops 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。这一点在第二篇详细展开。