# Jenkins Master/Slave 手搓部署指南(基于 Ubuntu 22.04)

> **备注**:本文档为在 Kubernetes 集群中从零构建 Jenkins CI/CD 系统的完整操作手册。  
> - 所有镜像均基于 `ubuntu:22.04` 自定义构建,不依赖官方 Jenkins 镜像。  
> - 使用 **WebSocket 模式连接 Agent**,无需开放 `50000` 端口。  
> - 数据通过 PVC + PV + hostPath 持久化至指定节点 `/data/jenkins-pv`。  
> - 通过 Ingress 暴露 Web UI,使用 HTTP 协议(无 TLS)。  
> - 已修正原文档中的多处逻辑错误与配置问题,并增强安全性建议。

---

## ✅ 最终目标

| 特性 | 实现方式 |
|------|----------|
| Jenkins Master Pod 名称 | `jenkins-0`(hostname) |
| 基础镜像 | `ubuntu:22.04` |
| 构建方式 | 手动 Dockerfile 构建并推送至私有仓库 |
| 数据持久化 | PVC + PV + hostPath → `/data/jenkins-pv` on `k8s-node1` |
| 外部访问 | Ingress(HTTP,无 TLS) |
| Kubernetes Plugin 配置 | 完整配置云环境 |
| Agent 连接模式 | WebSocket(无需 `50000` 端口) |
| Agent 生命周期 | 动态创建、构建完成后自动销毁 |
| 权限控制 | ServiceAccount + RBAC cluster-admin |
| 可扩展性 | 支持动态伸缩 Agent Pods |

---

## 🧱 准备工作(所有节点)

### 1. 环境信息

| 角色 | 主机名 | IP 地址 |
|------|--------|---------|
| K8s Master | k8s-master | 192.168.122.96 |
| K8s Worker | k8s-node1 | 192.168.122.190 |

> **操作说明**:
> 在所有节点上更新 `/etc/hosts`,确保主机名解析正确:

