CI/CD详解
介绍
CI/CD(持续集成/持续部署)是现代软件开发中的关键实践,它通过自动化构建、测试和部署流程,提高开发效率、减少错误并加快产品交付速度。对于前端开发来说,CI/CD尤为重要,因为前端技术更新快、依赖管理复杂,自动化流程可以有效减少人为错误。本章将详细介绍前端CI/CD的核心概念、主流工具、实现方法和最佳解决方案。
核心概念与原理
CI/CD的目标
- 自动化:减少手动操作,降低人为错误
- 快速反馈:及时发现和解决问题
- 持续集成:频繁合并代码,避免集成地狱
- 持续部署:自动化部署到生产环境
- 可靠性:确保每次部署都是可靠的
- 可追溯性:跟踪代码变更和部署历史
CI/CD流水线的阶段
- 代码提交:开发者提交代码到版本控制系统
- 构建:自动化构建项目,生成可部署的 artifacts
- 测试:运行自动化测试,确保代码质量
- 部署:将构建产物部署到目标环境
- 监控:监控应用性能和错误
持续集成 vs 持续部署 vs 持续交付
- 持续集成(CI):频繁合并代码到主分支,每次合并都运行构建和测试
- 持续交付(CD):确保代码可以随时部署到生产环境,但部署决策由人工控制
- 持续部署(CD):代码通过测试后自动部署到生产环境
| 特性 | 持续集成 (CI) | 持续交付 (CD) | 持续部署 (CD) |
|---|---|---|---|
| 自动化程度 | 中等 | 高 | 非常高 |
| 部署频率 | 不涉及部署 | 按需部署 | 每次变更自动部署 |
| 人工干预 | 代码审查 | 部署决策 | 最小化或无 |
| 风险级别 | 低 | 中 | 高(需要完善的自动化测试) |
| 适用场景 | 所有项目 | 大多数项目 | 成熟的项目和团队 |
主流CI/CD工具对比
| 工具 | 类型 | 特点 | 适用场景 | 配置复杂度 | 生态系统 | 社区活跃度 | 价格模型 |
|---|---|---|---|---|---|---|---|
| GitHub Actions | 云服务 | 与GitHub深度集成、易于使用、免费额度、市场丰富 | 开源项目、GitHub用户、前端项目 | 低 | 丰富 | 非常活跃 | 免费起步,按使用量计费 |
| GitLab CI/CD | 云服务/自托管 | 与GitLab集成、功能全面、可定制性高、内置容器注册表 | GitLab用户、DevOps团队 | 中 | 丰富 | 活跃 | 免费起步,高级功能付费 |
| Jenkins | 自托管 | 高度可定制、插件丰富、成熟稳定、支持分布式构建 | 企业级应用、复杂流水线、遗留系统集成 | 高 | 非常丰富 | 非常活跃 | 开源免费,维护成本高 |
| CircleCI | 云服务 | 性能优秀、配置简洁、支持多种语言、并行构建 | 各类项目、敏捷团队 | 中 | 丰富 | 活跃 | 免费起步,按使用量计费 |
| Travis CI | 云服务 | 历史悠久、配置简单、对开源友好、多环境测试 | 开源项目、简单项目 | 低 | 中等 | 中等 | 开源项目免费,商业项目付费 |
| Azure DevOps | 云服务 | 微软生态、功能全面、企业级支持、项目管理集成 | 微软技术栈用户、企业级项目 | 中 | 丰富 | 活跃 | 免费起步,按用户和功能付费 |
| Drone CI | 云服务/自托管 | 基于Docker、轻量级、配置简单、可扩展 | 容器化项目、小型团队 | 中 | 中等 | 中等 | 开源免费,企业版付费 |
| TeamCity | 自托管/云服务 | JetBrains产品、智能构建功能、历史构建分析 | 企业级项目、.NET项目 | 高 | 丰富 | 中等 | 专业版付费,小型项目免费 |
| Bamboo | 自托管 | Atlassian产品、与Jira集成、部署项目集成 | Atlassian生态用户 | 高 | 中等 | 中等 | 付费 |
| Bitbucket Pipelines | 云服务 | 与Bitbucket集成、简单易用、基于Docker | Bitbucket用户、小型团队 | 低 | 中等 | 中等 | 免费起步,按使用量计费 |
详细使用指南
GitHub Actions
GitHub Actions是GitHub提供的CI/CD服务,与GitHub仓库深度集成,易于配置和使用。
基本配置
在仓库中创建.github/workflows目录,并添加YAML配置文件。
工作流示例 (node.js.yml)
# 工作流名称:Node.js CI
name: Node.js CI
# 触发条件配置
on:
# 当代码推送到main或develop分支时触发
push:
branches: [ main, develop ]
# 当有PR提交到main分支时触发
pull_request:
branches: [ main ]
# 定时触发 - 每天凌晨2点运行(用于定期检查)
schedule:
- cron: '0 2 * * *'
# 环境变量定义
env:
NODE_VERSION: '18.x' # 指定Node.js版本
CACHE_NAME: cache-node-modules # 依赖缓存名称
# 作业定义
jobs:
# 作业1: 代码质量检查
lint:
runs-on: ubuntu-latest # 使用最新版Ubuntu运行器
steps:
# 步骤1: 检出代码
- uses: actions/checkout@v4
# 步骤2: 设置Node.js环境
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }} # 使用环境变量中定义的Node.js版本
cache: 'npm' # 启用npm依赖缓存以加快构建速度
# 步骤3: 安装依赖(使用ci命令确保依赖版本一致性)
- run: npm ci
# 步骤4: 运行代码 lint 检查
- run: npm run lint
# 步骤5: 运行类型检查
- run: npm run type-check
# 作业2: 构建和测试
build-and-test:
runs-on: ubuntu-latest # 使用最新版Ubuntu运行器
needs: lint
strategy:
matrix:
node-version: [16.x, 18.x, 20.x]
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build project
run: npm run build
- name: Run tests
run: |
npm test -- --coverage
npm run test:e2e
- name: Upload coverage reports
uses: codecov/codecov-action@v3
with:
file: ./coverage/lcov.info
flags: unittests
name: codecov-umbrella
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: build-files-${{ matrix.node-version }}
path: |
dist/
build/
retention-days: 30
部署示例 (deploy.yml)
# 工作流名称:部署到生产环境
name: Deploy to Production
# 触发条件配置
on:
# 当代码推送到main分支时触发
push:
branches: [ main ]
# 当创建版本标签时触发 (如v1.0.0)
tags:
- 'v*.*.*'
# 允许手动触发工作流
workflow_dispatch:
inputs:
environment:
description: '部署环境选择'
required: true
default: 'staging'
type: choice
options:
- staging
- production
env:
NODE_VERSION: '18.x' # 指定Node.js版本
jobs:
# 作业1: 构建阶段
build:
runs-on: ubuntu-latest # 使用最新版Ubuntu运行器
outputs:
version: ${{ steps.version.outputs.version }} # 输出版本号供其他作业使用
steps:
# 步骤1: 检出代码 (fetch-depth: 0 表示获取完整历史)
- uses: actions/checkout@v4
with:
fetch-depth: 0
# 步骤2: 设置Node.js环境
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }} # 使用环境变量中定义的Node.js版本
cache: 'npm' # 启用npm依赖缓存
# 步骤3: 安装依赖
- name: Install dependencies
run: npm ci # 使用ci命令确保依赖版本一致性
# 步骤4: 运行测试
- name: Run tests
run: npm test # 运行项目测试
# 步骤5: 构建项目
- name: Build project
run: |
npm run build # 构建项目
npm run build:analyze # 运行构建分析
# 步骤6: 获取版本号
- name: Get version
id: version
run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
# 步骤7: 上传构建产物
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build-artifacts # 构建产物名称
path: |
dist/
build/ # 要上传的文件路径
retention-days: 30 # 构建产物保留30天
# 作业2: 部署到Staging环境
deploy-staging:
runs-on: ubuntu-latest # 使用最新版Ubuntu运行器
needs: build # 依赖于build作业完成
# 触发条件: 主分支推送或手动选择staging环境
if: github.ref == 'refs/heads/main' || github.event.inputs.environment == 'staging'
environment:
name: staging # 环境名称
url: https://staging.example.com # 环境URL
steps:
# 步骤1: 下载构建产物
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: build-artifacts # 下载之前上传的构建产物
# 步骤2: 部署到Staging环境
- name: Deploy to Staging
run: |
echo "Deploying version ${{ needs.build.outputs.version }} to staging..."
# 部署到S3存储桶
aws s3 sync ./dist s3://${{ secrets.STAGING_S3_BUCKET }} --delete
# 清除CloudFront缓存
aws cloudfront create-invalidation --distribution-id ${{ secrets.STAGING_CLOUDFRONT_ID }} --paths "/*"
env:
# 从GitHub Secrets中获取AWS凭证
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1
# 作业3: 部署到Production环境
deploy-production:
runs-on: ubuntu-latest # 使用最新版Ubuntu运行器
needs: [build, deploy-staging] # 依赖于build和deploy-staging作业完成
# 触发条件: 版本标签推送或手动选择production环境
if: startsWith(github.ref, 'refs/tags/v') || github.event.inputs.environment == 'production'
environment:
name: production # 环境名称
url: https://example.com # 环境URL
steps:
# 步骤1: 下载构建产物
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: build-artifacts # 下载之前上传的构建产物
# 步骤2: 部署到Production环境
- name: Deploy to Production
run: |
echo "Deploying version ${{ needs.build.outputs.version }} to production..."
# 部署到S3存储桶
aws s3 sync ./dist s3://${{ secrets.PRODUCTION_S3_BUCKET }} --delete
# 清除CloudFront缓存
aws cloudfront create-invalidation --distribution-id ${{ secrets.PRODUCTION_CLOUDFRONT_ID }} --paths "/*"
env:
# 从GitHub Secrets中获取AWS凭证
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: us-east-1
# 步骤3: 创建GitHub Release
- name: Create GitHub Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # 从GitHub Secrets中获取token
with:
tag_name: ${{ github.ref }} # 标签名称
release_name: Release ${{ needs.build.outputs.version }} # 发布名称
body: |
## 本次发布变更
- 从GitHub Actions自动发布
- 版本: ${{ needs.build.outputs.version }}
draft: false # 不是草稿
prerelease: false # 不是预发布
# 步骤4: 通知Slack
- name: Notify Slack
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }} # 作业状态
channel: '#deployments' # Slack频道
webhook_url: ${{ secrets.SLACK_WEBHOOK }} # Slack Webhook URL
if: always() # 无论作业成功失败都通知
关键概念
核心概念详解:
-
Workflow(工作流):整个CI/CD流程的定义,存储在
.github/workflows/目录中的YAML文件- 可以有多个工作流文件
- 每个工作流可以独立触发和运行
- 支持复杂的触发条件和调度
-
Job(作业):工作流中的一个任务,由多个步骤组成
- 默认情况下并行运行
- 可以通过
needs关键字设置依赖关系 - 每个作业在独立的虚拟环境中运行
-
Step(步骤):作业中的一个操作步骤
- 可以是预定义的Action或自定义脚本
- 按顺序执行
- 可以共享环境变量和文件系统
-
Action(动作):可重用的步骤,由GitHub或社区提供
- 官方Actions:
actions/checkout、actions/setup-node等 - 社区Actions:数千个可用的Actions
- 自定义Actions:可以创建自己的Actions
- 官方Actions:
-
Runner(运行器):执行工作流的服务器
- GitHub托管的Runner:免费额度,多种操作系统
- 自托管Runner:更多控制权,可访问内部资源
-
Secret(密钥):存储敏感信息的环境变量
- 仓库级:只能在当前仓库使用
- 环境级:只能在特定环境使用
- 组织级:可在组织内多个仓库使用
-
Environment(环境):部署目标环境的抽象
- 可以设置保护规则
- 支持审批流程
- 可以配置环境特定的密钥和变量
-
Artifact(构件):作业生成的文件
- 可以在作业间传递
- 支持下载和长期存储
- 自动压缩和解压缩
GitLab CI/CD
GitLab CI/CD是GitLab内置的CI/CD服务,与GitLab仓库深度集成,功能全面。
基本配置
在仓库根目录创建.gitlab-ci.yml文件。
配置示例 (.gitlab-ci.yml)
# 定义流水线阶段
stages:
- build
- test
- security
- deploy
- notify
# 全局变量
variables:
NODE_VERSION: '18'
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "/certs"
FF_USE_FASTZIP: "true"
ARTIFACT_COMPRESSION_LEVEL: "fast"
CACHE_COMPRESSION_LEVEL: "fast"
# 全局缓存配置
default:
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
- .npm/
policy: pull-push
# 构建阶段
build:
stage: build
image: node:$NODE_VERSION-alpine
before_script:
- npm config set cache .npm
- npm ci --cache .npm --prefer-offline
script:
- npm run build
- npm run build:analyze
artifacts:
name: "build-$CI_COMMIT_SHORT_SHA"
paths:
- dist/
- build/
- coverage/
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
expire_in: 1 week
coverage: '/Lines\s*:\s*(\d+\.?\d*)%/'
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# 测试阶段
unit-test:
stage: test
image: node:$NODE_VERSION-alpine
script:
- npm ci --cache .npm --prefer-offline
- npm run test:unit -- --coverage
artifacts:
reports:
junit: junit.xml
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
paths:
- coverage/
coverage: '/Lines\s*:\s*(\d+\.?\d*)%/'
e2e-test:
stage: test
image: cypress/browsers:node18.12.0-chrome107
services:
- name: selenium/standalone-chrome:latest
alias: chrome
script:
- npm ci --cache .npm --prefer-offline
- npm run build
- npm run start:test &
- npm run test:e2e
artifacts:
when: always
paths:
- cypress/screenshots/
- cypress/videos/
expire_in: 1 week
allow_failure: true
# 安全扫描阶段
dependency-scanning:
stage: security
image: node:$NODE_VERSION-alpine
script:
- npm audit --audit-level high
- npm run security:check
allow_failure: true
sast:
stage: security
image: returntocorp/semgrep
script:
- semgrep --config=auto --json --output=sast-report.json .
artifacts:
reports:
sast: sast-report.json
allow_failure: true
# 部署到Staging
deploy-staging:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache curl
- apk add --no-cache aws-cli
script:
- echo "Deploying to staging environment..."
- aws s3 sync dist/ s3://$STAGING_S3_BUCKET --delete
- aws cloudfront create-invalidation --distribution-id $STAGING_CLOUDFRONT_ID --paths "/*"
environment:
name: staging
url: https://staging.example.com
deployment_tier: staging
needs:
- job: build
artifacts: true
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
# 部署到Production
deploy-production:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache curl
- apk add --no-cache aws-cli
script:
- echo "Deploying to production environment..."
- aws s3 sync dist/ s3://$PRODUCTION_S3_BUCKET --delete
- aws cloudfront create-invalidation --distribution-id $PRODUCTION_CLOUDFRONT_ID --paths "/*"
environment:
name: production
url: https://example.com
deployment_tier: production
needs:
- job: build
artifacts: true
- job: deploy-staging
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
when: manual
# 通知阶段
notify-success:
stage: notify
image: alpine:latest
script:
- echo "Pipeline completed successfully!"
- 'curl -X POST -H "Content-type: application/json" --data "{\"text\":\"✅ Pipeline for $CI_PROJECT_NAME completed successfully!\"}" $SLACK_WEBHOOK_URL'
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: on_success
notify-failure:
stage: notify
image: alpine:latest
script:
- echo "Pipeline failed!"
- 'curl -X POST -H "Content-type: application/json" --data "{\"text\":\"❌ Pipeline for $CI_PROJECT_NAME failed!\"}" $SLACK_WEBHOOK_URL'
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: on_failure
关键概念
核心概念详解:
-
Pipeline(流水线):一次完整的CI/CD执行过程
- 由一个或多个Stage组成
- 可以通过代码推送、合并请求、定时任务等方式触发
- 支持复杂的条件执行和依赖关系
-
Stage(阶段):流水线的逻辑分组,按顺序执行
- 常见阶段:build、test、deploy
- 同一阶段内的Job并行执行
- 前一阶段失败会阻止后续阶段执行
-
Job(作业):Stage中的具体任务
- 在独立的环境中执行
- 可以指定运行环境(Docker镜像、Runner等)
- 支持条件执行、依赖关系、重试机制
-
Script(脚本):Job中执行的具体命令
before_script:主脚本执行前的准备工作script:主要的执行脚本after_script:清理工作,总是执行
-
Artifact(构件):Job生成的文件或目录
- 可以在后续Job中使用
- 支持过期时间设置
- 可以生成测试报告、覆盖率报告等
-
Cache(缓存):在Job之间共享的文件或目录
- 提高构建速度,减少重复下载
- 支持不同的缓存策略(pull、push、pull-push)
- 可以设置缓存键值,实现精确控制
-
Runner(运行器):执行Job的服务器
- Shared Runner:GitLab.com提供的共享运行器
- Group Runner:组级别的运行器
- Project Runner:项目专用的运行器
- 自托管Runner:自己部署和管理的运行器
-
Environment(环境):部署目标的抽象
- 可以设置环境URL
- 支持部署历史跟踪
- 可以配置环境保护规则
-
Variables(变量):配置信息的存储
- 预定义变量:GitLab自动提供的变量
- 自定义变量:用户定义的变量
- 受保护变量:只在受保护分支/标签中可用
- 环境变量:特定环境中的变量
-
Rules(规则):控制Job执行的条件
- 替代传统的
only/except语法 - 支持复杂的条件逻辑
- 可以动态设置Job的行为
- 替代传统的
Jenkins
Jenkins是一个开源的CI/CD工具,高度可定制,插件丰富,适用于复杂的CI/CD场景。
安装与配置
-
下载安装Jenkins
# macOS (使用Homebrew)
brew install jenkins-lts
# Ubuntu/Debian
wget -q -O - https://pkg.jenkins.io/debian/jenkins.io.key | sudo apt-key add -
sudo sh -c 'echo deb http://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list'
sudo apt update
sudo apt install jenkins
# Docker
docker run -p 8080:8080 -p 50000:50000 -v jenkins_home:/var/jenkins_home jenkins/jenkins:lts -
初始化配置
- 访问 http://localhost:8080
- 使用初始管理员密码解锁Jenkins
- 安装推荐插件或自定义插件选择
- 创建管理员用户
-
必要插件安装
- NodeJS Plugin
- Git Plugin
- Pipeline Plugin
- Blue Ocean Plugin
- Docker Pipeline Plugin
- Slack Notification Plugin
Pipeline示例 (Jenkinsfile)
pipeline {
agent {
label 'nodejs'
}
environment {
NODE_VERSION = '18'
DOCKER_REGISTRY = 'your-registry.com'
APP_NAME = 'frontend-app'
SLACK_CHANNEL = '#ci-cd'
}
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
timeout(time: 30, unit: 'MINUTES')
skipStagesAfterUnstable()
parallelsAlwaysFailFast()
}
triggers {
pollSCM('H/5 * * * *')
cron('H 2 * * *')
}
parameters {
choice(
name: 'DEPLOY_ENV',
choices: ['staging', 'production'],
description: 'Select deployment environment'
)
booleanParam(
name: 'SKIP_TESTS',
defaultValue: false,
description: 'Skip test execution'
)
}
stages {
stage('Checkout') {
steps {
checkout scm
script {
env.GIT_COMMIT_SHORT = sh(
script: 'git rev-parse --short HEAD',
returnStdout: true
).trim()
env.BUILD_VERSION = "${env.BUILD_NUMBER}-${env.GIT_COMMIT_SHORT}"
}
}
}
stage('Install Dependencies') {
steps {
nodejs(nodeJSInstallationName: "Node.js ${NODE_VERSION}") {
sh '''
npm config set cache .npm
npm ci --prefer-offline
'''
}
}
}
stage('Code Quality') {
parallel {
stage('Lint') {
steps {
nodejs(nodeJSInstallationName: "Node.js ${NODE_VERSION}") {
sh 'npm run lint'
}
}
post {
always {
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'reports',
reportFiles: 'eslint.html',
reportName: 'ESLint Report'
])
}
}
}
stage('Type Check') {
steps {
nodejs(nodeJSInstallationName: "Node.js ${NODE_VERSION}") {
sh 'npm run type-check'
}
}
}
stage('Security Scan') {
steps {
nodejs(nodeJSInstallationName: "Node.js ${NODE_VERSION}") {
sh 'npm audit --audit-level high'
sh 'npx snyk test --severity-threshold=high'
}
}
}
}
}
stage('Build') {
steps {
nodejs(nodeJSInstallationName: "Node.js ${NODE_VERSION}") {
sh '''
npm run build
npm run build:analyze
'''
}
stash includes: 'dist/**', name: 'build-artifacts'
archiveArtifacts artifacts: 'dist/**', fingerprint: true
}
}
stage('Test') {
when {
not { params.SKIP_TESTS }
}
parallel {
stage('Unit Tests') {
steps {
nodejs(nodeJSInstallationName: "Node.js ${NODE_VERSION}") {
sh 'npm run test:unit -- --coverage --reporter=xunit --outputFile=test-results.xml'
}
}
post {
always {
junit 'test-results.xml'
publishHTML([
allowMissing: false,
alwaysLinkToLastBuild: true,
keepAll: true,
reportDir: 'coverage/lcov-report',
reportFiles: 'index.html',
reportName: 'Coverage Report'
])
}
}
}
stage('E2E Tests') {
agent {
docker {
image 'cypress/browsers:node18.12.0-chrome107'
args '-v /dev/shm:/dev/shm'
}
}
steps {
unstash 'build-artifacts'
sh '''
npm ci
npm run start:test &
npx wait-on http://localhost:3000
npm run test:e2e
'''
}
post {
always {
archiveArtifacts artifacts: 'cypress/screenshots/**,cypress/videos/**', allowEmptyArchive: true
}
}
}
}
}
stage('Docker Build') {
steps {
script {
def image = docker.build("${DOCKER_REGISTRY}/${APP_NAME}:${BUILD_VERSION}")
docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-registry-credentials') {
image.push()
image.push('latest')
}
}
}
}
stage('Deploy') {
when {
anyOf {
branch 'main'
expression { params.DEPLOY_ENV != null }
}
}
steps {
script {
def deployEnv = params.DEPLOY_ENV ?: 'staging'
unstash 'build-artifacts'
if (deployEnv == 'production') {
input message: 'Deploy to production?', ok: 'Deploy',
submitterParameter: 'DEPLOYER'
}
sh '''
echo "Deploying to ${deployEnv} environment..."
aws s3 sync dist/ s3://my-app-${deployEnv} --delete
aws cloudfront create-invalidation --distribution-id ${deployEnv.toUpperCase()}_CLOUDFRONT_ID --paths "/*"
'''
// 健康检查
sh '''
sleep 30
curl -f https://${deployEnv}.example.com/health || exit 1
'''
}
}
}
}
post {
always {
cleanWs()
}
success {
slackSend(
channel: env.SLACK_CHANNEL,
color: 'good',
message: "✅ Pipeline succeeded for ${env.JOB_NAME} - ${env.BUILD_NUMBER} (<${env.BUILD_URL}|Open>)"
)
}
failure {
slackSend(
channel: env.SLACK_CHANNEL,
color: 'danger',
message: "❌ Pipeline failed for ${env.JOB_NAME} - ${env.BUILD_NUMBER} (<${env.BUILD_URL}|Open>)"
)
}
unstable {
slackSend(
channel: env.SLACK_CHANNEL,
color: 'warning',
message: "⚠️ Pipeline unstable for ${env.JOB_NAME} - ${env.BUILD_NUMBER} (<${env.BUILD_URL}|Open>)"
)
}
}
}
关键概念
核心概念详解:
-
Pipeline(流水线)
- 声明式Pipeline: 使用预定义的结构,更易读和维护
- 脚本式Pipeline: 基于Groovy脚本,更灵活但复杂
- 共享库: 可重用的Pipeline代码,支持跨项目共享
- 多分支Pipeline: 自动为每个分支创建Pipeline
-
Agent(代理节点)
- Master节点: 负责调度、管理和提供Web界面
- Slave节点: 执行具体的构建任务
- Docker Agent: 在Docker容器中执行构建
- Kubernetes Agent: 动态创建Pod执行构建
-
Stage(阶段)
- 将Pipeline分解为逻辑阶段
- 支持并行执行多个阶段
- 可以设置阶段执行条件
- 提供可视化的执行进度
-
Step(步骤)
- 阶段内的具体执行单元
- 包括shell命令、插件调用等
- 支持条件执行和错误处理
- 可以跨阶段传递数据
-
Workspace(工作空间)
- 每个构建的独立工作目录
- 包含源代码和构建产物
- 支持工作空间清理和归档
- 可以在节点间传递工作空间
-
Artifact(构建产物)
- 构建过程中生成的文件
- 支持长期存储和版本管理
- 可以在不同Job间传递
- 支持指纹识别和依赖追踪
-
Plugin(插件)
- 扩展Jenkins核心功能
- 支持第三方工具集成
- 提供丰富的生态系统
- 支持自定义插件开发
高级特性
多环境部署
根据分支或标签自动部署到不同环境。
# GitHub Actions多环境部署示例
name: Multi-environment Deploy
on:
push:
branches:
- main
- develop
tags:
- 'v*.*.*'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js 16.x
uses: actions/setup-node@v3
with:
node-version: '16'
- run: npm ci
- run: npm run build
- name: Deploy to Production
if: startsWith(github.ref, 'refs/tags/v')
run: |
echo "Deploying to production..."
# 生产环境部署命令
- name: Deploy to Staging
if: github.ref == 'refs/heads/main'
run: |
echo "Deploying to staging..."
# 预发布环境部署命令
- name: Deploy to Development
if: github.ref == 'refs/heads/develop'
run: |
echo "Deploying to development..."
# 开发环境部署命令
金丝雀发布
逐步将新版本部署给部分用户,降低风险。
# GitLab CI/CD金丝雀发布示例
stages:
- build
- test
- deploy_canary
- monitor
- deploy_production
# ... 省略build和test阶段
deploy_canary:
stage: deploy_canary
script:
- echo "Deploying canary version..."
- # 部署5%的流量到新版本
only:
- main
monitor:
stage: monitor
script:
- echo "Monitoring canary deployment..."
- # 监控错误率、性能等指标
- # 如果指标正常,继续部署;否则回滚
needs:
- job: deploy_canary
# ... 省略deploy_production阶段
自动回滚
当监控发现问题时,自动回滚到上一个稳定版本。
# GitHub Actions自动回滚示例
name: Deploy with Rollback
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js 16.x
uses: actions/setup-node@v3
with:
node-version: '16'
- run: npm ci
- run: npm run build
- name: Deploy to Production
id: deploy
run: |
echo "Deploying to production..."
# 部署命令
echo "::set-output name=deploy_id::$(uuidgen)"
- name: Monitor Application
id: monitor
run: |
echo "Monitoring application..."
# 监控命令
# 如果发现问题,设置status为failure
echo "::set-output name=status::success"
- name: Rollback if Needed
if: steps.monitor.outputs.status == 'failure'
run: |
echo "Rolling back deployment..."
# 回滚命令
最佳实践
- 保持流水线简洁:避免过于复杂的流水线,提高可维护性
- 自动化一切:尽可能自动化构建、测试、部署流程
- 快速反馈:确保流水线快速执行,及时反馈问题
- 版本控制:将CI/CD配置文件纳入版本控制
- 基础设施即代码:使用代码定义CI/CD流程和环境配置
- 安全第一:保护敏感信息,避免在配置文件中硬编码密钥
- 测试驱动:在部署前运行充分的测试
- 渐进式部署:使用金丝雀发布或蓝绿部署降低风险
- 监控与告警:部署后监控应用性能和错误
- 持续改进:定期回顾和优化CI/CD流程
实际案例分析
案例1:大型科技公司的CI/CD实践
某大型科技公司采用Jenkins构建复杂的CI/CD流水线,支持多环境部署、自动回滚和金丝雀发布。通过CI/CD,该公司的部署频率从每周一次提高到每天多次,上线时间从几小时缩短到几分钟。
案例2:开源项目的GitHub Actions实践
一个流行的开源React组件库使用GitHub Actions进行CI/CD,每次提交都运行测试和构建,合并到主分支后自动部署到npm和GitHub Pages。通过自动化流程,维护者可以专注于代码开发,而不必担心部署问题。
总结
CI/CD是现代前端开发的关键实践,通过自动化构建、测试和部署流程,可以提高开发效率、减少错误并加快产品交付速度。GitHub Actions、GitLab CI/CD和Jenkins是主流的CI/CD工具,各有其适用场景。多环境部署、金丝雀发布和自动回滚是高级CI/CD特性,可以进一步提高部署的可靠性和安全性。遵循CI/CD最佳实践,持续改进流程,是现代前端团队成功的关键。