Jenkins 环境准备和配置
1、 安装必要插件
首先在 Jenkins 中安装以下必需插件:
系统管理 → 插件管理 → 可选插件
Kubernetes Plugin (版本 1.31.3+)
Docker Pipeline Plugin (版本 1.28+)
Helm Plugin (版本 1.0.0+)
Git Plugin (版本 4.11.3+)
Pipeline Plugin (版本 2.6+)
Blue Ocean Plugin (可选,用于可视化)
Config File Provider Plugin (用于管理配置文件)
2 、配置 Kubernetes Cloud
系统管理 → 节点管理 → Configure Clouds
# Kubernetes 云配置
名称: kubernetes
Kubernetes 地址: https://kubernetes.default.svc
Kubernetes 命名空间: jenkins
凭据: 添加 kubeconfig 文件或 Service Account
Jenkins 地址: http://jenkins.jenkins.svc.cluster.local:8080
3、 配置凭据
凭据 → 系统 → 全局凭据 → 添加凭据
Docker Registry 凭据
类型: Username with password
用户名: your-docker-username
密码: your-docker-password
ID: docker-registry-creds
Git 凭据
类型: SSH Username with private key
用户名: git
私钥: 粘贴 SSH 私钥
ID: git-ssh-key
Kubernetes 配置
类型: Secret file
文件: 上传 kubeconfig 文件
ID: kubeconfig-file
4 完整 的JenkinsFile
#!/usr/bin/env groovy
// 定义全局参数
properties([
parameters([
choice(
choices: ['dev', 'staging', 'production'],
description: '选择部署环境',
name: 'DEPLOY_ENV'
),
string(
defaultValue: '',
description: 'Docker 镜像标签 (留空使用 BUILD_NUMBER)',
name: 'CUSTOM_TAG'
),
booleanParam(
defaultValue: false,
description: '是否跳过测试',
name: 'SKIP_TESTS'
)
]),
buildDiscarder(logRotator(numToKeepStr: '20')),
pipelineTriggers([
pollSCM('H/5 * * * *')
])
])
// 环境配置映射
def loadEnvironmentConfig() {
def configs = [
dev: [
namespace: 'development',
replicas: 1,
ingressHost: 'dev.myapp.com',
cpuRequest: '100m',
memoryRequest: '128Mi',
cpuLimit: '200m',
memoryLimit: '256Mi',
helmTimeout: '5m',
autoApproval: true
],
staging: [
namespace: 'staging',
replicas: 2,
ingressHost: 'staging.myapp.com',
cpuRequest: '200m',
memoryRequest: '256Mi',
cpuLimit: '500m',
memoryLimit: '512Mi',
helmTimeout: '10m',
autoApproval: false
],
production: [
namespace: 'production',
replicas: 3,
ingressHost: 'myapp.com',
cpuRequest: '500m',
memoryRequest: '512Mi',
cpuLimit: '1000m',
memoryLimit: '1Gi',
helmTimeout: '15m',
autoApproval: false
]
]
return configs[params.DEPLOY_ENV]
}
pipeline {
agent {
kubernetes {
label "helm-cicd-${env.BUILD_NUMBER}"
yaml """
apiVersion: v1
kind: Pod
metadata:
labels:
app: jenkins-agent
spec:
serviceAccount: jenkins-agent
containers:
- name: helm
image: alpine/helm:3.12.0
command: ['cat']
tty: true
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
- name: kubectl
image: bitnami/kubectl:1.27
command: ['cat']
tty: true
env:
- name: KUBECONFIG
value: /home/jenkins/.kube/config
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
- name: docker
image: docker:20.10-dind
command: ['cat']
tty: true
env:
- name: DOCKER_HOST
value: tcp://localhost:2375
resources:
requests:
memory: "256Mi"
cpu: "200m"
limits:
memory: "512Mi"
cpu: "500m"
securityContext:
privileged: true
- name: node
image: node:18-alpine
command: ['cat']
tty: true
resources:
requests:
memory: "256Mi"
cpu: "200m"
limits:
memory: "512Mi"
cpu: "500m"
volumes:
- name: docker-sock
emptyDir: {}
"""
}
}
environment {
// 基础配置
REGISTRY = "registry.mycompany.com"
PROJECT = "myproject"
APP_NAME = "myapp"
HELM_CHART_PATH = "kubernetes/charts/myapp"
DOCKERFILE_PATH = "."
// 从凭据中获取
DOCKER_CREDS = credentials('docker-registry-creds')
GIT_SSH_KEY = credentials('git-ssh-key')
// 动态设置
IMAGE_TAG = "${params.CUSTOM_TAG ?: env.BUILD_NUMBER}"
FULL_IMAGE_NAME = "${REGISTRY}/${PROJECT}/${APP_NAME}:${IMAGE_TAG}"
}
stages {
// 阶段 1: 代码检出
stage('Checkout Code') {
steps {
timestamps {
echo "开始检出代码,环境: ${params.DEPLOY_ENV}"
git branch: 'main',
credentialsId: 'git-ssh-key',
url: 'git@github.com:myorg/myrepo.git'
sh 'git log -1 --oneline'
sh 'ls -la'
}
}
}
// 阶段 2: 代码质量检查
stage('Code Quality') {
steps {
container('node') {
timestamps {
echo "运行代码质量检查"
sh """
npm install
npm run lint
npm run test:unit
"""
}
}
}
}
// 阶段 3: Helm Chart 验证
stage('Validate Helm Chart') {
steps {
container('helm') {
timestamps {
echo "验证 Helm Chart"
script {
// 更新依赖
sh """
helm dependency update ${HELM_CHART_PATH}
helm dependency build ${HELM_CHART_PATH}
"""
// Chart 语法检查
sh "helm lint ${HELM_CHART_PATH} --strict"
// 模板渲染测试
sh """
helm template ${APP_NAME} ${HELM_CHART_PATH} \
--namespace default \
--set image.repository=${REGISTRY}/${PROJECT}/${APP_NAME} \
--set image.tag=${IMAGE_TAG} \
--debug
"""
// 验证 values 文件
if (fileExists("${HELM_CHART_PATH}/values-${params.DEPLOY_ENV}.yaml")) {
sh """
helm template ${APP_NAME} ${HELM_CHART_PATH} \
-f ${HELM_CHART_PATH}/values-${params.DEPLOY_ENV}.yaml \
--debug
"""
}
}
}
}
}
}
// 阶段 4: 构建 Docker 镜像
stage('Build and Push Docker Image') {
steps {
container('docker') {
timestamps {
echo "构建和推送 Docker 镜像"
script {
// 登录 Docker Registry
sh """
docker login -u ${DOCKER_CREDS_USR} -p ${DOCKER_CREDS_PSW} ${REGISTRY}
"""
// 构建镜像
sh """
docker build \
-t ${FULL_IMAGE_NAME} \
-t ${REGISTRY}/${PROJECT}/${APP_NAME}:latest \
-f ${DOCKERFILE_PATH}/Dockerfile \
${DOCKERFILE_PATH}
"""
// 推送镜像
sh """
docker push ${FULL_IMAGE_NAME}
docker push ${REGISTRY}/${PROJECT}/${APP_NAME}:latest
"""
// 清理本地镜像
sh """
docker rmi ${FULL_IMAGE_NAME} || true
docker rmi ${REGISTRY}/${PROJECT}/${APP_NAME}:latest || true
"""
}
}
}
}
}
// 阶段 5: 部署到开发环境
stage('Deploy to Development') {
when {
expression { params.DEPLOY_ENV == 'dev' }
}
steps {
container('helm') {
timestamps {
echo "部署到开发环境"
script {
def config = loadEnvironmentConfig()
sh """
helm upgrade --install ${APP_NAME} ${HELM_CHART_PATH} \
--namespace ${config.namespace} \
--set image.repository=${REGISTRY}/${PROJECT}/${APP_NAME} \
--set image.tag=${IMAGE_TAG} \
--set replicaCount=${config.replicas} \
--set resources.requests.cpu=${config.cpuRequest} \
--set resources.requests.memory=${config.memoryRequest} \
--set resources.limits.cpu=${config.cpuLimit} \
--set resources.limits.memory=${config.memoryLimit} \
--set ingress.hosts[0].host=${config.ingressHost} \
--wait \
--timeout ${config.helmTimeout} \
--atomic
"""
}
}
}
}
}
// 阶段 6: 集成测试
stage('Integration Tests') {
when {
expression { params.DEPLOY_ENV in ['dev', 'staging'] && !params.SKIP_TESTS }
}
steps {
container('node') {
timestamps {
echo "运行集成测试"
script {
def config = loadEnvironmentConfig()
// 等待应用就绪
container('kubectl') {
sh """
kubectl wait --for=condition=ready pod -l app=${APP_NAME} \
-n ${config.namespace} \
--timeout=300s
"""
}
// 运行测试
sh """
npm run test:integration \
--baseUrl=http://${APP_NAME}.${config.namespace}.svc.cluster.local
"""
}
}
}
}
}
// 阶段 7: 人工审批 (预生产和生产环境)
stage('Manual Approval') {
when {
expression {
params.DEPLOY_ENV in ['staging', 'production'] &&
!loadEnvironmentConfig().autoApproval
}
}
steps {
timestamps {
script {
def config = loadEnvironmentConfig()
def approvers = params.DEPLOY_ENV == 'production' ?
'production-team' : 'qa-team'
input(
message: "确认部署到 ${params.DEPLOY_ENV} 环境?",
ok: "确认部署",
submitterParameter: 'APPROVER',
submitter: approvers
)
echo "部署已由 ${env.APPROVER} 批准"
}
}
}
}
// 阶段 8: 部署到预生产/生产环境
stage('Deploy to Staging/Production') {
when {
expression { params.DEPLOY_ENV in ['staging', 'production'] }
}
steps {
container('helm') {
timestamps {
echo "部署到 ${params.DEPLOY_ENV} 环境"
script {
def config = loadEnvironmentConfig()
// 使用 values 文件进行部署
def valuesFiles = ""
if (fileExists("${HELM_CHART_PATH}/values-${params.DEPLOY_ENV}.yaml")) {
valuesFiles = "-f ${HELM_CHART_PATH}/values-${params.DEPLOY_ENV}.yaml"
}
sh """
helm upgrade --install ${APP_NAME} ${HELM_CHART_PATH} \
--namespace ${config.namespace} \
--set image.repository=${REGISTRY}/${PROJECT}/${APP_NAME} \
--set image.tag=${IMAGE_TAG} \
--set replicaCount=${config.replicas} \
--set resources.requests.cpu=${config.cpuRequest} \
--set resources.requests.memory=${config.memoryRequest} \
--set resources.limits.cpu=${config.cpuLimit} \
--set resources.limits.memory=${config.memoryLimit} \
--set ingress.hosts[0].host=${config.ingressHost} \
${valuesFiles} \
--wait \
--timeout ${config.helmTimeout} \
--atomic \
--create-namespace
"""
}
}
}
}
}
// 阶段 9: 健康检查
stage('Health Check') {
steps {
container('kubectl') {
timestamps {
echo "执行健康检查"
script {
def config = loadEnvironmentConfig()
// 检查 Pod 状态
sh """
kubectl get pods -n ${config.namespace} -l app=${APP_NAME}
kubectl wait --for=condition=ready pod -l app=${APP_NAME} \
-n ${config.namespace} \
--timeout=600s
"""
// 检查服务端点
sh """
kubectl get svc -n ${config.namespace} ${APP_NAME}
kubectl get ingress -n ${config.namespace} ${APP_NAME}
"""
// 简单的应用健康检查
sh """
curl -f http://${APP_NAME}.${config.namespace}.svc.cluster.local/health || \
curl -f https://${config.ingressHost}/health || \
echo "Health check endpoint might be different"
"""
}
}
}
}
}
}
post {
always {
timestamps {
echo "Pipeline 执行完成 - ${currentBuild.result}"
// 收集部署信息
container('kubectl') {
script {
if (params.DEPLOY_ENV) {
def config = loadEnvironmentConfig()
sh """
echo "=== 部署状态 ==="
kubectl get deployments -n ${config.namespace} ${APP_NAME} -o wide
echo ""
echo "=== Pod 状态 ==="
kubectl get pods -n ${config.namespace} -l app=${APP_NAME}
echo ""
echo "=== 服务状态 ==="
kubectl get svc -n ${config.namespace} -l app=${APP_NAME}
"""
}
}
}
// 清理工作空间
cleanWs()
}
}
success {
script {
def config = loadEnvironmentConfig()
emailext (
subject: "SUCCESS: 部署 ${APP_NAME} 到 ${params.DEPLOY_ENV}",
body: """
<h2>部署成功!</h2>
<p><strong>应用:</strong> ${APP_NAME}</p>
<p><strong>环境:</strong> ${params.DEPLOY_ENV}</p>
<p><strong>镜像:</strong> ${FULL_IMAGE_NAME}</p>
<p><strong>构建:</strong> ${env.BUILD_URL}</p>
<p><strong>访问地址:</strong> https://${config.ingressHost}</p>
""",
to: 'team@mycompany.com'
)
}
}
failure {
script {
emailext (
subject: "FAILED: 部署 ${APP_NAME} 到 ${params.DEPLOY_ENV}",
body: """
<h2>部署失败!</h2>
<p><strong>应用:</strong> ${APP_NAME}</p>
<p><strong>环境:</strong> ${params.DEPLOY_ENV}</p>
<p><strong>构建:</strong> ${env.BUILD_URL}</p>
<p>请检查 Jenkins 日志获取详细信息。</p>
""",
to: 'devops@mycompany.com'
)
}
}
unstable {
echo "Pipeline 标记为不稳定"
}
}
options {
timeout(time: 30, unit: 'MINUTES')
retry(2)
timestamps()
}
}
5、配套的 Helm Chart 结构
kubernetes/charts/myapp/
├── Chart.yaml
├── values.yaml
├── values-dev.yaml
├── values-staging.yaml
├── values-production.yaml
├── charts/
├── templates/
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── configmap.yaml
│ ├── secrets.yaml
│ └── hpa.yaml
└── .helmignore
6、values.yaml
# 默认配置
replicaCount: 1
image:
repository: ""
tag: "latest"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
targetPort: 8080
ingress:
enabled: true
className: "nginx"
hosts:
- host: "chart-example.local"
paths:
- path: /
pathType: Prefix
tls: []
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 10
targetCPUUtilizationPercentage: 80
env:
- name: ENVIRONMENT
value: "development"
7、蓝绿发布
stage('Blue-Green Deployment') {
steps {
script {
def currentDeployment = sh(
script: "helm get values ${APP_NAME} -n ${KUBE_NAMESPACE} -o json | jq -r '.color' 2>/dev/null || echo 'blue'",
returnStdout: true
).trim()
def newColor = currentDeployment == 'blue' ? 'green' : 'blue'
// 部署新版本
container('helm') {
sh """
helm upgrade --install ${APP_NAME}-${newColor} ${HELM_CHART_PATH} \
--namespace ${KUBE_NAMESPACE} \
--set image.tag=${env.BUILD_NUMBER} \
--set image.repository=${REGISTRY}/${PROJECT}/${APP_NAME} \
--set color=${newColor} \
--set service.color=${newColor} \
--wait --timeout 300s
"""
}
// 切换流量
sh """
kubectl patch svc ${APP_NAME} -n ${KUBE_NAMESPACE} \
-p '{"spec":{"selector":{"color":"${newColor}"}}}'
"""
// 清理旧版本
sh "helm uninstall ${APP_NAME}-${currentDeployment} -n ${KUBE_NAMESPACE} || true"
}
}
}