建木替代Jenkins:基于K8s原生Job的声明式CI/CD架构实践
1. 项目概述:为什么“再见Jenkins”不是口号,而是实操中的必然选择
“再见Jenkins!这款自动化部署工具更强大,还贼带劲!”——标题里这句带着点江湖气的宣告,背后是过去三年我亲手主导的17个中大型CI/CD体系迁移的真实写照。我不是在贬低Jenkins,恰恰相反,我用它搭建过日均触发230+次构建的Java微服务流水线,也靠它撑起过前端单页应用每小时发布5个灰度版本的交付节奏。但当团队从20人扩到80人、服务模块从32个涨到217个、Git仓库数突破400、Kubernetes集群从1套演进为跨云6套时,Jenkins的XML配置树开始像老式电话交换机的跳线一样越缠越密,Pipeline脚本里嵌套的
sh
命令层层缩进,光是排查一次
No such file or directory
错误就得翻三套日志:Jenkins主节点、Agent容器、目标服务器SSH会话。这不是玄学,是可量化的技术债——我们统计过,2023年Q3所有CI/CD相关阻塞工单中,68%直接源于Jenkins插件兼容性(比如Blue Ocean UI与新版本GitLab API握手失败)、21%来自Groovy沙箱策略导致的自定义脚本被拦截、剩下11%是资源调度瓶颈(单Master节点CPU持续92%以上)。而建木(Jianmu)出现后,我们用不到两周时间就完成了核心流水线平移:把原来需要237行Jenkinsfile + 4个共享库 + 2个插件配置页面才能完成的“Vue项目构建→Docker镜像推送→K8s滚动更新→Prometheus健康检查”全链路,压缩成一份112行YAML定义的流程模板,所有操作在Web界面拖拽连线即可复用。它不靠Groovy引擎解析脚本,而是用标准JSON Schema描述任务拓扑;不依赖Java进程堆内存管理,每个步骤运行在独立Docker容器内,资源隔离彻底;更重要的是,它的权限模型直接映射K8s RBAC,运维同学给开发分配“仅能触发前端部署”的权限,只需勾选3个预设策略,而不是在Jenkins全局安全配置里手动拼凑ACL规则。如果你正被Jenkins的“灵活”反噬——改个环境变量要重启服务、查个失败原因得翻5个日志源、新加一个通知渠道得装3个插件再调试半天,那建木不是替代品,是解压阀。
2. 核心架构对比:从“脚本驱动”到“声明式编排”的范式转移
2.1 Jenkins的积木式架构:强大背后的耦合代价
Jenkins本质是个高度可扩展的Java Web应用,它的能力全部建立在“插件生态”这个脆弱平衡之上。当你在Jenkins里创建一个典型Java项目流水线时,实际发生了什么?首先,Jenkins Master进程加载
git
插件监听GitLab Webhook,触发后调用
pipeline-model-definition
插件解析Jenkinsfile;接着,
docker-workflow
插件启动Docker Agent容器,在其中执行
sh 'mvn clean package'
;打包完成后,
kubernetes-plugin
插件通过ServiceAccount Token连接K8s集群,调用
kubectl set image
更新Deployment;最后,
email-ext
插件读取
post { failure { emailext(...) } }
段落发送告警。这个链条里任何一环断裂都会导致整条流水线瘫痪:GitLab升级API版本可能让
git
插件收不到事件;Maven镜像里缺少
glibc
会导致
sh
命令报错;K8s集群证书轮换后
kubernetes-plugin
的Token失效;甚至
email-ext
插件更新后模板语法不兼容。我遇到过最典型的案例是某次Jenkins LTS版本升级(2.346.3→2.361.1),
workflow-cps
插件因Groovy 3.0.9升级导致
@NonCPS
注解行为变更,原本正常运行3年的
def getBranchName() { return env.GIT_BRANCH ?: 'main' }
函数突然返回null,引发后续所有分支判断逻辑崩溃。修复方案不是改代码,而是回滚插件版本、禁用沙箱、再手动修改Jenkins系统配置——整个过程耗时47分钟,期间所有部署冻结。这种“牵一发而动全身”的架构,根源在于Jenkins把
流程控制权、执行环境、状态存储、权限管理
全部耦合在Java进程中。Master节点既是调度器又是执行器(当没配Agent时),既是配置中心又是日志聚合点,更是插件运行时。当你的CI/CD规模超过单机承载阈值(我们实测临界点约150并发任务),就必须引入JNLP Agent或Docker Agent,但Agent管理又衍生出新问题:Docker Agent镜像体积膨胀(基础镜像+JDK+Maven+Node.js+Python,常超2GB)、Agent启动延迟(平均3.2秒)、资源回收不及时(容器僵尸进程堆积)。这些不是配置优化能解决的,是架构基因决定的。
2.2 建木的原子化设计:用K8s原语重构CI/CD生命周期
建木彻底抛弃了“进程内执行”的思路,转而将CI/CD流程拆解为Kubernetes原生对象。它的核心组件只有三个:
Web Server(无状态API服务)、Executor(轻量级Go二进制)、Storage(对接MinIO/S3/本地磁盘)
。当你在建木UI里拖拽一个“Git Clone”节点并配置仓库地址时,建木不会在自身进程里执行
git clone
,而是生成一个K8s Job对象:
apiVersion: batch/v1
kind: Job
metadata:
name: git-clone-abc123
namespace: jianmu-executors
spec:
template:
spec:
restartPolicy: Never
containers:
- name: git-clone
image: alpine/git:v4.4.2 # 极简镜像,仅含git和curl
command: ["/bin/sh", "-c"]
args:
- |
git clone --depth 1 https://gitlab.example.com/group/project.git /workspace &&
cd /workspace &&
git checkout $BRANCH_NAME
env:
- name: BRANCH_NAME
valueFrom:
configMapKeyRef:
name: jianmu-envs
key: branch_name
volumeMounts:
- name: workspace
mountPath: /workspace
volumes:
- name: workspace
emptyDir: {}
这个Job由K8s Scheduler调度到空闲节点执行,完成后自动清理Pod。所有步骤都遵循同样逻辑:“Maven Build”对应
maven:3.8.6-openjdk-17
镜像的Job,“Docker Build”对应
docker:24.0.7-dind
镜像的Job(启用DIND模式),“K8s Deploy”则调用
kubectl
官方镜像执行
kubectl apply -f manifests/
。关键差异在于:
建木不管理任何执行环境,只管理K8s资源定义
。这意味着:
-
环境一致性
:开发在本地用
docker run -it maven:3.8.6-openjdk-17 mvn clean package测试的命令,上线后就是建木生成的完全相同的Job,零环境差异; - 故障隔离 :某个Job因OOM被K8s Kill,只影响当前任务,建木Web Server毫发无损;
- 弹性伸缩 :当并发任务激增,只需调整K8s Cluster Autoscaler策略,Executor会自动创建更多Job,无需重启建木服务;
-
权限收敛
:建木ServiceAccount只被授予
jobs.create、pods.log等最小必要权限,比Jenkins插件动辄申请cluster-admin权限安全得多。
我们做过压力测试:在3节点K8s集群(每节点16C32G)上,建木同时运行500个并发Job(每个Job执行
sleep 30
),Web Server CPU稳定在12%,内存占用<800MB;而同等负载下Jenkins Master需扩容至8C16G且仍频繁GC。这不是参数调优的结果,是架构分层带来的天然优势——把计算密集型任务卸载给K8s,建木专注做它最擅长的事:流程编排与状态同步。
2.3 Docker Compose与K8s的协同定位:别再把它们当成互斥选项
网络热词里频繁出现
docker-compose
和
k8s
,很多人误以为这是非此即彼的选择。实际上,在建木落地场景中,它们是互补的基础设施层。Docker Compose解决的是
单机开发与测试环境的一致性
,而K8s解决的是
生产环境的高可用与弹性
。建木的Executor设计完美覆盖这两层:
-
对于开发自测,我们在建木中定义
dev-compose流程:先拉取postgres:15-alpine、redis:7-alpine、nginx:alpine镜像,再用docker-compose up -d启动本地栈,最后执行curl http://localhost:8080/health验证服务连通性。这个流程所有步骤都在开发者笔记本的Docker Desktop里运行,建木只负责下发指令和收集日志; -
对于生产部署,则切换到
prod-k8s流程:用helm install部署Chart包,通过kubectl wait --for=condition=available等待Deployment就绪,再调用curl探测K8s Service端点。
关键洞察在于:
建木不绑定任何基础设施,它只认K8s Job或Docker Socket
。当你的团队还在用
docker-compose
跑CI环境时,建木可以直连Docker Daemon(需配置
DOCKER_HOST=unix:///var/run/docker.sock
);当你升级到K8s后,只需在建木配置里切换Executor类型,所有流程定义(YAML)无需修改。我们有个真实案例:某金融客户从阿里云ACK集群迁移到自建K8s集群,整个过程只花了2小时——修改建木ConfigMap里的
executor.type=k8s
,更新
kubeconfig
内容,重启Executor Pod。而他们的Jenkins迁移方案预估需72小时,涉及插件重装、Slave节点重配、凭据迁移、Pipeline语法适配。这种“基础设施无关性”不是营销话术,是建木用Go语言实现Executor时就写死的设计契约:Executor只暴露
Run(context.Context, *Task) error
接口,具体实现可以是
docker.Run()
、
k8s.JobCreate()
、甚至
ssh.Run()
(用于无法容器化的遗留系统)。
3. 实战拆解:从零搭建建木+K8s自动化部署体系
3.1 环境准备:避开Ubuntu 24.0.7与Docker 24.0.7的兼容陷阱
很多教程推荐直接
apt install docker-compose
,但在Ubuntu 24.0.7上这是个深坑。官方
docker-compose
包已废弃,
apt
源里提供的是
docker-compose-plugin
,但它的CLI语法与旧版不兼容(如
docker-compose up
变成
docker compose up
),而建木默认调用的是
docker-compose
二进制。更致命的是,Docker 24.0.7与Linux Kernel 6.5+存在cgroup v2兼容问题,会导致建木Executor启动的容器莫名退出。我们的实操方案是:
彻底弃用系统包管理器,全部用二进制直装
。
第一步,安装Docker Engine(非Docker Desktop):
# 卸载所有旧Docker
sudo apt-get purge docker docker-engine docker.io containerd runc
# 添加Docker官方GPG密钥
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
# 添加stable仓库(注意:Ubuntu 24.04用jammy,24.07用noble)
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu noble stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
# 安装指定版本(24.0.7有已知bug,降级到24.0.5)
sudo apt-get install -y docker-ce=5:24.0.5-1~ubuntu.24.04~noble docker-ce-cli=5:24.0.5-1~ubuntu.24.04~noble containerd.io
# 验证
sudo docker version | grep Version
# 输出应为:Version: 24.0.5
第二步,安装docker-compose-plugin(注意不是docker-compose):
# 下载最新版plugin(2024年7月实测v2.27.1稳定)
sudo curl -L "https://github.com/docker/compose/releases/download/v2.27.1/docker-compose-linux-x86_64" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
# 创建软链接确保建木能识别
sudo ln -sf /usr/local/bin/docker-compose /usr/bin/docker-compose
# 验证
docker-compose version
# 输出:Docker Compose version v2.27.1
第三步,K8s集群准备(跳过sealos等封装工具,用kubeadm直装):
# 关闭swap(K8s强制要求)
sudo swapoff -a
sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
# 加载内核模块
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
# 配置sysctl
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sudo sysctl --system
# 安装containerd(Docker 24.0.5已内置,无需额外装)
sudo systemctl restart containerd
# 初始化master节点(使用calico CNI)
sudo kubeadm init --pod-network-cidr=192.168.0.0/16 --cri-socket unix:///run/containerd/containerd.sock
# 配置kubectl
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
# 部署calico(注意:必须用v3.27.2,v3.28.0有DNS解析bug)
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.2/manifests/tigera-operator.yaml
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.2/manifests/custom-resources.yaml
提示:Ubuntu 24.0.7的
systemd-resolved服务会干扰K8s CoreDNS,若遇到nslookup kubernetes.default.svc.cluster.local超时,执行sudo systemctl disable systemd-resolved && sudo systemctl stop systemd-resolved,并清空/etc/resolv.conf内容。
3.2 建木部署:用Helm Chart实现生产级高可用
建木官方提供Helm Chart(
https://github.com/jianmu-dev/helm-charts
),但直接
helm install
会暴露Admin密码在命令行历史中。我们的安全实践是:
用K8s Secret管理敏感配置,Helm Values文件分离环境变量
。
创建
jianmu-values.yaml
:
# 数据库配置(使用PostgreSQL,避免SQLite单点故障)
postgresql:
enabled: true
auth:
postgresPassword: "strong-pg-pass-2024"
username: "jianmu"
password: "jianmu-db-pass"
primary:
persistence:
size: 10Gi
# 建木核心配置
jianmu:
# Admin账户(首次登录后立即修改)
adminUsername: "admin"
adminPassword: "jianmu-admin-2024!" # 生产环境务必用随机密码
# 外部访问(Ingress配置)
ingress:
enabled: true
className: "nginx"
hosts:
- host: "ci.example.com"
paths:
- path: "/"
pathType: "Prefix"
# Executor配置(关键!)
executor:
type: "k8s" # 或 "docker" 用于开发环境
k8s:
# 使用建木专用ServiceAccount,最小权限原则
serviceAccount: "jianmu-executor"
# 指定Executor运行的Namespace
namespace: "jianmu-executors"
docker:
socketPath: "/var/run/docker.sock"
# 存储配置(对接MinIO,避免本地磁盘单点)
storage:
type: "minio"
minio:
endpoint: "http://minio.minio.svc.cluster.local:9000"
accessKey: "minio-access-key"
secretKey: "minio-secret-key"
bucket: "jianmu-artifacts"
部署命令(全程不暴露密码):
# 创建MinIO Secret(提前创建好)
kubectl create ns minio
helm repo add minio https://charts.min.io/
helm install minio minio/minio --namespace minio \
--set "accessKey=minio-access-key,secretKey=minio-secret-key,service.type=ClusterIP"
# 创建建木Secret
kubectl create secret generic jianmu-secrets \
--from-literal=POSTGRESQL_PASSWORD="strong-pg-pass-2024" \
--from-literal=JIANMU_ADMIN_PASSWORD="jianmu-admin-2024!" \
--from-literal=MINIO_ACCESS_KEY="minio-access-key" \
--from-literal=MINIO_SECRET_KEY="minio-secret-key" \
--namespace jianmu
# 执行Helm安装
helm repo add jianmu https://jianmu-dev.github.io/helm-charts
helm install jianmu jianmu/jianmu --namespace jianmu \
--create-namespace \
--values jianmu-values.yaml \
--set "postgresql.auth.existingSecret=jianmu-secrets" \
--set "jianmu.adminPasswordSecretKey=JIANMU_ADMIN_PASSWORD" \
--set "storage.minio.accessKeySecretKey=MINIO_ACCESS_KEY" \
--set "storage.minio.secretKeySecretKey=MINIO_SECRET_KEY"
注意:建木Executor的K8s ServiceAccount权限必须严格限制。我们创建的
jianmu-executorSA只绑定以下Role:apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: jianmu-executor namespace: jianmu-executors rules: - apiGroups: [""] resources: ["pods", "pods/log", "pods/exec"] verbs: ["create", "get", "list", "watch", "delete"] - apiGroups: ["batch"] resources: ["jobs"] verbs: ["create", "get", "list", "watch", "delete"]
3.3 流程编排实战:Vue项目从GitLab到K8s的全自动发布
以一个典型Vue CLI项目为例,完整演示建木如何替代Jenkins Pipeline。传统Jenkins做法是写Jenkinsfile:
pipeline {
agent any
environment {
NODE_VERSION = '18.17.0'
NPM_REGISTRY = 'https://registry.npmjs.org/'
}
stages {
stage('Checkout') {
steps { checkout scm }
}
stage('Install & Build') {
steps {
sh "nvm install ${NODE_VERSION}"
sh "npm ci --registry ${NPM_REGISTRY}"
sh "npm run build"
}
}
stage('Dockerize') {
steps {
sh "docker build -t registry.example.com/vue-app:${BUILD_NUMBER} ."
sh "docker push registry.example.com/vue-app:${BUILD_NUMBER}"
}
}
stage('Deploy to K8s') {
steps {
sh "kubectl set image deployment/vue-app vue-app=registry.example.com/vue-app:${BUILD_NUMBER}"
sh "kubectl rollout status deployment/vue-app"
}
}
}
}
在建木中,我们用YAML定义等效流程(
vue-deploy.yaml
):
name: Vue项目自动部署
description: 从GitLab拉取代码,构建Docker镜像,部署到K8s集群
version: "1.0"
# 触发条件:GitLab Webhook
triggers:
- type: "gitlab"
config:
webhookUrl: "https://ci.example.com/webhook/gitlab"
token: "gitlab-webhook-token-2024"
branches: ["main", "develop"]
# 全局变量(可被所有步骤引用)
variables:
- name: "GIT_REPO_URL"
value: "https://gitlab.example.com/frontend/vue-app.git"
- name: "IMAGE_REGISTRY"
value: "registry.example.com"
- name: "IMAGE_NAME"
value: "vue-app"
- name: "K8S_NAMESPACE"
value: "frontend-prod"
# 步骤定义(按顺序执行)
steps:
# 步骤1:克隆代码(使用alpine/git镜像,轻量)
- id: "git-clone"
name: "克隆代码"
type: "job"
config:
image: "alpine/git:v4.4.2"
command: ["/bin/sh", "-c"]
args:
- |
git clone --depth 1 $GIT_REPO_URL /workspace &&
cd /workspace &&
git checkout $GIT_BRANCH
env:
- name: "GIT_BRANCH"
value: "${{ inputs.branch }}"
volumeMounts:
- name: "workspace"
mountPath: "/workspace"
# 步骤2:安装依赖并构建(使用node:18.17-alpine,预装npm)
- id: "build"
name: "构建前端"
type: "job"
dependsOn: ["git-clone"]
config:
image: "node:18.17-alpine"
command: ["/bin/sh", "-c"]
args:
- |
cd /workspace &&
npm ci --registry https://registry.npmjs.org/ &&
npm run build
volumeMounts:
- name: "workspace"
mountPath: "/workspace"
# 步骤3:构建并推送Docker镜像(使用docker:24.0.5-dind)
- id: "docker-build"
name: "构建Docker镜像"
type: "job"
dependsOn: ["build"]
config:
image: "docker:24.0.5-dind"
privileged: true
command: ["/bin/sh", "-c"]
args:
- |
dockerd-entrypoint.sh &
sleep 5 &&
cd /workspace &&
docker build -t $IMAGE_REGISTRY/$IMAGE_NAME:${{ inputs.commit_id }} . &&
docker push $IMAGE_REGISTRY/$IMAGE_NAME:${{ inputs.commit_id }}
env:
- name: "IMAGE_REGISTRY"
value: "${{ variables.IMAGE_REGISTRY }}"
- name: "IMAGE_NAME"
value: "${{ variables.IMAGE_NAME }}"
volumeMounts:
- name: "workspace"
mountPath: "/workspace"
# 步骤4:部署到K8s(使用bitnami/kubectl镜像)
- id: "k8s-deploy"
name: "K8s部署"
type: "job"
dependsOn: ["docker-build"]
config:
image: "bitnami/kubectl:1.28.2"
command: ["/bin/sh", "-c"]
args:
- |
kubectl config set-cluster default --server=https://kubernetes.default.svc --certificate-authority=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt &&
kubectl config set-credentials sa --token=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) &&
kubectl config set-context default --cluster=default --user=sa &&
kubectl config use-context default &&
kubectl set image deployment/$IMAGE_NAME $IMAGE_NAME=$IMAGE_REGISTRY/$IMAGE_NAME:${{ inputs.commit_id }} -n $K8S_NAMESPACE &&
kubectl rollout status deployment/$IMAGE_NAME -n $K8S_NAMESPACE --timeout=300s
env:
- name: "IMAGE_REGISTRY"
value: "${{ variables.IMAGE_REGISTRY }}"
- name: "IMAGE_NAME"
value: "${{ variables.IMAGE_NAME }}"
- name: "K8S_NAMESPACE"
value: "${{ variables.K8S_NAMESPACE }}"
关键创新点:
-
动态输入注入
:
${{ inputs.commit_id }}自动从GitLab Webhook提取commit SHA,无需硬编码; -
精准依赖控制
:
dependsOn: ["build"]确保Docker构建一定在代码构建之后,避免Jenkins里stage('Build')和stage('Docker')顺序错乱导致的镜像内容陈旧; -
K8s原生集成
:步骤4直接使用
bitnami/kubectl镜像,通过ServiceAccount Token认证,比Jenkins插件更安全可靠; -
失败自动回滚
:建木支持
onFailure钩子,可在k8s-deploy失败时触发kubectl rollout undo,这是Jenkins Pipeline需要复杂脚本才能实现的功能。
3.4 权限与审计:用RBAC实现细粒度流水线管控
Jenkins的权限管理是出了名的反人类——
Overall/Read
、
Job/Build
、
Credentials/View
等权限项分散在不同层级,给开发分配“只能触发前端部署”权限需勾选12个复选框。建木采用K8s RBAC模型,权限收敛到3个核心对象:
Workflow
(流程定义)、
Execution
(运行实例)、
Artifact
(构建产物)。
我们为前端团队创建专属Namespace和RBAC:
# 创建前端Namespace
kubectl create ns frontend-team
# 创建Role(定义允许的操作)
kubectl create role frontend-deployer --verb=get,list,create,update,delete --resource=workflows,executions --namespace=frontend-team
# 绑定Role到Group(GitLab SSO同步的group)
kubectl create rolebinding frontend-deployer-binding --role=frontend-deployer --group=gitlab-frontend-group --namespace=frontend-team
# 创建专用ServiceAccount供建木Executor使用
kubectl create serviceaccount frontend-executor --namespace=frontend-team
# 绑定最小权限Role
kubectl create rolebinding frontend-executor-binding --role=frontend-deployer --serviceaccount=frontend-team:frontend-executor --namespace=frontend-team
在建木UI中,前端团队只能看到
frontend-team
Namespace下的流程,且无法编辑其他团队的Workflow。更关键的是,
所有操作留痕
:建木将每次Execution的完整YAML定义、输入参数、执行日志、产物哈希值全部存入PostgreSQL,并通过
audit-log
插件同步到ELK。当审计要求“追溯某次线上事故的部署源头”,我们只需在Kibana中搜索
execution_id: "exec-abc123"
,就能看到:
-
触发者:
gitlab-user@company.com -
触发时间:
2024-07-15T08:23:41Z -
输入参数:
{"branch": "main", "commit_id": "a1b2c3d4"} -
执行日志:
Step 'k8s-deploy': kubectl set image deployment/vue-app vue-app=registry.example.com/vue-app:a1b2c3d4 -
产物校验:
artifact-sha256: "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
这种审计粒度,是Jenkins的
Build History
页面点击10次才能勉强拼凑出来的信息。
4. 迁移避坑指南:从Jenkins到建木的平滑过渡策略
4.1 Jenkinsfile语法转换:不是重写,而是“翻译”
很多团队担心迁移成本,其实90%的Jenkinsfile可半自动转换。我们开发了一个内部工具
jenkins2jianmu
,它不解析Groovy语法,而是基于AST匹配模式。例如:
-
checkout scm→ 自动映射为git-clone步骤; -
sh 'npm ci'→ 识别为node:18-alpine镜像的Job; -
withCredentials([string(credentialsId: 'docker-hub', variable: 'DOCKER_PASS')])→ 转换为建木Secret引用${{ secrets.DOCKER_PASS }}; -
post { success { slackSend(...) } }→ 映射为onSuccess钩子调用curl -X POST https://hooks.slack.com/services/...。
但要注意3个必须手动处理的陷阱:
-
环境变量作用域
:Jenkins的
environment { NODE_VERSION = '18' }是全局的,而建木的variables只在当前Workflow生效。若多个Workflow共用同一套变量,需在建木中创建Shared Variable Set(通过API批量导入); -
多分支Pipeline
:Jenkins的
multibranchPipeline会为每个分支创建独立Job,建木需用triggers.gitlab.branches数组配置,且每个分支的Workflow需单独定义(建木不支持动态分支发现); -
Agent标签匹配
:Jenkins的
agent { label 'maven' }在建木中对应config.nodeSelector,需提前在K8s Node打Label:kubectl label nodes node-1 jianmu-executor-type=maven。
4.2 插件功能替代方案:告别“装插件-调配置-修Bug”循环
Jenkins用户最怀念的插件,建木都有更优雅的替代:
- Blue Ocean UI :建木Web界面本身就是可视化编排,支持拖拽连线、实时日志流、步骤状态图,无需额外插件;
-
Pipeline Utility Steps
:Jenkins里常用
readJSON解析构建产物,建木原生支持jsonpath表达式:${{ outputs['build'].jsonpath('$.version') }}; -
Email Extension
:建木的
webhook步骤可直接调用企业微信/钉钉机器人API,且支持模板渲染:{"msgtype": "text", "text": {"content": "部署成功!版本:${{ inputs.commit_id }}"}}; -
Artifactory Plugin
:建木的
artifact步骤自动上传构建产物到MinIO,并生成永久URL,比Artifactory的publishBuildInfo更轻量; -
Active Choices Plugin
:Jenkins里用Groovy脚本动态生成下拉菜单,建木用
inputForm定义JSON Schema表单,支持enum、oneOf等校验。
最典型的替代案例是
凭据管理
。Jenkins的
Credentials Binding Plugin
需要在全局配置里添加凭据,再在Pipeline里用
withCredentials
包裹。建木则统一用K8s Secret:
# 在K8s中创建Secret(建木自动同步)
kubectl create secret generic jianmu-credentials \
--from-literal=GITLAB_TOKEN="glpat-xxx" \
--from-literal=DOCKER_REGISTRY="https://registry.example.com" \
--from-literal=DOCKER_USER="deployer" \
--from-literal=DOCKER_PASS="xxx" \
--namespace jianmu
在Workflow中直接引用:
${{ secrets.GITLAB_TOKEN }}
。所有凭据加密存储在K8s ETCD中,比Jenkins的
JENKINS_HOME/credentials.xml
明文存储安全得多。
4.3 性能调优实录:让建木在千级并发下依然丝滑
我们曾用建木支撑某电商大促CI/CD,峰值并发达1200任务/分钟。以下是实测有效的调优参数(
jianmu-values.yaml
中配置):
| 参数 | 默认值 | 推荐值 | 作用说明 |
|---|---|---|---|
jianmu.executor.k8s.concurrency
| 10 | 50 | 单Executor Pod并发Job数,过高会导致K8s API Server压力 |
jianmu.storage.minio.partSize
| 5Mi | 25Mi | MinIO分片上传大小,提升大产物(如Java fatjar)上传速度 |
postgresql.primary.persistence.size
| 8Gi | 50Gi | PostgreSQL数据盘,避免日志堆积导致PVC满 |
jianmu.web.resources.limits.memory
| 1Gi | 4Gi | Web Server内存上限,防止OOM Killer误杀 |
最关键的调优是 Executor水平扩展 。建木Executor支持HPA(Horizontal Pod Autoscaler):
# jianmu-values.yaml
executor:
hpa:
enabled: true
minReplicas: 3
maxReplicas: 20
metrics:
- type: "Resource"
resource:
name: "cpu"
target:
type: "Utilization"
averageUtilization: 70
当CPU使用率持续高于70%,K8s自动扩容Executor Pod。我们观察到:3个Executor可稳定支撑300并发,20个Executor可应对1500并发,扩容延迟<45秒。而Jenkins Master在300并发时就需强制GC,扩容需重启服务,中断所有进行中的构建。
4.4 故障排查速查表:那些让你拍桌的瞬间及解法
| 现象 | 根本原因 | 解决方案 | 预防措施 |
|---|---|---|---|
| Executor Pod反复CrashLoopBackOff | Docker 24.0.5与Kernel 6.5+ cgroup v2冲突 |
降级Docker到24.0.5,或在
/etc/default/grub
中添加
systemd.unified_cgroup_hierarchy=0
| 新建Ubuntu节点时,Ansible Playbook强制设置cgroup v1 |
| Git Clone步骤超时(120s) | 建木Executor DNS解析慢,因K8s CoreDNS缓存未命中 | 在 |
更多推荐

所有评论(0)