CI/CD 完整指南

CI/CD 完整指南

1. CI/CD 基础概念

1.1 核心流程

# CI/CD 流水线示例
Pipeline:
  Stages:
    - 代码检查 (Lint)
    - 单元测试 (Test)
    - 构建打包 (Build)
    - 安全扫描 (Security)
    - 部署测试 (Deploy to Staging)
    - 集成测试 (Integration Test)
    - 生产部署 (Deploy to Production)

2. GitHub Actions CI/CD 实现

2.1 基础工作流配置

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

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

env:
  NODE_VERSION: '18.x'
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  # 代码质量检查
  lint-and-test:
    name: Lint & Test
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: ${{ env.NODE_VERSION }}
        cache: 'npm'

    - name: Install dependencies
      run: npm ci

    - name: Run ESLint
      run: npm run lint

    - name: Run TypeScript check
      run: npm run type-check

    - name: Run unit tests
      run: npm run test:unit

    - name: Run coverage
      run: npm run test:coverage

    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v3
      with:
        file: ./coverage/lcov.info

  # 构建阶段
  build:
    name: Build
    runs-on: ubuntu-latest
    needs: lint-and-test
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: ${{ env.NODE_VERSION }}
        cache: 'npm'

    - name: Install dependencies
      run: npm ci

    - name: Build application
      run: npm run build

    - name: Run bundle analyzer
      run: npm run build:analyze

    - name: Upload build artifacts
      uses: actions/upload-artifact@v4
      with:
        name: build-output
        path: dist/
        retention-days: 7

  # 安全扫描
  security:
    name: Security Scan
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Run Snyk to check for vulnerabilities
      uses: snyk/actions/node@master
      env:
        SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
      with:
        args: --severity-threshold=high

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

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

2.2 Docker 构建和推送

# .github/workflows/docker.yml
name: Build and Push Docker Image

on:
  push:
    branches: [ main ]
    tags: [ 'v*.*.*' ]

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

jobs:
  build-and-push:
    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,prefix={{branch}}-

    - name: Build and push Docker image
      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

2.3 完整部署流水线

# .github/workflows/deploy.yml
name: Deploy to Environments

on:
  workflow_dispatch:
    inputs:
      environment:
        description: '部署环境'
        required: true
        default: 'staging'
        type: choice
        options:
        - staging
        - production

jobs:
  deploy-staging:
    name: Deploy to Staging
    runs-on: ubuntu-latest
    environment: staging
    if: github.event.inputs.environment == 'staging' || github.ref == 'refs/heads/develop'
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Deploy to Staging
      run: |
        echo "部署到测试环境..."
        # 这里可以是 kubectl, helm, ssh 等部署命令
        ./scripts/deploy.sh staging

    - name: Run smoke tests
      run: |
        echo "运行冒烟测试..."
        npm run test:smoke

  deploy-production:
    name: Deploy to Production
    runs-on: ubuntu-latest
    environment: production
    needs: deploy-staging
    if: github.event.inputs.environment == 'production' || github.ref == 'refs/heads/main'
    
    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Deploy to Production
      run: |
        echo "部署到生产环境..."
        ./scripts/deploy.sh production

    - name: Run health checks
      run: |
        echo "运行健康检查..."
        ./scripts/health-check.sh

    - name: Notify deployment
      uses: 8398a7/action-slack@v3
      with:
        status: ${{ job.status }}
        text: 生产环境部署完成
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

3. GitLab CI/CD 配置

3.1 .gitlab-ci.yml 完整配置

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

variables:
  NODE_VERSION: "18.19.0"
  DOCKER_REGISTRY: registry.gitlab.com
  DOCKER_IMAGE: $CI_REGISTRY_IMAGE

# 缓存配置
cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
    - node_modules/
    - .npm/

before_script:
  - npm ci --cache .npm --prefer-offline

lint:
  stage: test
  script:
    - npm run lint
    - npm run type-check
  only:
    - merge_requests
    - develop
    - main

test:
  stage: test
  script:
    - npm run test:unit
    - npm run test:coverage
  coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
  artifacts:
    reports:
      cobertura: coverage/cobertura-coverage.xml
    paths:
      - coverage/
    expire_in: 1 week

build:
  stage: build
  script:
    - npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 week
  only:
    - main
    - develop
    - tags

security-scan:
  stage: security
  image: node:$NODE_VERSION
  script:
    - npm audit --audit-level high
    - npx snyk test --severity-threshold=high
  allow_failure: true
  only:
    - main
    - develop

