Announcement

👇Official Account👇

图片

Welcome to join the group & private message

Article first/tail QR code

Skip to content

CI/CD 最佳实践:构建高效的持续集成与交付流水线

什么是 CI/CD?

CI(Continuous Integration,持续集成)CD(Continuous Delivery/Deployment,持续交付/部署) 是现代软件开发的核心实践:

  • 持续集成(CI):频繁地将代码变更合并到主干分支,通过自动化构建和测试验证代码质量
  • 持续交付(CD):自动将通过测试的代码发布到预生产环境,随时可以部署到生产环境
  • 持续部署(CD):自动将通过测试的代码部署到生产环境

CI/CD 核心原则

1. 频繁集成

传统方式:              CI 方式:
━━━━━━━━━              ━━━━━━━━━
开发一周               每天多次提交
↓                      ↓
合并冲突               小步快跑
↓                      ↓
解决冲突               快速反馈
↓                      ↓
测试失败               问题早发现

2. 自动化一切

阶段自动化内容
代码提交自动触发构建
构建编译、打包、镜像构建
测试单元测试、集成测试、E2E 测试
代码质量静态分析、安全扫描、覆盖率检查
部署环境配置、服务发布、健康检查
监控日志收集、指标监控、告警通知

3. 快速反馈

yaml
# 流水线阶段设计 - 快速失败
stages:
  - lint          # 1-2 分钟:代码规范检查
  - build         # 2-5 分钟:编译构建
  - unit-test     # 5-10 分钟:单元测试
  - integration   # 10-20 分钟:集成测试
  - deploy-staging # 自动部署到预生产
  - e2e-test      # 20-30 分钟:端到端测试
  - deploy-prod   # 手动触发部署到生产

CI/CD 流水线设计

典型流水线架构

┌─────────────────────────────────────────────────────────────┐
│                        代码提交                              │
│                   (Git Push / PR)                           │
└───────────────────────────┬─────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                      触发 CI/CD 流水线                        │
└───────────────────────────┬─────────────────────────────────┘

        ┌───────────────────┼───────────────────┐
        ↓                   ↓                   ↓
┌───────────────┐   ┌───────────────┐   ┌───────────────┐
│   代码检查     │   │   构建打包     │   │   安全扫描     │
│  - Lint       │   │  - 编译       │   │  - 依赖漏洞    │
│  - Format     │   │  - 单元测试    │   │  - 密钥检测    │
│  - 代码规范    │   │  - 覆盖率      │   │  - SAST       │
└───────┬───────┘   └───────┬───────┘   └───────┬───────┘
        │                   │                   │
        └───────────────────┼───────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                      镜像构建与推送                           │
│              (Docker Build & Push to Registry)              │
└───────────────────────────┬─────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                      部署到预生产环境                          │
│                    (Deploy to Staging)                      │
└───────────────────────────┬─────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                      集成测试与验收                           │
│              (Integration Tests / E2E Tests)                │
└───────────────────────────┬─────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                      手动审批(可选)                         │
│                    (Manual Approval)                        │
└───────────────────────────┬─────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                      部署到生产环境                           │
│                   (Deploy to Production)                    │
└───────────────────────────┬─────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                      生产环境验证                             │
│              (Smoke Tests / Health Checks)                  │
└───────────────────────────┬─────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                      监控与告警                              │
│              (Monitoring / Alerting / Logging)              │
└─────────────────────────────────────────────────────────────┘

GitHub Actions 实战

基础配置

yaml
# .github/workflows/ci.yml
name: CI/CD Pipeline

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

