Jenkins CI/CD
约 1991 字大约 7 分钟
JenkinsCI/CD持续集成持续部署
2026-04-16
Jenkins 简介
什么是 CI/CD
CI/CD 是现代软件开发中的核心实践:
- 持续集成(Continuous Integration):开发者频繁地将代码合并到主干,每次合并都通过自动化构建和测试来验证,帮助尽早发现集成错误
- 持续交付(Continuous Delivery):代码通过自动化测试后,可以随时部署到生产环境或 staging 环境
- 持续部署(Continuous Deployment):在持续交付的基础上,每一次通过测试的代码变更都自动部署到生产环境
Jenkins 的定位
Jenkins 是一个开源的自动化服务器,起源于 Hudson 项目。它提供了:
- 丰富的插件生态,支持构建、测试、部署等各种任务
- 分布式构建能力,可以协调多台机器进行并行构建
- 灵活的流水线配置,支持声明式和脚本式两种方式
- 强大的凭证管理和安全控制
Jenkins 安装与配置
Docker 安装
# 拉取镜像
docker pull jenkins/jenkins:lts
# 运行容器
docker run -d \
--name jenkins \
-p 8080:8080 \
-p 50000:50000 \
-v jenkins_home:/var/jenkins_home \
jenkins/jenkins:lts
# 查看初始密码
docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword访问 http://localhost:8080,按照向导完成插件安装和初始配置。
插件管理
进入 Manage Jenkins -> Manage Plugins:
| 分类 | 推荐插件 | 用途 |
|---|---|---|
| 源代码管理 | Git | Git 仓库支持 |
| 构建工具 | NodeJS | 前端项目构建 |
| 容器化 | Docker | 镜像构建和推送 |
| 通知 | Slack Notification | 构建结果通知 |
| 流水线 | Pipeline | 流水线编写 |
| 凭证 | Credentials Binding | 敏感信息管理 |
核心概念
Job / Project
Job 是 Jenkins 中的基本工作单元,用于定义一个完整的构建任务。类型包括:
- 自由风格(Freestyle):最灵活的 Job 类型,适合简单任务
- 流水线(Pipeline):支持复杂的构建流程,代码化配置
- 多配置项目(Matrix):在多个环境/配置下并行构建
- 文件夹(Folder):组织和管理多个 Job
Pipeline(流水线)
Pipeline 是 Jenkins 2.x 引入的核心功能,将构建过程代码化:
代码提交 → 单元测试 → 集成测试 → 构建 → 部署优点:
- 版本控制:配置文件存储在代码仓库中
- 可追溯:每个步骤都有详细日志
- 可复用:相同的 Pipeline 可用于多个项目
Stage / Step
- Stage(阶段):一个完整的构建步骤,如"构建"、"测试"、"部署",在视图中显示为独立的色块
- Step(步骤):具体的原子操作,如
git checkout、npm install
Agent / Executor
- Agent:执行构建任务的机器,可以是 Master 或 Slave
- Executor:Agent 上的执行槽位,决定可以并行运行多少个构建任务
Jenkinsfile 编写
声明式 Pipeline vs 脚本式 Pipeline
声明式 Pipeline
pipeline {
agent any
environment {
NODE_VERSION = '20'
APP_NAME = 'my-app'
}
options {
timeout(time: 30, unit: 'MINUTES')
disableConcurrentBuilds()
}
stages {
stage('Checkout') {
steps {
echo '正在检出代码...'
checkout scm
}
}
stage('Install Dependencies') {
steps {
echo '正在安装依赖...'
nodejs(nodeJSInstallationName: 'NodeJS') {
sh 'npm ci'
}
}
}
stage('Build') {
steps {
echo '正在构建项目...'
sh 'npm run build'
}
}
stage('Test') {
steps {
echo '正在运行测试...'
sh 'npm run test:unit'
}
}
stage('Deploy') {
when {
branch 'main'
}
steps {
echo '正在部署...'
sh 'npm run deploy'
}
}
}
post {
always {
echo '清理工作目录'
cleanWs()
}
success {
echo '构建成功!'
slackSend(message: '构建成功: ${env.JOB_NAME} #${env.BUILD_NUMBER}')
}
failure {
echo '构建失败!'
slackSend(message: '构建失败: ${env.JOB_NAME} #${env.BUILD_NUMBER}', color: 'danger')
}
}
}脚本式 Pipeline
node('build-agent') {
try {
stage('Checkout') {
checkout scm
}
stage('Build') {
sh 'npm install'
sh 'npm run build'
}
stage('Test') {
sh 'npm test'
}
} catch (Exception e) {
currentBuild.result = 'FAILURE'
throw e
} finally {
cleanWs()
}
}环境变量
// 全局环境变量(在 environment 块中定义)
environment {
DEPLOY_ENV = 'production'
}
// Stage 级环境变量
stage('Deploy') {
environment {
API_KEY = credentials('deploy-api-key')
}
steps {
sh 'deploy.sh --api-key=${API_KEY}'
}
}
// 常用内置变量
echo "Job名称: ${env.JOB_NAME}"
echo "构建编号: ${env.BUILD_NUMBER}"
echo "构建URL: ${env.BUILD_URL}"
echo "工作目录: ${env.WORKSPACE}"
echo "Git分支: ${env.GIT_BRANCH}"
echo "Git提交: ${env.GIT_COMMIT}"凭证管理
在 Manage Jenkins -> Manage Credentials 中添加凭证:
pipeline {
agent any
stages {
stage('Deploy') {
environment {
// 使用凭证 ID 引用
SSH_KEY = credentials('deploy-ssh-key')
NPM_TOKEN = credentials('npm-registry-token')
DOCKER_PASSWORD = credentials('docker-hub-password')
}
steps {
// SSH 密钥示例
sh 'eval $(ssh-agent -s) && ssh-add $SSH_KEY'
// Docker 登录示例
sh 'echo $DOCKER_PASSWORD | docker login -u username --password-stdin'
// NPM 令牌示例
sh 'npm config set //registry.npmjs.org/:_authToken=$NPM_TOKEN'
}
}
}
}支持的凭证类型:
- 用户名/密码
- SSH 密钥
- Secret 文件
- Secret 文本
- 证书
条件执行
stage('Deploy') {
when {
branch 'main' // 仅 main 分支
expression { env.BUILD_ENV == 'prod' } // 表达式条件
not { branch 'feature/*' } // 排除条件
anyOf { // 任一条件满足
branch 'main'
branch 'release/*'
}
}
steps {
sh './deploy.sh'
}
}并行执行
pipeline {
agent any
stages {
stage('Parallel Tests') {
parallel {
stage('Unit Tests') {
steps {
sh 'npm run test:unit'
}
}
stage('Integration Tests') {
steps {
sh 'npm run test:integration'
}
}
stage('E2E Tests') {
steps {
sh 'npm run test:e2e'
}
}
}
}
stage('Build and Push') {
matrix {
axes {
axis {
name 'PLATFORM'
values 'amd64', 'arm64'
}
}
agent { label "docker-${PLATFORM}" }
steps {
sh "docker build --platform ${PLATFORM} -t myapp:${PLATFORM} ."
sh "docker push myapp:${PLATFORM}"
}
}
}
}
}前端项目 CI/CD 流程示例
pipeline {
agent { label 'nodejs-agent' }
environment {
NODE_VERSION = '20'
REGISTRY = 'registry.example.com'
IMAGE_NAME = 'my-frontend-app'
}
options {
timeout(time: 45, unit: 'MINUTES')
buildDiscarder(logRotator(numToKeepStr: '10'))
}
stages {
stage('代码检出') {
steps {
echo '从 Git 仓库检出代码...'
checkout scm
}
}
stage('安装依赖') {
steps {
echo '安装项目依赖...'
nodejs(nodeJSInstallationName: 'NodeJS') {
sh 'npm ci --prefer-offline'
}
}
}
stage('代码检查') {
steps {
echo '运行 ESLint 和类型检查...'
nodejs(nodeJSInstallationName: 'NodeJS') {
sh 'npm run lint'
sh 'npm run type-check'
}
}
}
stage('单元测试') {
steps {
echo '执行单元测试...'
nodejs(nodeJSInstallationName: 'NodeJS') {
sh 'npm run test:unit -- --coverage'
}
}
post {
always {
junit 'coverage/junit.xml'
cobertura coberturaReportFile: 'coverage/cobertura-coverage.xml'
}
}
}
stage('构建') {
steps {
echo '构建生产版本...'
nodejs(nodeJSInstallationName: 'NodeJS') {
sh 'npm run build'
}
}
}
stage('E2E 测试') {
steps {
echo '运行端到端测试...'
nodejs(nodeJSInstallationName: 'NodeJS') {
sh 'npm run test:e2e'
}
}
}
stage('构建 Docker 镜像') {
when {
branch 'main'
}
steps {
echo '构建并推送 Docker 镜像...'
script {
def imageTag = "${env.BUILD_NUMBER}"
docker.withRegistry("https://${REGISTRY}", 'docker-registry-cred') {
def image = docker.build("${REGISTRY}/${IMAGE_NAME}:${imageTag}", '-f Dockerfile.production .')
image.push('latest')
image.push(imageTag)
}
}
}
}
stage('部署到测试环境') {
when {
branch 'main'
}
steps {
echo '部署到测试环境...'
sh 'kubectl apply -f k8s/ --namespace=test'
sh 'kubectl rollout status deployment/my-frontend-app -n test'
}
}
stage('部署到生产环境') {
when {
branch 'main'
expression { currentBuild.result == null || currentBuild.result == 'SUCCESS' }
}
input {
message '确认部署到生产环境?'
ok '确认部署'
}
steps {
echo '部署到生产环境...'
sh 'kubectl apply -f k8s/ --namespace=production'
sh 'kubectl rollout status deployment/my-frontend-app -n production'
}
}
}
post {
always {
echo '构建完成,记录日志...'
archiveArtifacts artifacts: 'dist/**/*', fingerprint: true
cleanWs()
}
success {
slackSend(
channel: '#devops',
message: "构建成功: ${env.JOB_NAME} #${env.BUILD_NUMBER}\n构建URL: ${env.BUILD_URL}",
color: 'good'
)
}
failure {
slackSend(
channel: '#devops',
message: "构建失败: ${env.JOB_NAME} #${env.BUILD_NUMBER}\n构建URL: ${env.BUILD_URL}",
color: 'danger'
)
}
}
}常用插件
| 插件名称 | 用途 | 关键功能 |
|---|---|---|
| Git | 源代码管理 | 分支检出、Git Hook、参数化构建 |
| NodeJS | 前端构建 | 自动安装 Node.js 版本管理 |
| Docker | 容器化 | 构建、推送、运行容器 |
| Slack Notification | 通知 | 构建结果实时通知 |
| Credentials Binding | 凭证管理 | 安全存储和使用敏感信息 |
| Pipeline | 流水线 | 声明式/脚本式流水线支持 |
| Generic Webhook Trigger | 外部触发 | 通过 HTTP 请求触发构建 |
| JUnit | 测试报告 | 测试结果可视化 |
| Cobertura | 代码覆盖率 | 覆盖率报告展示 |
| Allure | 测试报告 | 丰富的测试报告展示 |
| Config File Provider | 配置文件 | 管理构建所需的配置文件 |
分布式构建(Master/Slave)
架构说明
┌─────────────┐
│ Master │
│ (调度器) │
└──────┬──────┘
│
┌────────────┼────────────┐
│ │ │
┌─────┴─────┐ ┌─────┴─────┐ ┌─────┴─────┐
│ Slave 1 │ │ Slave 2 │ │ Slave 3 │
│ (构建节点) │ │ (构建节点) │ │ (构建节点) │
└───────────┘ └───────────┘ └───────────┘添加 Slave 节点
方式一:通过 Web 界面
Manage Jenkins->Manage Nodes->New Node- 配置节点名称和类型
- 设置远程根目录(如
/home/jenkins) - 配置启动方式(SSH、Java Web Start 等)
- 设置标签(用于 Job 指定节点)
方式二:通过 Docker
# 在 Slave 机器上运行
docker run -d \
--name jenkins-agent \
-e JENKINS_URL=http://jenkins-master:8080 \
-e JENKINS_AGENT_WORKDIR=/home/jenkins/agent \
-v /var/run/docker.sock:/var/run/docker.sock \
jenkins/inbound-agent:latest在 Pipeline 中使用
// 指定特定节点
stage('Build') {
agent { label 'docker-agent' }
steps {
sh 'docker build -t myapp .'
}
}
// 使用节点组
stage('Test') {
agent { label 'linux && npm' }
steps {
nodejs(nodeJSInstallationName: 'NodeJS') {
sh 'npm test'
}
}
}
// 动态分配节点
stage('Deploy') {
steps {
script {
def nodeLabel = env.BRANCH_NAME == 'main' ? 'prod-agent' : 'dev-agent'
sh "echo Deploying on ${nodeLabel}"
}
}
}最佳实践
- 为不同任务设置专用标签:
nodejs、docker、linux、windows - 使用标签组进行节点选择:
agent { label 'docker && ubuntu' } - 合理配置 Executor 数量,避免资源争抢
- 定期监控节点状态和构建负载
