跳转至

第05章 CI/CD流水线

CI/CD流水线图

📚 章节概述

本章将深入讲解CI/CD(持续集成/持续部署)流水线的构建,包括Jenkins、GitLab CI、GitHub Actions等主流工具的使用。通过本章学习,你将能够搭建完整的CI/CD流水线,实现自动化构建、测试和部署。

🎯 学习目标

完成本章后,你将能够:

  1. 理解CI/CD的核心概念和最佳实践
  2. 掌握Jenkins流水线的配置和使用
  3. 熟练使用GitLab CI/CD
  4. 掌握GitHub Actions的工作流程
  5. 了解蓝绿部署和金丝雀发布
  6. 能够搭建生产级的CI/CD流水线

5.1 CI/CD概述

5.1.1 什么是CI/CD

CI/CD是持续集成(Continuous Integration)、持续交付(Continuous Delivery)和持续部署(Continuous Deployment)的统称。

核心概念

  1. 持续集成(CI)
  2. 频繁地将代码集成到主干
  3. 自动化构建和测试
  4. 快速发现和修复问题

  5. 持续交付(CD)

  6. 确保代码可以随时部署
  7. 自动化部署到测试环境
  8. 人工审批后部署到生产环境

  9. 持续部署(CD)

  10. 自动化部署到生产环境
  11. 无需人工干预
  12. 快速反馈用户

5.1.2 CI/CD的价值

  1. 提高开发效率
  2. 自动化重复性工作
  3. 减少人为错误
  4. 加快交付速度

  5. 提高代码质量

  6. 自动化测试
  7. 代码审查
  8. 静态代码分析

  9. 降低风险

  10. 小步快跑
  11. 快速回滚
  12. 减少部署失败

  13. 增强团队协作

  14. 统一的工作流程
  15. 透明的构建状态
  16. 快速反馈

5.2 Jenkins流水线

5.2.1 Jenkins概述

Jenkins是最流行的开源CI/CD工具,支持丰富的插件生态系统。

Jenkins的核心特性

  1. 分布式构建
  2. 支持多Agent
  3. 负载均衡
  4. 资源隔离

  5. 丰富的插件

  6. 1000+插件
  7. 支持各种工具集成
  8. 高度可扩展

  9. Pipeline as Code

  10. Jenkinsfile
  11. 版本控制
  12. 可重复的构建

5.2.2 安装Jenkins

Docker安装

Bash
# 拉取Jenkins镜像
docker pull jenkins/jenkins:lts

# 运行Jenkins
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

Kubernetes安装

YAML
# Jenkins Deployment 定义
apiVersion: apps/v1
kind: Deployment
metadata:
  name: jenkins
  namespace: devops              # 部署到 devops 命名空间
spec:
  replicas: 1                    # 单实例部署
  selector:
    matchLabels:
      app: jenkins
  template:
    metadata:
      labels:
        app: jenkins
    spec:
      containers:
      - name: jenkins
        image: jenkins/jenkins:lts
        ports:
        - containerPort: 8080    # Web UI 端口
        - containerPort: 50000   # Agent 通信端口
        volumeMounts:
        - name: jenkins-home
          mountPath: /var/jenkins_home  # 挂载持久化存储目录
      volumes:
      - name: jenkins-home
        persistentVolumeClaim:
          claimName: jenkins-pvc  # 引用 PVC 实现数据持久化
---  # YAML文档分隔符
# Jenkins Service 定义(对外暴露服务)
apiVersion: v1
kind: Service
metadata:
  name: jenkins
  namespace: devops
spec:
  type: LoadBalancer             # 通过负载均衡器暴露服务
  ports:
  - port: 8080
    targetPort: 8080
    name: http                   # Web UI 端口
  - port: 50000
    targetPort: 50000
    name: agent                  # Agent 连接端口
  selector:
    app: jenkins
---
# 持久化存储声明(PVC)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: jenkins-pvc
  namespace: devops
