GitLab CI/CD + Docker Registry + K8s 部署完整实战指南
之前搞过Jenkins的CI CD :CI/CD详解 & Jenkins 介绍与实战,而这次我准备使用gitlab自带的CI CD 来将编译,打包,构建&推送镜像以及部署到k8s 全部自动化,从而实现持续集成持续发布(CI CD)的效果。
说明 :本文以
my-project作为示例项目名称,实际使用时请替换为你自己的项目名称。 gitlab部署在a机器 ,gitlab runner部署在k8s master机器。docker仓库使用gitlab自有的(5050端口对应的就是gitlab docker仓库)
1、Gitlab CI CD 流程图示意
使用Gitlab实现CI CD 大致是这个流程:
java
1. 在me的电脑上写代码
│
▼
2. git push 推送到 GitLab
│
▼
3. GitLab 检测到代码更新,触发 CI/CD
│
▼
4. GitLab Runner 开始工作:
├── 拉取代码
├── Maven 编译 Java 代码
├── Docker 构建镜像(根据各个服务中的 DockerFile)
└── 推送镜像到 GitLab Registry
│
▼
5. GitLab Runner 执行 kubectl 部署命令:
├── 连接到 K8s 集群
├── 拉取新镜像
└── 创建/更新 Pod
│
▼
6. 用户访问 http://xxxxxx.yyy.com
│
▼
7. Ingress Controller (NGINX) 接收请求
│
▼
8. 转发到 Gateway Pod
│
▼
9. Gateway 路由到具体的Pod对应的接口
ok我们看到其实主要干活的就是gitlab runner ,下边介绍和安装下gitlab runner。
2、GitLab Runner 核心概念与实战解析
2.1、GitLab Runner 是什么?
GitLab Runner 是 GitLab CI/CD 生态的核心执行组件,本质是承载 GitLab 自动化任务的 "执行器"------GitLab 仅负责定义自动化流程(如代码编译、镜像构建、K8s 部署),而 Runner 是实际执行这些流程的 "工作节点",是连接代码仓库与生产 / 测试环境的关键桥梁。
简单来说,GitLab 如同 "项目管理中心",.gitlab-ci.yml 是 "自动化流程图纸",而 GitLab Runner 就是照着图纸完成具体工作的 "工人":没有 Runner,GitLab 中定义的所有 CI/CD 规则都只是无法落地的配置,代码无法自动从仓库流转到部署环境。
2.2、GitLab Runner 的核心作用
GitLab Runner 围绕 "代码交付全流程自动化" 发挥作用,结合 K8s 集群部署场景,核心能力可总结为三类:
-
任务触发与执行 :当代码推送到 GitLab 仓库(或触发合并请求、定时任务)时,GitLab 会自动通知已注册的 Runner 启动任务;Runner 会拉取最新代码,严格按照
.gitlab-ci.yml中定义的步骤执行操作(如代码编译、单元测试、Docker 镜像构建、镜像推送至仓库)。 -
环境部署与交付 :完成代码构建后,Runner 可直接执行集群操作命令(如
kubectl),将新构建的镜像部署到 K8s 集群,实现 "代码提交→镜像构建→集群部署" 的全自动化,无需人工介入。 -
结果反馈与日志留存:任务执行过程中,Runner 会实时将每一步的日志、执行状态回传给 GitLab;执行完成后,反馈 "成功 / 失败" 结果,用户可在 GitLab 界面直观查看整个流程的问题与详情。
3、Gitlab Runner 下载&安装
gitlab runner安装在gitlib中写的很详细,位置在这:


按照上边的下载和注册runner命令 安装就行,但是网络原因下载的挺慢的(没办法等会吧)。
下载&安装gitlab runner (注意gitlab runner最好是和k8s在一台机器或者网络延迟非常低的机器,因为他们之间要通信)(我是下载到本地再上传到k8s master的服务器的)过程如下:

