Kubernetes 部署 GitLab Runner 及 Java CI/CD 实践指南

Kubernetes 部署 GitLab Runner 及 Java CI/CD 实践指南

本文档包含如何在 Kubernetes 集群中部署 GitLab Runner,并配置基于 Git 管理部署清单的 Java 项目 CI/CD 流水线。

1. 在 Kubernetes 中部署 GitLab Runner

推荐使用官方 Helm Chart 部署 GitLab Runner。

1.1 前置准备

  1. 确保已安装 Helm。
  2. 在 GitLab 中获取全局 Runner 注册令牌 (Registration Token):访问 http://gitlab.aioil.top/admin/runners(Admin area -> Runners),点击页面右上角 "New instance runner" 旁边的三个点 按钮,在弹出的菜单中点击复制图标获取 "Registration token"。

1.2 添加 Helm 仓库并配置

bash 复制代码
helm repo add gitlab https://charts.gitlab.io/
helm repo update

创建 values.yaml 配置文件:

yaml 复制代码
# GitLab 服务器的 URL
cat << 'EOF' > values.yaml
# GitLab 服务器的 URL
gitlabUrl: "http://gitlab.aioil.top/"

# 刚才获取的 Runner Token
runnerRegistrationToken: "_J5xUEZT-4tbU-yhAghi"

# 开启 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: |
    [[runners]]
      [runners.kubernetes]
        namespace = "{{.Release.Namespace}}"
        image = "ubuntu:22.04"
        # 推荐使用非特权模式
        privileged = false
EOF

注意:现代 K8s CI/CD 推荐使用 Kaniko 构建镜像,因此不需要开启 privileged = true

1.3 执行部署

bash 复制代码
helm upgrade --install gitlab-runner gitlab/gitlab-runner \
  --namespace gitlab-runner --create-namespace \
  -f values.yaml

部署完成后,可以通过以下几种方式验证服务是否正常运行:

1. 检查 Kubernetes 中的 Pod 状态

bash 复制代码
kubectl get pods -n gitlab-runner

正常情况下,你应该能看到类似 gitlab-runner-xxxx-xxxx 的 Pod 处于 Running 状态。

2. 查看 Runner 注册日志

bash 复制代码
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 目录。

text 复制代码
my-java-app/
├── src/
├── pom.xml
├── Dockerfile
├── k8s-manifests/           <-- Git 管理的部署测试文档
│   ├── deployment.yaml
│   └── service.yaml
└── .gitlab-ci.yml

2.2 编写 .gitlab-ci.yml

yaml 复制代码
stages:
  - build
  - test
  - package
  - deploy

variables:
  MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
  # 镜像标签,使用本次提交的 SHA
  IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
  # Git 管理的部署文档所在目录
  K8S_DEPLOY_DIR: "k8s-manifests"

# 缓存 Maven 依赖加速构建
cache:
  paths:
    - .m2/repository/
    - target/

# 1. 编译
build:
  stage: build
  image: maven:3.8.6-openjdk-11
  script:
    - mvn clean compile
  tags:
    - kubernetes

# 2. 测试
test:
  stage: test
  image: maven:3.8.6-openjdk-11
  script:
    - mvn test
  tags:
    - kubernetes

# 3. 构建 Docker 镜像 (使用 Kaniko,无需 Docker in Docker)
package:
  stage: package
  image:
    name: gcr.io/kaniko-project/executor:debug
    entrypoint: [""]
  script:
    # 准备打包用的 jar 包 (Kaniko 容器内没有 maven)
    - mvn package -DskipTests
    # 配置 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:
    - kubernetes

# 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
    # 应用 Git 管理的清单文件进行部署
    - kubectl apply -f $K8S_DEPLOY_DIR/
  environment:
    name: production
  tags:
    - kubernetes
  only:
    - main

注意:在 deploy 阶段,Runner 使用的 ServiceAccount 需要有操作对应 Namespace 资源的权限(在安装 Runner 的 values.yaml 中配置 RBAC)。

2.3 Git 管理的 Kubernetes 部署文档示例

k8s-manifests/deployment.yaml:

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:

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 中生成 Deploy Token
  1. 进入您的项目页面。
  2. 左侧菜单导航至 Settings (设置) -> Repository (仓库)
  3. 展开 Deploy tokens (部署令牌) 区域。
  4. 点击 Add token (添加令牌)
    • Name (名称) :填入 k8s-registry-token
    • Expiration date (过期时间):留空(永久有效,除非手动撤销)
    • Username (用户名) :留空(GitLab 会自动生成,例如 gitlab+deploy-token-1
    • Scopes (权限范围) :只勾选 read_registry
  5. 点击 Create deploy token
  6. 屏幕顶部会显示生成的 UsernameToken请立刻复制它们,刷新页面后将无法再次查看该密码。
第二步:在 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 节点)上执行以下命令,将上一步复制的凭证填入:

bash 复制代码
kubectl create secret docker-registry gitlab-registry-secret \
  --docker-server=gitlab.aioil.top:5005 \
  --docker-username=<替换为生成的Username> \
  --docker-password=<替换为生成的Token> \
  --namespace=default

(注意:请确保 --namespace 与您的业务应用部署的 Namespace 保持一致。)

总结优势
  1. 安全性高:Deploy Token 只与当前项目绑定,且仅具备读镜像权限,不会泄露个人账号权限。
  2. 稳定性强:Token 永久有效,无论未来 K8s 何时重启 Pod 或扩容,拉取镜像都不会报错。
  3. CI 解耦:CI 流水线脚本无需处理权限生成逻辑,保持干净整洁。