spec:
  accessModes:
    - ReadWriteOnce              # 单节点读写模式
  resources:
    requests:
      storage: 20Gi              # 申请 20GB 存储空间

5.2.3 Jenkinsfile

基本Pipeline

Groovy
pipeline {
    agent any    // 在任意可用的 Agent 上运行

    stages {
        // 阶段1:代码检出
        stage('Checkout') {
            steps {
                checkout scm
            }
        }

        // 阶段2:编译构建
        stage('Build') {
            steps {
                sh 'mvn clean package'
            }
        }

        // 阶段3:运行测试
        stage('Test') {
            steps {
                sh 'mvn test'
            }
        }

        // 阶段4:部署到 Kubernetes
        stage('Deploy') {
            steps {
                sh 'kubectl apply -f k8s/'
            }
        }
    }

    // 构建后处理
    post {
        success {
            echo 'Pipeline succeeded!'
        }
        failure {
            echo 'Pipeline failed!'
        }
    }
}

声明式Pipeline

Groovy
pipeline {
    agent any    // 在任意可用的 Agent 上运行

    // 构建参数定义,支持手动选择分支和部署环境
    parameters {
        string(name: 'BRANCH', defaultValue: 'main', description: 'Git branch to build')
        choice(name: 'ENVIRONMENT', choices: ['dev', 'staging', 'production'], description: 'Deployment environment')
    }

    // 环境变量定义
    environment {
        DOCKER_REGISTRY = 'myregistry.com'
        IMAGE_NAME = 'myapp'
        IMAGE_TAG = "${BUILD_NUMBER}"    // 使用构建号作为镜像标签
    }

    stages {
        // 检出指定分支代码
        stage('Checkout') {
            steps {
                git branch: params.BRANCH, url: 'https://github.com/myorg/myapp.git'
            }
        }

        // 编译构建(跳过测试加速构建)
        stage('Build') {
            steps {
                sh 'mvn clean package -DskipTests'
            }
        }

        // 单元测试并收集报告
        stage('Unit Test') {
            steps {
                sh 'mvn test'
                junit 'target/surefire-reports/*.xml'    // 发布 JUnit 测试报告
            }
        }

        // 代码质量扫描(SonarQube)
        stage('Code Quality') {
            steps {
                sh 'mvn sonar:sonar'
            }
        }

        // 构建 Docker 镜像并打标签
        stage('Build Docker Image') {
            steps {
                sh "docker build -t ${DOCKER_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} ."
                sh "docker tag ${DOCKER_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} ${DOCKER_REGISTRY}/${IMAGE_NAME}:latest"
            }
        }

        // 推送镜像到仓库
        stage('Push Docker Image') {
            steps {
                // 使用凭据安全登录镜像仓库
                withCredentials([usernamePassword(credentialsId: 'docker-registry', usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASS')]) {
                    // 使用 --password-stdin 避免密码出现在进程列表和CI日志中
                    sh "echo \${DOCKER_PASS} | docker login ${DOCKER_REGISTRY} -u ${DOCKER_USER} --password-stdin"
                    sh "docker push ${DOCKER_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}"
                    sh "docker push ${DOCKER_REGISTRY}/${IMAGE_NAME}:latest"
                }
            }
        }

        // 部署到目标 Kubernetes 环境
        stage('Deploy to Kubernetes') {
            steps {
                sh "sed -i 's|IMAGE_TAG|${IMAGE_TAG}|g' k8s/deployment.yaml"    // 替换镜像标签
                sh "kubectl apply -f k8s/ -n ${params.ENVIRONMENT}"              // 应用 K8s 资源配置
                sh "kubectl rollout status deployment/myapp -n ${params.ENVIRONMENT}"  // 等待部署完成
            }
        }
    }

    // 构建后处理
    post {
        always {
            cleanWs()    // 每次构建后清理工作空间
        }
        // 成功时发送邮件通知
        success {
            echo 'Pipeline succeeded!'
            emailext(
                subject: "Jenkins Pipeline Success: ${JOB_NAME} - ${BUILD_NUMBER}",
                body: "The pipeline ${JOB_NAME} build ${BUILD_NUMBER} completed successfully.",
                to: 'team@example.com'
            )
        }
        // 失败时发送告警邮件
        failure {
            echo 'Pipeline failed!'
            emailext(
                subject: "Jenkins Pipeline Failure: ${JOB_NAME} - ${BUILD_NUMBER}",
                body: "The pipeline ${JOB_NAME} build ${BUILD_NUMBER} failed.",
                to: 'team@example.com'
            )
        }
    }
}

5.3 GitLab CI/CD

5.3.1 GitLab CI概述

GitLab CI/CD是GitLab内置的CI/CD工具,与Git仓库深度集成。

核心特性

  1. 内置集成
  2. 无需额外安装
  3. 与Git仓库无缝集成
  4. 可视化Pipeline

  5. YAML配置

  6. .gitlab-ci.yml
  7. 简单易用
  8. 版本控制

  9. Docker支持

  10. 原生Docker支持
  11. 自定义Runner
  12. 缓存和Artifacts

5.3.2 .gitlab-ci.yml

基本配置

YAML
# 定义流水线阶段顺序
stages:
  - build
  - test
  - deploy

# 全局变量定义
variables:
  DOCKER_REGISTRY: "myregistry.com"
  IMAGE_NAME: "myapp"
  IMAGE_TAG: "$CI_COMMIT_SHORT_SHA"      # 使用 Git 短 SHA 作为镜像标签

# 构建任务:构建 Docker 镜像
build:
  stage: build
  image: docker:latest
  services:
    - docker:dind                        # 启用 Docker-in-Docker 服务
  script:
    - docker build -t $DOCKER_REGISTRY/$IMAGE_NAME:$IMAGE_TAG .
    - docker tag $DOCKER_REGISTRY/$IMAGE_NAME:$IMAGE_TAG $DOCKER_REGISTRY/$IMAGE_NAME:latest
  only:                                  # 仅在指定分支上触发
    - main
    - develop

# 测试任务:运行测试并收集覆盖率
test:
  stage: test
  image: python:3.11
  script:
    - pip install -r requirements.txt
    - pytest tests/
  coverage: '/TOTAL.*\s+(\d+%)$/'       # 覆盖率提取正则表达式
  artifacts:                             # 测试产物配置
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage.xml

# 部署到开发环境
deploy_dev:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - kubectl config use-context dev-cluster          # 切换到开发集群
    - sed -i "s|IMAGE_TAG|$IMAGE_TAG|g" k8s/deployment.yaml
    - kubectl apply -f k8s/ -n dev
    - kubectl rollout status deployment/myapp -n dev  # 等待部署完成
  only:
    - develop                                         # 仅 develop 分支触发
  environment:
    name: dev
    url: https://dev.example.com

# 部署到预发布环境
deploy_staging:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - kubectl config use-context staging-cluster
    - sed -i "s|IMAGE_TAG|$IMAGE_TAG|g" k8s/deployment.yaml
    - kubectl apply -f k8s/ -n staging
    - kubectl rollout status deployment/myapp -n staging
  only:
    - main
  when: manual                                        # 需手动触发部署
  environment:
    name: staging
    url: https://staging.example.com

# 部署到生产环境
deploy_production:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - kubectl config use-context prod-cluster
    - sed -i "s|IMAGE_TAG|$IMAGE_TAG|g" k8s/deployment.yaml
    - kubectl apply -f k8s/ -n production
    - kubectl rollout status deployment/myapp -n production
  only:
    - tags                                            # 仅在打 tag 时触发
  when: manual                                        # 需手动确认后部署
  environment:
    name: production
    url: https://example.com

高级配置

YAML
stages:
  - build
  - test
  - security
  - deploy

variables:
  DOCKER_REGISTRY: "myregistry.com"
  IMAGE_NAME: "myapp"
  IMAGE_TAG: "$CI_COMMIT_SHORT_SHA"    # 使用 Git 短 SHA 作为镜像标签
  FF_USE_FASTZIP: "true"               # 启用快速压缩,提升产物传输速度
  ARTIFACT_COMPRESSION_LEVEL: "fast"   # 使用快速压缩级别

# 缓存配置(按分支缓存依赖目录,加速构建)
cache:
  key: ${CI_COMMIT_REF_SLUG}           # 按分支名称隔离缓存
  paths:
    - node_modules/
    - .m2/
    - vendor/

# 构建阶段
build:
  stage: build
  image: docker:latest
  services:
    - docker:dind
  before_script:
    - echo "$DOCKER_REGISTRY_PASSWORD" | docker login -u "$DOCKER_REGISTRY_USER" --password-stdin $DOCKER_REGISTRY
  script:
    - docker build -t $DOCKER_REGISTRY/$IMAGE_NAME:$IMAGE_TAG .
    - docker tag $DOCKER_REGISTRY/$IMAGE_NAME:$IMAGE_TAG $DOCKER_REGISTRY/$IMAGE_NAME:latest
    - docker push $DOCKER_REGISTRY/$IMAGE_NAME:$IMAGE_TAG
    - docker push $DOCKER_REGISTRY/$IMAGE_NAME:latest
  only:
    - main
    - develop
    - merge_requests

# 单元测试
unit_test:
  stage: test
  image: python:3.11
  services:
    - postgres:13
    - redis:latest
  variables:
    POSTGRES_DB: testdb
    POSTGRES_USER: testuser
    POSTGRES_PASSWORD: testpass
    DATABASE_URL: "postgresql://testuser:testpass@postgres:5432/testdb"
    REDIS_URL: "redis://redis:6379/0"
  before_script:
    - pip install -r requirements.txt
  script:
    - pytest tests/unit/ -v --cov=app --cov-report=xml --cov-report=html
  coverage: '/TOTAL.*\s+(\d+%)$/'
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage.xml
    paths:
      - htmlcov/
    expire_in: 30 days
  only:
    - branches

# 集成测试
integration_test:
  stage: test
  image: python:3.11
  services:
    - postgres:13
    - redis:latest
  variables:
    POSTGRES_DB: testdb
    POSTGRES_USER: testuser
    POSTGRES_PASSWORD: testpass
    DATABASE_URL: "postgresql://testuser:testpass@postgres:5432/testdb"
    REDIS_URL: "redis://redis:6379/0"
  before_script:
    - pip install -r requirements.txt
  script:
    - pytest tests/integration/ -v
  only:
    - main
    - merge_requests

# 安全扫描(Trivy 镜像漏洞检测)
security_scan:
  stage: security
  image: aquasec/trivy:latest
  script:
    - trivy image --exit-code 1 --severity HIGH,CRITICAL $DOCKER_REGISTRY/$IMAGE_NAME:$IMAGE_TAG  # 扫描高危和严重漏洞
  allow_failure: true                  # 允许失败,不阻塞流水线
  only:
    - main
    - merge_requests

# 部署到开发环境
deploy_dev:
  stage: deploy
  image: bitnami/kubectl:latest
  environment:
    name: dev
    url: https://dev.example.com
    on_stop: stop_dev                  # 关联停止操作(用于清理环境)
  script:
    - kubectl config use-context dev-cluster
    - helm upgrade --install myapp ./helm-chart --namespace dev --set image.tag=$IMAGE_TAG --set environment=dev  # 使用 Helm 部署
    - kubectl rollout status deployment/myapp -n dev  # 等待部署完成
  only:
    - develop

# 停止开发环境(清理资源)
stop_dev:
  stage: deploy
  image: bitnami/kubectl:latest
  environment:
    name: dev
    action: stop                       # 标记为停止环境操作
  script:
    - kubectl config use-context dev-cluster
    - helm uninstall myapp --namespace dev  # 卸载 Helm Release
  when: manual                         # 需手动触发
  only:
    - develop

# 部署到预发布环境
deploy_staging:
  stage: deploy
  image: bitnami/kubectl:latest
  environment:
    name: staging
    url: https://staging.example.com
  script:
    - kubectl config use-context staging-cluster
    - helm upgrade --install myapp ./helm-chart --namespace staging --set image.tag=$IMAGE_TAG --set environment=staging
    - kubectl rollout status deployment/myapp -n staging
  when: manual                         # 需手动确认后部署
  only:
    - main                             # 仅 main 分支可触发

# 部署到生产环境(最终发布)
deploy_production:
  stage: deploy
  image: bitnami/kubectl:latest
  environment:
    name: production
    url: https://example.com
  script:
    - kubectl config use-context prod-cluster
    - helm upgrade --install myapp ./helm-chart --namespace production --set image.tag=$IMAGE_TAG --set environment=production
    - kubectl rollout status deployment/myapp -n production
  when: manual                         # 需手动确认后部署
  only:
    - tags                             # 仅在打 tag 时触发(版本发布)

5.4 GitHub Actions

5.4.1 GitHub Actions概述

GitHub Actions是GitHub的CI/CD服务,与GitHub仓库深度集成。

核心特性

  1. 原生集成
  2. 无需额外配置
  3. 与GitHub无缝集成
  4. 可视化Workflow

  5. YAML配置

  6. .github/workflows/*.yml
  7. 简单易用
  8. 版本控制

  9. Marketplace

  10. 丰富的Actions
  11. 社区支持
  12. 高度可扩展

5.4.2 Workflow配置

基本Workflow

YAML
name: CI/CD Pipeline

# 触发条件
on:
  push:
    branches: [ main, develop ]      # 推送到 main/develop 分支时触发
  pull_request:
    branches: [ main ]               # PR 到 main 分支时触发
  workflow_dispatch:                  # 支持手动触发

jobs:
  # 构建与测试任务
  build:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.12'

    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt

    - name: Run tests
      run: |
        pytest tests/ -v --cov=app --cov-report=xml

    - name: Upload coverage            # 上传覆盖率报告到 Codecov
      uses: codecov/codecov-action@v5
      with:
        file: ./coverage.xml

  # 部署任务(依赖 build 成功后执行)
  deploy:
    needs: build                       # 依赖 build 任务
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'  # 仅 main 分支触发部署

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Set up Docker Buildx       # 配置 Docker 多平台构建
      uses: docker/setup-buildx-action@v3

    - name: Login to Docker Registry   # 登录镜像仓库
      uses: docker/login-action@v3
      with:
        registry: myregistry.com
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_PASSWORD }}

    - name: Build and push             # 构建并推送 Docker 镜像
      uses: docker/build-push-action@v6
      with:
        context: .
        push: true
        tags: |
          myregistry.com/myapp:latest
          myregistry.com/myapp:${{ github.sha }}

    - name: Deploy to Kubernetes       # 部署到 K8s 集群
      uses: azure/k8s-deploy@v4
      with:
        manifests: |
          k8s/deployment.yaml
          k8s/service.yaml
        images: |
          myregistry.com/myapp:${{ github.sha }}
        kubeconfig: ${{ secrets.KUBE_CONFIG }}

高级Workflow

YAML
name: Advanced CI/CD Pipeline

# 触发条件
on:
  push:
    branches: [ main, develop ]      # 推送到 main/develop 时触发
  pull_request:
    branches: [ main ]               # PR 到 main 时触发
  workflow_dispatch:                  # 手动触发,可选择部署环境
    inputs:
      environment:
        description: 'Deployment environment'
        required: true
        default: 'staging'
        type: choice
        options:
        - dev
        - staging
        - production

# 全局环境变量
env:
  DOCKER_REGISTRY: myregistry.com
  IMAGE_NAME: myapp

jobs:
  # 代码规范检查(Lint)
  lint:
    name: Code Linting
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4

    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.12'

    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install flake8 black isort

    - name: Run flake8
      run: |
        flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
        flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics

    - name: Run black
      run: black --check .

    - name: Run isort
      run: isort --check-only .

  # 测试任务
  test:
    name: Run Tests
    runs-on: ubuntu-latest
    needs: lint                        # 依赖 lint 通过后执行

    # 服务容器(测试依赖的数据库和缓存)
    services:  # services定义各个服务容器
      postgres:
        image: postgres:13
        env:
          POSTGRES_DB: testdb
          POSTGRES_USER: testuser
          POSTGRES_PASSWORD: testpass
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432

      redis:
        image: redis:latest
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 6379:6379

    steps:
    - uses: actions/checkout@v4

    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.12'
        cache: 'pip'

    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt

    - name: Run unit tests
      env:
        DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb
        REDIS_URL: redis://localhost:6379/0
      run: |
        pytest tests/unit/ -v --cov=app --cov-report=xml --cov-report=html

    - name: Run integration tests
      env:
        DATABASE_URL: postgresql://testuser:testpass@localhost:5432/testdb
        REDIS_URL: redis://localhost:6379/0
      run: |
        pytest tests/integration/ -v

    - name: Upload coverage reports
      uses: codecov/codecov-action@v5
      with:
        file: ./coverage.xml
        flags: unittests

    - name: Archive coverage reports
      uses: actions/upload-artifact@v4
      with:
        name: coverage-report
        path: htmlcov/

  # 安全扫描
  security:
    name: Security Scan
    runs-on: ubuntu-latest
    needs: test                        # 依赖测试通过后执行

    steps:
    - uses: actions/checkout@v4

    - name: Run Trivy vulnerability scanner
      uses: aquasecurity/trivy-action@0.28.0
      with:
        scan-type: 'fs'
        scan-ref: '.'
        format: 'sarif'
        output: 'trivy-results.sarif'

    - name: Upload Trivy results to GitHub Security tab
      uses: github/codeql-action/upload-sarif@v3
      with:
        sarif_file: 'trivy-results.sarif'

  # 构建 Docker 镜像
  build:
    name: Build Docker Image
    runs-on: ubuntu-latest
    needs: [test, security]            # 依赖测试和安全扫描通过

    # 输出镜像信息供后续部署任务使用
    outputs:
      image-tag: ${{ steps.meta.outputs.tags }}
      image-digest: ${{ steps.build.outputs.digest }}

    steps:
    - uses: actions/checkout@v4

    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v3

    - name: Log in to Docker Registry
      uses: docker/login-action@v3
      with:
        registry: ${{ env.DOCKER_REGISTRY }}
        username: ${{ secrets.DOCKER_USERNAME }}
        password: ${{ secrets.DOCKER_PASSWORD }}

    - name: Extract metadata
      id: meta
      uses: docker/metadata-action@v5
      with:
        images: ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}
        tags: |
          type=ref,event=branch
          type=ref,event=pr
          type=semver,pattern={{version}}
          type=semver,pattern={{major}}.{{minor}}
          type=sha,prefix=

    - name: Build and push Docker image
      id: build
      uses: docker/build-push-action@v6
      with:
        context: .
        push: true
        tags: ${{ steps.meta.outputs.tags }}
        labels: ${{ steps.meta.outputs.labels }}
        cache-from: type=gha           # 使用 GitHub Actions 缓存加速构建
        cache-to: type=gha,mode=max    # 缓存所有构建层

    - name: Generate SBOM              # 生成软件物料清单(供应链安全)
      uses: anchore/sbom-action@v0
      with:
        image: ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
        format: spdx-json
        output-file: sbom.json

    - name: Upload SBOM
      uses: actions/upload-artifact@v4
      with:
        name: sbom
        path: sbom.json

  # 部署到开发环境
  deploy_dev:
    name: Deploy to Development
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/develop'  # 仅 develop 分支触发
    environment:
      name: development
      url: https://dev.example.com

    steps:
    - uses: actions/checkout@v4

    - name: Configure kubectl
      uses: azure/k8s-set-context@v3
      with:
        method: kubeconfig
        kubeconfig: ${{ secrets.KUBE_CONFIG_DEV }}

    - name: Deploy to Kubernetes
      uses: azure/k8s-deploy@v4
      with:
        manifests: |
          k8s/deployment.yaml
          k8s/service.yaml
        images: |
          ${{ needs.build.outputs.image-tag }}
        namespace: dev

  # 部署到预发布环境
  deploy_staging:
    name: Deploy to Staging
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/main'     # 仅 main 分支触发
    environment:
      name: staging
      url: https://staging.example.com

    steps:
    - uses: actions/checkout@v4

    - name: Configure kubectl
      uses: azure/k8s-set-context@v3
      with:
        method: kubeconfig
        kubeconfig: ${{ secrets.KUBE_CONFIG_STAGING }}

    - name: Deploy to Kubernetes
      uses: azure/k8s-deploy@v4
      with:
        manifests: |
          k8s/deployment.yaml
          k8s/service.yaml
        images: |
          ${{ needs.build.outputs.image-tag }}
        namespace: staging

  # 部署到生产环境(需手动触发且选择 production)
  deploy_production:
    name: Deploy to Production
    runs-on: ubuntu-latest
    needs: deploy_staging                   # 依赖预发布部署成功
    if: github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'production'
    environment:
      name: production
      url: https://example.com

    steps:
    - uses: actions/checkout@v4

    - name: Configure kubectl
      uses: azure/k8s-set-context@v3
      with:
        method: kubeconfig
        kubeconfig: ${{ secrets.KUBE_CONFIG_PROD }}

    - name: Deploy to Kubernetes
      uses: azure/k8s-deploy@v4
      with:
        manifests: |
          k8s/deployment.yaml
          k8s/service.yaml
        images: |
          ${{ needs.build.outputs.image-tag }}
        namespace: production

    - name: Verify deployment              # 验证部署状态
      run: |
        kubectl rollout status deployment/myapp -n production
        kubectl get pods -n production

5.5 部署策略

5.5.1 蓝绿部署

蓝绿部署通过维护两个相同的生产环境(蓝和绿),实现零停机部署。

实现方式

YAML
# Argo Rollouts 蓝绿部署配置
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: myapp
spec:
  replicas: 5
  strategy:
    blueGreen:
      activeService: myapp-active          # 当前活跃服务(接收生产流量)
      previewService: myapp-preview        # 预览服务(新版本验证)
      autoPromotionEnabled: false          # 禁用自动晋升,需手动确认切换
      scaleDownDelaySeconds: 30            # 切换后旧版本保留 30 秒(便于快速回滚)
      prePromotionAnalysis:                # 晋升前自动分析
        templates:
        - templateName: success-rate
          args:
          - name: service-name
            value: myapp-preview           # 验证预览版本的成功率
      postPromotionAnalysis:               # 晋升后自动分析
        templates:
        - templateName: success-rate
          args:
          - name: service-name
            value: myapp-active            # 验证活跃版本的成功率
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: myregistry.com/myapp:v1.0
        ports:
        - containerPort: 8080

5.5.2 金丝雀发布

金丝雀发布逐步将新版本发布给一小部分用户,验证后再全量发布。

实现方式

YAML
# Argo Rollouts 金丝雀发布配置
apiVersion: argoproj.io/v1alpha1  # apiVersion指定K8s API版本
kind: Rollout  # kind指定资源类型
metadata:
  name: myapp
spec:  # spec定义资源的期望状态
  replicas: 10
  strategy:
    canary:
      canaryService: myapp-canary          # 金丝雀服务(新版本)
      stableService: myapp-stable          # 稳定版本服务
      trafficRouting:                      # 通过 Istio 进行流量路由
        istio:
          virtualService:
            name: myapp-vsvc
      steps:                               # 发布步骤:逐步增加流量比例
      - setWeight: 10                      # 第1步:10% 流量到新版本
      - pause: {duration: 10m}             # 暂停观察 10 分钟
      - analysis:                          # 自动分析金丝雀版本指标
          templates:
          - templateName: success-rate
          args:
          - name: service-name
            value: myapp-canary
      - setWeight: 20                      # 第2步:增加到 20%
      - pause: {duration: 10m}
      - analysis:                          # 再次验证成功率
          templates:
          - templateName: success-rate
          args:
          - name: service-name
            value: myapp-canary
      - setWeight: 40                      # 第3步:增加到 40%
      - pause: {duration: 10m}
      - setWeight: 60                      # 第4步:增加到 60%
      - pause: {duration: 10m}
      - setWeight: 80                      # 第5步:增加到 80%,之后全量发布
      - pause: {duration: 10m}
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: myregistry.com/myapp:v1.0
        ports:
        - containerPort: 8080

5.6 练习题

基础题

  1. 选择题
  2. CI/CD中的CI代表什么?

    • A. Continuous Integration
    • B. Continuous Delivery
    • C. Continuous Deployment
    • D. Continuous Infrastructure
  3. 简答题

  4. 解释CI、CD的区别和联系。
  5. 说明Jenkins、GitLab CI、GitHub Actions的优缺点。

进阶题

  1. 实践题
  2. 搭建一个完整的Jenkins流水线。
  3. 配置GitLab CI/CD实现自动化部署。
  4. 使用GitHub Actions实现多环境部署。

  5. 设计题

  6. 设计一个生产级的CI/CD流水线。
  7. 设计一个蓝绿部署方案。

答案

1. 选择题答案

  1. A(CI代表Continuous Integration,持续集成)

2. 简答题答案

CI、CD的区别和联系: - CI(持续集成):频繁集成代码,自动化构建和测试 - CD(持续交付/部署):自动化部署,确保代码可以随时部署 - 联系:CI是CD的基础,CD是CI的延伸

Jenkins、GitLab CI、GitHub Actions的优缺点: - Jenkins:插件丰富,但配置复杂 - GitLab CI:集成度高,但功能相对有限 - GitHub Actions:简单易用,但生态较小

3. 实践题答案

参见5.2-5.4节的示例。

4. 设计题答案

参见5.5-5.6节的示例。

5.7 面试准备

大厂面试题

字节跳动

  1. 解释CI/CD的流程和价值。
  2. 如何实现零停机部署?
  3. Jenkins Pipeline的类型有哪些?
  4. 如何优化CI/CD流水线性能?

腾讯

  1. GitLab CI/CD的缓存机制是什么?
  2. 如何实现金丝雀发布?
  3. GitHub Actions的Secrets如何管理?
  4. 如何处理CI/CD中的失败?

阿里云

  1. 如何设计多环境部署策略?
  2. CI/CD中的安全最佳实践是什么?
  3. 如何实现自动化测试集成?
  4. 如何监控CI/CD流水线?

📚 参考资料

🎯 本章小结

本章深入讲解了CI/CD流水线技术,包括:

  1. CI/CD的核心概念和价值
  2. Jenkins流水线的配置和使用
  3. GitLab CI/CD的工作流程
  4. GitHub Actions的实践
  5. 蓝绿部署和金丝雀发布
  6. 完整的实战案例

通过本章学习,你掌握了CI/CD的核心技术,能够搭建生产级的自动化流水线。下一章将深入学习监控告警系统。