3.1 CentOS/RHEL 安装命令
bash
# 方式1:使用官方脚本
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh | sudo bash
sudo yum install -y gitlab-runner
# 方式2:手动下载安装(推荐,网络问题时使用)
# 先下载 rpm 包,然后上传到服务器
sudo rpm -ivh gitlab-runner-xxx.rpm
3.2 验证安装
bash
# 查看 Runner 版本
gitlab-runner --version
# 查看 Runner 状态
gitlab-runner status
# 查看 Runner 配置
cat /etc/gitlab-runner/config.toml
4、注册 Gitlab Runner 到 Gitlab 中去
注册过程如下(注册过程中需要填些信息):
先点击到项目->设置->CI/CD中找到令牌

然后执行注册命令:
bash
sudo gitlab-runner register --url http://192.168.1.xxx:19096/ --registration-token GR23cdcgbvsfcsfsct45tfdbgfBqhyZxsrny47xRj
填写一些注册需要的信息。
返回gitlib中可以看到 gitlib runner注册成功了。

点击到runner可以看到具体信息:

4.1 注册过程中的关键选项说明
| 选项 | 说明 | 建议值 |
|---|---|---|
| Enter the GitLab instance URL | GitLab 服务地址 | http://192.168.1.xxx:19096/ |
| Enter the registration token | 注册令牌 | 从 GitLab 项目设置中获取 |
| Enter a description for the runner | Runner 描述 | shell-runner |
| Enter tags for the runner | Runner 标签 | shell-runner(重要!CI 配置中要用到) |
| Enter optional maintenance note | 维护备注 | 可留空 |
| Enter an executor | 执行器类型 | shell(直接在服务器执行命令) |
4.2 验证 Runner 注册成功
bash
# 验证 Runner 连接
gitlab-runner verify
# 查看 Runner 列表
gitlab-runner list
# 查看 Runner 配置
cat /etc/gitlab-runner/config.toml
5、Shell 执行器环境准备
使用 Shell 执行器时,CI/CD 任务会直接在 Runner 服务器的 Shell 环境中执行。因此,必须确保服务器上已安装所有必要的工具。
5.1 必需的基础设施
| 工具 | 用途 | 安装/验证命令 |
|---|---|---|
| Git | 拉取代码 | git --version |
| JDK 21 | Java 编译运行 | java -version |
| Maven | 项目构建 | mvn --version |
| Docker | 构建和推送镜像 | docker --version |
| kubectl | 部署到 K8s | kubectl version --client |
5.2 安装 JDK 21
bash
# 下载 JDK 21(根据系统选择)
# 官网下载:https://adoptium.net/
# 解压到指定目录
tar -zxvf OpenJDK21U-jdk_x64_linux_hotspot_21.0.2_13.tar.gz -C /opt/
mv /opt/jdk-21.0.2+13 /opt/jdk-21
# 配置环境变量
vi /etc/profile
# 添加以下内容
export JAVA_HOME=/opt/jdk-21
export PATH=$JAVA_HOME/bin:$PATH
# 使配置生效
source /etc/profile
# 验证安装
java -version
5.3 安装 Maven
bash
# 下载 Maven
wget https://dlcdn.apache.org/maven/maven-3/3.9.6/binaries/apache-maven-3.9.6-bin.tar.gz
# 解压
tar -zxvf apache-maven-3.9.6-bin.tar.gz -C /opt/
mv /opt/apache-maven-3.9.6 /opt/maven
# 配置环境变量
vi /etc/profile
# 添加以下内容
export MAVEN_HOME=/opt/maven
export PATH=$MAVEN_HOME/bin:$PATH
# 使配置生效
source /etc/profile
# 验证安装
mvn --version
5.4 安装 Docker
bash
# CentOS/RHEL 安装 Docker
yum install -y yum-utils
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
yum install -y docker-ce docker-ce-cli containerd.io
# 启动 Docker
systemctl start docker
systemctl enable docker
# 验证安装
docker --version
5.5 安装 kubectl
bash
# 下载 kubectl
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
# 赋予执行权限
chmod +x kubectl
# 移动到 PATH 目录
mv kubectl /usr/local/bin/
# 验证安装
kubectl version --client
5.6 验证所有工具
bash
# 一键验证所有必需工具
echo "=== 验证 CI/CD 环境工具 ==="
echo "Git: $(git --version)"
echo "Java: $(java -version 2>&1 | head -1)"
echo "Maven: $(mvn --version | head -1)"
echo "Docker: $(docker --version)"
echo "kubectl: $(kubectl version --client --short 2>/dev/null || kubectl version --client | head -1)"

