基于 GitLab CI/CD + Harbor + Kubernetes 的自动化部署实战手册
一、 项目概述与前置准备
本项目基于 Rocky Linux 10.1 系统,搭建了一套完整的 DevOps 自动化流水线。实现了开发人员推送代码并打上标签(Tag)后,系统自动完成代码拉取、镜像构建、镜像推送到 Harbor 私有仓库、以及 Kubernetes 集群自动滚动更新的全流程。
1. 环境规划与主机映射
所有节点需配置 /etc/hosts(Windows 端同样需配置 hosts 文件用于访问域名):
bash
[root@k8s-master01 ~]# cat /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.110.166 k8s-master01 m1
192.168.110.167 k8s-node01 n1
192.168.110.168 k8s-node02 n2
192.168.110.165 hb.reg.com
192.168.110.166 gitlab.test.com
2.节点规划表
| 主机名 | IP地址 | 操作系统 | 核心角色 | 主要部署组件 | 域名/Hosts映射 |
|---|---|---|---|---|---|
| k8s-master01 (m1) | 192.168.110.166 | Rocky Linux 10.1 | K8s Master节点 / GitLab服务器 | Kubernetes控制平面、Docker、GitLab (容器)、项目源码目录 | 192.168.110.166 gitlab.test.com 192.168.110.166 k8s-master01 m1 |
| k8s-node01 (n1) | 192.168.110.167 | Rocky Linux 10.1 | K8s Node1 / GitLab Runner节点 | Kubernetes工作节点、Docker、GitLab Runner (容器)、kubectl二进制文件 (/opt/kubectl-v1.36.3) |
192.168.110.167 k8s-node01 n1 |
| k8s-node02 (n2) | 192.168.110.168 | Rocky Linux 10.1 | K8s Node2 | Kubernetes工作节点、Docker | 192.168.110.168 k8s-node02 n2 |
| harbor | 192.168.110.165 | redhat Linux 10.1 | Harbor 镜像仓库 | Harbor (容器),提供私有镜像的存储与分发 | 192.168.110.165 hb.reg.com |
3. 环境初始化
基础环境要求:关闭防火墙、SELinux,配置好时间同步,并确保所有节点可以正常通信。
4.harbor前置部署
参考上篇部署方案,在docker配置源中新增
cat > /etc/docker/daemon.json << EOF
{
"insecure-registries": ["hb.reg.com"]
}
EOF
systemctl daemon-reload
systemctl restart docker
解决ssl证书报错
二、 核心组件部署与配置
1. GitLab 部署(Master节点)
采用 Docker 容器方式部署,配置文件挂载到宿主机 /root/xm 目录下:
bash
[root@k8s-master01 xm]# mkdir -p config logs data
[root@k8s-master01 xm]# docker run --name gitlab \
--hostname gitlab.test.com \
--env GITLAB_OMNIBUS_CONFIG="external_url 'http://gitlab.test.com'" \
--publish 443:443 --publish 80:80 --publish 8022:22 \
--restart always \
--volume /root/xm/config:/etc/gitlab \
--volume /root/xm/logs:/var/log/gitlab \
--volume /root/xm/data:/var/opt/gitlab \
--shm-size 256m \
hb.reg.com/k8s/gitlab-ce:17.0.0-ce.0
修改 GitLab 核心配置(优化资源占用并配置 SSH) :
进入 /root/xm/config/gitlab.rb 修改:
bash
external_url 'http://gitlab.test.com' # 外部访问地址
gitlab_rails['gitlab_ssh_host'] = '192.168.110.166' # 宿主机真实IP
gitlab_rails['gitlab_shell_ssh_port'] = 8022 # SSH 访问端口
gitlab_rails['time_zone'] = 'Asia/Shanghai' # 设置时区为上海
prometheus['enable'] = false # 关闭 Prometheus 监控
alertmanager['enable'] = false # 关闭 Alertmanager 告警
gitlab_rails['gitlab_email_enabled'] = false # 关闭邮件服务
postgresql['shared_buffers'] = "128MB" # 减少数据库缓存
postgresql['max_connections'] = 200 # 减少数据库并发连接数
nginx['worker_processes'] = 2 # 减少 nginx 工作进程数
执行 gitlab-ctl reconfigure 使其生效。初始密码存储在 /root/xm/config/initial_root_password 中。
2. Harbor 仓库准备(独立服务器)
在 Harbor 网页端(http://hb.reg.com)提前创建项目名为 cicd 的私有项目,并创建具备推送权限的账号(推荐创建机器人账号,本例使用 admin / hb123456 便于测试)。
3. GitLab Runner 部署(Node1节点)
关键前提:由于使用的是 Docker Executor,为了让容器内可以调用宿主机的 Docker 引擎以及 Kubernetes API,必须在启动时挂载宿主机资源。
Node01 宿主机制作 kubectl 二进制(解决容器内找不到 kubectl 的问题):
bash
curl -LO "https://dl.k8s.io/release/v1.36.3/bin/linux/amd64/kubectl"
mv kubectl kubectl-v1.36.3
chmod +x kubectl-v1.36.3
mv kubectl-v1.36.3 /opt/
/opt/kubectl-v1.36.3 version --client
启动 Runner 容器:
bash
docker run --name gitlab-runner -itd \
-v /root/gitlab-runner/gitlab-runner:/etc/gitlab-runner \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /etc/hosts:/etc/hosts \
-v /opt/kubectl-v1.36.3:/usr/local/bin/kubectl:ro \
--restart always hb.reg.com/k8s/gitlab-runner:v17.0.0
(注:利用 -v 将宿主机的 kubectl 二进制文件以只读方式挂载到容器内,这是解决 Docker 执行器无法使用宿主机 kubectl 的标准方案之一)
进入容器注册 Runner:
bash
docker exec -it gitlab-runner bash
gitlab-runner register --non-interactive --executor "docker" --docker-image docker:latest --url "http://192.168.110.166" --token "glrt-Es2N__FFwnZjCdgLKr7o"
此处token在gitlab中获取令牌
GitLab 权限和 Token 的生成步骤(解决 403报错 的细节)
- 登录 GitLab,点击头像 -> 偏好设置 -> 访问令牌。
- 勾选
api和write_repository权限。 - 复制生成的
glpat-开头的 Token。 - 重要 :去
nginx-demo项目 -> 设置 -> 仓库 -> 受保护分支,将main分支"解除保护"或者将"允许推送"改为"允许所有人",否则会报 403 错误。
修改宿主机 config.toml 挂载透传 (/root/gitlab-runner/gitlab-runner/config.toml):
[root@k8s-node01 gitlab-runner]# cat config.toml
concurrent = 10 # 并行执行作业数
check_interval = 0
connection_max_age = "15m0s"
shutdown_timeout = 0
[session_server]
session_timeout = 1800
[[runners]]
name = "client2" # Runner 名称
url = "http://192.168.110.166" # 你的 GitLab IP
id = 1 # ID可以随便写,GitLab 会自动覆盖
token = "glrt-Es2N__FFwnZjCdgLKr7o" # 你的 GitLab 注册 Token
token_obtained_at = 2026-09-02T00:00:00Z
token_expires_at = 0001-01-01T00:00:00Z
executor = "docker"
[runners.cache]
MaxUploadedArchiveSize = 0
[runners.docker]
pull_policy = "if-not-present" # 配置镜像拉取策略
tls_verify = false
image = "docker:latest" # 配置默认镜像(因为你用的是 docker executor,用这个)
privileged = true
disable_entrypoint_overwrite = false
oom_kill_disable = false
disable_cache = false
volumes = ["/cache", "/var/run/docker.sock:/var/run/docker.sock", "/etc/hosts:/etc/hosts", "/opt/kubectl-v1.36.3:/usr/local/bin/kubectl:ro"]
shm_size = 0
network_mtu = 0
4. Kubernetes 集群准备(Master节点执行)
提前在 K8s 中创建命名空间,并创建用于拉取 Harbor 私有镜像的 Secret:
bash
kubectl create ns cicd
kubectl create secret docker-registry regcred \
--namespace=cicd \
--docker-server=hb.reg.com \
--docker-username=admin \
--docker-password=hb123456
三、 项目代码与核心配置文件
在 Master 节点的 /root/xm/deploy/ 目录下创建项目:
bash
[root@k8s-master01 deploy]# ls -la
deployment.yaml Dockerfile index.html .dockerignore .gitlab-ci.yml
1. 业务代码与打包文件
Dockerfile:
FROM hb.reg.com/k8s/nginx:1.27.4
COPY . /usr/share/nginx/html/
(注:直接使用内网私有仓库固定版本,避免拉取外网超时)
index.html:
hello,CICD
.dockerignore (排除不需要打进镜像的文件):
Dockerfile
deployment.yaml
.git
.gitlab-ci.yml
2. Kubernetes 部署清单 deployment.yaml
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-demo
namespace: cicd
spec:
replicas: 2
selector:
matchLabels:
app: nginx-demo
template:
metadata:
labels:
app: nginx-demo
spec:
imagePullSecrets:
- name: regcred
containers:
- name: nginx-demo
image: hb.reg.com/k8s/nginx:1.27.4
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-demo-svc
namespace: cicd
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
nodePort: 30080
selector:
app: nginx-demo
3. 流水线脚本 .gitlab-ci.yml
利用挂载的宿主机 Docker Socket 和 kubectl,无需额外在容器内安装工具,防止构建卡死。
stages:
- build
- push
- deploy
variables:
IMAGE_NAME: hb.reg.com/cicd/nginx-demo:$CI_COMMIT_SHORT_SHA
build:
stage: build
image: docker:latest
script:
- docker build -t $IMAGE_NAME .
push:
stage: push
image: docker:latest
script:
- docker login hb.reg.com -u admin -p hb123456
- docker push $IMAGE_NAME
deploy:
stage: deploy
image: docker:latest
before_script:
- apk add --no-cache curl
script:
- kubectl apply -f deployment.yaml
- kubectl set image deployment/nginx-demo nginx-demo=$IMAGE_NAME -n cicd
- kubectl rollout status deployment/nginx-demo -n cicd --timeout=120s
四、 一键发布流程测试
在项目目录下执行(推荐在 Master 节点操作,已配置好连接):
git init --initial-branch=main
git remote add origin http://root:你的Token@gitlab.test.com/root/nginx-demo.git
git add .
git commit -m "项目初始提交"
git tag -a v1.0 -m "版本1.0"
git push origin main
git push origin v1.0
验证结果:
- 进入 GitLab 网页查看
Build -> Pipelines,流水线三个 Job 依次变绿。 - 验证 K8s 状态:
kubectl get pods -n cicd和kubectl get svc -n cicd。 - 在浏览器访问
http://192.168.110.166:30080即可看到页面。
五、 项目亮点与踩坑总结
1. 纯内网环境下的镜像与版本管理策略
- 踩坑过程 :初期使用
FROM nginx:latest等默认外网镜像时,CI/CD 流水线在"拉取镜像"环节频繁超时卡死。经排查发现,生产环境/实验环境为纯内网隔离,无法访问公网 Docker Hub。 - 解决方案 :将构建所需的所有基础镜像(Nginx、Docker CLI 等)和工具链镜像(Kubectl)提前拉取,打上固定版本标签(如
nginx:1.27.4),推送到内网 Harbor 私有仓库(hb.reg.com/cicd和hb.reg.com/k8s),并在代码中严格锁定版本号。彻底消灭了latest标签带来的不确定性(版本漂移风险)和对外网的强依赖。 - 工程亮点:体现了"不可变基础设施"的思维,保证了构建环境的高稳定性与可重复性。
2. 解决 Docker Executor 与宿主机的深度隔离问题(核心难点)
- 踩坑过程 :使用 Docker Executor 运行流水线时,CI 作业是在一个"临时新建的隔离容器"内执行的。日志报错
kubectl: not found,且执行docker build也报错。虽然宿主机安装了 kubectl 和 Docker,但临时容器内却无法调用。 - 解决方案 :利用 Docker 的卷挂载原理,在启动 Runner 容器时,将宿主机的核心资源"透传"给容器:
- 挂载
/var/run/docker.sock,使容器内的docker命令直接调用宿主机的 Docker 引擎,实现"套娃"式的构建; - 将宿主机上预下载的 kubectl 二进制文件(
/opt/kubectl-v1.36.3)以只读方式挂载到临时容器(-v /opt/kubectl-v1.36.3:/usr/local/bin/kubectl:ro),使其具备操作 K8s 集群的能力。
- 挂载
- 工程亮点:这一方案完美复刻了真实生产环境中"CI 环境与宿主机解耦"又"复用宿主机资源"的平衡艺术。避免了为每个任务准备臃肿的"全家桶"镜像,显著提高了流水线的执行速度与资源利用率。
3. 跨主机的权限与安全控制(GitLab 403 拦路虎)
- 踩坑过程 :在 Linux 宿主机执行
git push时,反复报出403 Forbidden和Authentication failed。网页端使用管理员密码登录正常,但命令行始终被拒绝。 - 解决方案 :经过深入排查,确认新版 GitLab 严格限制默认密码通过 HTTP 协议推送代码。因此在 GitLab 网页端重新生成了具备
api和write_repository权限的 Personal Access Token(个人访问令牌) ,并将其写入远程仓库的 URL 中。同时,去"设置->仓库->受保护分支"中将main分支解除了保护,或者将项目可见性设为公开,最终彻底解决权限拦截。 - 工程亮点:掌握了生产级 GitLab 的安全认证机制,理解了"网页登录"与"命令行令牌认证"的区别,避免了明文密码泄露的隐患。
4. Kubernetes 私有仓库认证与无状态应用滚动部署
- 踩坑过程 :K8s 默认无法直接拉取 Harbor 私有仓库中的镜像,部署时报
ImagePullBackOff错误。 - 解决方案 :在 K8s 的
cicd命名空间中预先创建了docker-registry类型的 Secret(regcred),并在 Deployment 的spec.template.spec下通过imagePullSecrets字段进行引用。同时,利用kubectl set image配合kubectl rollout status,实现了零停机的滚动更新。 - 工程亮点:将 CI 流水线的产出(镜像)与 CD 的入口(K8s 拉取)完美闭环。不仅解决了私有镜像认证问题,还掌握了服务发布的"优雅上线"机制。
5. 解决内网 Harbor 信任证书问题
- 踩坑过程 :由于 Harbor 未配置 HTTPS 证书,Docker 及 K8s 在尝试连接
hb.reg.com时,经常报x509: certificate signed by unknown authority或连接被拒绝。 - 解决方案 :在所有节点的
/etc/docker/daemon.json中配置了"insecure-registries": ["hb.reg.com"]并重启 Docker,强制其信任内网 HTTP 协议仓库。 - 工程亮点:深入理解了 HTTPS 加密与内网 HTTP 环境的共存方案,熟悉了常见基础组件的"信任链"配置方法。