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-executor SA只绑定以下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个必须手动处理的陷阱:

  1. 环境变量作用域 :Jenkins的 environment { NODE_VERSION = '18' } 是全局的,而建木的 variables 只在当前Workflow生效。若多个Workflow共用同一套变量,需在建木中创建 Shared Variable Set (通过API批量导入);
  2. 多分支Pipeline :Jenkins的 multibranchPipeline 会为每个分支创建独立Job,建木需用 triggers.gitlab.branches 数组配置,且每个分支的Workflow需单独定义(建木不支持动态分支发现);
  3. 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缓存未命中 在

更多推荐