5.7 重要提示
为什么 Shell 执行器需要预装这些工具?
Shell 执行器与 Docker 执行器不同,它不会在每个任务中启动新的容器,而是直接在宿主机的 Shell 环境中执行命令。因此:
- 所有工具必须提前安装在 Runner 服务器上
- 环境变量(如
JAVA_HOME、MAVEN_HOME)需要正确配置- CI 任务中的
source /etc/profile就是为了加载这些环境变量
6、Runner 配置文件详解
Runner 配置文件位于 /etc/gitlab-runner/config.toml:
toml
concurrent = 1 # 并发任务数
check_interval = 0 # 检查间隔
connection_max_age = "15m0s" # 连接最大存活时间
shutdown_timeout = 0 # 关闭超时时间
[session_server]
session_timeout = 1800 # 会话超时时间
[[runners]]
name = "shell-runner" # Runner 名称
url = "http://192.168.1.131:19096/"
id = 12
token = "your-token"
token_obtained_at = 2026-03-02T07:21:18Z
token_expires_at = 0001-01-01T00:00:00Z
executor = "shell" # 执行器类型
tags = ["shell-runner"] # 标签(重要!)
[runners.cache]
MaxUploadedArchiveSize = 0
7、配置 GitLab CI/CD 变量
GitLab Runner 配好后,接下来我们进行 GitLab 变量配置。
进入项目 Settings → CI/CD → Variables → Expand,添加以下变量:
| Variable Key | Value | Protected | Masked | 说明 |
|---|---|---|---|---|
CI_REGISTRY_USER |
root |
❌ | ❌ | GitLab 用户名 |
CI_REGISTRY_PASSWORD |
glpat-xxx |
❌ | ✅ | Personal Access Token |
KUBE_CONFIG |
base64编码内容 |
❌ | ✅ | K8s kubeconfig 文件 |
K8S_NAMESPACE |
my-project |
❌ | ❌ | K8s 命名空间 |
6.1 获取 Personal Access Token
- 点击右上角头像 → Preferences
- 左侧选择 Access Tokens
- 创建 Token:
- Name :
ci-registry - Expiration date: 选择有效期
- Scopes : 勾选
api,read_registry,write_registry
- Name :
- 复制生成的 Token(只显示一次!)
6.2 获取 KUBE_CONFIG
bash
# 在有 kubectl 的机器上执行
cat ~/.kube/config | base64 -w 0
7、启用 GitLab Container Registry
GitLab 自带的 Docker 镜像仓库默认可能未启用,需要手动配置。
7.1 配置 GitLab Registry
bash
# 编辑 GitLab 配置文件
vi /etc/gitlab/gitlab.rb
添加以下配置:
ruby
# Container Registry 配置
registry_external_url 'http://192.168.1.131:5050'
gitlab_rails['registry_enabled'] = true
registry['enable'] = true
registry['http_addr'] = '192.168.1.131' # 只监听内网地址
registry['http_port'] = 5050
7.2 应用配置
bash
# 重新配置 GitLab
gitlab-ctl reconfigure
# 重启服务
gitlab-ctl restart
# 验证 Registry 状态
gitlab-ctl status registry
7.3 验证端口
bash
# 检查 5050 端口是否监听
netstat -tlnp | grep 5050
7.4 在gitlab中配置变量
有些敏感信息在在gitlab-ci.yaml中是变量形式存在,所以我们需要再页面配下变量,保证不外泄。如下: 
8、配置 Docker 信任 HTTP Registry
由于我们使用 HTTP(非 HTTPS),需要配置 Docker 信任该仓库。
8.1 配置 daemon.json
bash
# 配置 Docker
cat > /etc/docker/daemon.json << 'EOF'
{
"insecure-registries": ["192.168.1.131:5050"],
"registry-mirrors": [
"https://docker.m.daocloud.io",
"https://docker.1panel.live",
"https://hub.rat.dev"
]
}
EOF