```bash
echo "192.168.122.96 k8s-master" | sudo tee -a /etc/hosts
echo "192.168.122.190 k8s-node1"  | sudo tee -a /etc/hosts

🔹 第一步:准备 k8s-node1 节点

1. 创建 Jenkins 数据目录

说明:该路径将作为 PersistentVolume 的本地存储路径。

# 在 k8s-node1 上执行
sudo mkdir -p /data/jenkins-pv
sudo chown -R 1000:1000 /data/jenkins-pv

⚠️ 注意:Jenkins 默认运行用户 UID=1000,必须提前设置权限。


2. 给节点打标签(用于调度)

说明:确保 Jenkins Master 固定调度到 k8s-node1

# 在 master 节点执行
kubectl label node k8s-node1 jenkins/master=true --overwrite

🔹 第二步:构建 Jenkins Master 镜像(基于 ubuntu:22.04)

1. 创建项目目录

mkdir -p ~/jenkins-setup/{master,agent,certs}
cd ~/jenkins-setup/master
目录结构示例:
.
├── Dockerfile.master
├── entrypoint.sh
├── jdk-17.0.12.zip
├── jdk-17.0.12/
└── jenkins.war

所需文件

  • jdk-17.0.12.zip:Java 17 压缩包(解压后使用)
  • jenkins.war:Jenkins WAR 包(版本建议 2.516.2 或以上)

2. Dockerfile.master

# Dockerfile.master
FROM ubuntu:22.04

LABEL maintainer="devops@example.com"

# 设置变量
ENV JAVA_HOME=/data/jdk-17.0.12 \
    JENKINS_HOME=/var/jenkins_home \
    JENKINS_VERSION=2.516.2

# 换国内源加速
RUN sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && \
    sed -i 's/security.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list

# 创建目录并复制 JDK
RUN mkdir -p /data
COPY jdk-17.0.12 /data/jdk-17.0.12

# 安装基础工具
RUN apt-get update && \
    apt-get install -y wget net-tools curl git libfreetype6 fonts-dejavu-core fontconfig && \
    rm -rf /var/lib/apt/lists/*

# 创建 jenkins 用户(UID=1000)
RUN groupadd --gid 1000 jenkins && \
    useradd -m -u 1000 -g jenkins -d /home/jenkins jenkins && \
    mkdir -p /var/jenkins_home && \
    chown -R jenkins:jenkins /var/jenkins_home

# 添加 Java 到 PATH
ENV PATH="${JAVA_HOME}/bin:${PATH}"

WORKDIR /home/jenkins

COPY jenkins.war /home/jenkins/jenkins.war
RUN chown jenkins:jenkins jenkins.war

# 使用 root 用户运行(便于初始化)
USER root

# 复制启动脚本
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

EXPOSE 8080

ENTRYPOINT ["/entrypoint.sh"]

说明

  • 使用阿里云镜像源提升下载速度。
  • 显式声明 USER root 是为了在启动时能修改挂载卷权限。

3. entrypoint.sh

#!/bin/bash
set -e

# ✅ 正确指向 WAR 文件
JAR="/home/jenkins/jenkins.war"

# 确保 Jenkins home 目录权限正确
chown -R jenkins:jenkins $JENKINS_HOME

# 启动 Jenkins
exec java \
  -Djenkins.install.runSetupWizard=false \
  -Djenkins.CLI.disabled=true \
  -jar ${JAR} \
  --httpPort=8080 \
  --webroot=$JENKINS_HOME/war \
  --argumentsRealm.passwd.jenkins=jenkins \
  --argumentsRealm.roles.jenkins=admin

关键参数说明

  • runSetupWizard=false:跳过首次设置向导。
  • CLI.disabled=true:禁用 CLI 接口以提高安全性。
  • 内置账号密码:jenkins/jenkins

4. 构建并推送镜像

chmod +x entrypoint.sh

docker build -f Dockerfile.master -t swr.cn-east-3.myhuaweicloud.com/bocheng-test/jenkins-master:v1 .

# 推送至私有仓库
docker push swr.cn-east-3.myhuaweicloud.com/bocheng-test/jenkins-master:v1

🔹 第三步:构建 Jenkins Agent 镜像(基于 ubuntu:22.04)

cd ~/jenkins-setup/agent
目录结构示例:
.
├── Dockerfile.agent
├── agent.jar
├── apache-maven-3.8.8/
├── jdk-17.0.12/
└── jenkins-agent.sh

1. Dockerfile.agent

# Dockerfile.agent
FROM ubuntu:22.04

LABEL maintainer="devops@example.com"

# 设置环境变量
ENV AGENT_WORKDIR=/home/jenkins/agent \
    JAVA_HOME=/data/jdk-17.0.12 \
    MAVEN_HOME=/opt/apache-maven-3.8.8 \
    NVM_DIR=/home/jenkins/.nvm \
    USER=jenkins

# 添加 Java 和 Maven 到 PATH
ENV PATH="${JAVA_HOME}/bin:${MAVEN_HOME}/bin:${PATH}"

# 换源为阿里云加速 apt
RUN sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && \
    sed -i 's/security.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list

# 安装基础工具
RUN apt-get update && \
    apt-get install -y curl git vim python3-pip && \
    rm -rf /var/lib/apt/lists/*

# 创建 jenkins 用户
RUN groupadd --gid 1000 jenkins && \
    useradd -m -u 1000 -g jenkins -d /home/jenkins jenkins

# 创建 agent 工作目录
RUN mkdir -p $AGENT_WORKDIR && \
    chown -R jenkins:jenkins $AGENT_WORKDIR

# 拷贝本地 JDK
COPY jdk-17.0.12 /data/jdk-17.0.12

# 拷贝本地 Maven
COPY apache-maven-3.8.8 /opt/apache-maven-3.8.8

# 设置 Maven 权限
RUN chmod +x /opt/apache-maven-3.8.8/bin/mvn && \
    chown -R jenkins:jenkins /opt/apache-maven-3.8.8

# 安装 NVM、Node.js v18.20.6 和 pnpm
RUN su - jenkins <<'EOF'
    export NVM_DIR="$HOME/.nvm"
    mkdir -p "$NVM_DIR"

    # 安装 NVM(使用 Gitee 镜像)
    curl -fsSL https://gitee.com/cubxxw/nvm/raw/master/install.sh | \
        NVM_DIR='$NVM_DIR' \
        PROFILE=/dev/null \
        bash

    # 加载 NVM
    [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

    # 设置国内镜像源
    nvm node_mirror https://npmmirror.com/mirrors/node/
    nvm npm_mirror https://npmmirror.com/mirrors/npm/

    # 安装 Node.js v18.20.6
    nvm install v18.20.6
    nvm use v18.20.6

    # 设置 npm 国内源并安装 pnpm
    npm config set registry https://registry.npmmirror.com
    npm install -g pnpm

    # 创建自动加载脚本(用于非登录 shell)
    mkdir -p "$HOME/.profile.d"
    cat > "$HOME/.profile.d/nvm.sh" <<'INNER'
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
INNER
EOF

# 拷贝启动脚本
COPY jenkins-agent.sh /usr/local/bin/jenkins-agent.sh
RUN chmod +x /usr/local/bin/jenkins-agent.sh && \
    chown jenkins:jenkins /usr/local/bin/jenkins-agent.sh

# 确保归属正确
RUN chown -R jenkins:jenkins /home/jenkins

# 关键:切换用户并定义入口点
USER jenkins
WORKDIR $AGENT_WORKDIR
ENTRYPOINT ["/usr/local/bin/jenkins-agent.sh"]

2. jenkins-agent.sh

#!/bin/bash
# jenkins-agent.sh

set -e

if [ -z "$1" ] || [ -z "$2" ]; then
  echo "❌ 错误:缺少 JNLP secret 或 agent name"
  echo "💡 用法: $0 <secret> <agent-name>"
  exit 1
fi

export AGENT_WORKDIR="/home/jenkins/agent"
export JAVA_HOME="/data/jdk-17.0.12"
export PATH="$JAVA_HOME/bin:$PATH"

cd "$AGENT_WORKDIR"

# 从 Jenkins Master 下载 agent.jar(支持 WebSocket)
curl -fsSL -o agent.jar http://jenkins-master.jenkins.svc.cluster.local:8080/jnlpJars/agent.jar

exec java \
  -Duser.home=/home/jenkins \
  -Djava.awt.headless=true \
  -jar ./agent.jar \
  -url http://jenkins-master.jenkins.svc.cluster.local:8080 \
  -webSocket \
  -workDir "$AGENT_WORKDIR" \
  -headless \
  "$1" "$2"

说明

  • -webSocket:启用 WebSocket 连接,避免暴露 50000 端口。
  • jenkins-master.jenkins.svc.cluster.local:Kubernetes 内部 DNS 名称。

3. 构建并推送 Agent 镜像

docker build -f Dockerfile.agent -t swr.cn-east-3.myhuaweicloud.com/bocheng-test/jenkins-agent:v1 .

docker push swr.cn-east-3.myhuaweicloud.com/bocheng-test/jenkins-agent:v1

🔹 第四步:创建 PersistentVolume 和 PersistentVolumeClaim

1. jenkins-pv.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: jenkins-pv
spec:
  capacity:
    storage: 10Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: local-storage
  local:
    path: /data/jenkins-pv
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
                - k8s-node1

说明

  • nodeAffinity 确保 PV 绑定到 k8s-node1
  • ReadWriteOnce:单节点读写。

2. jenkins-pvc.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: jenkins-pvc
  namespace: jenkins
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  storageClassName: local-storage
  volumeName: jenkins-pv

⚠️ 修正:原文件中 volumeName 缩进错误导致未生效。


3. 应用 PV/PVC

kubectl create ns jenkins

kubectl apply -f ~/jenkins-setup/jenkins-pv.yaml
kubectl apply -f ~/jenkins-setup/jenkins-pvc.yaml

🔹 第五步:部署 Jenkins Master(Pod hostname=jenkins-0)

jenkins-master.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: jenkins
  namespace: jenkins
spec:
  replicas: 1
  selector:
    matchLabels:
      app: jenkins-master
  template:
    metadata:
      labels:
        app: jenkins-master
      annotations:
        pod.beta.kubernetes.io/hostname: jenkins-0
    spec:
      serviceAccountName: jenkins
      hostname: jenkins-0
      nodeSelector:
        kubernetes.io/hostname: k8s-node1
      tolerations:
        - key: node-role.kubernetes.io/control-plane
          operator: Exists
          effect: NoSchedule
        - key: node-role.kubernetes.io/master
          operator: Exists
          effect: NoSchedule
      containers:
        - name: jenkins
          image: swr.cn-east-3.myhuaweicloud.com/bocheng-test/jenkins-master:v1
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 8080
              name: http
          volumeMounts:
            - name: jenkins-home
              mountPath: /var/jenkins_home
      volumes:
        - name: jenkins-home
          persistentVolumeClaim:
            claimName: jenkins-pvc

---
apiVersion: v1
kind: Service
metadata:
  name: jenkins-master
  namespace: jenkins
spec:
  selector:
    app: jenkins-master
  ports:
    - name: http
      port: 8080
      targetPort: 8080
    - name: jnlp
      port: 50000
      targetPort: 50000

说明

  • hostname: jenkins-0 显式设置 Pod 主机名。
  • nodeSelector 强制调度至 k8s-node1
  • tolerations 允许容忍 master 污点(如需部署在 master 上)。

部署命令

kubectl apply -f ~/jenkins-setup/jenkins-master.yaml

🔹 第六步:安装 Nginx Ingress Controller(若未安装)

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/baremetal/deploy.yaml

等待启动完成

kubectl get pods -n ingress-nginx --watch

🔹 第七步:创建 Ingress(HTTP,无 TLS)

jenkins-ingress.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: jenkins-ingress
  namespace: jenkins
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
    nginx.ingress.kubernetes.io/proxy-body-size: "0"
    nginx.ingress.kubernetes.io/proxy-connect-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-request-buffering: "off"
    nginx.ingress.kubernetes.io/websocket-services: jenkins-master
spec:
  ingressClassName: nginx
  rules:
    - host: jenkins.local
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: jenkins-master
                port:
                  number: 8080

说明

  • websocket-services:启用 WebSocket 支持。
  • proxy-* 超时时间延长,防止大任务中断。

应用 Ingress

kubectl apply -f ~/jenkins-setup/jenkins-ingress.yaml

🔹 第八步:获取初始密码并登录 Jenkins

POD_NAME=$(kubectl get pod -n jenkins -l app=jenkins-master -o jsonpath='{.items[0].metadata.name}')
kubectl exec -n jenkins $POD_NAME -- cat /var/jenkins_home/secrets/initialAdminPassword

登录地址:http://jenkins.local
用户名:jenkins
密码:上述输出内容


🔹 第九步:配置 Jenkins 系统设置(关键)

进入 Manage Jenkins → Configure System

字段
Jenkins URL http://jenkins.local/
Quiet Period 5 sec
# of executors 0

⚠️ 必须设置 Jenkins URL,否则 Agent 回调失败!


🔹 第十步:创建 ServiceAccount 并获取 Token

jenkins-sa.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: jenkins
  namespace: jenkins
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: jenkins-cluster-admin
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: jenkins
    namespace: jenkins

说明:赋予 cluster-admin 权限以便动态管理 Pod。


应用并获取 Token

kubectl apply -f ~/jenkins-setup/jenkins-sa.yaml

SECRET_NAME=$(kubectl get secret -n jenkins -o jsonpath='{.items[?(@.type=="kubernetes.io/service-account-token")&&contains(@.metadata.annotations."kubernetes.io/service-account.name","jenkins")]).metadata.name}')

kubectl get secret $SECRET_NAME -n jenkins -o jsonpath='{.data.token}' | base64 -d

复制此 token,后续用于 Kubernetes Cloud 配置。


🔹 第十一步:配置 Kubernetes Cloud(完整版)

进入:Manage Jenkins → Configure System → Cloud → Add a new cloud → Kubernetes

Kubernetes 云配置

字段
Name k8s-cluster
Kubernetes URL https://kubernetes.default.svc.cluster.local
Kubernetes Namespace jenkins
Credentials ➕ 添加 → Kind = Secret text → Scope = Global → Secret = 粘贴上面的 token → ID = k8s-sa-token
Test Connection ✅ 显示 “Connected to Kubernetes …”

Jenkins 配置

字段
Jenkins URL http://jenkins.local
Jenkins Tunnel 留空(启用 WebSocket)

添加 Pod Template

点击 Add Pod Template

字段
Name jnlp-slave
Namespace jenkins
Labels custom-agent jnlp-slave
Usage Use this node as much as possible
Run As User 1000
Run As Group 1000
Add Container

Container 配置

字段
Name jnlp
Docker Image swr.cn-east-3.myhuaweicloud.com/bocheng-test/jenkins-agent:v1
Allocate pseudo-TTY ✅ 勾选
Command / Args 留空(由 entrypoint 控制)

保存配置。


🔹 第十二步:DNS 测试与访问

1. 配置本地 DNS

修改本地电脑的 hosts 文件:

<INGRESS-IP> jenkins.local

<INGRESS-IP> 是运行 Ingress Controller 的节点 IP(如 192.168.122.190

2. 访问 Jenkins

浏览器打开:

http://jenkins.local

你应该看到 Jenkins 登录页面。


🔹 第十三步:测试 Pipeline

pipeline {
    agent { label 'jnlp-slave' }
    stages {
        stage('Build') {
            steps {
                sh 'echo "Hello from Ubuntu 22.04 Agent"'
                sh 'java -version'
                sh 'git --version'
                sh 'whoami'
                sh 'df -h'
            }
        }
    }
}

验证

  • 是否动态创建 Agent Pod?
  • 构建完成后是否自动销毁?
  • 日志中是否显示来自自定义镜像?

✅ 验证清单(全部手动验证)

验证项 命令 / 方法
Master 是否在 k8s-node1 kubectl get pod -n jenkins -o wide
hostname 是否为 jenkins-0 kubectl exec -it POD -n jenkins -- hostname
数据是否落盘 ls /data/jenkins-pv on k8s-node1
PVC 是否绑定 kubectl get pvc -n jenkins
Ingress 是否生效 kubectl get ingress -n jenkins
Jenkins URL 是否正确 Web UI → Configure System → Jenkins URL
Kubernetes Plugin 连接成功 Cloud 配置页 Test Connection 成功
Agent 是否动态创建 kubectl get pods -n jenkins during build
Agent 使用自定义镜像 kubectl get pod AGENT_POD -n jenkins -o jsonpath='{.spec.containers[0].image}'

🏁 总结

你现在拥有一个 完全手搓、无 TLS、基于 ubuntu:22.04、Ingress 暴露、WebSocket 连接、主从分离 的 Jenkins CI/CD 系统。

特性 实现方式
Master 镜像 自定义 ubuntu:22.04
Agent 镜像 自定义 ubuntu:22.04 + remoting.jar
存储 PVC + PV + hostPath
暴露方式 Ingress(HTTP)
主从连接 WebSocket(无需 50000 端口)
Pod 名 hostname=jenkins-0
权限 SA + RBAC
可扩展 Kubernetes Plugin 动态伸缩

🔧 附加:Jenkins Agent 高级权限配置(可选)

适用于需要构建容器或访问宿主机资源的场景:

spec:
  containers:
    - name: jnlp
      securityContext:
        privileged: true
        capabilities:
          add:
            - SYS_ADMIN
            - SETFCAP
        allowPrivilegeEscalation: true
      volumeMounts:
        - name: containers-storage
          mountPath: /var/lib/containers
        - name: dev-mapper
          mountPath: /dev/mapper
        - name: containerd-socket
          mountPath: /run/containerd/containerd.sock
        - name: local-time
          mountPath: /etc/localtime
        - name: maven-cache
          mountPath: /root/.m2/repository
  volumes:
    - name: containers-storage
      emptyDir: {}
    - name: dev-mapper
      hostPath:
        path: /dev/mapper
    - name: containerd-socket
      hostPath:
        path: /run/containerd/containerd.sock
    - name: local-time
      hostPath:
        path: /etc/localtime
    - name: maven-cache
      persistentVolumeClaim:
        claimName: maven-cache-pvc
  tolerations:
    - key: "node-role.kubernetes.io/master"
      operator: "Exists"
      effect: "NoSchedule"

⚠️ 注意:privileged: true 存在安全风险,请谨慎使用。


📦 附加:项目流水线模板

1. Jenkinsfile 示例

pipeline {
    agent { label 'jnlp-slave' }

    environment {
        registry            = "swr.cn-east-3.myhuaweicloud.com"
        project             = "bocheng-test"
        app_name            = "${JOB_NAME}"
        image_name          = "${registry}/${project}/${app_name}:${BUILD_NUMBER}"
        app_port            = "8901"
        git_address         = "https://gitlab.dbblive.com/dbbjt/wanyan-test.git"
        rollback_image_name = "${registry}/${project}/${app_name}:${version}"
        docker_registry_auth = "swr-secret"
        git_auth            = "a9f095dd-abfc-411f-b4c4-c10518839eea"
    }

    parameters {
        gitParameter(
            name: 'Branch',
            type: 'PT_BRANCH_TAG',
            branch: '',
            branchFilter: '.*',
            tagFilter: '*',
            defaultValue: 'master',
            selectedValue: 'NONE',
            sortMode: 'NONE',
            description: 'Select the branch to deploy'
        )
        choice(
            name: 'Namespace',
            choices: 'prod',
            description: 'Select deployment environment'
        )
        choice(
            name: 'deploy_env',
            choices: ['deploy', 'rollback'],
            description: 'deploy: deploy new version, rollback: roll back to specified image'
        )
        string(
            name: 'version',
            defaultValue: '',
            description: 'Enter the image version (BUILD_NUMBER) for rollback, used only in rollback mode'
        )
    }

    stages {
        stage('Checkout Code') {
            steps {
                echo "Checking out application code from ${git_address}"

                checkout([
                    $class: 'GitSCM',
                    branches: [[name: "${params.Branch}"]],
                    doGenerateSubmoduleConfigurations: false,
                    extensions: [],
                    submoduleCfg: [],
                    userRemoteConfigs: [[
                        credentialsId: "${git_auth}",
                        url: "${git_address}"
                    ]]
                ])

                echo "Cloning Kubernetes deployment configurations"

                dir('yyh-devops-config') {
                    git(
                        branch: 'master',
                        url: 'https://gitlab.dbblive.com/kubernetes/yyh-devops.git',
                        credentialsId: "${git_auth}"
                    )
                }

                echo "Code checkout completed"
            }
        }

        stage('Build and Push Image') {
            when {
                expression { params.deploy_env == 'deploy' }
            }
            steps {
                echo "Starting build and container image creation"

                sh """
                    mvn clean install -am -pl bc-gateway -Dmaven.test.skip=true -P test -T 4C
                    /usr/bin/buildah login -u cn-east-3@HPUAAL2AC4J1SRYWA1NW -p b39facac2c5dbffb4aa0defa8d4750ce3d148c81a0cb0f3b9d184f755d0fff3f ${registry}
                    cd \${WORKSPACE}/bc-gateway
                    /usr/bin/buildah bud -t ${image_name} .
                    /usr/bin/buildah push ${image_name}
                """

                echo "Image built and pushed successfully: ${image_name}"
            }
        }

        stage('Deploy to Kubernetes') {
            when {
                expression { params.deploy_env == 'deploy' }
            }
            steps {
                echo "Deploying application to Kubernetes cluster"

                dir('yyh-devops-config/dbbjt/wanyan-test') {
                    sh '''
                        echo "Current working directory: $(pwd)"
                        echo "Listing contents:"
                        ls -la

                        if [ ! -f k8s-deployment.yaml ]; then
                            echo "ERROR: k8s-deployment.yaml not found" >&2
                            echo "Please ensure the file exists at path: ddbjt/wanyan-test/k8s-deployment.yaml in the yyh-devops repo" >&2
                            exit 1
                        fi

                        echo "Replacing template variables..."
                        sed -i "s#{APP_NAME}#${JOB_NAME}#g" k8s-deployment.yaml
                        sed -i "s#{APP_PORT}#${app_port}#g" k8s-deployment.yaml
                        sed -i "s#{IMAGE_NAME}#${image_name}#g" k8s-deployment.yaml
                        sed -i "s#{NAME_SPACE}#${Namespace}#g" k8s-deployment.yaml
                        sed -i "s#{ADD_ENV_LABEL}#${Namespace}#g" k8s-deployment.yaml

                        echo "Applying deployment configuration..."
                        kubectl apply -f k8s-deployment.yaml -n ${Namespace}
                        echo "Deployment applied successfully"
                    '''
                }
            }
        }

        stage('Wait for Service Readiness') {
            when {
                expression { params.deploy_env == 'deploy' }
            }
            steps {
                echo "Waiting for service to start..."
                sleep 63

                timeout(time: 31, unit: 'SECONDS') {
                    waitUntil {
                        script {
                            def podStatus = sh(
                                returnStdout: true,
                                script: "kubectl get replicasets -n ${Namespace} | grep ${JOB_NAME} | awk '{if (\$2 >= 1 && \$4 == 0) print \"not_ready\"}'"
                            ).trim()

                            def notRunningPod = sh(
                                returnStdout: true,
                                script: "kubectl get pod -n ${Namespace} | grep ${JOB_NAME} | awk '{if (\$2 == \"0/1\") print \$1}'"
                            ).trim()

                            if (podStatus || notRunningPod) {
                                echo "Service is not ready yet. Retrying in 10 seconds..."
                                sleep(10)
                                return false
                            } else {
                                echo "Service ${JOB_NAME} is now running and ready"
                                return true
                            }
                        }
                    }
                }
            }
        }

        stage('Rollback Image') {
            when {
                expression { params.deploy_env == 'rollback' }
            }
            steps {
                echo "Performing rollback for ${JOB_NAME} to version: ${version}"

                dir('yyh-devops-config/dbbjt/wanyan-test') {
                    sh '''
                        echo "Current directory: $(pwd)"
                        ls -la

                        if [ ! -f k8s-deployment.yaml ]; then
                            echo "ERROR: k8s-deployment.yaml not found, cannot proceed with rollback" >&2
                            exit 1
                        fi

                        echo "Updating image to rollback version..."
                        sed -i "s#{APP_NAME}#${JOB_NAME}#g" k8s-deployment.yaml
                        sed -i "s#{APP_PORT}#${app_port}#g" k8s-deployment.yaml
                        sed -i "s#{IMAGE_NAME}#${rollback_image_name}#g" k8s-deployment.yaml
                        sed -i "s#{NAME_SPACE}#${Namespace}#g" k8s-deployment.yaml
                        sed -i "s#{ADD_ENV_LABEL}#${Namespace}#g" k8s-deployment.yaml

                        echo "Applying rollback configuration..."
                        kubectl apply -f k8s-deployment.yaml -n ${Namespace}
                        echo "Rollback completed"
                    '''
                }
            }
        }
    }

    post {
        success {
            echo "Pipeline succeeded: Deployment or rollback completed successfully"
        }
        failure {
            echo "Pipeline failed: Check logs for details"
        }
        always {
            echo "Pipeline execution finished"
        }
    }
}

2. Kubernetes Deployment 模板(k8s-deployment.yaml)

---
apiVersion: v1
kind: Service
metadata:
  name: {APP_NAME}
  namespace: {NAME_SPACE}
  labels:
    app: {APP_NAME}
    env: {ADD_ENV_LABEL}
spec:
  ports:
  - name: http
    port: {APP_PORT}
    protocol: TCP
    targetPort: {APP_PORT}
  selector:
    app: {APP_NAME}
    env: {ADD_ENV_LABEL}
  sessionAffinity: None
  type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {APP_NAME}
  namespace: {NAME_SPACE}
  labels:
    app: {APP_NAME}
    env: {ADD_ENV_LABEL}
spec:
  replicas: 1
  selector:
    matchLabels:
      app: {APP_NAME}
      env: {ADD_ENV_LABEL}
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: {APP_NAME}
        env: {ADD_ENV_LABEL}
    spec:
      imagePullSecrets:
      - name: swr-registry-secret
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - {APP_NAME}
              topologyKey: kubernetes.io/hostname
            weight: 100
      containers:
      - env:
        - name: TZ
          value: Asia/Shanghai
        - name: LANG
          value: en_US.UTF-8
        image: {IMAGE_NAME}
        imagePullPolicy: IfNotPresent
        name: {APP_NAME}
        ports:
        - name: http
          containerPort: {APP_PORT}
          protocol: TCP
        readinessProbe:
          failureThreshold: 2
          initialDelaySeconds: 30
          periodSeconds: 10
          successThreshold: 1
          tcpSocket:
            port: {APP_PORT}
          timeoutSeconds: 2
        livenessProbe:
          failureThreshold: 2
          initialDelaySeconds: 30
          periodSeconds: 10
          successThreshold: 1
          tcpSocket:
            port: {APP_PORT}
          timeoutSeconds: 2          
        resources:
          limits:
            cpu: 1000m
            memory: 1024Mi
          requests:
            cpu: 200m
            memory: 256Mi
        volumeMounts:
        - mountPath: /data/logs
          name: logs
        - mountPath: /etc/localtime
          name: localtime
          readOnly: true
      dnsPolicy: ClusterFirstWithHostNet
      restartPolicy: Always
      securityContext:
        fsGroup: 2049
        runAsGroup: 2049
        runAsUser: 2049
      volumes:
      - emptyDir: {}
        name: logs
      - hostPath:
          path: /etc/localtime
          type: File
        name: localtime

说明

  • 使用 {} 占位符,在 Jenkins 中通过 sed 替换。
  • 添加反亲和性策略防止同一节点部署多个副本。

附加jenkins的配置(web页面)

 **Kubernetes 云配置 (Cloud Kubernetes Configuration)** 和 **Pod 模板设置 (Pod Template Settings)**,它们是 Jenkins 与 Kubernetes 集成以动态创建构建代理(Agent)的核心配置。

---

### **第一步:配置 Kubernetes 云 (Cloud Kubernetes Configuration)**

这是在 Jenkins 系统管理中定义如何连接到 Kubernetes 集群的全局设置。

1.  **名称 (Name)**
    *   设置为 `Kubernetes`。
    *   这是此云配置的唯一标识符,在后续配置 Pod 模板时会引用它。

2.  **Kubernetes 地址 (Kubernetes URL)**
    *   填写为 `https://kubernetes.default.svc.cluster.local`。
    *   这是集群内部 API Server 的地址,Jenkins Master 通过这个地址与 Kubernetes 通信。