docker-build:
  stage: build
  image: docker:20.10
  services:
    - docker:20.10-dind
  variables:
    DOCKER_TLS_CERTDIR: "/certs"
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    - docker build -t $DOCKER_IMAGE:$CI_COMMIT_SHA .
    - docker push $DOCKER_IMAGE:$CI_COMMIT_SHA
    - docker tag $DOCKER_IMAGE:$CI_COMMIT_SHA $DOCKER_IMAGE:latest
    - docker push $DOCKER_IMAGE:latest
  only:
    - main
    - tags

deploy-staging:
  stage: deploy
  environment:
    name: staging
    url: https://staging.example.com
  script:
    - echo "部署到测试环境"
    - kubectl set image deployment/my-app app=$DOCKER_IMAGE:$CI_COMMIT_SHA -n staging
  only:
    - develop

deploy-production:
  stage: deploy
  environment:
    name: production
    url: https://example.com
  script:
    - echo "部署到生产环境"
    - kubectl set image deployment/my-app app=$DOCKER_IMAGE:$CI_COMMIT_SHA -n production
  when: manual
  only:
    - main

4. Jenkins Pipeline 配置

4.1 Jenkinsfile 完整配置

// Jenkinsfile
pipeline {
    agent {
        docker {
            image 'node:18-alpine'
            args '-p 3000:3000'
        }
    }
    
    environment {
        NODE_ENV = 'production'
        DOCKER_REGISTRY = 'registry.example.com'
        DOCKER_IMAGE = "${DOCKER_REGISTRY}/my-app"
    }
    
    options {
        timeout(time: 30, unit: 'MINUTES')
        buildDiscarder(logRotator(numToKeepStr: '10'))
        disableConcurrentBuilds()
    }
    
    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }
        
        stage('Install') {
            steps {
                sh 'npm ci'
            }
        }
        
        stage('Lint') {
            steps {
                sh 'npm run lint'
            }
        }
        
        stage('Test') {
            parallel {
                stage('Unit Tests') {
                    steps {
                        sh 'npm run test:unit'
                    }
                    post {
                        always {
                            junit 'test-results/**/*.xml'
                        }
                    }
                }
                
                stage('Integration Tests') {
                    steps {
                        sh 'npm run test:integration'
                    }
                }
            }
        }
        
        stage('Build') {
            steps {
                sh 'npm run build'
            }
            post {
                success {
                    archiveArtifacts artifacts: 'dist/**/*', fingerprint: true
                }
            }
        }
        
        stage('Docker Build') {
            agent {
                docker {
                    image 'docker:20.10'
                    args '--privileged -v /var/run/docker.sock:/var/run/docker.sock'
                }
            }
            steps {
                script {
                    docker.build("${DOCKER_IMAGE}:${env.BUILD_ID}")
                }
            }
        }
        
        stage('Security Scan') {
            steps {
                sh 'npm audit --audit-level high'
                sh 'trivy image ${DOCKER_IMAGE}:${env.BUILD_ID}'
            }
        }
        
        stage('Deploy to Staging') {
            when {
                branch 'develop'
            }
            steps {
                script {
                    sh "kubectl set image deployment/my-app app=${DOCKER_IMAGE}:${env.BUILD_ID} -n staging"
                }
            }
        }
        
        stage('Deploy to Production') {
            when {
                branch 'main'
            }
            steps {
                input message: '部署到生产环境?', ok: 'Deploy'
                script {
                    sh "kubectl set image deployment/my-app app=${DOCKER_IMAGE}:${env.BUILD_ID} -n production"
                }
            }
        }
    }
    
    post {
        always {
            emailext (
                subject: "构建结果: ${currentBuild.fullDisplayName}",
                body: "构建结果: ${currentBuild.result}\n\n查看详情: ${env.BUILD_URL}",
                to: 'dev-team@example.com'
            )
        }
        success {
            slackSend(
                channel: '#builds',
                message: "构建成功: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
            )
        }
        failure {
            slackSend(
                channel: '#builds',
                message: "构建失败: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
            )
        }
    }
}

5. 部署脚本和配置

5.1 Kubernetes 部署配置

# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend-app
  namespace: production
  labels:
    app: frontend
spec:
  replicas: 3
  selector:
    matchLabels:
      app: frontend
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: frontend
        image: registry.example.com/my-app:latest
        ports:
        - containerPort: 3000
        env:
        - name: NODE_ENV
          value: "production"
        - name: API_URL
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: api.url
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "200m"
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
  name: frontend-service
  namespace: production
spec:
  selector:
    app: frontend
  ports:
  - port: 80
    targetPort: 3000
  type: LoadBalancer

