一、前言 CI/CD(持续集成/持续交付)是现代软件开发的核心理念。根据 2026 年 DevOps 状态报告,采用 CI/CD 的团队:
部署频率提升 46 倍
变更前置时间缩短 440 倍
服务恢复时间缩短 2564 倍
变更失败率降低 7 倍
CI/CD 的核心价值:
✅ 自动化构建 :代码提交自动触发构建
✅ 自动化测试 :单元测试、集成测试自动执行
✅ 自动化部署 :通过流水线自动部署到各环境
✅ 快速反馈 :问题尽早发现和修复
✅ 降低风险 :小步快跑,降低单次变更风险
本文将详细介绍 CI/CD 流水线设计,涵盖主流平台配置、最佳实践和实战案例。
二、CI/CD 核心概念 2.1 流水线阶段 1 2 3 4 5 6 7 8 9 10 11 12 graph LR A[代码提交] --> B[构建 Build] B --> C[测试 Test] C --> D[扫描 Scan] D --> E[部署 Deploy] E --> F[监控 Monitor] B --> B1[编译<br/>打包<br/>镜像构建] C --> C1[单元测试<br/>集成测试<br/>E2E 测试] D --> D1[代码质量<br/>安全扫描<br/>漏洞检测] E --> E1[开发环境<br/>测试环境<br/>生产环境] F --> F1[性能监控<br/>日志收集<br/>告警通知]
2.2 环境策略 1 2 3 4 5 6 7 8 9 graph TB A[开发环境 Dev] -->|自动部署 | B[测试环境 Test] B -->|手动确认 | C[预发环境 Staging] C -->|手动确认 | D[生产环境 Prod] A --> A1[每次提交] B --> B1[测试通过] C --> C1[验收通过] D --> D1[正式发布]
环境说明 :
环境
用途
部署方式
数据
Dev
开发测试
自动
模拟数据
Test
集成测试
自动
测试数据
Staging
预发验证
手动
生产脱敏
Prod
生产环境
手动
真实数据
三、GitHub Actions 流水线 3.1 基础配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 name: CI Pipeline on: push: branches: [main , develop ] pull_request: branches: [main ] jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '18' cache: 'npm' - name: Install dependencies run: npm ci - name: Run linter run: npm run lint - name: Run type check run: npm run type-check test: runs-on: ubuntu-latest needs: lint steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '18' cache: 'npm' - name: Install dependencies run: npm ci - name: Run tests run: npm test -- --coverage - name: Upload coverage uses: codecov/codecov-action@v3 with: file: ./coverage/lcov.info build: runs-on: ubuntu-latest needs: test steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '18' cache: 'npm' - name: Install dependencies run: npm ci - name: Build run: npm run build - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: dist path: dist/
3.2 Docker 构建与推送 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 name: Docker Build and Push on: push: branches: [main ] tags: ['v*.*.*' ] env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: build: runs-on: ubuntu-latest permissions: contents: read packages: write steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | type=ref,event=branch type=ref,event=pr type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=sha - name: Build and push uses: docker/build-push-action@v5 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max - name: Run security scan uses: aquasecurity/trivy-action@master with: image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} format: 'sarif' output: 'trivy-results.sarif' - name: Upload Trivy scan results uses: github/codeql-action/upload-sarif@v2 with: sarif_file: 'trivy-results.sarif'
3.3 多环境部署 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 name: Deploy to Environments on: push: branches: [main , develop ] workflow_dispatch: inputs: environment: description: 'Deployment environment' required: true default: 'dev' type: choice options: - dev - test - staging - prod jobs: deploy: runs-on: ubuntu-latest environment: name: ${{ github.event.inputs.environment || 'dev' }} url: ${{ vars.DEPLOY_URL }} steps: - uses: actions/checkout@v4 - name: Deploy to ${{ github.event.inputs.environment || 'dev' }} run: | echo "Deploying to ${{ github.event.inputs.environment || 'dev' }}" # 添加实际部署命令 # kubectl set image deployment/app app=myapp:${{ github.sha }} - name: Health check run: | curl -f ${{ vars.DEPLOY_URL }}/health || exit 1 - name: Notify Slack if: always() uses: slackapi/slack-github-action@v1 with: payload: | { "text": "Deployed to ${{ github.event.inputs.environment || 'dev' }}: ${{ job.status }}" } env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
四、GitLab CI 流水线 4.1 完整配置示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 stages: - validate - test - build - scan - deploy - monitor variables: DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA DOCKER_TLS_CERTDIR: "/certs" validate: stage: validate image: node:18-alpine script: - npm ci - npm run lint - npm run type-check rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH unit-test: stage: test image: node:18-alpine script: - npm ci - npm test -- --coverage artifacts: reports: coverage_report: coverage_format: cobertura path: coverage/cobertura-coverage.xml paths: - coverage/ expire_in: 1 week coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/' rules: - if: $CI_COMMIT_BRANCH integration-test: stage: test image: docker:24 services: - docker:24-dind - postgres:15-alpine - redis:7-alpine script: - docker build -t $DOCKER_IMAGE . - docker run --rm \ -e DATABASE_URL=postgresql://postgres:postgres@postgres/postgres \ -e REDIS_URL=redis://redis:6379 \ $DOCKER_IMAGE npm run test:integration rules: - if: $CI_COMMIT_BRANCH == "develop" - if: $CI_COMMIT_BRANCH == "main" build: stage: build image: docker:24 services: - docker:24-dind before_script: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY script: - docker build -t $DOCKER_IMAGE . - docker push $DOCKER_IMAGE artifacts: reports: dotenv: build.env rules: - if: $CI_COMMIT_BRANCH == "develop" - if: $CI_COMMIT_BRANCH == "main" security-scan: stage: scan image: aquasec/trivy:latest script: - trivy image --exit-code 0 --format sarif --output trivy-results.sarif $DOCKER_IMAGE artifacts: reports: container_scanning: trivy-results.sarif paths: - trivy-results.sarif rules: - if: $CI_COMMIT_BRANCH == "main" allow_failure: true deploy-dev: stage: deploy image: bitnami/kubectl:latest script: - kubectl set image deployment/app app=$DOCKER_IMAGE environment: name: development url: https://dev.example.com rules: - if: $CI_COMMIT_BRANCH == "develop" deploy-test: stage: deploy image: bitnami/kubectl:latest script: - kubectl set image deployment/app app=$DOCKER_IMAGE environment: name: test url: https://test.example.com rules: - if: $CI_COMMIT_BRANCH == "develop" when: delayed start_in: 5 minutes deploy-staging: stage: deploy image: bitnami/kubectl:latest script: - kubectl set image deployment/app app=$DOCKER_IMAGE environment: name: staging url: https://staging.example.com rules: - if: $CI_COMMIT_BRANCH == "main" when: manual needs: - build - security-scan deploy-prod: stage: deploy image: bitnami/kubectl:latest script: - kubectl set image deployment/app app=$DOCKER_IMAGE environment: name: production url: https://www.example.com rules: - if: $CI_COMMIT_BRANCH == "main" when: manual needs: - deploy-staging when: manual post-deploy-check: stage: monitor image: curlimages/curl:latest script: - curl -f $DEPLOY_URL/health - curl -f $DEPLOY_URL/api/status environment: name: production rules: - if: $CI_COMMIT_BRANCH == "main" when: on_success
4.2 模板复用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 .node_template: &node_template image: node:18-alpine cache: key: ${CI_COMMIT_REF_SLUG} paths: - node_modules/ before_script: - npm ci .deploy_template: &deploy_template image: bitnami/kubectl:latest before_script: - kubectl config use-context $KUBE_CONTEXT after_script: - kubectl rollout status deployment/app test: <<: *node_template script: - npm test deploy: <<: *deploy_template script: - kubectl set image deployment/app app=$DOCKER_IMAGE
五、Jenkins Pipeline 5.1 声明式 Pipeline 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 pipeline { agent any options { buildDiscarder(logRotator(numToKeepStr: '10' )) timeout(time: 1 , unit: 'HOURS' ) timestamps() } environment { DOCKER_IMAGE = "myapp:${env.BUILD_NUMBER}" REGISTRY = "harbor.example.com" } stages { stage('Checkout' ) { steps { checkout scm } } stage('Lint' ) { steps { sh 'npm ci' sh 'npm run lint' sh 'npm run type-check' } } stage('Test' ) { steps { sh 'npm test -- --coverage' } post { always { junit 'reports/*.xml' publishCoverage adapters: [coberturaAdapter('coverage/cobertura-coverage.xml' )] } } } stage('Build Docker Image' ) { steps { script { docker.build("${env.DOCKER_IMAGE}" ) } } } stage('Security Scan' ) { steps { sh ''' docker run --rm \ -v /var/run/docker.sock:/var/run/docker.sock \ aquasec/trivy image ${DOCKER_IMAGE} ''' } } stage('Push Image' ) { steps { script { docker.withRegistry("https://${env.REGISTRY}" , 'harbor-credentials' ) { docker.image("${env.DOCKER_IMAGE}" ).push() } } } } stage('Deploy to Dev' ) { when { branch 'develop' } steps { sh ''' kubectl set image deployment/app \ app=${REGISTRY}/${DOCKER_IMAGE} ''' } } stage('Deploy to Prod' ) { when { branch 'main' } input { message "Deploy to production?" ok "Deploy" } steps { sh ''' kubectl set image deployment/app \ app=${REGISTRY}/${DOCKER_IMAGE} kubectl rollout status deployment/app ''' } } } post { always { cleanWs() } success { slackSend(color: 'good' , message: "Build ${env.BUILD_NUMBER} succeeded" ) } failure { slackSend(color: 'danger' , message: "Build ${env.BUILD_NUMBER} failed" ) } } }
5.2 脚本式 Pipeline 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 node('docker' ) { stage('Checkout' ) { checkout scm } stage('Build' ) { docker.image('node:18-alpine' ).inside { sh 'npm ci' sh 'npm run build' } } stage('Test' ) { docker.image('node:18-alpine' ).inside { sh 'npm test' } } stage('Deploy' ) { if (env.BRANCH_NAME == 'main' ) { input message: 'Deploy to production?' } sh "kubectl set image deployment/app app=myapp:${env.BUILD_NUMBER}" } }
六、实战案例 案例 1:Node.js 微服务 CI/CD 项目结构 :
1 2 3 4 5 6 7 8 9 10 11 12 myapp/ ├── .github/ │ └── workflows/ │ ├── ci.yml │ ├── docker.yml │ └── deploy.yml ├── src/ ├── tests/ ├── Dockerfile ├── docker-compose.yml ├── package.json └── README.md
完整流水线 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 name: Full Pipeline on: push: branches: [main , develop ] pull_request: branches: [main ] jobs: validate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '18' cache: 'npm' - name: Install dependencies run: npm ci - name: Lint run: npm run lint - name: Type check run: npm run type-check - name: Security audit run: npm audit --audit-level=moderate test: runs-on: ubuntu-latest needs: validate services: postgres: image: postgres:15 env: POSTGRES_PASSWORD: postgres options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 ports: - 5432 :5432 redis: image: redis:7-alpine options: >- --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 ports: - 6379 :6379 steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '18' cache: 'npm' - name: Install dependencies run: npm ci - name: Unit tests run: npm test -- --coverage - name: Integration tests run: npm run test:integration env: DATABASE_URL: postgresql://postgres:postgres@localhost:5432/postgres REDIS_URL: redis://localhost:6379 - name: Upload coverage uses: codecov/codecov-action@v3 build: runs-on: ubuntu-latest needs: test if: github.event_name == 'push' steps: - uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Registry uses: docker/login-action@v3 with: registry: harbor.example.com username: ${{ secrets.HARBOR_USERNAME }} password: ${{ secrets.HARBOR_PASSWORD }} - name: Build and push uses: docker/build-push-action@v5 with: context: . push: true tags: | harbor.example.com/myapp:${{ github.sha }} harbor.example.com/myapp:${{ github.ref_name }} cache-from: type=gha cache-to: type=gha,mode=max deploy-dev: runs-on: ubuntu-latest needs: build if: github.ref == 'refs/heads/develop' environment: development steps: - uses: actions/checkout@v4 - name: Setup kubectl uses: azure/setup-kubectl@v3 - name: Configure kubectl run: | echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > kubeconfig export KUBECONFIG=kubeconfig - name: Deploy run: | kubectl set image deployment/myapp \ myapp=harbor.example.com/myapp:${{ github.sha }} - name: Health check run: | kubectl rollout status deployment/myapp curl -f https://dev.example.com/health deploy-prod: runs-on: ubuntu-latest needs: build if: github.ref == 'refs/heads/main' environment: production steps: - uses: actions/checkout@v4 - name: Setup kubectl uses: azure/setup-kubectl@v3 - name: Configure kubectl run: | echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > kubeconfig export KUBECONFIG=kubeconfig - name: Deploy run: | kubectl set image deployment/myapp \ myapp=harbor.example.com/myapp:${{ github.sha }} kubectl rollout status deployment/myapp - name: Smoke tests run: | curl -f https://www.example.com/health curl -f https://www.example.com/api/status
案例 2:Python 数据平台 CI/CD 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 stages: - test - build - deploy variables: PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA cache: paths: - .cache/pip/ - venv/ test: stage: test image: python:3.11-slim before_script: - pip install --upgrade pip - pip install -r requirements-dev.txt script: - pytest --cov=src --cov-report=xml - flake8 src/ - mypy src/ artifacts: reports: coverage_report: coverage_format: cobertura path: coverage.xml rules: - if: $CI_COMMIT_BRANCH build: stage: build image: docker:24 services: - docker:24-dind before_script: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY script: - docker build -t $DOCKER_IMAGE . - docker push $DOCKER_IMAGE rules: - if: $CI_COMMIT_BRANCH == "main" deploy: stage: deploy image: bitnami/kubectl:latest script: - kubectl set image deployment/data-platform api=$DOCKER_IMAGE - kubectl rollout status deployment/data-platform environment: name: production rules: - if: $CI_COMMIT_BRANCH == "main" when: manual
七、最佳实践 7.1 流水线设计原则 1 2 3 4 5 6 7 8 9 10 11 12 graph TB A[流水线设计原则] --> B[快速反馈] A --> C[失败优先] A --> D[幂等性] A --> E[可追溯] A --> F[安全性] B --> B1[快速失败<br/>并行执行<br/>缓存优化] C --> C1[先 lint 后测试<br/>先单元后集成] D --> D1[可重复执行<br/>状态无关] E --> E1[完整日志<br/>版本标记] F --> F1[密钥管理<br/>权限控制]
7.2 优化技巧 1. 并行执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 jobs: test: strategy: matrix: node-version: [16 , 18 , 20 ] os: [ubuntu-latest , macos-latest ] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm test
2. 缓存优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - name: Cache node modules uses: actions/cache@v3 with: path: node_modules key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node- - name: Build and push uses: docker/build-push-action@v5 with: cache-from: type=gha cache-to: type=gha,mode=max
3. 条件执行
1 2 3 4 5 6 7 8 9 10 11 12 deploy-prod: if: github.ref == 'refs/heads/main' steps: ... on: push: branches: [main ] paths-ignore: - '**.md' - 'docs/**'
7.3 安全实践 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v2 with: role-to-assume: arn:aws:iam::123456789:role/github-role aws-region: us-east-1 permissions: contents: read packages: write - name: Secret scanning uses: trufflesecurity/trufflehog@main with: path: ./ base: ${{ github.event.repository.default_branch }}
八、总结 CI/CD 检查清单
关键指标
指标
目标
测量方式
构建时间
< 10 分钟
流水线耗时
测试覆盖率
> 80%
代码覆盖率报告
部署频率
每天多次
部署次数统计
部署成功率
> 95%
成功/总部署
恢复时间
< 1 小时
故障恢复耗时
最后更新 : 2026-03-12
标签 : #CI/CD #流水线 #DevOps #自动化 #持续交付
分类 : DevOps/CI/CD
参考资料 :