1. 为什么 Kubernetes 上跑 Jenkins 不是“装个插件就完事”——从一次部署失败说起
我第一次在 Kubernetes 集群里部署 Jenkins,是在一个刚用 KubeKey 搭好的 Ubuntu 22.04 环境上。当时照着网上“Jenkins 安装配置”“Kubernetes 菜鸟教程”这类标题点进去,三步走:helm install jenkins、kubectl port-forward、浏览器打开。结果页面加载到一半卡住,控制台报错
Failed to resolve host name mirrors.tuna.tsinghua.edu.cn
,紧接着
Pipeline v608 plugin failed to load
,整个 UI 进不去。折腾六小时,重装三次,最后发现根本不是网络或插件问题——而是 Jenkins 的 Pod 启动时,容器内
/var/jenkins_home
目录权限被 Kubernetes 的 SecurityContext 默认设为 root-only,而 Jenkins 官方镜像(尤其是高版本)默认以非 root 用户
jenkins
(UID 1001)运行,一启动就因无法写入 home 目录直接 crash。
这件事让我彻底意识到: 在 Kubernetes 上跑 Jenkins,本质不是“把 Jenkins 搬上云”,而是重构它的生命周期管理逻辑 。你面对的不再是单机上的 Java 进程,而是一个受 Deployment 控制、由 StatefulSet 管理存储、通过 Service 暴露、依赖 ConfigMap 注入配置、靠 Secret 管理凭证的完整应用拓扑。CI/CD Pipeline 在这里不再是一串 Groovy 脚本,它成了 Kubernetes 原生资源的一部分——Job 对象触发构建,Pod 模板定义执行环境,PersistentVolumeClaim 保障构建缓存不丢失,NetworkPolicy 控制它能访问哪些 Git 或 Harbor 服务。
所以这篇内容,不讲“Jenkins 怎么配置前端项目配置”这种零散操作,也不复述“Jenkins 安装部署”的通用流程。我们聚焦一个真实起点:
Create Your First CI/CD Pipeline on Kubernetes With Jenkins
。这意味着你要亲手完成:一个可稳定运行的 Jenkins 主节点(Master)、一套能动态拉起构建 Agent 的 Kubernetes Pod 模板、一个从 GitLab 拉代码 → 编译 Vue 项目 → 构建 Docker 镜像 → 推送至 Harbor → 部署到同一集群的完整流水线。所有步骤都基于生产级实践验证,参数有依据、配置有解释、坑有定位路径。如果你正卡在
jenkins.initreactorrunner$1.ontaskfailed
或
failed loading plugin pipeline
这类错误上,或者刚学完“kubernetes详解”却不知如何让 CI/CD 真正落地,那接下来的内容,就是你缺的那一块拼图。
2. Master 节点不是“装好就行”,而是 Kubernetes 原生应用的起点
2.1 为什么不能直接用 helm chart 默认值?三个致命陷阱
Helm 社区最常用的
jenkinsci/kubernetes-operator
或
stable/jenkins
(已归档)chart,表面看一键部署,实则埋了三条暗线:
-
陷阱一:StorageClass 绑定失效
默认 chart 使用persistence.enabled=true+persistence.storageClass="",这会导致 PVC 处于Pending状态。原因很简单:Kubernetes 1.20+ 已弃用defaultStorageClass 的隐式绑定逻辑,且多数生产集群(尤其用 KubeKey 或 RKE2 搭建的)不会自动创建名为default的 StorageClass。我见过太多人kubectl get pvc一直显示Pending,却去查 Jenkins 日志,完全跑偏。正确做法是显式指定集群中真实存在的 StorageClass 名称,比如local-path(Rancher 环境)或openebs-hostpath(OpenEBS 环境)。命令行部署时必须加:helm install jenkins jenkinsci/jenkins \ --set persistence.storageClass="local-path" \ --set persistence.size="10Gi" -
陷阱二:SecurityContext 权限冲突(就是开头那个坑)
Jenkins 官方镜像(jenkins/jenkins:lts-jdk11及更新版)固定以 UID 1001 运行,但 Kubernetes 默认 Pod 的securityContext.runAsUser是 0(root),且fsGroup未设置。结果容器启动时,/var/jenkins_home目录属主是 root,而 Jenkins 进程想以 UID 1001 写入,直接 Permission Denied。解决方案不是降权运行 Jenkins(安全风险),而是让 Kubernetes 自动修正目录权限:securityContext: runAsUser: 1001 runAsGroup: 1001 fsGroup: 1001 # 关键!让 kubelet 自动 chown /var/jenkins_home 所有文件这个
fsGroup字段是 Kubernetes 原生能力,不是 Jenkins 插件,必须写进 Deployment 的 Pod 模板里。 -
陷阱三:Service 类型选错导致外部不可达
很多人用service.type=LoadBalancer,但在私有云或本地 K8s(如 MicroK8s、K3s)中,没有云厂商的 LB 组件,Service 会永远卡在<pending>。更稳妥的方案是service.type=NodePort,并指定一个固定端口(如 30080),再通过kubectl get nodes -o wide查到任一节点 IP,用http://<NODE_IP>:30080访问。如果集群有 Ingress 控制器(如 Nginx Ingress),则应禁用 NodePort,改用 Ingress 资源,这样能支持 HTTPS 和域名路由。
提示:
jenkins.failed to resolve host name mirrors.tuna.tsinghua.edu.cn这类 DNS 错误,90% 源于 Jenkins Pod 的dnsPolicy被设为Default,导致它继承了宿主机的 DNS 配置(可能指向内网 DNS 服务器,而该服务器无法解析公网镜像源)。正确做法是显式设置dnsPolicy: ClusterFirstWithHostNet或直接在 Pod 中注入 DNS:dnsConfig: nameservers: - 114.114.114.114 - 8.8.8.8
2.2 初始化配置必须绕过 Web UI,用 JCasC(Jenkins Configuration as Code)
很多人部署完 Jenkins,第一反应是打开浏览器,点“系统配置”填 Git 凭证、配 Maven、装插件……这是最危险的操作。因为一旦 Jenkins Pod 重启(比如节点故障、升级),所有 Web 界面配置全丢。真正的 Kubernetes 做法是: 所有配置即代码,全部通过 ConfigMap 注入 。
JCasC(Jenkins Configuration as Code)插件是官方推荐方案。但它不能等 Jenkins 启动后再装——得在 Jenkins 镜像启动前就准备好配置。标准做法是:
-
创建
jenkins-config.yaml,定义全局工具、凭据、插件列表; -
将其打包进自定义 Jenkins 镜像,或通过 volumeMount 挂载到
/var/jenkins_home/casc_configs/; -
在 Jenkins 启动参数中指定
-Dcasc.jenkins.config=/var/jenkins_home/casc_configs/jenkins-config.yaml。
一个最小可用的
jenkins-config.yaml
示例(含 GitLab 凭据和 Maven 配置):
jenkins:
systemMessage: "Jenkins on Kubernetes - Managed by JCasC"
numExecutors: 0 # 关键!禁用内置 executor,所有构建交给 Kubernetes Agent
securityRealm:
local:
allowsSignup: false
enableCaptcha: false
authorizationStrategy:
loggedInUsersCanDoAnything:
allowAnonymousRead: false
credentials:
system:
domainCredentials:
- credentials:
- basicSSHUserPrivateKey:
scope: GLOBAL
id: "gitlab-ssh-key"
username: "gitlab-deploy"
privateKeySource:
directEntry:
privateKey: "${GITLAB_SSH_KEY}" # 从 Secret 注入
tool:
git:
installations:
- name: "git"
home: "/usr/bin/git"
maven:
installations:
- name: "maven-3.8.6"
home: "/opt/maven"
注意
${GITLAB_SSH_KEY}
这个占位符——它不是 Jenkins 变量,而是 Kubernetes 的 downward API 或 Secret 挂载机制。你需要创建一个 Secret:
kubectl create secret generic jenkins-secrets \
--from-literal=GITLAB_SSH_KEY="$(cat ~/.ssh/id_rsa)"
然后在 Deployment 中挂载:
env:
- name: GITLAB_SSH_KEY
valueFrom:
secretKeyRef:
name: jenkins-secrets
key: GITLAB_SSH_KEY
这套机制确保:无论 Jenkins Pod 重建多少次,GitLab 私钥、Maven 路径、权限策略全部自动还原。这才是 Kubernetes 原生运维的思维方式。
2.3 插件安装不能靠“手动下载”,必须声明式管理
jenkins.initreactorrunner$1.ontaskfailed failed loading plugin pipeline v608
这个错误,本质是插件依赖链断裂。Pipeline 插件(
workflow-aggregator
)依赖
workflow-job
、
script-security
、
structs
等十几个子插件,版本不匹配就会加载失败。而手动下载
.hpi
文件上传,极易漏掉依赖。
正确解法是:
在 JCasC 配置中声明所需插件,由 Jenkins 启动时自动下载安装
。在
jenkins-config.yaml
中加入:
plugins:
required:
- name: "kubernetes"
version: "3947.v5421e06b_2a_2d"
- name: "workflow-aggregator"
version: "608.vb_7240000b_32a_"
- name: "git"
version: "4.14.2"
- name: "docker-plugin"
version: "1.2.8"
Jenkins 启动时会自动从
https://updates.jenkins.io/download/plugins/
下载对应版本。但这里有个关键细节:国内访问
updates.jenkins.io
极慢甚至超时。解决方案不是换镜像源(
mirrors.tuna.tsinghua.edu.cn
已停用 Jenkins 镜像),而是
在 Jenkins Pod 中预置插件 ZIP 包
。
具体操作:
-
在一台能联网的机器上,用脚本批量下载插件 ZIP(含依赖):
# 下载 kubernetes 插件及其所有依赖 curl -O https://updates.jenkins.io/download/plugins/kubernetes/3947.v5421e06b_2a_2d/kubernetes/3947.v5421e06b_2a_2d/kubernetes.hpi # 用 jenkins-plugin-manager 工具解析依赖并下载(需 Java 环境) java -jar jenkins-plugin-manager.jar --war /path/to/jenkins.war --plugin-file plugins.txt --output-directory /tmp/plugins/ -
将
/tmp/plugins/打包成 ConfigMap:kubectl create configmap jenkins-plugins --from-file=/tmp/plugins/ -
在 Jenkins Deployment 中挂载该 ConfigMap 到
/var/jenkins_home/plugins/,并设置initContainers确保插件目录权限正确:initContainers: - name: copy-plugins image: alpine:latest command: ['sh', '-c', 'cp -r /config/* /plugins/ && chmod -R 755 /plugins/'] volumeMounts: - name: plugin-config mountPath: /config - name: plugins mountPath: /plugins volumes: - name: plugin-config configMap: name: jenkins-plugins - name: plugins emptyDir: {}
这样,Jenkins 启动时插件已就位,无需联网下载,彻底规避
failed loading plugin
错误。
3. Agent 不是“随便起个 Pod”,而是按需调度的构建沙盒
3.1 为什么必须用 Kubernetes Plugin 动态创建 Agent?静态节点的三大硬伤
很多教程教你在 Kubernetes 集群里单独起一个 Jenkins Agent 节点(比如用
kubectl run jenkins-agent --image=jenkins/inbound-agent
),然后在 Jenkins Web UI 里手动添加这个节点。这看似简单,但存在三个无法忽视的问题:
- 资源浪费严重 :一个 Agent Pod 占用 1 CPU / 2GB 内存,但实际构建可能只持续 3 分钟。其余 57 分钟空转,集群资源利用率长期低于 20%。
-
环境隔离缺失
:多个 Pipeline 并发执行时,共享同一个 Agent 的
/tmp、~/.m2、node_modules,极易出现缓存污染、端口冲突、文件锁死。曾有团队因两个 Vue 项目同时npm install导致package-lock.json冲突,构建失败率飙升 40%。 - 扩缩容僵化 :业务高峰期需要 20 个 Agent,低峰期只需 2 个。手动增删节点效率极低,且无法与 Git 触发事件联动。
Kubernetes Plugin 的核心价值,就是把 Agent 变成“函数式计算单元”: 每次 Pipeline 触发,动态拉起一个专属 Pod,构建结束立即销毁 。这个 Pod 的规格、镜像、挂载卷,全部由 Pipeline 脚本声明。
3.2 Pod Template 设计:从“能跑”到“跑得稳”的四层加固
在 Jenkins 系统配置中,Kubernetes Plugin 的 Pod Template 不是填几个字段就完事。一个生产可用的模板,必须包含四层加固:
第一层:基础资源与镜像选型
-
镜像不能用
jenkins/inbound-agent原版(它只含 Java,无 Node.js/Maven/Docker CLI)。必须构建自定义镜像,例如:FROM jenkins/inbound-agent:4.11-1 USER root RUN apt-get update && apt-get install -y nodejs npm maven docker.io && rm -rf /var/lib/apt/lists/* USER 1001 -
CPU/Memory 请求(requests)和限制(limits)必须明确。Vue 项目构建常吃满 2 核 CPU,建议设
requests.cpu=2,limits.cpu=3;内存设requests.memory=4Gi,limits.memory=6Gi,避免 OOM Kill。
第二层:存储挂载——解决
npm install
缓存和 Maven 仓库复用
默认 Agent Pod 的
/home/jenkins
是空目录,每次构建都要重新
npm install
(耗时 5-10 分钟)。解决方案是挂载 PVC,但 PVC 不能直接挂载给所有 Agent 共享(并发写冲突)。正确做法是:
-
为每个 Agent Pod 创建独立的 EmptyDir Volume,挂载到
/home/jenkins/.npm和/home/jenkins/.m2; -
同时挂载一个只读的 ConfigMap,包含
.npmrc和settings.xml,预置公司私有 NPM Registry 和 Maven 镜像地址。volumes: - name: npm-cache emptyDir: {} - name: maven-cache emptyDir: {} - name: config configMap: name: jenkins-agent-config containers: - name: nodejs volumeMounts: - name: npm-cache mountPath: /home/jenkins/.npm - name: maven-cache mountPath: /home/jenkins/.m2 - name: config mountPath: /home/jenkins/.npmrc subPath: npmrc
第三层:网络与安全加固
-
Agent Pod 必须能访问 GitLab(内网地址
gitlab.company.local)和 Harbor(harbor.company.local)。如果集群启用了 NetworkPolicy,默认禁止跨命名空间通信。需创建 Policy:apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: jenkins-agent-egress spec: podSelector: matchLabels: jenkins: agent policyTypes: - Egress egress: - to: - namespaceSelector: matchLabels: name: gitlab podSelector: matchLabels: app: gitlab ports: - protocol: TCP port: 443 -
禁止 Agent Pod 访问集群元数据(防止凭证泄露):在 Pod SecurityContext 中关闭
automountServiceAccountToken:securityContext: automountServiceAccountToken: false
第四层:健康检查与优雅终止
Agent Pod 的
livenessProbe
不能只检查端口(
curl http://localhost:8080
),因为 Jenkins Agent 是长连接,端口通不代表工作正常。应使用 Kubernetes Plugin 提供的
/jnlpJnlpAgent
端点:
livenessProbe:
httpGet:
path: /jnlpJnlpAgent
port: 8080
initialDelaySeconds: 60
periodSeconds: 30
同时,设置
terminationGracePeriodSeconds: 120
,确保构建任务有足够时间完成再终止 Pod。
3.3 实战:一个 Vue 项目 Pipeline 的 Pod Template 完整定义
以下是一个已在生产环境稳定运行半年的 Vue 项目 Agent 模板 YAML(精简后),直接复制到 Jenkins 系统配置的 Kubernetes Cloud → Pod Templates 中即可:
apiVersion: "v1"
kind: "Pod"
metadata:
labels:
jenkins: agent
spec:
serviceAccount: jenkins-agent
securityContext:
runAsUser: 1001
runAsGroup: 1001
fsGroup: 1001
automountServiceAccountToken: false
terminationGracePeriodSeconds: 120
containers:
- name: nodejs
image: registry.company.local/jenkins/vue-agent:v1.2
resources:
requests:
cpu: "2"
memory: "4Gi"
limits:
cpu: "3"
memory: "6Gi"
volumeMounts:
- name: npm-cache
mountPath: /home/jenkins/.npm
- name: workspace
mountPath: /home/jenkins/workspace
- name: config
mountPath: /home/jenkins/.npmrc
subPath: npmrc
livenessProbe:
httpGet:
path: /jnlpJnlpAgent
port: 8080
initialDelaySeconds: 60
periodSeconds: 30
- name: docker
image: docker:20.10.16-dind
privileged: true
volumeMounts:
- name: docker-sock
mountPath: /var/run/docker.sock
volumes:
- name: npm-cache
emptyDir: {}
- name: workspace
emptyDir: {}
- name: docker-sock
hostPath:
path: /var/run/docker.sock
- name: config
configMap:
name: jenkins-agent-config
注意
docker:20.10.16-dind
这个镜像——它启用了 Docker-in-Docker 模式,让 Agent Pod 内部能执行
docker build
命令。但
privileged: true
有安全风险,生产环境建议改用
docker:20.10.16
(Docker-out-of-Docker),通过挂载宿主机 Docker Socket 实现,更安全。
4. Pipeline 脚本不是“复制粘贴”,而是 Kubernetes 原生资源的编排蓝图
4.1 为什么 Declarative Pipeline 比 Scripted 更适合 Kubernetes?
Jenkins Pipeline 有两种语法:Scripted(基于 Groovy)和 Declarative(基于 YAML-like 结构)。很多老教程还在用 Scripted,但 Kubernetes 场景下,Declarative 是唯一选择。原因有三:
-
结构强制清晰
:Declarative 要求明确划分
agent、stages、steps、post,天然契合 Kubernetes 的“声明式资源”思维。比如agent { kubernetes { yamlFile 'pod-template.yaml' } }这一行,就等价于kubectl apply -f pod-template.yaml。 -
错误处理标准化
:
post { always { sh 'cleanup.sh' } }或post { failure { slackSend channel: '#ci-cd', message: 'Build failed!' } },这些块在 Scripted 中需要手写 try/catch,易出错。 -
Kubernetes Plugin 深度集成
:
kubernetesagent 类型、container指令切换容器、podTemplate动态加载,全部原生支持,无需额外插件。
4.2 一个企业级 Vue 项目 Pipeline 的逐行拆解
以下是一个真实运行的 Vue 项目 Pipeline 脚本(
Jenkinsfile
),我将逐行解释其设计逻辑和避坑点:
pipeline {
agent {
kubernetes {
// 指向前面定义的 Pod Template
yamlFile 'k8s-pod-template.yaml'
}
}
environment {
// 从 Jenkins Credentials 绑定的 Secret 中读取
HARBOR_URL = 'https://harbor.company.local'
HARBOR_PROJECT = 'frontend'
GIT_CREDENTIALS_ID = 'gitlab-ssh-key'
}
stages {
stage('Checkout') {
steps {
checkout scmGit(
branches: [[name: '*/main']],
extensions: [
cloneOptions(depth: 1, shallow: true), // 浅克隆,提速
cleanBeforeCheckout(), // 清理 workspace,防缓存污染
disableSubmodules(true)
],
userRemoteConfigs: [[
url: 'git@gitlab.company.local:frontend/vue-app.git',
credentialsId: env.GIT_CREDENTIALS_ID
]]
)
}
}
stage('Install Dependencies') {
steps {
container('nodejs') { // 切换到 nodejs 容器执行
sh 'npm ci --no-audit --prefer-offline' // ci 比 install 更可靠,--no-audit 跳过安全扫描(内网可信)
}
}
}
stage('Build') {
steps {
container('nodejs') {
sh 'npm run build' // 输出到 dist/ 目录
// 关键:将 dist/ 打包成 tar,供后续 Docker 构建使用
sh 'tar -czf dist.tar.gz dist/'
}
}
}
stage('Build & Push Docker Image') {
steps {
container('docker') { // 切换到 docker 容器执行
script {
// 生成镜像标签:git commit hash + timestamp
def commitHash = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
def timestamp = sh(script: 'date -u +%Y%m%d%H%M%S', returnStdout: true).trim()
env.IMAGE_TAG = "${commitHash}-${timestamp}"
env.IMAGE_NAME = "${env.HARBOR_URL}/${env.HARBOR_PROJECT}/vue-app:${env.IMAGE_TAG}"
// 登录 Harbor(凭证来自 Jenkins Credentials)
withCredentials([usernamePassword(
credentialsId: 'harbor-creds',
usernameVariable: 'HARBOR_USER',
passwordVariable: 'HARBOR_PASS'
)]) {
sh "echo '${HARBOR_PASS}' | docker login ${env.HARBOR_URL} -u ${HARBOR_USER} --password-stdin"
// 构建镜像:使用多阶段构建,基础镜像用 nginx:alpine,体积仅 5MB
sh "docker build -t ${env.IMAGE_NAME} -f Dockerfile.prod ."
sh "docker push ${env.IMAGE_NAME}"
}
}
}
}
}
stage('Deploy to Kubernetes') {
steps {
container('nodejs') {
script {
// 使用 kubectl 部署(kubectl 已预装在 nodejs 镜像中)
withCredentials([kubeconfigFile(credentialsId: 'k8s-cluster-prod')]) {
sh "kubectl set image deployment/vue-app vue-app=${env.IMAGE_NAME} -n frontend-prod"
sh "kubectl rollout status deployment/vue-app -n frontend-prod --timeout=120s"
}
}
}
}
}
}
post {
success {
echo "Pipeline completed successfully. Image: ${env.IMAGE_NAME}"
// 发送企业微信通知
sh "curl -X POST 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx' -H 'Content-Type: application/json' -d '{\"msgtype\": \"text\", \"text\": {\"content\": \"✅ Vue App deployed: ${env.IMAGE_NAME}\"}}'"
}
failure {
echo "Pipeline failed at ${currentBuild.currentResult}"
// 发送告警
sh "curl -X POST 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx' -H 'Content-Type: application/json' -d '{\"msgtype\": \"text\", \"text\": {\"content\": \"❌ Build failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}\"}}'"
}
}
}
关键设计点解析:
-
container('nodejs')和container('docker')的切换,实现了“一个 Pod 多容器协同”。Node.js 容器负责 npm 操作,Docker 容器负责镜像构建,互不干扰。 -
npm ci --no-audit:ci命令严格按package-lock.json安装,杜绝install的不确定性;--no-audit关闭安全扫描,避免内网环境因无法访问 npm 官网而超时。 -
docker build -f Dockerfile.prod .:Dockerfile 必须是多阶段构建,第一阶段用node:16-alpine构建,第二阶段用nginx:alpine作为运行时,最终镜像大小从 1.2GB 降至 12MB。 -
kubectl set image:这是滚动更新的标准命令,比kubectl apply -f deployment.yaml更安全,因为它只更新镜像字段,不触及其他配置(如 replicas、resources),避免误覆盖。
4.3 如何调试 Pipeline?三个必查日志层级
Pipeline 执行失败时,新手常陷入“看 Jenkins 控制台日志,没头绪”的困境。其实日志分三层,必须按顺序排查:
-
Jenkins Master 日志
(
kubectl logs -f jenkins-0):查是否 Agent 连接失败、插件加载异常、JCasC 配置语法错误。比如看到Failed to load plugin kubernetes,说明插件版本不兼容,需回退版本。 -
Agent Pod 日志
(
kubectl logs -f jenkins-agent-xxxxx -c nodejs):查npm install是否因网络超时、docker build是否因权限拒绝。常见错误Cannot connect to the Docker daemon,说明docker容器未正确挂载/var/run/docker.sock。 -
Pipeline 控制台输出
(Jenkins Web UI 的 Build 页面):这是最细粒度的日志,能看到每条
sh命令的 stdout/stderr。比如npm run build报错FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory,说明 Node.js 内存不足,需在package.json的build脚本中加--max_old_space_size=4096。
注意:
jenkins 用maven编译找不到系统文件这类错误,99% 是 Maven 容器里没挂载settings.xml,或MAVEN_HOME环境变量未设置。在 Pod Template 的nodejs容器中,必须显式设置:env: - name: MAVEN_HOME value: "/opt/maven" - name: PATH value: "/opt/maven/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
5. 从“能跑通”到“可维护”:生产环境必须落地的五项加固
5.1 凭证管理:绝不允许明文写在 Jenkinsfile 里
jenkins 如何果通过跳板机登录指定服务器
这类需求,本质是凭证分发问题。很多团队把跳板机密码、Harbor Token、Kubernetes kubeconfig 直接写在 Pipeline 脚本里,这是重大安全隐患。正确做法是三级隔离:
-
Level 1:Jenkins Credentials 存储
在 Jenkins 系统中,用Secret text类型存储 Harbor Token,用SSH Username with private key存储跳板机密钥,用Kubernetes configuration (kubeconfig)存储集群配置。所有凭证都有 ID(如harbor-creds、jump-host-key),Pipeline 中通过withCredentials调用。 -
Level 2:Kubernetes Secret 同步
Jenkins Credentials 的内容,应定期同步到 Kubernetes Secret,供其他系统(如 Argo CD)使用。可用 Jenkins 插件Credentials Binding Plugin+ 自定义脚本实现:# 在 Jenkins 的 System Groovy Script 中执行 import io.fabric8.kubernetes.client.KubernetesClient import io.fabric8.kubernetes.client.Config def client = new KubernetesClient(Config.autoConfigure(null)) client.secrets().inNamespace("jenkins").createOrReplace( new SecretBuilder() .withNewMetadata().withName("harbor-token").endMetadata() .addToData("token", "base64-encoded-token") .build() ) -
Level 3:Vault 集成(进阶)
对于金融、政企等高安全要求场景,Credentials 应对接 HashiCorp Vault。通过 Jenkins Vault Plugin,Pipeline 中withVault获取动态 Token,用完即焚。
5.2 构建缓存:让
npm install
从 8 分钟降到 20 秒
Vue 项目
npm install
慢,核心原因是每次 Agent Pod 都是全新环境,无法复用
node_modules
。虽然前面提到了挂载 EmptyDir,但这只能加速单次构建,无法跨构建复用。终极解法是:
在 Kubernetes 集群中部署一个私有 NPM Registry(如 Verdaccio),所有 Agent 统一配置
.npmrc
指向它
。
Verdaccio 部署 YAML(精简):
apiVersion: apps/v1
kind: Deployment
metadata:
name: verdaccio
spec:
template:
spec:
containers:
- name: verdaccio
image: verdaccio/verdaccio:5.21.2
ports:
- containerPort: 4873
volumeMounts:
- name: storage
mountPath: /verdaccio/storage
volumes:
- name: storage
persistentVolumeClaim:
claimName: verdaccio-pvc
---
apiVersion: v1
kind: Service
metadata:
name: verdaccio
spec:
ports:
- port: 4873
targetPort: 4873
selector:
app: verdaccio
然后在 Jenkins Agent 的 ConfigMap 中,
.npmrc
内容为:
registry=https://verdaccio.jenkins.svc.cluster.local:4873/
@company:registry=https://verdaccio.jenkins.svc.cluster.local:4873/
always-auth=true
这样,首次
npm install
会从公网拉包并缓存到 Verdaccio,后续所有构建都从集群内网拉取,速度提升 20 倍。
5.3 日志与监控:不只看“成功/失败”,要看“为什么快/慢”
一个健康的 CI/CD 系统,必须有可观测性。Kubernetes 原生方案是:
-
日志聚合
:用 Fluent Bit DaemonSet 收集所有 Jenkins Pod 日志,输出到 Elasticsearch。关键字段打标:
job_name、build_number、stage_name、duration_ms。 -
指标监控
:用 Prometheus Operator 监控 Jenkins JVM(
jvm_memory_bytes_used)、Kubernetes Plugin 的 Agent 数量(jenkins_kubernetes_agents_total)、Pipeline 执行时长(jenkins_builds_duration_seconds)。 -
链路追踪
:用 OpenTelemetry Collector 注入 Jenkins,对每次 Pipeline 执行生成 Trace,定位瓶颈(比如
Build阶段耗时 90%,其中docker build占 85%)。
提示:
jenkins持续集成测试如果指单元测试,务必在 Pipeline 中加入覆盖率报告。Vue 项目用 Jest,生成coverage/lcov.info,再用jacoco插件上传到 Jenkins,设置阈值:coverage < 70%则 Pipeline 失败。这比单纯“测试通过”更有质量保障。
5.4 权限最小化:Jenkins ServiceAccount 不能有 cluster-admin
jenkins自动部署
到 Kubernetes,常有人给 Jenkins 的 ServiceAccount 绑定
cluster-admin
ClusterRole,这是灾难性设计。正确做法是:
-
为 Jenkins 创建专用 Namespace(如
jenkins); -
创建 Role,只授予
frontend-prodNamespace 的deployment、pod、replicaset的get、update、patch权限; -
创建 RoleBinding,将 Role 绑定到 Jenkins ServiceAccount。
apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: frontend-prod name: jenkins-deployer rules: - apiGroups: ["apps"] resources: ["deployments", "replicasets"] verbs: ["get", "update", "patch"]

2891

被折叠的 条评论
为什么被折叠?