env:
  GO_VERSION: '1.22'
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  # 代码检查
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}
      
      - name: golangci-lint
        uses: golangci/golangci-lint-action@v6
        with:
          version: latest
          args: --timeout=5m

  # 构建与测试
  build:
    runs-on: ubuntu-latest
    needs: lint
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: ${{ env.GO_VERSION }}
      
      - name: Cache Go modules
        uses: actions/cache@v4
        with:
          path: ~/go/pkg/mod
          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
          restore-keys: |
            ${{ runner.os }}-go-
      
      - name: Download dependencies
        run: go mod download
      
      - name: Run tests
        run: go test -v -race -coverprofile=coverage.out ./...
      
      - name: Upload coverage
        uses: codecov/codecov-action@v4
        with:
          file: ./coverage.out

  # 安全扫描
  security:
    runs-on: ubuntu-latest
    needs: build
    steps:
      - uses: actions/checkout@v4
      
      - name: Run Gosec Security Scanner
        uses: securego/gosec@master
        with:
          args: '-no-fail -fmt sarif -out results.sarif ./...'
      
      - name: Upload SARIF file
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: results.sarif

  # 镜像构建
  docker:
    runs-on: ubuntu-latest
    needs: [build, security]
    permissions:
      contents: read
      packages: write
    steps:
      - 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=sha,prefix={{branch}}-
            type=raw,value=latest,enable={{is_default_branch}}
      
      - 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

  # 部署到预生产
  deploy-staging:
    runs-on: ubuntu-latest
    needs: docker
    if: github.ref == 'refs/heads/develop'
    environment:
      name: staging
      url: https://staging.example.com
    steps:
      - uses: actions/checkout@v4
      
      - name: Deploy to Staging
        run: |
          echo "Deploying to staging..."
          # kubectl set image deployment/app app=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}

  # 部署到生产
  deploy-production:
    runs-on: ubuntu-latest
    needs: docker
    if: github.ref == 'refs/heads/main'
    environment:
      name: production
      url: https://example.com
    steps:
      - uses: actions/checkout@v4
      
      - name: Deploy to Production
        run: |
          echo "Deploying to production..."
          # kubectl set image deployment/app app=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}

多环境部署策略

yaml
# .github/workflows/deploy.yml
name: Deploy

