Skip to content

CI/CD 集成指南

自动化构建、测试和部署 API 服务的最佳实践

CI/CD 流程概览

GitHub Actions 配置

主工作流

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

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]
  release:
    types: [ created ]

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

jobs:
  # 代码质量检查
  quality-check:
    name: Code Quality Check
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Set up Node.js
        uses: actions/setup-node@v3
        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
      
      - name: Check code formatting
        run: npm run format:check
      
      - name: Run security audit
        run: npm audit --audit-level=moderate
      
      - name: SonarCloud Scan
        uses: SonarSource/sonarcloud-github-action@master
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

  # 单元测试
  unit-tests:
    name: Unit Tests
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [16, 18, 20]
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run unit tests
        run: npm run test:unit -- --coverage
      
      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v3
        with:
          file: ./coverage/lcov.info
          flags: unittests
          name: codecov-umbrella

  # 集成测试
  integration-tests:
    name: Integration Tests
    runs-on: ubuntu-latest
    needs: [quality-check, unit-tests]
    
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: testdb
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432
      
      redis:
        image: redis:7
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 6379:6379
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Set up Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run database migrations
        env:
          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb
        run: npm run db:migrate
      
      - name: Run integration tests
        env:
          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb
          REDIS_URL: redis://localhost:6379
        run: npm run test:integration
      
      - name: Run API tests
        run: npm run test:api

  # 构建和发布 Docker 镜像
  build-and-push:
    name: Build and Push Docker Image
    runs-on: ubuntu-latest
    needs: [integration-tests]
    if: github.event_name != 'pull_request'
    
    permissions:
      contents: read
      packages: write
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2
      
      - name: Log in to Container Registry
        uses: docker/login-action@v2
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      
      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v4
        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,prefix={{branch}}-
      
      - name: Build and push Docker image
        uses: docker/build-push-action@v4
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
          build-args: |
            BUILD_VERSION=${{ github.sha }}
            BUILD_TIME=${{ steps.meta.outputs.created }}

  # 安全扫描
  security-scan:
    name: Security Scan
    runs-on: ubuntu-latest
    needs: [build-and-push]
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Run Trivy vulnerability scanner
        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'
      
      - name: OWASP Dependency Check
        uses: dependency-check/Dependency-Check_Action@main
        with:
          project: 'api-service'
          path: '.'
          format: 'HTML'
          args: >
            --enableRetired

  # 部署到测试环境
  deploy-staging:
    name: Deploy to Staging
    runs-on: ubuntu-latest
    needs: [security-scan]
    if: github.ref == 'refs/heads/develop'
    environment:
      name: staging
      url: https://staging.api.example.com
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Deploy to Kubernetes
        uses: azure/k8s-deploy@v4
        with:
          namespace: staging
          manifests: |
            k8s/staging/deployment.yaml
            k8s/staging/service.yaml
            k8s/staging/ingress.yaml
          images: |
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}

  # E2E 测试
  e2e-tests:
    name: E2E Tests
    runs-on: ubuntu-latest
    needs: [deploy-staging]
    if: github.ref == 'refs/heads/develop'
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Run E2E tests
        uses: cypress-io/github-action@v5
        with:
          config: baseUrl=https://staging.api.example.com
          record: true
        env:
          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  # 部署到生产环境
  deploy-production:
    name: Deploy to Production
    runs-on: ubuntu-latest
    needs: [e2e-tests]
    if: github.ref == 'refs/heads/main'
    environment:
      name: production
      url: https://api.example.com
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Deploy to Production
        uses: azure/k8s-deploy@v4
        with:
          namespace: production
          manifests: |
            k8s/production/deployment.yaml
            k8s/production/service.yaml
            k8s/production/ingress.yaml
          images: |
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
      
      - name: Create Release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: v${{ github.run_number }}
          release_name: Release v${{ github.run_number }}
          body: |
            Changes in this Release
            - Automated release from commit ${{ github.sha }}
          draft: false
          prerelease: false

GitLab CI/CD 配置

yaml
# .gitlab-ci.yml
stages:
  - build
  - test
  - security
  - package
  - deploy

variables:
  DOCKER_DRIVER: overlay2
  DOCKER_TLS_CERTDIR: "/certs"
  MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository"
  MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version"

# 缓存配置
.maven-cache:
  cache:
    key: "$CI_COMMIT_REF_SLUG"
    paths:
      - .m2/repository/
      - target/