3.  **禁用 HTTPS 证书检查 (Disable https certificate check)**
    *   **已勾选**。
    *   此选项用于跳过对 Kubernetes API Server 证书的有效性验证。在测试或内部环境中常用,但在生产环境中不推荐,存在安全风险。

4.  **Kubernetes 命名空间 (Kubernetes Namespace)**
    *   设置为 `jenkins`。
    *   指定 Jenkins 将在此命名空间内创建和管理所有的 Agent Pod。

5.  **WebSocket**
    *   **已勾选**。
    *   启用 WebSocket 连接,这是 Jenkins Agent 与 Master 之间建立持久连接的一种高效方式。

6.  **Jenkins 地址 (Jenkins URL)**
    *   填写为 `http://jenkins-master.jenkins.svc.cluster.local:8080`。
    *   这是 Kubernetes 集群内部访问 Jenkins Master 的地址。Agent Pod 会使用这个地址来连接并注册到 Master。

7.  **容器数量 (Container Cap)**
    *   设置为 `10`。
    *   限制同时运行的 Agent Pod 最大数量为 10 个。

8.  **Pod Labels (Pod 标签)**
    *   添加了一个标签:
        *   **键 (Key)**: `jenkins`
        *   **值 (Value)**: `jnlp`
    *   这个标签会被附加到所有由该云配置创建的 Pod 上,用于标识和筛选这些 Pod。

