本文在 Kubernetes 内部署 Jenkins Controller,并由 Kubernetes 插件为每次构建创建一次性 Agent Pod。流水线完成代码检出、测试、rootless BuildKit 构建镜像、推送仓库、更新 Deployment、健康验证和失败回滚。所有关键凭据都通过 Jenkins Credentials 或 Kubernetes Secret 注入,不写进 Jenkinsfile。
一、最终架构与安全边界
Git Webhook -> Jenkins Controller(PVC, 无构建负载)
|
+-> Ephemeral Agent Pod
|- test container
|- rootless BuildKit container -> Image Registry
`- kubectl container -> staging namespace
|- Deployment rollout
`- Service smoke test- Controller 只负责编排与保存配置,构建在临时 Pod 中执行。
- 不用宿主机 Docker Socket,避免构建任务获得节点 root 等价权限。
- 部署 ServiceAccount 只允许操作目标 namespace 中约定的 Deployment、Service 与 Pod 日志。
- 生产发布与测试发布使用不同凭据;生产阶段增加人工批准与变更窗口。
二、准备命名空间、存储与 Helm 版本
kubectl create namespace jenkins --dry-run=client -o yaml | kubectl apply -f -
kubectl create namespace staging --dry-run=client -o yaml | kubectl apply -f -
helm repo add jenkinsci https://charts.jenkins.io
helm repo update
helm search repo jenkinsci/jenkins -l | head
export JENKINS_CHART_VERSION='<经验证的版本>'
kubectl get storageclass先确认 Jenkins chart 对应的 Jenkins LTS 与插件最低版本。插件版本需要与 Controller 核心版本兼容,不要在生产启动时自动拉取未审核的最新插件。
三、部署 Jenkins Controller
3.1 最小可运营 values
controller:
image:
tag: "<固定的-jenkins-lts-jdk21-镜像标签>"
installPlugins:
- kubernetes:<固定版本>
- workflow-aggregator:<固定版本>
- git:<固定版本>
- credentials-binding:<固定版本>
- configuration-as-code:<固定版本>
resources:
requests: {cpu: 500m, memory: 2Gi}
limits: {cpu: "2", memory: 4Gi}
serviceType: ClusterIP
ingress:
enabled: false
JCasC:
defaultConfig: true
probes:
startupProbe:
failureThreshold: 30
periodSeconds: 10
persistence:
enabled: true
storageClass: fast-rwo
size: 50Gi
serviceAccount:
create: true
name: jenkins
agent:
enabled: true
podRetention: never
idleMinutes: 0
workspaceVolume:
emptyDirWorkspaceVolume:
memory: falsehelm upgrade --install jenkins jenkinsci/jenkins \
-n jenkins --version "$JENKINS_CHART_VERSION" \
-f jenkins-values.yaml --atomic --timeout 20m
kubectl -n jenkins get pods,pvc,svc
kubectl -n jenkins rollout status statefulset/jenkins --timeout=10m
kubectl -n jenkins port-forward svc/jenkins 8080:8080正式环境应通过 TLS Ingress 和企业 SSO 访问,限制管理入口来源,并把 JCasC、插件锁定清单和 job 定义纳入备份。JENKINS_HOME 的 PVC 不是唯一备份:还要导出凭据加密所需的 secrets、插件清单和恢复步骤。
四、创建最小权限部署身份
apiVersion: v1
kind: ServiceAccount
metadata:
name: jenkins-deployer
namespace: staging
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: jenkins-deployer
namespace: staging
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "patch", "update"]
- apiGroups: [""]
resources: ["pods", "pods/log", "services", "endpoints"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: jenkins-deployer
namespace: staging
subjects:
- kind: ServiceAccount
name: jenkins-deployer
namespace: staging
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: jenkins-deployerkubectl apply -f staging-jenkins-rbac.yaml
kubectl auth can-i patch deployments -n staging \
--as system:serviceaccount:staging:jenkins-deployer
kubectl auth can-i delete secrets -n staging \
--as system:serviceaccount:staging:jenkins-deployer
# 第二条必须输出 noAgent Pod 若运行在 jenkins namespace,不能直接挂载另一个 namespace 的 ServiceAccount。推荐由 Jenkins Kubernetes Credentials Provider/短期 kubeconfig 或受控部署 Job 取得 staging 身份;无论采用哪种方式,都先用 kubectl auth can-i 验证允许和拒绝两组权限。
五、准备仓库凭据、镜像仓库与部署清单
- 在 Jenkins Credentials 中创建 Git 只读凭据,ID 为
git-readonly。 - 创建镜像仓库机器人账号,ID 为
registry-robot,仅允许推送项目路径。 - 创建 staging kubeconfig Secret Text,ID 为
staging-kubeconfig,只绑定前述最小权限身份。 - 在目标 namespace 创建只读
imagePullSecret,Deployment 只负责拉取。
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-api
namespace: staging
spec:
replicas: 3
strategy:
rollingUpdate: {maxUnavailable: 0, maxSurge: 1}
selector:
matchLabels: {app: demo-api}
template:
metadata:
labels: {app: demo-api}
spec:
imagePullSecrets: [{name: registry-pull}]
containers:
- name: app
image: registry.example.com/team/demo-api:bootstrap
ports: [{name: http, containerPort: 8080}]
readinessProbe:
httpGet: {path: /ready, port: http}
periodSeconds: 5
livenessProbe:
httpGet: {path: /live, port: http}
initialDelaySeconds: 20
resources:
requests: {cpu: 100m, memory: 128Mi}
limits: {cpu: "1", memory: 512Mi}六、编写可审计 Jenkinsfile
pipeline {
agent {
kubernetes {
retries 2
yaml '''
apiVersion: v1
kind: Pod
spec:
securityContext:
runAsNonRoot: true
containers:
- name: test
image: maven:3.9.9-eclipse-temurin-21
command: ["cat"]
tty: true
resources:
requests: {cpu: 500m, memory: 1Gi}
- name: buildkit
image: moby/buildkit:rootless
command: ["sh", "-c", "sleep 99d"]
securityContext:
runAsUser: 1000
runAsGroup: 1000
allowPrivilegeEscalation: false
resources:
requests: {cpu: "1", memory: 2Gi}
- name: kubectl
image: bitnami/kubectl:<固定版本>
command: ["sh", "-c", "sleep 99d"]
securityContext:
runAsNonRoot: true
'''
}
}
options {
timestamps()
disableConcurrentBuilds(abortPrevious: false)
timeout(time: 30, unit: 'MINUTES')
}
environment {
REGISTRY = 'registry.example.com'
IMAGE_REPO = 'team/demo-api'
}
stages {
stage('Checkout') {
steps { checkout scm }
}
stage('Test') {
steps {
container('test') { sh 'mvn -B clean verify' }
}
post { always { junit allowEmptyResults: false, testResults: '**/surefire-reports/*.xml' } }
}
stage('Build and Push') {
steps {
container('buildkit') {
withCredentials([usernamePassword(credentialsId: 'registry-robot', usernameVariable: 'REG_USER', passwordVariable: 'REG_PASS')]) {
sh '''#!/bin/sh
set -eu
mkdir -p "$HOME/.docker"
AUTH=$(printf '%s:%s' "$REG_USER" "$REG_PASS" | base64 | tr -d '\n')
printf '{"auths":{"%s":{"auth":"%s"}}}' "$REGISTRY" "$AUTH" > "$HOME/.docker/config.json"
export IMAGE_TAG="${GIT_COMMIT}-${BUILD_NUMBER}"
buildctl-daemonless.sh build \
--frontend dockerfile.v0 \
--local context=. --local dockerfile=. \
--output "type=image,name=${REGISTRY}/${IMAGE_REPO}:${IMAGE_TAG},push=true"
rm -f "$HOME/.docker/config.json"
printf '%s' "$IMAGE_TAG" > image-tag.txt
'''
}
}
}
}
stage('Deploy staging') {
steps {
container('kubectl') {
withCredentials([file(credentialsId: 'staging-kubeconfig', variable: 'KUBECONFIG')]) {
sh '''#!/bin/sh
set -eu
IMAGE_TAG=$(cat image-tag.txt)
kubectl -n staging set image deployment/demo-api \
app="${REGISTRY}/${IMAGE_REPO}:${IMAGE_TAG}"
kubectl -n staging rollout status deployment/demo-api --timeout=5m
kubectl -n staging get deployment demo-api -o jsonpath='{.status.updatedReplicas}/{.status.availableReplicas}{"\n"}'
'''
}
}
}
}
}
post {
unsuccessful {
container('kubectl') {
withCredentials([file(credentialsId: 'staging-kubeconfig', variable: 'KUBECONFIG')]) {
sh 'kubectl -n staging rollout undo deployment/demo-api || true'
}
}
}
always { deleteDir() }
}
}示例把镜像标签绑定到 Git SHA 和构建号,确保可追溯且不覆盖旧镜像。真正生产流水线还应生成 SBOM、执行依赖/镜像扫描、签名镜像,并用镜像 digest 部署。rollout undo 只是紧急兜底:数据库迁移若不向后兼容,回滚旧镜像仍可能失败。
七、验证、门禁与回滚演练
kubectl -n staging rollout status deployment/demo-api --timeout=5m
kubectl -n staging get rs,pods -l app=demo-api -o wide
kubectl -n staging describe deployment demo-api
kubectl -n staging logs deployment/demo-api --tail=100
kubectl -n staging rollout history deployment/demo-api
# 集群内冒烟测试
kubectl -n staging run smoke --rm -i --restart=Never \
--image=curlimages/curl:<固定版本> -- \
curl -fsS --retry 10 --retry-delay 2 http://demo-api/ready成功标准包括:测试报告已归档、镜像 digest 可查询、Deployment 三个副本全部 Available、readiness 通过、冒烟测试返回预期内容、监控无新增错误率。任何一项失败都不能用“构建绿色”替代。
八、生产优化与常见故障
| 问题 | 证据 | 常见原因 | 修复 |
|---|---|---|---|
| Agent Pending | Pod Events | 资源、污点、配额 | 设置 requests/limits 与专用节点策略 |
| 构建推送 401 | BuildKit 日志 | 凭据范围或 registry 地址错误 | 核对机器人权限与 auth 文件 |
| rollout 超时 | Deployment/Pod Events | 镜像拉取、探针、资源不足 | 先修新 ReplicaSet,再决定回滚 |
| 偶发并发覆盖 | 流水线时间线 | 同环境并行发布 | disableConcurrentBuilds 或环境锁 |
| Controller 卡顿 | 队列、GC、CPU | 构建跑在 controller | 迁移到临时 Agent 并限制队列 |
- Controller、Agent 和部署权限分离;不同团队使用不同 Jenkins folder 与 cloud 限制。
- 所有镜像和插件固定版本,建立定期升级窗口,不在启动时漂移。
- 对队列长度、构建耗时、失败率、Agent 启动耗时、Controller PVC 与 JVM 建立监控。
- 发布采用向后兼容数据库迁移:先扩展 schema,再发布新代码,最后清理旧字段。
- 生产阶段使用人工批准、变更单和 digest 白名单;紧急回滚定期演练。
九、验收清单
- Webhook 触发一次构建,Agent Pod 自动创建并在结束后删除。
- 测试失败不会构建或部署;凭据不会出现在控制台日志和工作区归档。
- 镜像以不可变标签或 digest 推送,部署记录可关联 Git commit、构建号与审批人。
- staging ServiceAccount 的越权检查返回 no,生产权限与 staging 完全分离。
- 探针失败会阻止 rollout;回滚演练恢复到上一版本并通过冒烟测试。
- JENKINS_HOME、JCasC、插件锁定清单和 credentials secrets 完成恢复演练。
十、总结
Kubernetes 上的 Jenkins 不是把传统构建机搬进 Pod,而是让控制器保持轻量、让构建环境一次一建、让权限按发布目标收敛,并让每次部署都有可验证和可回滚证据。流水线只有同时满足可复现、最小权限、不可变制品和自动验收,才具备生产价值。