Jenkins Master/Slave 手搓部署指南(基于 Ubuntu 22.04)
你现在拥有一个完全手搓、无 TLS、基于 ubuntu:22.04、Ingress 暴露、WebSocket 连接、主从分离的 Jenkins CI/CD 系统。特性实现方式Master 镜像自定义 ubuntu:22.04Agent 镜像自定义 ubuntu:22.04 + remoting.jar存储暴露方式主从连接WebSocket(无需 50000 端口)Pod 名权限SA + RBAC可
# 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 的方式,极大地提高了资源利用率和系统的可扩展性。
更多推荐
所有评论(0)