on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'Environment to deploy'
        required: true
        default: 'staging'
        type: choice
        options:
          - staging
          - production
      version:
        description: 'Version to deploy'
        required: true
        type: string

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: ${{ github.event.inputs.environment }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      
      - name: Configure kubectl
        uses: azure/setup-kubectl@v3
      
      - name: Set up Helm
        uses: azure/setup-helm@v3
      
      - name: Deploy
        run: |
          helm upgrade --install app ./helm-chart \
            --namespace ${{ github.event.inputs.environment }} \
            --set image.tag=${{ github.event.inputs.version }} \
            --wait --timeout 5m

部署策略

1. 滚动更新(Rolling Update)

yaml
# Kubernetes Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 5
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1        # 最多多启动 1 个 Pod
      maxUnavailable: 1  # 最多 1 个 Pod 不可用
  template:
    spec:
      containers:
        - name: app
          image: my-app:v1.2.3
          readinessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 5

2. 蓝绿部署(Blue-Green Deployment)

yaml
# GitHub Actions - Blue-Green Deploy
- name: Blue-Green Deploy
  run: |
    # 获取当前活跃版本(蓝色)
    CURRENT_COLOR=$(kubectl get service app -o jsonpath='{.spec.selector.color}')
    
    # 确定新版本颜色(绿色)
    if [ "$CURRENT_COLOR" = "blue" ]; then
      NEW_COLOR="green"
    else
      NEW_COLOR="blue"
    fi
    
    # 部署新版本到 $NEW_COLOR
    kubectl set image deployment/app-$NEW_COLOR app=my-app:${{ github.sha }}
    kubectl rollout status deployment/app-$NEW_COLOR
    
    # 切换流量
    kubectl patch service app -p '{"spec":{"selector":{"color":"'$NEW_COLOR'"}}}'
    
    # 等待验证
    sleep 30
    
    # 健康检查
    if ! curl -f https://example.com/health; then
      # 回滚
      kubectl patch service app -p '{"spec":{"selector":{"color":"'$CURRENT_COLOR'"}}}'
      exit 1
    fi

3. 金丝雀发布(Canary Deployment)

yaml
# Argo Rollout - Canary
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: my-app
spec:
  replicas: 10
  strategy:
    canary:
      steps:
        - setWeight: 10      # 10% 流量到新版本
        - pause: {duration: 10m}  # 观察 10 分钟
        - setWeight: 25
        - pause: {duration: 10m}
        - setWeight: 50
        - pause: {duration: 10m}
        - setWeight: 100
      analysis:
        templates:
          - templateName: success-rate

测试策略

测试金字塔

         /\
        /  \
       / E2E \          # 端到端测试(少量)
      /────────\        # 慢、不稳定、成本高
     /          \
    / Integration \     # 集成测试(中等)
   /────────────────\   # 测试组件交互
  /                  \
 /     Unit Tests     \ # 单元测试(大量)
/────────────────────────\ # 快、稳定、成本低

自动化测试流水线

yaml
# .github/workflows/test.yml
name: Test Suite

on: [push, pull_request]

jobs:
  unit-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
      
      - name: Unit Tests
        run: go test -short ./...

  integration-test:
    runs-on: ubuntu-latest
    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
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
      
      - name: Integration Tests
        env:
          DATABASE_URL: postgres://postgres:postgres@localhost:5432/test?sslmode=disable
        run: go test -run Integration ./...

  e2e-test:
    runs-on: ubuntu-latest
    needs: [unit-test, integration-test]
    steps:
      - uses: actions/checkout@v4
      
      - name: Start Services
        run: docker-compose -f docker-compose.test.yml up -d
      
      - name: Run E2E Tests
        run: |
          npx playwright test
      
      - name: Stop Services
        if: always()
        run: docker-compose -f docker-compose.test.yml down

监控与可观测性

健康检查

go
package main

import (
    "net/http"
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()
    
    // 存活检查
    r.GET("/health/live", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"status": "alive"})
    })
    
    // 就绪检查
    r.GET("/health/ready", func(c *gin.Context) {
        // 检查数据库连接
        if err := db.Ping(); err != nil {
            c.JSON(http.StatusServiceUnavailable, gin.H{
                "status": "not ready",
                "error": err.Error(),
            })
            return
        }
        c.JSON(http.StatusOK, gin.H{"status": "ready"})
    })
    
    r.Run()
}

部署监控

yaml
# .github/workflows/monitor.yml
- name: Monitor Deployment
  run: |
    for i in {1..30}; do
      STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://example.com/health)
      if [ "$STATUS" = "200" ]; then
        echo "Deployment successful!"
        exit 0
      fi
      echo "Waiting for deployment... ($i/30)"
      sleep 10
    done
    echo "Deployment failed!"
    exit 1

安全最佳实践

密钥管理

yaml
# 使用 GitHub Secrets
- name: Deploy
  env:
    DATABASE_URL: ${{ secrets.DATABASE_URL }}
    API_KEY: ${{ secrets.API_KEY }}
  run: |
    kubectl create secret generic app-secrets \
      --from-literal=database-url="$DATABASE_URL" \
      --from-literal=api-key="$API_KEY"

SAST/DAST 扫描

yaml
- name: SAST Scan
  uses: returntocorp/semgrep-action@v1
  with:
    config: >-
      p/security-audit
      p/owasp-top-ten

- name: Container Scan
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: my-app:${{ github.sha }}
    format: 'sarif'
    output: 'trivy-results.sarif'

总结

构建高效的 CI/CD 流水线需要关注以下关键点:

维度最佳实践
速度并行执行、缓存优化、快速失败
质量自动化测试、代码审查、安全扫描
可靠性健康检查、自动回滚、灰度发布
可观测性日志收集、指标监控、链路追踪
安全密钥管理、漏洞扫描、最小权限

关键指标:

  • 部署频率:每天部署次数
  • 变更前置时间:代码提交到生产部署的时间
  • 变更失败率:导致故障的部署比例
  • 服务恢复时间:故障发生到恢复的时间

相关文章推荐:

上次更新于: