Jenkins_Master_Slave_手搓部署指南基于_Ubuntu_22
原創
Jenkins Master/Slave 手搓部署指南(基于 Ubuntu 22.04)
# 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 的方式,極大地提高了資源利用率和系統的可擴展性。

浙公網安備 33010602011771號