5.2 部署脚本

#!/bin/bash
# scripts/deploy.sh

set -e

ENVIRONMENT=$1
VERSION=${2:-latest}

deploy_staging() {
    echo "🚀 部署到测试环境..."
    
    # 更新 Kubernetes 部署
    kubectl set image deployment/frontend-app frontend=registry.example.com/my-app:$VERSION -n staging
    
    # 等待部署完成
    kubectl rollout status deployment/frontend-app -n staging --timeout=300s
    
    # 运行冒烟测试
    echo "运行冒烟测试..."
    npm run test:smoke -- --base-url https://staging.example.com
    
    echo "✅ 测试环境部署完成"
}

deploy_production() {
    echo "🚀 部署到生产环境..."
    
    # 蓝绿部署策略
    CURRENT_VERSION=$(kubectl get deployment frontend-app -n production -o=jsonpath='{.spec.template.spec.containers[0].image}')
    
    # 创建新版本部署
    kubectl apply -f k8s/production-deployment-v2.yaml
    
    # 等待新版本就绪
    kubectl rollout status deployment/frontend-app-v2 -n production --timeout=300s
    
    # 切换流量
    kubectl patch service frontend-service -n production -p '{"spec":{"selector":{"version":"v2"}}}'
    
    # 健康检查
    ./scripts/health-check.sh production
    
    # 清理旧版本
    kubectl delete deployment frontend-app-v1 -n production
    
    echo "✅ 生产环境部署完成"
}

case $ENVIRONMENT in
    "staging")
        deploy_staging
        ;;
    "production")
        deploy_production
        ;;
    *)
        echo "❌ 未知环境: $ENVIRONMENT"
        echo "用法: $0 {staging|production} [version]"
        exit 1
        ;;
esac

5.3 健康检查脚本

#!/bin/bash
# scripts/health-check.sh

set -e

ENVIRONMENT=$1
MAX_ATTEMPTS=30
ATTEMPT=0

check_health() {
    local url=$1
    local response=$(curl -s -o /dev/null -w "%{http_code}" $url/health)
    
    if [ "$response" = "200" ]; then
        return 0
    else
        return 1
    fi
}

case $ENVIRONMENT in
    "staging")
        URL="https://staging.example.com"
        ;;
    "production")
        URL="https://example.com"
        ;;
    *)
        echo "❌ 未知环境: $ENVIRONMENT"
        exit 1
        ;;
esac

echo "🔍 检查 $ENVIRONMENT 环境健康状态..."

while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
    if check_health $URL; then
        echo "✅ 健康检查通过"
        exit 0
    else
        ATTEMPT=$((ATTEMPT + 1))
        echo "⏳ 等待服务就绪... ($ATTEMPT/$MAX_ATTEMPTS)"
        sleep 10
    fi
done

echo "❌ 健康检查失败: 服务在指定时间内未就绪"
exit 1

6. 监控和告警

6.1 监控配置

# monitoring/prometheus.yml
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'frontend-app'
    static_configs:
      - targets: ['frontend-service:3000']
    metrics_path: '/metrics'
    
  - job_name: 'node-exporter'
    static_configs:
      - targets: ['node-exporter:9100']

6.2 告警规则

# monitoring/alerts.yml
groups:
- name: frontend-app
  rules:
  - alert: HighErrorRate
    expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.1
    for: 5m
    labels:
      severity: critical
    annotations:
      summary: "高错误率检测"
      description: "错误率超过 10%"
  
  - alert: HighResponseTime
    expr: histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) > 1
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "高响应时间"
      description: "95% 分位响应时间超过 1 秒"

7. 最佳实践

7.1 安全最佳实践

# 安全扫描集成
security-scan:
  stage: security
  script:
    # 依赖漏洞扫描
    - npm audit --audit-level high
    # 容器漏洞扫描
    - docker scan $IMAGE_NAME
    # 静态代码安全分析
    - npx semgrep --config=auto .
  allow_failure: false

7.2 性能优化

# 缓存优化
cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
    - node_modules/
    - .npm/
    - .cache/
  policy: pull-push

# 并行执行
test:
  parallel:
    matrix:
      - SUITE: [unit, integration, e2e]
  script:
    - npm run test:$SUITE

这个完整的 CI/CD 配置涵盖了从代码提交到生产部署的全流程,包括代码检查、测试、安全扫描、容器化、部署和监控等关键环节。

posted @ 2025-10-14 22:55  阿木隆1237  阅读(7)  评论(0)    收藏  举报