9.  **其他重要设置**
    *   **Pod Retention**: 设置为 `Never`,意味着一旦构建完成,Pod 会被立即删除。
    *   **连接 Kubernetes API 的最大连接数**: 设置为 `32`。
    *   **Seconds to wait for pod to be running**: 设置为 `600` 秒(10分钟),等待 Pod 启动并进入 Running 状态的最大时间。
    *   **Container Cleanup Timeout**: 设置为 `5` 秒,清理容器的超时时间。

---

### **第二步:配置 Pod 模板 (Pod Template Settings)**

这是在 Kubernetes 云配置下定义具体 Agent Pod 结构的模板。当有构建任务需要执行时,Jenkins 会根据此模板动态创建一个 Pod。

1.  **名称 (Name)**
    *   设置为 `jnlp-slave`。
    *   这是此 Pod 模板的唯一名称。

2.  **命名空间 (Namespace)**
    *   设置为 `jenkins`。
    *   与上一步的云配置保持一致,确保 Pod 在正确的命名空间内创建。

3.  **标签列表 (Labels)**
    *   设置为 `jnlp-slave`。
    *   这是分配给此 Pod 模板的标签。在 Jenkins 的 Job 配置中,可以通过指定这个标签来选择使用此模板创建的 Agent。

4.  **用法 (Usage)**
    *   设置为“只允许运行绑定到这台机器的Job”。
    *   这意味着只有明确指定了 `jnlp-slave` 标签的 Job 才能使用这个模板创建的 Agent。

5.  **容器列表 (Container Templates)**

    *   **容器名称 (Name)**: `jnlp`
    *   **Docker 镜像 (Docker Image)**: `swr.cn-east-3.myhuaweicloud.com/bocheng-test/jenkins-agentv2`
        *   这是 Agent 容器所使用的镜像。
    *   **工作目录 (Working Directory)**: `/home/jenkins/agent`
        *   容器启动后的工作目录。
    *   **命令参数 (Arguments)**: `${computer.jnlpMac} ${computer.name}`
        *   这是传递给 `jnlp` 容器的启动参数,用于让 Agent 连接到 Jenkins Master。
    *   **分配伪终端 (Allocate TTY)**: **已勾选**。
        *   为容器分配一个伪终端,这对于某些需要交互式环境的构建脚本是必需的。

6.  **环境变量 (Environment Variables)**

    *   **CONTAINERD_ADDRESS**: `/run/containerd/containerd.sock`
        *   指向 Containerd 的 Unix Socket,可能用于容器运行时相关的操作。
    *   **CONTAINERD_NAMESPACE**: `default`
        *   设置 Containerd 的命名空间。

7.  **卷 (Volumes)**

    *   **Host Path Volume (主机路径卷)**
        *   第一个:
            *   主机路径: `/run/containerd/containerd.sock`
            *   挂载路径: `/run/containerd/containerd.sock`
            *   用于将主机上的 Containerd Socket 挂载到容器内。
        *   第二个:
            *   主机路径: `/etc/localtime`
            *   挂载路径: `/etc/localtime`
            *   用于同步主机的时间配置到容器内,保证时间一致性。
    *   **Persistent Volume Claim (持久卷声明)**
        *   申明值: `maven-cache-pvc`
        *   挂载路径: `/root/.m2/repository`
        *   用于挂载一个名为 `maven-cache-pvc` 的 PVC 到容器内的 Maven 本地仓库目录,实现构建缓存的持久化,加快后续构建速度。

