Kubernetes 部署 GitLab Runner 及 Java CI/CD 实践指南
Kubernetes 部署 GitLab Runner 及 Java CI/CD 实践指南
本文档包含如何在 Kubernetes 集群中部署 GitLab Runner,并配置基于 Git 管理部署清单的 Java 项目 CI/CD 流水线。
1. 在 Kubernetes 中部署 GitLab Runner
⚠️ 重要前置:配置 Kubernetes 全局域名解析
由于我们使用内部 IP 搭建了 GitLab 和 Registry(例如
172.30.187.7),如果在 Kubernetes 中不配置全局解析,会导致两个致命问题:
- Kubelet 无法解析
registry.aioil.top,导致拉取基础镜像时报ErrImagePull: no such host。- Runner 无法解析
gitlab.aioil.top,导致无法拉取代码。解决步骤:
- 修改 CoreDNS 的 ConfigMap 添加全局解析:
kubectl edit configmap coredns -n kube-system- 在
Corefile的ready下方插入hosts配置:ready # 新增这块配置 hosts { 172.30.187.7 gitlab.aioil.top 172.30.187.7 registry.aioil.top fallthrough } kubernetes cluster.local in-addr.arpa ip6.arpa {- 重启 CoreDNS 生效:
kubectl rollout restart deployment coredns -n kube-system- 双保险(极其重要):为了防止某些容器运行时直接读取宿主机网络,请在所有 Kubernetes 节点(Master 和 Worker)的宿主机上执行:
echo "172.30.187.7 gitlab.aioil.top registry.aioil.top" | sudo tee -a /etc/hosts
推荐使用官方 Helm Chart 部署 GitLab Runner。
1.1 前置准备
- 确保已安装 Helm。
- 在 GitLab 中获取全局 Runner 注册令牌 (Registration Token):访问
http://gitlab.aioil.top/admin/runners(Admin area -> Runners),点击页面右上角 “New instance runner” 旁边的三个点⋮按钮,在弹出的菜单中点击复制图标获取 “Registration token”。
1.2 添加 Helm 仓库并配置
helm repo add gitlab https://charts.gitlab.io/
helm repo update
创建 values.yaml 配置文件:
# GitLab 服务器的 URL
cat << 'EOF' > values.yaml
# GitLab 服务器的 URL
gitlabUrl: "https://gitlab.aioil.top/"
# 解决 Helm Chart 注册阶段报错 x509: certificate is valid for higress-gateway
# 将这三项全部设置为 true/空 才能彻底跳过注册时的验证
certsSecretName: ""
cert: ""
tlsVerify: false
# 刚才获取的 Runner Token
runnerRegistrationToken: "X8cmMT-a5nEdFsREEJcA"
# 开启 RBAC 权限(允许 Runner 在 K8s 中创建 Pod 来执行 Job)
rbac:
create: true
clusterWideAccess: true
# Runner 的并发数限制
concurrent: 10
runners:
# 默认的镜像,当 .gitlab-ci.yml 中没有指定 image 时使用
image: ubuntu:22.04
# 分配给这个 Runner 的标签,必须与注册时填写的匹配
tags: "k8s-runner"
# K8s 执行器配置
config: |
concurrent = 10
[[runners]]
environment = ["GIT_SSL_NO_VERIFY=true"]
tls-skip-verify = true
request_concurrency = 4
[runners.kubernetes]
namespace = "{{.Release.Namespace}}"
image = "ubuntu:22.04"
privileged = false
EOF
注意:现代 K8s CI/CD 推荐使用 Kaniko 构建镜像,因此不需要开启 privileged = true。
1.3 执行部署
helm upgrade --install gitlab-runner gitlab/gitlab-runner \
--namespace gitlab-runner --create-namespace \
-f values.yaml
部署完成后,可以通过以下几种方式验证服务是否正常运行:
1. 检查 Kubernetes 中的 Pod 状态
kubectl get pods -n gitlab-runner
正常情况下,你应该能看到类似 gitlab-runner-xxxx-xxxx 的 Pod 处于 Running 状态。
2. 查看 Runner 注册日志
kubectl logs -f -l app=gitlab-runner -n gitlab-runner
在日志中,寻找包含 Registering runner... succeeded 的输出,这说明 Runner 已成功连接到你的 GitLab 实例。
3. 在 GitLab 界面验证
访问您的 GitLab 管理后台:http://gitlab.aioil.top/admin/runners(Admin area -> Runners)。
如果一切正常,你应该能在页面上看到一个**带绿色圆点(在线状态)**的新 Runner,并且带有 k8s-runner 的标签。
2. Java CI/CD 流水线配置
通过 .gitlab-ci.yml 实现构建、测试、打包(Kaniko)以及使用 Git 中管理的 Kubernetes YAML 文档进行部署。
2.1 目录结构要求
项目代码库中除了 Java 代码,还应包含 Dockerfile 和用于部署的 k8s-manifests 目录。
my-java-app/
├── src/
├── pom.xml
├── Dockerfile
├── k8s-manifests/ <-- Git 管理的部署测试文档
│ ├── deployment.yaml
│ └── service.yaml
└── .gitlab-ci.yml
2.2 编写 .gitlab-ci.yml
workflow:
rules:
# 如果是 Tag 提交,使用 Tag 作为镜像标签
- if: $CI_COMMIT_TAG
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
# 否则,默认使用 SHA 作为镜像标签
- if: $CI_COMMIT_BRANCH
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
stages:
- build
- test
- package
- deploy
variables:
MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
# Git 管理的部署文档所在目录
K8S_DEPLOY_DIR: "k8s-manifests"
# 在每个 Job 执行前运行,用于打印变量方便 Debug
before_script:
- echo "====== Print Variables for Debugging ======"
- echo "MAVEN_OPTS=$MAVEN_OPTS"
- echo "IMAGE_TAG=$IMAGE_TAG"
- echo "K8S_DEPLOY_DIR=$K8S_DEPLOY_DIR"
- echo "CI_REGISTRY_IMAGE=$CI_REGISTRY_IMAGE"
- echo "CI_COMMIT_SHORT_SHA=$CI_COMMIT_SHORT_SHA"
- echo "CI_REGISTRY=$CI_REGISTRY"
- echo "CI_REGISTRY_USER=$CI_REGISTRY_USER"
- echo "CI_PROJECT_DIR=$CI_PROJECT_DIR"
- echo "==========================================="
# 缓存 Maven 依赖加速构建
cache:
paths:
- .m2/repository/
- target/
# 1. 编译与打包
build:
stage: build
# 替换为您私有仓库中的 Maven 基础镜像
image: registry.aioil.top/prod/infra/base-images/maven:3.8.6-openjdk-11
script:
- mvn clean package -DskipTests
artifacts:
paths:
- target/*.jar
expire_in: 1 hour
tags:
- k8s-runner
# 2. 测试
test:
stage: test
image: registry.aioil.top/prod/infra/base-images/maven:3.8.6-openjdk-11
script:
- mvn test
tags:
- k8s-runner
# 3. 构建 Docker 镜像 (使用 Kaniko,无需 Docker in Docker)
package:
stage: package
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
# 因为 Kaniko 镜像里没有 maven 等常规工具,所以需要覆盖全局的 before_script
before_script:
- echo "Skip global before_script in Kaniko job to avoid 'command not found' errors."
script:
# 配置 Registry 认证
- mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
# 构建并推送镜像 (由于线上 Registry 配置为 HTTP 协议,需要增加 insecure 参数)
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $IMAGE_TAG --insecure --insecure-pull
tags:
- k8s-runner
# 4. 部署到 K8s
deploy:
stage: deploy
image: bitnami/kubectl:latest
script:
# 将 Git 部署文档中的镜像占位符替换为刚刚构建的镜像 TAG
- sed -i "s|APP_IMAGE_PLACEHOLDER|$IMAGE_TAG|g" $K8S_DEPLOY_DIR/deployment.yaml
# 打印修改后的 deployment.yaml,确认镜像已被正确替换
- "cat $K8S_DEPLOY_DIR/deployment.yaml | grep image:"
# 应用 Git 管理的清单文件进行部署
- kubectl apply -f $K8S_DEPLOY_DIR/
# 强制 K8s 重启该 Deployment 以拉取新镜像并应用最新状态;如果是首次部署报错则忽略
- "kubectl rollout restart deployment/java-app --namespace=default || true"
# 等待部署完成,确保 Pod 正常运行
- kubectl rollout status deployment/java-app --namespace=default --timeout=300s
environment:
name: production
tags:
- k8s-runner
only:
# 触发条件:只有当 Git 标签 (Tag) 满足 vX.Y.Z 格式时才执行部署
- /^v\d+\.\d+\.\d+$/
注意:在 deploy 阶段,Runner 使用的 ServiceAccount 需要有操作对应 Namespace 资源的权限(在安装 Runner 的 values.yaml 中配置 RBAC)。
2.3 Git 管理的 Kubernetes 部署文档示例
k8s-manifests/deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: java-app
spec:
replicas: 2
selector:
matchLabels:
app: java-app
template:
metadata:
labels:
app: java-app
spec:
containers:
- name: java-app
# 这里的 APP_IMAGE_PLACEHOLDER 会被 CI 脚本中的 sed 命令替换
image: APP_IMAGE_PLACEHOLDER
ports:
- containerPort: 8080
# 容器内健康检查测试配置
livenessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 30
imagePullSecrets:
- name: gitlab-registry-secret
k8s-manifests/service.yaml:
apiVersion: v1
kind: Service
metadata:
name: java-app-svc
spec:
selector:
app: java-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
2.4 配置 Kubernetes 拉取私有镜像仓库的凭证
在 Kubernetes 部署应用时,集群需要从 GitLab Container Registry 拉取我们构建好的私有镜像。最官方、最稳定、最安全的做法是使用 Deploy Tokens (部署令牌) 创建 Kubernetes Secret,而不是使用个人访问令牌或 CI 环境变量(CI_JOB_TOKEN 的有效期仅限于流水线执行期间,会导致 Pod 重启时拉取镜像失败)。
第一步:在 GitLab 中生成访问凭证 (Token)
根据您的使用场景,您可以选择生成项目级、群组级或全局级别的 Token:
方案 A:生成项目级 Deploy Token(推荐用于单个项目)
- 进入您的项目页面。
- 左侧菜单导航至 Settings (设置) -> Repository (仓库)。
- 展开 Deploy tokens (部署令牌) 区域。
- 点击 Add token (添加令牌):
- Name (名称):填入
k8s-registry-token - Scopes (权限范围):勾选
read_registry(如果 CI 还需要推送镜像,需一并勾选write_registry)
- Name (名称):填入
- 点击 Create deploy token,复制屏幕顶部生成的
Username和Token。
方案 B:生成群组级 Deploy Token(推荐用于微服务架构)
如果您有多个微服务项目放在同一个 GitLab Group(群组)下,可以在 Group 级别生成:
- 进入您的 Group (群组) 页面。
- 导航至 Settings (设置) -> Repository (仓库) -> Deploy tokens。
- 创建步骤同上。使用这个 Token,K8s 可以拉取该群组下所有项目的镜像。
方案 C:生成全局管理员 Token(仅限私有环境,通杀所有项目)
如果您希望 Kubernetes 集群只需配置一个 Secret 就能拉取 GitLab 里的任意镜像,可以创建一个全局的个人访问令牌:
- 使用管理员账号(如
root)登录 GitLab。 - 点击右上角头像 -> Edit profile (编辑个人资料) -> 左侧菜单选择 Access Tokens (访问令牌)。
- 点击 Add new token:
- Token name:
k8s-global-registry-token - Expiration date: 留空(不过期)或设置一个较长的日期
- Scopes: 勾选
read_registry(仅用于 K8s 拉取镜像)
- Token name:
- 点击 Create personal access token。
- 注意:这种情况下,您的 Username 就是您登录的用户名(例如
root),Password 就是生成的这个 Token。
第二步:在 K8s 中创建 Registry Secret
概念辨析:为什么需要创建 Secret(2.4 章节)还要配置 DOCKER_AUTH_CONFIG(2.5 章节)?
- 2.4 章节的 Secret:是给业务应用使用的。当 CI/CD 流水线跑完,您的代码已经被打包成镜像。此时 K8s 需要把这个业务镜像拉下来运行,它使用的是
deployment.yaml中配置的imagePullSecrets。- 2.5 章节的 DOCKER_AUTH_CONFIG:是给 GitLab Runner 的 CI 任务容器使用的。当流水线刚开始运行时,Runner 需要拉取一个“基础环境镜像”(比如带有 Maven、Node.js 的环境)来执行编译脚本,此时 K8s 还没有开始部署业务,它拉取基础镜像的凭证来自于
DOCKER_AUTH_CONFIG变量注入。结论:这两个配置必须同时存在。前者负责最终业务上线时的镜像拉取,后者负责 CI 过程构建环境的镜像拉取,它们作用于 CI/CD 生命周期的不同阶段。
在 Kubernetes 集群控制节点(Master 节点)上执行以下命令,将上一步复制的凭证填入:
kubectl create secret docker-registry gitlab-registry-secret \
--docker-server=registry.aioil.top \
--docker-username=root \
--docker-password=glpat-3_8PwL74JJ8YLxW9ydVn \
--namespace=default
(注意:请确保 --namespace 与您的业务应用部署的 Namespace 保持一致。)
总结优势
- 安全性高:Deploy Token 只与当前项目绑定,且仅具备读镜像权限,不会泄露个人账号权限。
- 稳定性强:Token 永久有效,无论未来 K8s 何时重启 Pod 或扩容,拉取镜像都不会报错。
- CI 解耦:CI 流水线脚本无需处理权限生成逻辑,保持干净整洁。
2.5 解决 GitLab Runner 无法拉取私有基础镜像的问题
当我们在 .gitlab-ci.yml 的 image 字段指定使用我们自己私有仓库(例如 registry.aioil.top/...)的基础镜像时,可能会遇到拉取失败(insufficient_scope: authorization failed)的问题。
原因分析:虽然 CI 脚本有 $CI_JOB_TOKEN 的权限,但负责底层创建 Pod 的 K8s Kubelet 默认是不携带这些认证信息的,导致被 Registry 拒绝。
解决办法:通过配置 DOCKER_AUTH_CONFIG 变量,GitLab Runner 在向 K8s 发起创建 Pod 的请求时,会自动将拉取凭证注入给 Kubelet。
操作步骤:
1. 生成认证字符串
在任意终端执行以下命令(建议使用前面生成的 Deploy Token 用户名和密码):
# 将 Username 和 Password 替换为您实际的 Deploy Token 信息
echo -n "root:glpat-3_8PwL74JJ8YLxW9ydVn" | base64 -w 0
假设输出的结果是 cm9vdDpnbHBhdC0zXzhQd0w3NEpKOFlMeFc5eWRWbg==。
2. 组装 JSON 字符串
将上面的 Base64 结果填入以下 JSON 模板的 auth 字段中,并替换您的 Registry 地址:
{
"auths": {
"registry.aioil.top": {
"auth": "cm9vdDpnbHBhdC0zXzhQd0w3NEpKOFlMeFc5eWRWbg=="
}
}
}
3. 在 GitLab 中配置变量 (支持项目/群组/全局)
您可以根据实际需求,将这个变量配置在不同的级别,作用范围越广,需要重复配置的次数越少。
选项 A:配置为全局级(Instance-level) - 推荐私有化环境
如果配置在此处,GitLab 实例中的所有项目在运行 CI 时都会自动携带这个拉取凭证。
- 以管理员(root)身份登录 GitLab。
- 访问管理后台:Admin area -> Settings -> CI/CD。
- 展开 Variables,点击 Add variable。
- 填写要求同下。
选项 B:配置为群组级(Group-level)
- 进入对应的 Group (群组) 页面。
- 左侧菜单导航至 Settings (设置) -> CI/CD。
- 展开 Variables,点击 Add variable。
- 填写要求同下。
选项 C:配置为项目级(Project-level)
- 进入具体的 项目页面。
- 导航至 Settings (设置) -> CI/CD -> Variables。
变量具体填写内容:
- Key (键): 必须严格填写
DOCKER_AUTH_CONFIG - Value (值): 把上面组装好的整段 JSON 粘贴进去。
- Type (类型): 保持
Variable不变。 - 取消勾选
Protect variable(确保非保护分支也能使用该凭证拉取基础镜像)。 - 点击 Add variable 保存。
配置完成后,重新运行流水线,Runner 就能成功拉取私有仓库中的基础镜像并启动 CI 容器了。
2.6 CI/CD 部署到 K8s 的权限配置(两种方案)
在 deploy 阶段执行 kubectl apply 时,需要有操作 K8s 集群的权限。以下提供两种常见的配置方案,您可以根据实际需求任选其一:
方案一:为 Runner 配置 RBAC 权限(推荐,同集群部署)
如果您的 GitLab Runner 与目标业务应用部署在同一个 Kubernetes 集群,最安全且原生的方式是通过 RBAC 赋予执行 Job 的 Pod 权限。
1. 权限范围解析与安全建议
- ClusterRole (
gitlab-runner-deploy-clusterrole): 定义了集群级别的权限规则,允许对所有命名空间下的 Deployment、Service、Pod 和 ReplicaSet 等核心资源执行增删改查操作。 - ClusterRoleBinding (
gitlab-runner-deploy-clusterrolebinding): 将上述定义好的全局权限角色绑定给gitlab-runner命名空间下的defaultServiceAccount。因为 Runner 动态创建的执行 Job 的 Pod 默认会使用这个 ServiceAccount。
⚠️ 安全评估:
当前示例使用的是ClusterRole和ClusterRoleBinding,这意味着 GitLab Runner 拥有操作整个 K8s 集群所有命名空间的能力。
- 优点:配置极其简单,适合公司内部完全互信的私有环境,一个配置打通所有项目的部署。
- 风险:如果 GitLab 被恶意人员访问,他们可以通过编写恶意的
.gitlab-ci.yml去删除或篡改集群中其他不相关项目(甚至是kube-system)的资源。- 加固建议(可选):如果是多团队共享的生产集群,建议将
ClusterRole改为Role,将ClusterRoleBinding改为RoleBinding,并逐个指定允许操作的业务命名空间(Namespace),实现严格的权限隔离。
2. 生成文件并应用配置
为了方便后期排查和维护,建议将配置输出到文件中,然后再应用。在服务器终端执行以下 Shell 命令:
cat << 'EOF' > gitlab-runner-rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: gitlab-runner-deploy-clusterrole
rules:
- apiGroups: ["", "apps", "extensions"]
resources: ["deployments", "services", "pods", "replicasets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: gitlab-runner-deploy-clusterrolebinding
subjects:
- kind: ServiceAccount
name: default
namespace: gitlab-runner
roleRef:
kind: ClusterRole
name: gitlab-runner-deploy-clusterrole
apiGroup: rbac.authorization.k8s.io
EOF
# 应用配置
kubectl apply -f gitlab-runner-rbac.yaml
方案二:通过 CI/CD 变量注入 kubeconfig(适合跨集群部署)
如果您不需要/不想在集群内配置 RBAC,或者您需要将应用部署到其他远程 Kubernetes 集群,可以将具有目标集群操作权限的 kubeconfig 文件内容作为 GitLab CI 变量注入到流水线中。
1. 在 GitLab 中配置变量 (支持项目/群组/全局)
您可以根据管理范围,将 KUBECONFIG_CONTENT 变量配置在不同级别:
- 全局级(Instance-level):访问 Admin area -> Settings -> CI/CD -> Variables。
⚠️ 安全警告:如果配置在全局,意味着 GitLab 上的所有项目都能获取您的 K8s 集群操作权限。除非这是您个人的私有环境且所有项目绝对互信,否则极度不推荐!
- 群组级(Group-level):在群组的 Settings -> CI/CD -> Variables 配置,适合将业务部署到同一个集群的微服务群组。
- 项目级(Project-level):在单个项目的 Settings -> CI/CD -> Variables 配置,权限隔离最好。
配置具体参数:
- 复制有权限的集群
~/.kube/config文件内容。 - 添加一个变量:
- Key:
KUBECONFIG_CONTENT - Value: 粘贴您的 kubeconfig 文件内容
- Type: Variable(纯文本)
- 取消勾选 “Protect variable”(除非只在受保护的分支上运行)
- Key:
2. 修改 .gitlab-ci.yml 部署脚本
在 deploy 阶段的 script 中,在执行 kubectl 之前生成 .kube/config 文件:
deploy:
stage: deploy
image: bitnami/kubectl:latest
script:
# 1. 注入 kubeconfig (使用纯文本变量)
- mkdir -p ~/.kube
- echo "$KUBECONFIG_CONTENT" > ~/.kube/config
# 2. 替换镜像标签并部署
- sed -i "s|APP_IMAGE_PLACEHOLDER|$IMAGE_TAG|g" $K8S_DEPLOY_DIR/deployment.yaml
- kubectl apply -f $K8S_DEPLOY_DIR/
更多推荐




所有评论(0)