8.2 重启 Docker
bash
systemctl daemon-reload
systemctl restart docker
# 验证配置
docker info | grep -A 3 "Insecure Registries"
docker info | grep -A 3 "Registry Mirrors"
8.3 测试登录 Registry
bash
# 登录 Registry
docker login 192.168.1.131:5050 -u root -p "你的Token"
# 看到 "Login Succeeded" 表示成功

9、创建 K8s 命名空间
bash
# 创建命名空间
kubectl create namespace my-project
# 或者通过 YAML 文件创建
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Namespace
metadata:
name: my-project
EOF
# 验证
kubectl get namespace my-project

10、踩坑与解决方案汇总
在实际配置过程中,我遇到了很多坑,下面逐一总结。
坑 1:Runner 标签不匹配
现象
Pipeline 一直处于 pending 状态,不会执行。
原因
CI 配置中的 tags 与 Runner 标签不匹配。
解决方案
方式1:在 CI 配置中指定正确的 tags
yaml
# .gitlab-ci.yml
job:
tags:
- shell-runner # 必须与 Runner 标签一致
方式2:在 GitLab 上配置 Runner 接受无标签任务
- 进入 Settings → CI/CD → Runners
- 找到 Runner,点击编辑(铅笔图标)
- 勾选 "Run untagged jobs"
坑 2:Git 版本太旧导致 shallow clone 失败
现象
vbnet
fatal: git fetch-pack: expected shallow list
fatal: The remote end hung up unexpectedly
原因
服务器 Git 版本太旧(< 2.x),不支持 shallow clone。
解决方案
方式1:禁用 shallow clone(推荐)
yaml
# .gitlab-ci.yml 添加变量
variables:
GIT_DEPTH: 0
GIT_STRATEGY: clone
方式2:升级 Git
bash
# CentOS 安装新版 Git
yum install -y https://repo.ius.io/ius-release-el7.rpm
yum remove -y git
yum install -y git236
git --version
坑 3:Shell 执行器不需要 Docker 镜像
现象
YAML 验证失败或运行异常。
原因
Shell 执行器直接在服务器上运行命令,不需要指定 Docker 镜像。
解决方案
yaml
# 错误写法
default:
image: ""
# 正确写法 - 直接移除 default 配置
stages:
- build
坑 4:Docker Registry 未启用
现象
arduino
dial tcp 192.168.1.131:5050: connect: connection refused
原因
GitLab Container Registry 服务未启动。
解决方案
参见 第 7 节:启用 GitLab Container Registry
坑 5:HTTP Registry 被拒绝
现象
vbscript
Error response from daemon: Get "http://192.168.1.131:5050/v2/": http: server gave HTTP response to HTTPS client
原因
Docker 默认使用 HTTPS,HTTP 仓库需要配置为 insecure。
解决方案
参见 第 8 节:配置 Docker 信任 HTTP Registry
坑 6:Docker Hub 拉取镜像超时
现象
vbnet
Error response from daemon: Get "https://registry-1.docker.io/v2/": context deadline exceeded
原因
国内访问 Docker Hub 超时。
解决方案
配置镜像加速器:
bash
cat > /etc/docker/daemon.json << 'EOF'
{
"insecure-registries": ["192.168.1.131:5050"],
"registry-mirrors": [
"https://docker.m.daocloud.io",
"https://docker.1panel.live",
"https://hub.rat.dev"
]
}
EOF
systemctl restart docker
坑 7:2FA 导致密码登录失败
现象
csharp
HTTP Basic: Access denied. The provided password or token is incorrect or your account has 2FA enabled
原因
开启了双因素认证,不能使用密码登录 Registry。
解决方案
参见 第 6.1 节:获取 Personal Access Token
坑 8:镜像路径错误(最大的坑!)
现象
vbnet
denied: requested access to the resource is denied
原因
镜像路径不包含完整的项目路径。
解决方案
bash
# 错误路径 ❌
192.168.1.131:5050/my-group/my-service
# 正确路径 ✅(必须包含 Group/Project)
192.168.1.131:5050/my-group/my-project/my-service
镜像路径规则:
makefile
Registry地址/Group名/Project名/镜像名:标签
例如,如果你的 GitLab 项目 URL 是:
perl
http://192.168.1.131:19096/my-group/my-project
那么镜像路径必须是:
perl
192.168.1.131:5050/my-group/my-project/xxx
坑 9:Docker login 凭证不持久化
现象
登录成功但推送失败。
原因
在多行脚本块中登录,凭证没有被后续命令继承。
解决方案
方式1:把 login 放在 before_script 中(推荐)
yaml
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" 192.168.1.131:5050
方式2:在服务器上预配置凭证
bash
mkdir -p ~/.docker
echo '{
"auths": {
"192.168.1.131:5050": {
"auth": "'$(echo -n "root:你的Token" | base64)'"
}
}
}' > ~/.docker/config.json
我实际使用的第二种: 
坑 10:envsubst 变量未定义
现象
vbnet
error: error parsing STDIN: error converting YAML to JSON: yaml: line 26: mapping values are not allowed in this context
原因
K8s YAML 中使用了变量,但 CI 脚本中未定义。
解决方案
在 deploy 脚本中导出变量:
yaml
script:
- |
export REGISTRY="192.168.1.131:5050/xxxxyy/xxxx"
export IMAGE_TAG="${CI_COMMIT_SHORT_SHA}"
envsubst < k8s/deployments.yaml | kubectl apply -f -
11、完整配置文件
11.1 .gitlab-ci.yml
yaml
stages:
- build-and-docker
- deploy
variables:
GIT_DEPTH: 0
GIT_STRATEGY: clone
build-and-docker:
stage: build-and-docker
tags:
- shell-runner
before_script:
- source /etc/profile
- java -version
- mvn --version
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" 192.168.1.131:5050
script:
- |
set -e
echo "Step 1: Maven Build..."
mvn clean package -DskipTests -Ptest
echo "Step 2: Build Docker Images..."
for SERVICE in user xxx admin gateway; do
IMAGE_TAG="192.168.1.131:5050/my-group/my-project/my-service-${SERVICE}:${CI_COMMIT_SHORT_SHA}"
echo "Building my-service-${SERVICE}..."
docker build -t $IMAGE_TAG -f my-service-${SERVICE}/Dockerfile .
echo "Pushing my-service-${SERVICE}..."
docker push $IMAGE_TAG
done
echo "Done!"
only:
- main
- test
deploy-k8s:
stage: deploy
tags:
- shell-runner
before_script:
- kubectl version --client
script:
- |
set -e
export REGISTRY="192.168.1.131:5050/my-group/my-project"
export IMAGE_TAG="${CI_COMMIT_SHORT_SHA}"
mkdir -p ~/.kube
echo "$KUBE_CONFIG" | base64 -d > ~/.kube/config
chmod 600 ~/.kube/config
kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/configmap.yaml
kubectl apply -f k8s/secrets.yaml
envsubst < k8s/deployments.yaml | kubectl apply -f -
kubectl apply -f k8s/services.yaml
kubectl apply -f k8s/ingress.yaml
sleep 30
kubectl rollout status deployment/my-service-gateway -n ${K8S_NAMESPACE} --timeout=300s || true
kubectl rollout status deployment/my-service-user -n ${K8S_NAMESPACE} --timeout=300s || true
kubectl rollout status deployment/my-service-yyy -n ${K8S_NAMESPACE} --timeout=300s || true
kubectl rollout status deployment/my-service-admin -n ${K8S_NAMESPACE} --timeout=300s || true
kubectl get pods -n ${K8S_NAMESPACE} -o wide
environment:
name: test
url: http://your-domain.com
only:
- main
- test
needs:
- build-and-docker
11.2 /etc/docker/daemon.json
json
{
"insecure-registries": ["192.168.1.131:5050"],
"registry-mirrors": [
"https://docker.m.daocloud.io",
"https://docker.1panel.live",
"https://hub.rat.dev"
]
}
11.3 简化的 Dockerfile
由于 CI 已经构建好了 jar 包,Dockerfile 只需要复制并运行:
dockerfile
# Dockerfile - CI 预构建版本
FROM eclipse-temurin:21-jre-alpine
LABEL maintainer="your-name"
LABEL service="my-service-user"
# 设置时区
RUN apk add --no-cache tzdata && \
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone && \
apk del tzdata
# 创建非 root 用户
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
# 复制 jar 包(CI 已构建好)
COPY my-service-user/target/my-service-user-*.jar app.jar
# 设置权限
RUN chown -R appuser:appgroup /app
USER appuser
EXPOSE 18080
ENV JAVA_OPTS="-Xms256m -Xmx512m -XX:+UseG1GC -XX:MaxGCPauseMillis=200"
ENV SPRING_PROFILES_ACTIVE="test"
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -Dspring.profiles.active=${SPRING_PROFILES_ACTIVE} -jar app.jar"]
12、常用调试命令
bash
# ============ Runner 相关 ============
# 查看 Runner 日志
journalctl -u gitlab-runner -f
# 手动运行 Runner(调试模式)
gitlab-runner --debug run
# 验证 Runner
gitlab-runner verify
# 查看 Runner 列表
gitlab-runner list
# ============ Docker 相关 ============
# 测试登录 Registry
docker login 192.168.1.131:5050 -u root -p "your-token"
# 测试推送镜像
docker push 192.168.1.131:5050/my-group/my-project/my-service-user:test
# 查看 Registry 中的仓库
curl -u root:token http://192.168.1.131:5050/v2/_catalog
# ============ K8s 相关 ============
# 验证 YAML 格式
kubectl apply -f k8s/deployments.yaml --dry-run=client
# 本地测试 envsubst
export REGISTRY="192.168.1.131:5050/my-group/my-project"
export IMAGE_TAG="test"
envsubst < k8s/deployments.yaml
# 查看 Pod 状态
kubectl get pods -n my-project -o wide
# 查看 Pod 日志
kubectl logs -f deployment/my-service-user -n my-project
# 查看部署状态
kubectl rollout status deployment/my-service-user -n my-project
13、踩坑总结速查表
| 问题类型 | 现象 | 核心解决思路 |
|---|---|---|
| Runner 配置 | Pipeline pending | 确保 tags 匹配,使用 Shell 执行器 |
| Git 兼容性 | shallow clone 失败 | 设置 GIT_DEPTH: 0 禁用 |
| Registry 启用 | 连接被拒绝 | 配置 gitlab.rb 启用 Registry |
| HTTP 访问 | HTTPS/HTTP 冲突 | 配置 insecure-registries |
| 认证问题 | 2FA 登录失败 | 使用 Personal Access Token |
| 镜像路径 | denied | 必须包含完整的 Group/Project 路径 |
| 变量替换 | YAML 解析错误 | 确保 envsubst 所需变量已 export |
14、部署到 K8s 遇到的坑
k8s部署比较顺利没什么坑。
常用命令:
csharp
# 1. 查看命名空间下的所有 Pod
kubectl get pods -n my-project -o wide
# 2. 查看所有服务
kubectl get svc -n my-project
# 3. 查看 Ingress 配置
kubectl get ingress -n my-project
# 4. 查看部署状态
kubectl get deployments -n my-project
# 5. 一键查看所有资源
kubectl get all -n my-project
# 6. 查看 Pod 详细信息
kubectl describe pod <pod-name> -n my-project
# 7. 查看 Pod 日志
kubectl logs -f <pod-name> -n my-project
# 8. 查看服务端口映射
kubectl get endpoints -n my-project
快速检查命令:
# 检查所有服务是否正常运行
kubectl get pods,svc,ingress -n my-project
部署结果如下:

也可访问rancher看下服务:

至此,完活。一个CI CD 搞了挺长时间,所以在次记录下。