2.5 解决 GitLab Runner 无法拉取私有基础镜像的问题

当我们在 .gitlab-ci.ymlimage 字段指定使用我们自己私有仓库(例如 gitlab.aioil.top:5005/...)的基础镜像时,可能会遇到拉取失败(insufficient_scope: authorization failed)的问题。

原因分析 :虽然 CI 脚本有 $CI_JOB_TOKEN 的权限,但负责底层创建 Pod 的 K8s Kubelet 默认是不携带这些认证信息的,导致被 Registry 拒绝。

解决办法 :通过配置 DOCKER_AUTH_CONFIG 变量,GitLab Runner 在向 K8s 发起创建 Pod 的请求时,会自动将拉取凭证注入给 Kubelet。

操作步骤:

1. 生成认证字符串

在任意终端执行以下命令(建议使用前面生成的 Deploy Token 用户名和密码):

bash 复制代码
# 将 Username 和 Password 替换为您实际的 Deploy Token 信息
echo -n "gitlab+deploy-token-1:您的Token密码" | base64 -w 0

假设输出的结果是 Z2l0bGFiK2RlcGxveS10b2tlbS0xOnh4eHh4

2. 组装 JSON 字符串

将上面的 Base64 结果填入以下 JSON 模板的 auth 字段中,并替换您的 Registry 地址:

json 复制代码
{
    "auths": {
        "gitlab.aioil.top:5005": {
            "auth": "Z2l0bGFiK2RlcGxveS10b2tlbS0xOnh4eHh4"
        }
    }
}

3. 在 GitLab 项目中配置变量

  1. 进入当前项目(或群组级别)的 GitLab 页面。
  2. 左侧菜单导航至 Settings (设置) -> CI/CD
  3. 展开 Variables (变量)
  4. 点击 Add variable (添加变量)
    • Key (键) : 必须严格填写 DOCKER_AUTH_CONFIG
    • Value (值): 把上面组装好的整段 JSON 粘贴进去。
    • Type (类型) : 保持 Variable 不变。
    • 取消勾选 Protect variable(确保非保护分支也能使用)。
  5. 点击 Add variable 保存。

配置完成后,重新运行流水线,Runner 就能成功拉取私有仓库中的基础镜像并启动 CI 容器了。


2.6 CI/CD 部署到 K8s 的权限配置(两种方案)

deploy 阶段执行 kubectl apply 时,需要有操作 K8s 集群的权限。以下提供两种常见的配置方案,您可以根据实际需求任选其一:

方案一:为 Runner 配置 RBAC 权限(推荐,同集群部署)

如果您的 GitLab Runner 与目标业务应用部署在同一个 Kubernetes 集群,最安全且原生的方式是通过 RBAC 赋予执行 Job 的 Pod 权限。

1. YAML 配置功能解析
  • ClusterRole (gitlab-runner-deploy-clusterrole): 定义了集群级别的权限规则,允许对所有命名空间下的 Deployment、Service、Pod 和 ReplicaSet 等核心资源执行增删改查操作。
  • ClusterRoleBinding (gitlab-runner-deploy-clusterrolebinding) : 将上述定义好的全局权限角色绑定给 gitlab-runner 命名空间下的 default ServiceAccount。因为 Runner 动态创建的执行 Job 的 Pod 默认会使用这个 ServiceAccount。
2. 生成文件并应用配置

为了方便后期排查和维护,建议将配置输出到文件中,然后再应用。在服务器终端执行以下 Shell 命令:

bash 复制代码
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 中配置变量
  1. 复制有权限的集群 ~/.kube/config 文件内容。
  2. 进入 GitLab 项目 -> Settings -> CI/CD -> Variables
  3. 添加一个变量:
    • Key : KUBECONFIG_CONTENT
    • Value: 粘贴您的 kubeconfig 文件内容
    • Type: Variable(纯文本)
    • 取消勾选 "Protect variable"(除非只在受保护的分支上运行)
2. 修改 .gitlab-ci.yml 部署脚本

deploy 阶段的 script 中,在执行 kubectl 之前生成 .kube/config 文件:

yaml 复制代码
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/
相关推荐
武子康2 分钟前
Java-07 深入浅出 MyBatis数据库一对多关系模型实战:表结构设计与查询实现
java·后端
REDcker2 小时前
Linux OverlayFS详解
java·linux·运维
Royzst2 小时前
xml知识点
java·服务器·前端
鱼鳞_3 小时前
苍穹外卖-Day08(缓存套餐)
java·redis·缓存
过期动态3 小时前
【LeetCode 热题 100】移动零
java·数据结构·算法·leetcode·职场和发展·rabbitmq
sinat_255487814 小时前
IDEA:查找文件/类
java·ide·设计模式·intellij-idea
安当加密4 小时前
Kubernetes Secret不安全?External Secrets Operator接入凭据管理服务实战,自动轮转零停机
安全·容器·kubernetes
AI人工智能+电脑小能手5 小时前
【大白话说Java面试题 第77题】【Mysql篇】第7题:回表查询与全表扫描的区别?
java·开发语言·数据库·mysql·面试
lulu12165440785 小时前
Claude Code SpringBoot技能体系架构设计与演进
java·人工智能·spring boot·后端·ai编程
callJJ5 小时前
Nacos 详解——从概念到实战
java·spring boot·spring·spring cloud·微服务·nacos