# 构建阶段
build:
  stage: build
  image: maven:3.8-openjdk-17
  extends: .maven-cache
  script:
    - mvn $MAVEN_CLI_OPTS clean compile
  artifacts:
    paths:
      - target/
    expire_in: 1 week

# 测试阶段
unit-test:
  stage: test
  image: maven:3.8-openjdk-17
  extends: .maven-cache
  script:
    - mvn $MAVEN_CLI_OPTS test
    - mvn jacoco:report
  coverage: '/Total.*?([0-9]{1,3})%/'
  artifacts:
    reports:
      junit:
        - target/surefire-reports/TEST-*.xml
    paths:
      - target/site/jacoco/
    expire_in: 1 week

integration-test:
  stage: test
  image: maven:3.8-openjdk-17
  extends: .maven-cache
  services:
    - postgres:15
    - redis:7
  variables:
    POSTGRES_DB: testdb
    POSTGRES_USER: test
    POSTGRES_PASSWORD: test
    SPRING_DATASOURCE_URL: "jdbc:postgresql://postgres:5432/testdb"
    SPRING_REDIS_HOST: redis
  script:
    - mvn $MAVEN_CLI_OPTS verify -DskipUnitTests=true

# 代码质量检查
code-quality:
  stage: test
  image: maven:3.8-openjdk-17
  extends: .maven-cache
  script:
    - mvn $MAVEN_CLI_OPTS sonar:sonar
      -Dsonar.projectKey=$CI_PROJECT_NAME
      -Dsonar.host.url=$SONAR_HOST_URL
      -Dsonar.login=$SONAR_TOKEN
  only:
    - merge_requests
    - main
    - develop

# 安全扫描
security-scan:
  stage: security
  image: 
    name: aquasec/trivy:latest
    entrypoint: [""]
  script:
    - trivy fs --no-progress --security-checks vuln,secret,config .
  allow_failure: true

dependency-check:
  stage: security
  image: owasp/dependency-check:latest
  script:
    - /usr/share/dependency-check/bin/dependency-check.sh
      --project "$CI_PROJECT_NAME"
      --scan .
      --format "ALL"
      --enableExperimental
  artifacts:
    paths:
      - dependency-check-report.*
    expire_in: 1 week

# Docker 镜像构建
docker-build:
  stage: package
  image: docker:latest
  services:
    - docker:dind
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    - docker build
      --cache-from $CI_REGISTRY_IMAGE:latest
      --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
      --tag $CI_REGISTRY_IMAGE:latest
      .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - docker push $CI_REGISTRY_IMAGE:latest
  only:
    - main
    - develop

# 部署阶段
deploy-staging:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - kubectl config use-context $K8S_CONTEXT_STAGING
    - kubectl set image deployment/api-service
      api-service=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
      --namespace=staging
    - kubectl rollout status deployment/api-service --namespace=staging
  environment:
    name: staging
    url: https://staging.api.example.com
  only:
    - develop

deploy-production:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - kubectl config use-context $K8S_CONTEXT_PRODUCTION
    - kubectl set image deployment/api-service
      api-service=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
      --namespace=production
    - kubectl rollout status deployment/api-service --namespace=production
  environment:
    name: production
    url: https://api.example.com
  only:
    - main
  when: manual

Jenkins Pipeline

groovy
// Jenkinsfile
pipeline {
    agent {
        kubernetes {
            yaml '''
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: maven
    image: maven:3.8-openjdk-17
    command: ['sleep', '99999']
  - name: docker
    image: docker:latest
    command: ['sleep', '99999']
    volumeMounts:
    - name: docker-sock
      mountPath: /var/run/docker.sock
  - name: kubectl
    image: bitnami/kubectl:latest
    command: ['sleep', '99999']
  volumes:
  - name: docker-sock
    hostPath:
      path: /var/run/docker.sock
'''
        }
    }
    
    environment {
        REGISTRY = 'registry.example.com'
        IMAGE_NAME = 'api-service'
        SONAR_HOST = credentials('sonar-host')
        SONAR_TOKEN = credentials('sonar-token')
    }
    
    stages {
        stage('Checkout') {
            steps {
                checkout scm
                script {
                    env.GIT_COMMIT = sh(
                        script: 'git rev-parse HEAD',
                        returnStdout: true
                    ).trim()
                    env.GIT_BRANCH = sh(
                        script: 'git rev-parse --abbrev-ref HEAD',
                        returnStdout: true
                    ).trim()
                }
            }
        }
        
        stage('Build & Test') {
            parallel {
                stage('Java Build') {
                    steps {
                        container('maven') {
                            sh 'mvn clean package'
                        }
                    }
                }
                
                stage('Frontend Build') {
                    steps {
                        container('node') {
                            sh '''
                                npm ci
                                npm run build
                                npm run test
                            '''
                        }
                    }
                }
            }
        }
        
        stage('Code Analysis') {
            steps {
                container('maven') {
                    withSonarQubeEnv('SonarQube') {
                        sh """
                            mvn sonar:sonar \
                                -Dsonar.projectKey=${env.JOB_NAME} \
                                -Dsonar.host.url=${env.SONAR_HOST} \
                                -Dsonar.login=${env.SONAR_TOKEN}
                        """
                    }
                }
            }
        }
        
        stage('Quality Gate') {
            steps {
                timeout(time: 1, unit: 'HOURS') {
                    waitForQualityGate abortPipeline: true
                }
            }
        }
        
        stage('Build Docker Image') {
            steps {
                container('docker') {
                    script {
                        docker.build("${REGISTRY}/${IMAGE_NAME}:${GIT_COMMIT}")
                        docker.build("${REGISTRY}/${IMAGE_NAME}:latest")
                    }
                }
            }
        }
        
        stage('Push Docker Image') {
            when {
                branch pattern: "(main|develop)", comparator: "REGEXP"
            }
            steps {
                container('docker') {
                    script {
                        docker.withRegistry("https://${REGISTRY}", 'docker-registry-creds') {
                            docker.image("${REGISTRY}/${IMAGE_NAME}:${GIT_COMMIT}").push()
                            docker.image("${REGISTRY}/${IMAGE_NAME}:latest").push()
                        }
                    }
                }
            }
        }
        
        stage('Deploy to Staging') {
            when {
                branch 'develop'
            }
            steps {
                container('kubectl') {
                    withKubeConfig([credentialsId: 'kubeconfig-staging']) {
                        sh """
                            kubectl set image deployment/${IMAGE_NAME} \
                                ${IMAGE_NAME}=${REGISTRY}/${IMAGE_NAME}:${GIT_COMMIT} \
                                --namespace=staging
                            kubectl rollout status deployment/${IMAGE_NAME} \
                                --namespace=staging
                        """
                    }
                }
            }
        }
        
        stage('Deploy to Production') {
            when {
                branch 'main'
            }
            input {
                message "Deploy to production?"
                ok "Deploy"
            }
            steps {
                container('kubectl') {
                    withKubeConfig([credentialsId: 'kubeconfig-production']) {
                        sh """
                            kubectl set image deployment/${IMAGE_NAME} \
                                ${IMAGE_NAME}=${REGISTRY}/${IMAGE_NAME}:${GIT_COMMIT} \
                                --namespace=production
                            kubectl rollout status deployment/${IMAGE_NAME} \
                                --namespace=production
                        """
                    }
                }
            }
        }
    }
    
    post {
        always {
            junit '**/target/surefire-reports/*.xml'
            archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true
        }
        success {
            slackSend(
                color: 'good',
                message: "Build Successful: ${env.JOB_NAME} - ${env.BUILD_NUMBER}"
            )
        }
        failure {
            slackSend(
                color: 'danger',
                message: "Build Failed: ${env.JOB_NAME} - ${env.BUILD_NUMBER}"
            )
        }
    }
}

自动化测试集成

测试策略

yaml
# test-strategy.yml
test-pyramid:
  unit-tests:
    coverage: 80%
    tools:
      - Jest (JavaScript/TypeScript)
      - JUnit (Java)
      - pytest (Python)
    
  integration-tests:
    coverage: 60%
    tools:
      - TestContainers
      - REST Assured
      - Supertest
    
  api-tests:
    tools:
      - Postman/Newman
      - Pact (契约测试)
      - Dredd (OpenAPI 验证)
    
  e2e-tests:
    tools:
      - Cypress
      - Playwright
      - Selenium
    
  performance-tests:
    tools:
      - K6
      - JMeter
      - Gatling

契约测试

javascript
// Pact 契约测试示例
const { Pact } = require('@pact-foundation/pact');
const { like, term } = require('@pact-foundation/pact/dsl/matchers');

describe('Product API Contract', () => {
    const provider = new Pact({
        consumer: 'Frontend',
        provider: 'ProductAPI',
        port: 8080,
        log: path.resolve(process.cwd(), 'logs', 'pact.log'),
        dir: path.resolve(process.cwd(), 'pacts'),
        logLevel: 'warn',
    });

    beforeAll(() => provider.setup());
    afterEach(() => provider.verify());
    afterAll(() => provider.finalize());

    describe('get product', () => {
        test('should return a product', async () => {
            // 定义期望的交互
            await provider.addInteraction({
                state: 'a product with ID 123 exists',
                uponReceiving: 'a request to get a product',
                withRequest: {
                    method: 'GET',
                    path: '/api/v1/products/123',
                    headers: {
                        Accept: 'application/json',
                    },
                },
                willRespondWith: {
                    status: 200,
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: like({
                        id: '123',
                        name: 'iPhone 15',
                        price: 999.99,
                        category: like({
                            id: '456',
                            name: 'Electronics',
                        }),
                    }),
                },
            });

            // 执行测试
            const response = await fetch('http://localhost:8080/api/v1/products/123');
            const product = await response.json();

            expect(product.id).toBe('123');
            expect(product.name).toBe('iPhone 15');
        });
    });
});

性能测试

javascript
// k6 性能测试脚本
import http from 'k6/http';
import { check, group } from 'k6';
import { Rate } from 'k6/metrics';

export const errorRate = new Rate('errors');

export const options = {
    stages: [
        { duration: '2m', target: 10 },   // 预热
        { duration: '5m', target: 50 },   // 爬升
        { duration: '10m', target: 100 }, // 压力测试
        { duration: '5m', target: 50 },   // 下降
        { duration: '2m', target: 0 },    // 冷却
    ],
    thresholds: {
        http_req_duration: ['p(95)<500'], // 95% 请求在 500ms 内
        errors: ['rate<0.1'],             // 错误率低于 10%
    },
};

const BASE_URL = 'https://api.example.com';

export default function () {
    group('API Performance Test', () => {
        // 测试获取产品列表
        group('GET /products', () => {
            const response = http.get(`${BASE_URL}/api/v1/products`);
            
            check(response, {
                'status is 200': (r) => r.status === 200,
                'response time < 500ms': (r) => r.timings.duration < 500,
                'has products': (r) => JSON.parse(r.body).data.length > 0,
            }) || errorRate.add(1);
        });

        // 测试创建产品
        group('POST /products', () => {
            const payload = JSON.stringify({
                name: `Product ${Date.now()}`,
                price: 99.99,
                category: 'test',
            });

            const params = {
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': 'Bearer ' + __ENV.API_TOKEN,
                },
            };

            const response = http.post(`${BASE_URL}/api/v1/products`, payload, params);
            
            check(response, {
                'status is 201': (r) => r.status === 201,
                'has product id': (r) => JSON.parse(r.body).id !== undefined,
            }) || errorRate.add(1);
        });
    });
}

部署策略

蓝绿部署

yaml
# kubernetes/blue-green-deployment.yaml
apiVersion: v1
kind: Service
metadata:
  name: api-service
spec:
  selector:
    app: api-service
    version: green  # 切换到 blue 或 green
  ports:
    - port: 80
      targetPort: 8080

---
# Blue 部署
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-service-blue
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api-service
      version: blue
  template:
    metadata:
      labels:
        app: api-service
        version: blue
    spec:
      containers:
      - name: api-service
        image: registry.example.com/api-service:v1.0.0
        ports:
        - containerPort: 8080

---
# Green 部署
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-service-green
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api-service
      version: green
  template:
    metadata:
      labels:
        app: api-service
        version: green
    spec:
      containers:
      - name: api-service
        image: registry.example.com/api-service:v1.1.0
        ports:
        - containerPort: 8080

金丝雀部署

yaml
# kubernetes/canary-deployment.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-service-canary
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "10"  # 10% 流量
spec:
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: api-service-canary
            port:
              number: 80

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-service-canary
spec:
  replicas: 1
  selector:
    matchLabels:
      app: api-service
      version: canary
  template:
    metadata:
      labels:
        app: api-service
        version: canary
    spec:
      containers:
      - name: api-service
        image: registry.example.com/api-service:v1.1.0-canary
        ports:
        - containerPort: 8080

滚动更新

bash
#!/bin/bash
# rolling-update.sh

# 设置变量
DEPLOYMENT="api-service"
NAMESPACE="production"
NEW_IMAGE="registry.example.com/api-service:v1.1.0"

