☰
自建CI/CD平台实战:GitLab+Jenkins+Docker+Harbor+K8s+Rancher
2026/10/2 1:04:28 网站建设 项目流程

做运维和开发的同学,多半都有过被“手动部署”支配的经历:登录服务器,把 jar 包拷上去,重启进程,再一遍遍确认端口到底起没起来。项目少的时候还能忍,一旦开始同时维护二三十个微服务、每周还要迭代两三次,手搓部署绝对能把人逼疯。

这篇文章就以 GitLab + Jenkins + Docker + Harbor + K8s + Rancher 这套技术栈为例,记录我完整搭建一套自建 CICD 平台的全过程。它解决的核心问题只有一句话:开发提交代码后,从代码拉取、镜像构建、镜像推送、到 K8s 集群滚动更新,全自动跑完,全程不需要人工上服务器敲命令。适合正在从“手动部署”转向“自动化流水线”的团队,也适合那些想学会 CI/CD 全链路、但一直卡在某一步的同学参考。

1. 为什么放着托管 CI/CD 不用,非要自己拼一整套

1.1 我遇到的实际问题

先交代背景。我之前所在的项目组经历过一次非常典型的转型:从单体应用切到微服务,一次发布涉及十几个服务,每个服务都要单独打包。最初我们用的是 GitLab CI 自带的 Runner,配合一台打包机手动执行,勉强能跑通。但问题随着放开手脚拆服务后迅速暴露:

  • 构建机环境混乱,不同项目依赖的 JDK、Maven、Node 版本互相冲突,改一次全局环境就要炸一片;
  • 镜像仓库不规范,有人推到 Docker Hub 公开仓库,有人推到内网一台临时搭的 Registry,还有人手抖覆盖了线上正在用的镜像 Tag;
  • 没有统一的集群纳管入口,线上环境在哪个节点跑、Pod 状态怎么样、日志去哪看,都要逐台上服务器 grep。

这正是我决定重新搭一套完整平台的直接原因。并不是说 GitLab CI 不好,而是对于一个多语言、多服务、多环境的团队来说,流程中必须有一个更明确的“构建中间层”,把代码到运行环境之间的所有脏活累活标准化。

1.2 方案对比:托管服务与自建平台的取舍

很多朋友会问:GitHub Actions 不香吗?云厂商的 DevOps 流水线不适配吗?当然香,但如果你的代码必须放在内网、或者对数据出境/供应商依赖有严格要求,托管方案基本直接被排除了。退一步说,就算网络环境允许,规模化之后按构建时长计费的成本,对中小团队也是一笔不小的开销。

自建这套,核心优势是三个:

  • 内网闭环,代码、镜像、集群全都在自己手里,不依赖外部网络,断网也能发版;
  • 全链路可见,任何一次构建失败都能顺着流水线日志从代码层追到容器层,中间没有“黑盒”环节;
  • 可定制空间大,想加自定义扫描、自制发布策略、对接内部工单系统,都有明确的扩展点。

说句实话,这套栈也是社区生态最成熟、踩坑资料最多的组合。GitLab 管代码,Jenkins 管任务编排,Docker 负责镜像标准化,Harbor 管镜像资产,K8s 管运行编排,Rancher 管集群界面。每个组件负责一摊事,边界清晰,出了问题知道去哪个环节查。

2. 机器规划与版本选型:先把地基打稳

2.1 一套最小可运行环境的硬件清单

别急着装系统,先盘一盘手头有多少资源。我的经验是:如果是学习或小型团队试用,4 台 4C8G 的服务器足够;如果期望支撑正式线上业务,建议至少 6 台 8C16G。下面是我这次搭建用的最小清单,供你参考:

角色配置要求部署组件说明
代码服务器4C8GGitLab内存是硬指标,低于 6G 很痛苦
构建服务器4C8GJenkins + Docker构建越频繁,磁盘和 CPU 要求越高
镜像仓库服务器4C8GHarbor主要吃磁盘,镜像膨胀之后很占空间
集群节点 1(控制+工作)4C8GK8s + Rancher最小环境可单节点兼任
集群节点 2(工作)4C8GK8s有预算就加到 3 个节点以上
集群节点 3(工作)4C8GK8s生产环境务必奇数节点

如果只有一台高配服务器,也可以用虚拟机或 Docker 把 GitLab、Jenkins、Harbor 跑在一台,K8s 节点再单独分几台虚拟机,原理一样,只是故障域共享了,生产环境不建议这么省。

磁盘方面特别提醒:Harbor 和 GitLab 都是存储大户。GitLab 的仓库数据、构建缓存、CI 日志会持续膨胀,Harbor 的镜像层哪怕定期清理也会有一段快速增长期。建议这两台服务器数据盘单独挂载,预留至少 200G 起步,用 LVM 或直接大分区都行,避免后期扩盘时停服务。

2.2 版本锁定和依赖关系:没有文档比锁版本更可靠

CICD 平台最怕的不是组件新,而是组件之间的版本互相不认识。我以前就吃过亏:Harbor 2.x 配旧版 Docker daemon,manifest 上传时直接报 schema 版本错误;Jenkins 插件和核心版本偏差太大,GitLab Plugin 直接加载失败。所以这次我全程用一张版本对照表锁定基线和理由,实测下来非常省心。

组件版本关键依赖备注
GitLab CE16.x 最新稳定版无硬性依赖,建议 Docker 部署老版本有已知高危漏洞,务必保持小版本升级
Jenkins2.4xx LTS 版本JDK 11/17插件版本与核心版本必须配套
Docker24.x 稳定版宿主内核 3.10+用于 Jenkins 构建机节点
Harbor2.9.x 离线安装包Docker Compose 1.29+自带 docker-compose 编排
Kubernetes1.28.xContainerd 1.7.x1.24 后默认运行时不再是 Docker
Rancher2.7.x/2.8.xK8s 1.25~1.29管理面与业务集群解耦

版本确定后就别再频繁追新了。CICD 平台是典型的“基础工程”,稳定性比新功能重要。建议小版本升级按季度评估,跨大版本除非有必须用到的功能,否则不要主动动。所有组件的升级都要先在测试环境复制一份跑通再上生产。

3. 代码源:GitLab 部署与仓库准备

3.1 用 Docker Compose 在十分钟内拉起 GitLab

GitLab 是我在这套平台里最先部署的组件,因为后面 Jenkins 拉代码、Harbor 对接都需要它。我选择了 Docker Compose 部署而不是裸机 RPM,理由很直接:卸载干净、升级方便、不会污染系统环境。

先写一个最精简的 docker-compose.yml:

version: '3.8' services: gitlab: image: gitlab/gitlab-ce:16.11.3-ce.0 container_name: gitlab restart: always hostname: gitlab.ops.internal environment: GITLAB_OMNIBUS_CONFIG: | external_url 'http://gitlab.ops.internal' gitlab_rails['gitlab_shell_ssh_port'] = 2222 # 关键优化:关掉 Prometheus 和 Grafana,能省 1G+ 内存 prometheus_monitoring['enable'] = false grafana['enable'] = false # 关闭不需要的组件,减轻小机器压力 alertmanager['enable'] = false node_exporter['enable'] = false redis_exporter['enable'] = false postgres_exporter['enable'] = false gitlab_exporter['enable'] = false ports: - '80:80' - '2222:22' volumes: - /data/gitlab/config:/etc/gitlab - /data/gitlab/logs:/var/log/gitlab - /data/gitlab/data:/var/opt/gitlab shm_size: '256m'

这里有两个地方对新手特别不友好,我单独说。