8.  **Raw YAML for the Pod**

    *   这里直接提供了完整的 Pod YAML 定义,它覆盖了上面所有图形化界面的配置。
    *   关键点包括:
        *   **securityContext**: 设置了 `privileged: true` 和 `capabilities`,赋予容器特权模式和特定能力(SYS_ADMIN, SETFCAP),这通常是为了支持 Docker-in-Docker 或其他需要高权限的操作。
        *   **volumeMounts & volumes**: 明确定义了上述的三个卷挂载。
        *   **tolerations**: 添加了容忍度 `node-role.kubernetes.io/master: Exists`,允许 Pod 调度到 Master 节点上(如果集群中有 Master 节点且允许调度)。

9.  **Service Account**
    *   设置为 `jenkins`。
    *   指定 Pod 使用的服务账户,用于控制 Pod 在 Kubernetes 中的权限。

10. **Run As User ID / Run As Group ID**
    *   均设置为 `0`。
    *   以 root 用户身份运行容器,这与 `privileged: true` 权限相匹配。

11. **Image Pull Secret**
    *   名称: `swr-secret`
    *   用于从私有仓库 `swr.cn-east-3.myhuaweicloud.com` 拉取镜像时进行身份认证。

12. **其他设置**
    *   **Pod Retention**: 设置为 `Default`,遵循云配置中的 `Never`。
    *   **连接 Jenkins 的超时时间 (秒)**: 设置为 `1000` 秒。
    *   **Yaml merge strategy**: 设置为 `Override`,表示 Raw YAML 会完全覆盖图形化配置。

---

### **总结**

通过以上两步配置,Jenkins 成功集成了 Kubernetes:

*   **第一步**建立了与 Kubernetes 集群的连接通道,并设定了全局的资源限制和行为。
*   **第二步**定义了一个具体的、功能完备的 Agent Pod 模板 (`jnlp-slave`),它包含了运行构建所需的镜像、环境变量、持久化存储、权限设置以及网络连接等所有细节。

当用户创建一个 Job 并指定其运行在 `jnlp-slave` 标签的节点上时,Jenkins 会自动调用 Kubernetes API,在 `jenkins` 命名空间下创建一个符合 `jnlp-slave` 模板的 Pod。该 Pod 启动后会连接到 Jenkins Master,接收并执行构建任务,任务完成后 Pod 会被自动清理。这种动态按需创建 Agent 的方式,极大地提高了资源利用率和系统的可扩展性。
Logo

更多推荐