Kubernetes上运行的CI/CD自动化服务器
Jenkins 是使用最广泛的持续集成/持续交付服务器。把它跑在 Kubernetes 上有两种截然不同的思路:
控制器 Pod 内固定有 Kubernetes 插件(Kubernetes plugin,旧名 kubernetes-plugin),它通过集群内 API 创建 Agent Pod。因此部署 Jenkins 的核心工作其实是三件事:装控制器、给控制器访问 Pod 的 RBAC、配置 Pod 模板。
# 添加官方 Helm 仓库(官方文档用 jenkinsci 作为仓库别名)
helm repo add jenkinsci https://charts.jenkins.io
helm repo update
# 创建命名空间
kubectl create namespace jenkins
# 默认安装(不推荐用于生产:没有持久化)
helm install jenkins -n jenkins jenkinsci/jenkins
# 推荐:导出默认 values 后修改
helm show values jenkinsci/jenkins > jenkins-values.yaml
helm install jenkins -n jenkins -f jenkins-values.yaml jenkinsci/jenkins
# 指定 Chart 版本(Chart 5.6.0 起也可以用 OCI 地址
# oci://ghcr.io/jenkinsci/helm-charts/jenkins)
helm install jenkins -n jenkins -f jenkins-values.yaml \
jenkinsci/jenkins --version 5.8.0
# 取出 admin 初始密码(Secret 名默认等于 release 名)
kubectl get secret -n jenkins jenkins \
-o jsonpath="{.data.jenkins-admin-password}" | base64 --decode && echo
# 端口转发
kubectl -n jenkins port-forward svc/jenkins 8080:8080
# 浏览器访问 http://127.0.0.1:8080,用户名 admin
controller:
# 对外暴露方式:ClusterIP / NodePort / LoadBalancer
serviceType: ClusterIP
# 控制器资源,务必显式设置
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "2"
memory: "4Gi"
# 预装插件列表
installPlugins:
- kubernetes
- workflow-aggregator
- git
- configuration-as-code
- job-dsl
# 配置即代码(JCasC)
JCasC:
defaultConfig: true
configScripts:
welcome: |
jenkins:
systemMessage: "Managed by Helm + JCasC"
# 升级时递增,用于触发配置重载
# sidecar 容器在配置变更时自动 reload
sidecars:
configAutoReload:
enabled: true
# 必须:持久化,否则重启即丢全部任务与凭据
persistence:
enabled: true
storageClass: "standard"
size: "20Gi"
# 必须:允许控制器创建 Agent Pod
rbac:
create: true
serviceAccount:
create: true
name: jenkins
# 动态 Agent
agent:
enabled: true
# 控制器地址,必须是 Agent Pod 能解析到的集群内地址
jenkinsUrl: "http://jenkins.jenkins.svc.cluster.local:8080"
jenkinsTunnel: "jenkins-agent.jenkins.svc.cluster.local:50000"
# 直连模式:Agent 直接连控制器,不经过隧道
# directConnection: true
podTemplates: {}
如果不用 Chart 的 rbac.create,需要自己准备。Kubernetes 插件至少需要创建、查看、删除 Pod 以及进入容器执行命令的权限:
apiVersion: v1
kind: ServiceAccount
metadata:
name: jenkins
namespace: jenkins
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: jenkins-agent
namespace: jenkins
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create", "delete", "get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: jenkins-agent
namespace: jenkins
subjects:
- kind: ServiceAccount
name: jenkins
namespace: jenkins
roleRef:
kind: Role
name: jenkins-agent
apiGroup: rbac.authorization.k8s.io
控制器的 Pod 模板有两种维护方式:UI 手点(系统管理 → 节点与云 → Clouds)或 JCasC。生产环境应使用 JCasC,因为它可以被 Git 管理:
jenkins:
clouds:
- kubernetes:
name: "kubernetes"
serverUrl: "https://kubernetes.default.svc"
namespace: "jenkins"
# Agent 回调控制器用的地址,必须从 Pod 内可解析
jenkinsUrl: "http://jenkins.jenkins.svc.cluster.local:8080"
jenkinsTunnel: "jenkins-agent.jenkins.svc.cluster.local:50000"
containerCapStr: "10"
connectTimeout: 5
readTimeout: 15
# 新版插件用 templates,旧版插件里这个键叫 podTemplates
templates:
- name: "default"
label: "k8s-agent"
idleMinutes: 5
instanceCapStr: "20"
containers:
# 容器名必须是 jnlp,插件靠这个名字注入通信逻辑
- name: "jnlp"
image: "jenkins/inbound-agent:latest"
alwaysPullImage: false
workingDir: "/home/jenkins/agent"
resourceRequestCpu: "200m"
resourceRequestMemory: "256Mi"
- name: "maven"
image: "maven:3.9-eclipse-temurin-17"
command: "cat"
ttyEnabled: true
声明式流水线里用 kubernetes agent 直接声明一次性 Pod,是 Kubernetes 上最常用的写法:
pipeline {
agent {
kubernetes {
// 只需声明业务容器,jnlp 容器由插件自动注入
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: maven
image: maven:3.9-eclipse-temurin-17
command: ['cat']
tty: true
- name: kaniko
image: gcr.io/kaniko-project/executor:debug
command: ['sleep']
args: ['99d']
'''
}
}
stages {
stage('Build') {
steps {
container('maven') {
sh 'mvn -B -DskipTests clean package'
}
}
}
}
}
# 进入控制器容器执行 CLI
kubectl -n jenkins exec -it jenkins-0 -c jenkins -- bash
# 列出已装插件
jenkins-plugin-cli --list
# 安装插件
jenkins-plugin-cli --plugins kubernetes:latest git:latest
凭据建议用 JCasC 的 credentials: 块引用环境变量或 Kubernetes Secret,而不是在 UI 里手填 —— 这样凭据来源可追溯、可重建:
credentials:
system:
domainCredentials:
- credentials:
- string:
scope: GLOBAL
id: "registry-token"
secret: "${REGISTRY_TOKEN}"
jnlp。插件靠这个名字识别哪个容器负责启动 Agent 通信;改名成 agent 或 slave 后 Pod 会起来,但永远连不上控制器,构建卡在 Waiting for next available executor。jenkinsUrl 必须是 Agent Pod 能从集群内解析的地址。填外网 Ingress 域名是经典错误 —— 域名在集群内解析不到(或指向外部网关回不来),表现为 Agent Pod 反复重启、日志里 404 或 connection refused。集群内一律用 http://<service>.<namespace>.svc.cluster.local:8080。agent.directConnection: true)可绕过隧道。persistence.enabled=false 时,控制器 Pod 重建后所有任务、凭据、插件配置全部丢失。生产环境必须挂 PVC。controller.JCasC.defaultConfig: true 后,每次重载配置都以 configScripts 为准,在 UI 里手改的云配置、安全域会在下一次 reload 时被抹掉。rbac.create: false 又用了默认 ServiceAccount,控制器调用 API 会被拒(403),任务报 Forbidden: User "system:serviceaccount:..." cannot create resource "pods"。latest 会让集群静默漂移。Agent 镜像默认 alwaysPullImage: false,节点上缓存了新版本也不会拉取,导致同样的流水线在不同节点行为不一致 —— 应当给 Agent 镜像打固定 tag 或 digest。podRetention 默认策略下,失败或中断的构建 Pod 会被保留用于看日志,长期运行会积累大量 Completed/Error 状态的 Pod,需要配合 idleMinutes、instanceCapStr 与定期清理。jenkins 用户运行,hostPath 或未设置 fsGroup 的 PVC 会导致 /var/jenkins_home 写入失败并启动崩溃;Chart 默认用 fsGroup: 1000 解决这一点。helm upgrade 会重建控制器 Pod,正在执行的构建随之中断(它们跑在独立的 Agent Pod 里,但会失去与控制器的心跳)。升级应安排在构建空窗期,并先把控制器置为静默(进入「Prepare for Shutdown」)。installPlugins 里的插件可能要求更高版本的 Jenkins,表现为启动时报插件不兼容;应锁定插件版本,并在升级前看发布说明。另外,给控制器设置 numExecutors: 0 可强制所有任务走 Agent,避免构建把控制器拖死。kubectl — Kubernetes集群管理工具helm — Kubernetes包管理器tekton — Kubernetes原生CI/CD流水线框架gitlab-runner — GitLab CI 执行器