# 更新镜像
kubectl set image deployment/${DEPLOYMENT} \
    ${DEPLOYMENT}=${NEW_IMAGE} \
    --namespace=${NAMESPACE} \
    --record

# 监控更新状态
kubectl rollout status deployment/${DEPLOYMENT} \
    --namespace=${NAMESPACE}

# 检查部署历史
kubectl rollout history deployment/${DEPLOYMENT} \
    --namespace=${NAMESPACE}

# 如果需要回滚
# kubectl rollout undo deployment/${DEPLOYMENT} --namespace=${NAMESPACE}

监控和通知

部署监控

yaml
# prometheus-rules.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: deployment-alerts
spec:
  groups:
  - name: deployment
    interval: 30s
    rules:
    - alert: DeploymentFailed
      expr: |
        kube_deployment_status_replicas_available{deployment="api-service"} 
        < kube_deployment_spec_replicas{deployment="api-service"}
      for: 5m
      labels:
        severity: critical
      annotations:
        summary: "Deployment {{ $labels.deployment }} has insufficient replicas"
        description: "Deployment {{ $labels.deployment }} has {{ $value }} available replicas, expected {{ $labels.spec_replicas }}"
    
    - alert: HighErrorRate
      expr: |
        rate(http_requests_total{status=~"5.."}[5m]) > 0.05
      for: 5m
      labels:
        severity: warning
      annotations:
        summary: "High error rate detected"
        description: "Error rate is {{ $value }} errors per second"

Slack 通知

javascript
// slack-notification.js
const { WebClient } = require('@slack/web-api');

const slack = new WebClient(process.env.SLACK_TOKEN);

async function notifyDeployment(environment, version, status) {
    const color = status === 'success' ? 'good' : 'danger';
    const emoji = status === 'success' ? ':rocket:' : ':x:';
    
    await slack.chat.postMessage({
        channel: '#deployments',
        attachments: [{
            color: color,
            blocks: [
                {
                    type: 'header',
                    text: {
                        type: 'plain_text',
                        text: `${emoji} Deployment ${status.toUpperCase()}`
                    }
                },
                {
                    type: 'section',
                    fields: [
                        {
                            type: 'mrkdwn',
                            text: `*Environment:*\n${environment}`
                        },
                        {
                            type: 'mrkdwn',
                            text: `*Version:*\n${version}`
                        },
                        {
                            type: 'mrkdwn',
                            text: `*Time:*\n${new Date().toISOString()}`
                        },
                        {
                            type: 'mrkdwn',
                            text: `*Triggered by:*\n${process.env.GITHUB_ACTOR || 'System'}`
                        }
                    ]
                },
                {
                    type: 'actions',
                    elements: [
                        {
                            type: 'button',
                            text: {
                                type: 'plain_text',
                                text: 'View Deployment'
                            },
                            url: `https://github.com/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`
                        },
                        {
                            type: 'button',
                            text: {
                                type: 'plain_text',
                                text: 'View Metrics'
                            },
                            url: `https://grafana.example.com/d/deployment-${environment}`
                        }
                    ]
                }
            ]
        }]
    });
}

回滚策略

bash
#!/bin/bash
# rollback-strategy.sh

# 自动回滚脚本
rollback_deployment() {
    local deployment=$1
    local namespace=$2
    local health_check_url=$3
    
    echo "Starting rollback for $deployment in $namespace..."
    
    # 回滚到上一个版本
    kubectl rollout undo deployment/$deployment -n $namespace
    
    # 等待回滚完成
    kubectl rollout status deployment/$deployment -n $namespace
    
    # 健康检查
    for i in {1..30}; do
        if curl -f $health_check_url > /dev/null 2>&1; then
            echo "Rollback successful - service is healthy"
            return 0
        fi
        echo "Waiting for service to be healthy... ($i/30)"
        sleep 10
    done
    
    echo "Rollback completed but service health check failed"
    return 1
}

# 使用示例
rollback_deployment "api-service" "production" "https://api.example.com/health"

最佳实践

  1. 自动化一切: 从代码提交到生产部署全流程自动化
  2. 快速反馈: 尽早发现问题,快速失败
  3. 环境一致性: 使用容器确保各环境一致
  4. 版本管理: 所有制品都要有版本标记
  5. 回滚能力: 确保能快速回滚到稳定版本
  6. 监控告警: 部署后持续监控服务状态
  7. 安全扫描: 在流程中集成安全检查
  8. 文档更新: 自动更新 API 文档和变更日志

相关资源

SOLO Development Guide