第一,external_url一定不要配成http://localhost:80或http://<容器ID>,因为 GitLab 内部会用这个 URL 生成所有项目的克隆地址和 Webhook 回调地址。你要是配错了,后面 Jenkins 收到的 Webhook 推送地址全是错的,或者开发者复制出来的 HTTP 克隆地址打不开。正确做法是填你准备对外提供的域名或固定 IP,比如http://gitlab.ops.internal或http://192.168.1.10。

第二,hostname要与external_url保持一致。如果只改 URL 不写 hostname,GitLab 内部的 Rails 应用在重定向时会频繁跳到默认的主机名,一堆莫名其妙的 302 循环问题都是从这里来的。

启动后等 2~3 分钟,容器健康检查通过后,执行:

docker exec -it gitlab grep -r 'Password:' /etc/gitlab/initial_root_password

这个初始密码是系统自动生成的,24 小时后会被删除,务必第一时间登录并修改。记住:管理员账号默认是 root。

GitLab 跑起来之后,我建议顺手做两个操作:

  • 在“管理区域 -> 网络 -> 用户和 IP 限制”中把默认注册功能关闭,避免内网被无关人员注册账号;
  • 在项目创建时统一用群组(Group)管理,仓库路径从/root/xxx变成/dev/xxx,后面 Jenkins 配置凭据和权限时清晰很多。

3.2 仓库接入细节:SSH 密钥、HTTP 地址和机器人令牌

代码放上 GitLab 后,第一件事是把开发机的 SSH 密钥配好。每个人的~/.ssh/id_rsa.pub内容填到“用户设置 -> SSH 密钥”里,然后本地执行:

ssh -T git@gitlab.ops.internal -p 2222

能返回 Welcome to GitLab 就说明通了。这里有个好多人反复踩的坑:如果你特意把 SSH 端口改成了 2222,本地的~/.ssh/config必须写清楚:

Host gitlab.ops.internal HostName gitlab.ops.internal User git Port 2222 IdentityFile ~/.ssh/id_rsa

否则 Git 默认走 22 端口,连不上时会纠结半天。

HTTP 克隆地址同样容易出问题。GitLab 默认展示的地址是由external_url自动拼出来的,很多人因为没有把external_url配成正确域名,导致 HTTP 克隆链接变成http://容器ID/group/repo.git。这个问题在“3.1 节”已经提前预防了,但要注意:如果当前已经部署完成才发现 URL 配错,修改external_url后需要重启 GitLab,并且原来已克隆的本地仓库 remote 地址要手动改一遍:

git remote set-url origin http://gitlab.ops.internal/dev/my-app.git

还有很重要的一步:为 Jenkins 准备一个独立的 GitLab 用户和 Access Token。不要在 Pipeline 里用个人账号的密码,一是不安全,二是人离职后流水线会集体挂掉。我是在“管理区域 -> 用户”中新建了一个jenkins机器人用户,然后在该用户的“访问令牌”里生成一个只勾选read_repository、write_repository和api权限的 Token,这个 Token 后面配置 Jenkins 时会用到。

4. 构建引擎:Jenkins 安装与 GitLab 联动配置

4.1 安装方式选择与插件最小集

Jenkins 的部署方式,我最终选了jenkins/jenkins:ltsDocker 镜像方式。War 包方式也能用,但 Docker 方式的好处是环境隔离,即使 Jenkins 进程崩了,也不会污染宿主机;升级时换一个 tag 重启容器就完事。

docker run -d \ --name jenkins \ --restart=always \ -p 8080:8080 \ -p 50000:50000 \ -v /data/jenkins:/var/jenkins_home \ -v /var/run/docker.sock:/var/run/docker.sock \ # 让 Jenkins 容器能直接调用宿主 Docker,注意安全风险 -v /usr/bin/docker:/usr/bin/docker \ jenkins/jenkins:2.440.1-lts

启动后从/data/jenkins/secrets/initialAdminPassword取初始密码解锁,然后安装插件。插件别贪多,缺什么再补什么。我的最小插件集如下:

  • GitLab Plugin(GitLab 集成、Webhook 触发)
  • Pipeline(流水线核心)
  • Docker Pipeline(Docker 构建和 push 的 DSL 支持)
  • Kubernetes CLI(Pipeline 中调用 kubectl)
  • Credentials Binding(凭据注入)
  • Blue Ocean(可选,可视化流水线,新手友好)

安装完插件后,记得在“仪表盘 -> 系统管理 -> Tools”里配 JDK、Maven、Node 的自动安装或指定本机路径。Jenkins 的 Docker 镜像默认没有 JDK 之外的构建工具,后面 Pipeline 里要用 Maven 就报 command not found,这是最常见的首个报错。

4.2 GitLab Connection 常见的登录失败问题排查

配 GitLab Connection 是这一节最容易卡人的地方。路径在“系统管理 -> 系统配置 -> GitLab -> Add GitLab Server”,需要填 Connection Name、GitLab Host URL、Credentials。

很多人照着网上教程配置后,点 Test Connection 直接收到一句Failed to connect to GitLab: login failed. check api token or gitlab version.这句报错信息特别坑,因为它把所有错误都归成一个提示。根据我的实际排查经验,常见原因按概率排列如下:

  1. URL 多写了路径。GitLab Host URL 只填http://gitlab.ops.internal即可,不需要加/api/v4。加了之后 Jenkins 会拼接出http://gitlab.ops.internal/api/v4/api/v4这种鬼地址,必然报错;
  2. Token 权限不足。如果你建的 Token 只勾了read_user,没有勾api权限,GitLab API 无法被正常调用。重新去生成 Token,把api勾上;
  3. Token 选错了类型。注意 Jenkins Credentials 里要选的类型是“GitLab API token”,而不是“Username with password”或“SSH Username with private key”。选错类型后,Jenkins 会把你的 Token 当成用户名/密码去请求,同样报这个错;
  4. 网络不通。Jenkins 容器内访问不到 GitLab 域名,先在容器里curl http://gitlab.ops.internal验证,不通的话检查 DNS 和/etc/hosts。

我把这些原因整理成了表格,方便你对照排查:

报错场景可能原因验证方法解决方案
立即失败URL 拼错浏览器访问 GitLab 根路径只填 scheme+host,不填路径
401/403Token 权限不足用 Token 在命令行 curl API重新生成并勾选 api 权限
404项目路径不对检查项目 URL用群组/项目完整路径
连接超时容器网络隔离容器内 curl GitLab配置 DNS 或使用静态 IP

4.3 凭据管理:从 SSH 私钥到 Harbor 账号

Jenkins Pipeline 里所有涉及账号密码的地方,都不要在 Jenkinsfile 里写明文。用凭据管理统一管理,这是平台安全的底线。

我的建议是建立一套命名规范:gitlab-token、harbor-username-password、k8s-kubeconfig分别对应不同类型凭据。

  • GitLab Token:类型选GitLab API token,值为 3.2 节生成的机器用户 Token;
  • GitLab SSH 私钥:类型选SSH Username with private key,私钥填 Jenkins 容器里生成的id_rsa私钥内容,用于拉取私有仓库代码;
  • Harbor 账号:类型选Username with password,用于 Pipeline 中docker login;
  • K8s kubeconfig:类型选Secret file,上传~/.kube/config文件,用于kubectl调用集群。

在 Jenkinsfile 里通过credentials('jenkins-gitlab-token')这种方式引用,日志里不会出现明文。

5. 镜像产线:Docker 构建环境与 Harbor 镜像仓库

5.1 构建机的 Docker 配置与加速

Jenkins 使用 Docker 构建镜像有两种思路:一种是在 Jenkins 容器内安装 Docker CLI,然后通过挂载宿主机的 docker.sock 调用宿主机 Docker 守护进程;另一种是用 Kubernetes 集群里的 Pod 作为构建环境,每次构建动态创建容器。后者更先进,但复杂度也更高,ACL 和权限控制要额外设计。我这次采用的是前一种思路,简单直接,适合绝大多数中小团队。

构建机的 Docker 配置文件/etc/docker/daemon.json我建议这样写:

{ "exec-opts": ["native.cgroupdriver=systemd"], "log-driver": "json-file", "log-opts": { "max-size": "50m", "max-file": "5" }, "registry-mirrors": ["https://docker.m.daocloud.io"], "insecure-registries": ["harbor.ops.internal", "192.168.1.12"] }

registry-mirrors是镜像加速地址,如果你的服务器访问 Docker Hub 很稳,不配也行;访问不稳定的话,配置一个公共加速地址能明显加快基础镜像拉取速度。这里要记住:镜像加速只对 Docker Hub 生效,拉取 Harbor 或其他仓库镜像不走这个配置。

insecure-registries是给后面 Harbor 用的。为了保证安全,生产环境应该给 Harbor 配 HTTPS,但内网环境没那么讲究,很多团队先用 HTTP 模式跑起来。如果 Harbor 是 HTTP 协议,而 Docker daemon 没加insecure-registries,那么执行docker push harbor.ops.internal时一定报http: server gave HTTP response to HTTPS client,这是新手必踩的坑,提前配好能省不少时间。

改完配置后执行systemctl restart docker,然后验证一下:

docker info | grep -A 5 "Registry Mirrors" docker info | grep -A 5 "Insecure Registries"

5.2 Harbor 离线部署和 config validation 坑

Harbor 的安装方式很统一:下载离线安装包,解压后改配置,执行install.sh。离线包的下载地址是 GitHub Releases,如果服务器访问不了 GitHub,可以在一台能访问的机器上下载后通过内网传输。

解压并进入目录后,先复制配置模板,然后重点修改。这里有一个高频报错,说出来大家都懂:happened in config validation。这个提示本身很模糊,实际原因几乎都是harbor.yml出了问题。我遇到过的配置校验失败原因:

  • 缩进不对:harbor.yml是 YAML 格式,DPDK 的官方示例中部分字段缩进是 4 空格,如果你用 2 空格或混用了 Tab,校验直接失败;
  • 端口被占用:80 或 443 端口已经被宿主机其他进程占用,安装脚本检查失败;
  • HTTP 模式下属性缺失:如果不用 HTTPS,必须把https:整个段落注释掉或删除,而不是只把port: 443改成 80;
  • 证书文件路径不存在:如果保留了https:段但证书路径是无效的,也会报这个错。

我的建议很简单:在内网环境,配置开头直接用 HTTP 模式,对应配置如下:

hostname: harbor.ops.internal http: port: 80 https: # 全部注释掉 # port: 443 # certificate: /your/cert.pem # private_key: /your/private_key.pem harbor_admin_password: [这里设置强密码] database: password: [这里设置数据库密码] data_volume: /data/harbor

改完后执行./install.sh,脚本会依次检查 Docker、Docker Compose,然后拉取镜像并启动服务。启动完成后访问http://harbor.ops.internal,用默认账号admin加上你设置的harbor_admin_password登录。

在系统初始化时,把仓库做成 HTTPS 更稳妥;如果只是内网测试,HTTP 配合insecure-registries也能跑得动,但别把这条路直接搬到公网环境。

5.3 私有仓库的访问策略:对外认证与对内白名单

Harbor 起来后,第一步是创建项目(Project)。注意 Harbor 的项目名就是镜像仓库命名空间的一部分,比如你在 Harbor 建了个叫library的项目,那镜像地址就是harbor.ops.internal/library/my-app:1.0.0。

权限模型建议这样设计:

  • 每个业务域建独立项目,比如backend、frontend,不要全塞到 library 里;
  • 给 Jenkins 机器人账号分配对应项目的“维护者”角色,给开发查看权限分配“访客”角色;
  • 开启“自动创建项目”,这样 Jenkins 推送不存在的项目时会自动创建,省去手工建项目的麻烦。

从镜像仓库的角度,Harbor 的 Webhook 也很有价值。我配置了“复制规则”和“垃圾回收”定时任务,前者用于跨机房同步镜像,后者用于定期清理未被引用的镜像层。配置入口在“系统管理 -> 垃圾回收”,建议每周执行一次。

镜像 Tag 的规划直接影响后面的发布回滚。我这里的规范是:日常构建用latest-${BUILD_NUMBER},稳定发布用带版本号的 Tag,比如v1.2.3。只打latest不带构建号的话,K8s 那边滚动更新死活不会触发,因为镜像 Tag 没变,Kubelet 认为镜像没变化,这是“明明推送了新镜像,集群却一直用旧镜像”这类问题的头号根源。

6. 运行底座:K8s 集群搭建与 Rancher 纳管

6.1 单节点起步的集群方案选择

K8s 集群的搭建方式有很多种,我调研后锁定了两个方向:kubeadm 和 RKE2。kubeadm 是社区标准方式,资料多、可定制性强,但全流程手动操作,出错了要自己排查;RKE2 是 Rancher 官方推荐的发行版,内置了 containerd、网络插件和 ingress controller,装完基本就是可用状态。

因为我后面要用 Rancher,所以直接选择了 RKE2。用 RKE2 加 Rancher 的搭配,好处是 Rancher 能识别 RKE2 集群的版本和升级路径,界面上的诊断工具也能正常用。

单节点集群的规划我建议这样:k8s-master节点同时充当控制平面和工作节点,也就是去掉 taint,这样一台 4C8G 的机器就能跑起一整套,适合学习和小规模内部系统。如果后续要支撑生产,就加两三个 worker 节点,保持 master 只跑控制面组件。

RKE2 的安装命令非常简单:

curl -sfL https://get.rke2.io | sh - systemctl enable rke2-server.service systemctl start rke2-server.service

服务启动后,kubectl 配置保存在/etc/rancher/rke2/rke2.yaml,路径在节点本地。你可能会好奇为什么这里不像 kubeadm 一样生成在~/.kube/config,这是 RKE2 的一种保护机制,避免其他用户直接读到管理员的权限文件。

执行下面的命令,把配置文件复制到常规位置,并替换其中的127.0.0.1为实际的 master 节点 IP:

mkdir -p ~/.kube cp /etc/rancher/rke2/rke2.yaml ~/.kube/config sed -i 's/127.0.0.1/<master节点IP>/g' ~/.kube/config

6.2 从 Jenkins 到 K8s 的镜像拉取授权问题

集群和镜像仓库打通这一步,很多人会栽一个大跟头:Harbor 里明明推了镜像,K8s 却始终报ImagePullBackOff或ErrImagePull。原因通常是两件事:

第一,containerd 也像 Docker 一样需要配置私服白名单。RKE2 的 containerd 配置在/var/lib/rancher/rke2/agent/etc/containerd/config.toml.tmpl,建议在/var/lib/rancher/rke2/agent/etc/containerd/config.toml中确认是否包含了 harbor 的 endpoint。如果拉取的是一个 HTTP 的私有仓库,你需要确保 containerd 也认为它是安全的。RKE2 默认会读取一个基础配置,但如果你发现拉取时报http: server gave HTTP response to HTTPS client,就说明 containerd 层面也需要体现 insecure-registry 的信任关系。

第二,K8s 集群里没有配置 imagePullSecret。哪怕 Docker CLI 能正常 push/pull Harbor,K8s 的 kubelet 在拉镜像时也不会自动使用宿主机的 Docker 登录凭证。你需要先在 K8s 里创建一个 Secret,然后把 Secret 挂到 Deployment 或绑定到 ServiceAccount。创建命令:

kubectl create secret docker-registry harbor-secret \ --namespace=default \ --docker-server=harbor.ops.internal \ --docker-username=admin \ --docker-password=<密码>

然后在 Deployment 的spec.template.spec中加一段:

imagePullSecrets: - name: harbor-secret

6.3 Rancher 安装和 kubeconfig 的正确携带方式

Rancher 的安装方式,我选用 Docker 单容器方式。执行:

docker run -d \ --name rancher \ --restart=always \ -p 443:443 \ -v /data/rancher:/var/lib/rancher \ rancher/rancher:v2.8.2

Rancher 启动后,浏览器访问https://rancher.ops.internal,首次访问会要求设置管理员密码。然后通过“添加集群 -> 导入现有集群”的方式把 RKE2 集群纳入管理。导入时 Rancher 会给你一段kubectl apply的命令,在 master 节点执行后,集群状态会变为 Active。

到这里,你的 CICD 平台已经有了完整的运行底座:GitLab 管理代码,Jenkins 负责构建,Harbor 存储镜像,RKE2 承载各种业务 Pod,Rancher 提供可视化界面。

还有一个问题必须提前想清楚:Jenkins 所在的构建机,凭什么能操作 Kubernetes 集群?答案是 kubeconfig 文件。但我不建议直接拿着 master 节点的超级管理员 kubeconfig 到处分发,那等于把整个集群的钥匙交了出去。更好一点的做法是,在集群里创建一个专用 ServiceAccount,然后生成一个限权 kubeconfig,只让它具备业务命名空间下 Deployment 的get/list/patch权限。

7. 拉通全链路:一份可直接改的 Jenkinsfile

7.1 流水线阶段拆解

所有基础设施就绪后,终于到了最后一步:用 Jenkins Pipeline 把整条链路串起来。我提供的这份 Jenkinsfile 是可直接套用的最小版本,覆盖从拉代码到集群更新验证的完整闭环。

pipeline { agent any environment { // 这些变量来自 Jenkins 系统配置或凭据,不写明文 HARBOR_ADDR = "harbor.ops.internal" IMAGE_REPO = "backend/my-app" K8S_NAMESPACE = "default" DEPLOYMENT_NAME = "my-app" } stages { stage('拉取代码') { steps { checkout scm } } stage('单元测试') { steps { sh 'mvn clean test' } } stage('构建镜像') { steps { script { docker.withRegistry('http://' + env.HARBOR_ADDR, 'harbor-credentials') { docker.build("${env.HARBOR_ADDR}/${env.IMAGE_REPO}:latest-${env.BUILD_NUMBER}") } } } } stage('推送镜像') { steps { script { docker.withRegistry('http://' + env.HARBOR_ADDR, 'harbor-credentials') { docker.image("${env.HARBOR_ADDR}/${env.IMAGE_REPO}:latest-${env.BUILD_NUMBER}").push() docker.image("${env.HARBOR_ADDR}/${env.IMAGE_REPO}:latest-${env.BUILD_NUMBER}").push("v1.0.0") // 按需打 Tag } } } } stage('更新 K8s 集群') { steps { withKubeConfig([credentialsId: 'k8s-kubeconfig']) { sh """ kubectl set image deployment/${env.DEPLOYMENT_NAME} \ app=${env.HARBOR_ADDR}/${env.IMAGE_REPO}:latest-${env.BUILD_NUMBER} \ -n ${env.K8S_NAMESPACE} kubectl rollout status deployment/${env.DEPLOYMENT_NAME} -n ${env.K8S_NAMESPACE} """ } } } } }

这个流水线做了四件事:拉代码、跑测试、构建镜像并推送到 Harbor、调用 kubectl 更新 K8s Deployment 的镜像并等待滚动完成。

7.2 发布策略与回滚思路

kubectl set image是就地更新,配合 Deployment 的滚动更新策略,它天然是“覆盖式发布”,适合大多数内部系统。如果业务对可用性要求更高,建议再引入“蓝绿发布”或“金丝雀发布”,但那一套需要在 K8s 侧维护多套 Service 和 Ingress,复杂度明显增加。

回滚的思路也要提前想好。在 Pipeline 的构建号小于线上版本时,latest-${BUILD_NUMBER}这个 tag 的设计反而成了问题——你不能简单地通过重新构建“找回旧镜像”。所以正规做法是把历史稳定版本的镜像打上语义化版本 tag,比如 git tag 关联的v1.0.0。回滚时直接执行:

kubectl set image deployment/my-app app=harbor.ops.internal/backend/my-app:v1.0.0 -n default

或者用 K8s 原生的 rollout undo:

kubectl rollout undo deployment/my-app -n default

后者会回滚到上一个 revision,无需关心镜像 tag 具体是什么。

7.3 实测中常见的三类故障与处理

这套流程跑通之后,接下来就是长期运维与磨合的阶段。我整理了三类我在实测中反复遇到的问题,先给结论再讲排查链路。

第一类:镜像 Tag 没变,集群不更新。这是最高频的问题。你发现新镜像推到了 Harbor,但访问服务还是旧版本。原因如前面说的,kubelet 不会主动去看镜像仓库发生了什么变化,只认镜像名和 tag 的组合。解决办法就是让 tag 每构建一次都不同,或使用kubectl rollout restart强制重新拉取。我在 5.3 节提到过,用latest-${BUILD_NUMBER}作为日常 tag,就是绕开这个问题的关键。

第二类:拉取镜像超时或失败。日志中看到manifest unknown或no such host,检查方向分两个:如果是 Harbor 域名解析问题,看 Jenkins 构建机和 K8s 节点的/etc/hosts;如果是 HTTP 协议问题,检查 Docker daemon 和 containerd 的 insecure 配置。ImagePullBackOff的话,还可以用一句话快速排查:

kubectl describe pod <pod-name> -n <namespace>

事件栏里会写清楚拉取失败的底层原因。

第三类:Jenkins 执行 kubectl 提示 permission denied。这是因为 kubeconfig 中的用户权限不够。最稳的排查方法是用同一个 kubeconfig 在 Jenkins 容器内手动执行kubectl get pods -n default,看看报错是不是forbidden。如果是,说明 ServiceAccount 缺权限,需要补充 Role/RoleBinding,而不是粗暴地在 Jenkins 里放置管理员 kubeconfig。

配合钉钉或企业微信的通知插件,还可以在 Pipeline 的 post 阶段发送构建结果到群里:

post { success { // 调用钉钉/企微机器人 Webhook,附带构建号、镜像地址、访问链接 } failure { // 通知负责人 } }

Jenkins 社区这两年的 MCP 生态也起来了,流水线排错时可以接入 AI 助手辅助分析日志,结合jenkins可用环境变量快速定位问题,但核心还是要有清晰的排查链路做支撑。

整个平台搭建到这里,链路已经完整对齐了:开发 push 代码到 GitLab,GitLab 通过 Webhook 通知 Jenkins,Jenkins 拉代码、跑测试、构建镜像、推送到 Harbor,再由 Pipeline 调用 kubectl 更新 RKE2 集群里的 Deployment,Rancher 统一监控所有资源状态。如果让我给正准备动手的人一个建议,那就是“先小后大”:用一台服务器把 GitLab + Jenkins + Harbor 全跑在 Docker 里,K8s 用单节点 RKE2,先跑通一条最简单的流水线,再逐步增加节点和发布策略。这套平台的价值不在于每个组件多高大上,而在于让“从提交代码到上线”的全流程变得可重复、可追溯、可回滚。等你能在一分钟内完成一次全自动发布之后,就会明白前期那些环境配置和踩坑都是值得的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询