CI/CD流水线设置Skill cicd-pipeline-setup

设计和实现自动化的持续集成和部署流水线,用于代码测试、构建、安全检查和多环境部署。

DevOps 0 次安装 0 次浏览 更新于 3/3/2026

CI/CD 流水线设置

概览

构建自动化的持续集成和部署流水线,测试代码,构建工件,运行安全检查,并部署到多个环境,最小化手动干预。

何时使用

  • 自动化代码测试和质量检查
  • 容器化应用程序构建
  • 多环境部署
  • 版本控制和发布管理
  • 自动化安全扫描
  • 性能测试集成
  • 工件管理和注册表

实施示例

1. GitHub Actions 工作流

# .github/workflows/deploy.yml
name: 构建和部署

on:
  push:
    branches:
      - main
      - develop
  pull_request:
    branches:
      - main
  workflow_dispatch:

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

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [18.x, 20.x]

    steps:
      - uses: actions/checkout@v4

      - name: 设置 Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: npm

      - name: 安装依赖
        run: npm ci

      - name: 运行 linting
        run: npm run lint

      - name: 运行测试
        run: npm run test:coverage

      - name: 上传覆盖率到 Codecov
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage/coverage-final.json
          flags: unittests
          name: codecov-umbrella

  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: 运行 Snyk 安全扫描
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
        with:
          args: --severity-threshold=high

      - name: 运行 Trivy 漏洞扫描
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          scan-ref: '.'

  build:
    needs: [test, security]
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v4

      - name: 设置 Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: 登录到容器注册表
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: 提取元数据
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=semver,pattern={{version}}
            type=sha

      - name: 构建和推送 Docker 镜像
        uses: docker/build-push-action@v5
        with:
          context: .
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  deploy:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'

    steps:
      - uses: actions/checkout@v4

      - name: 配置 AWS 凭证
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/GithubActionsRole
          aws-region: us-east-1

      - name: 部署到 ECS
        run: |
          aws ecs update-service \
            --cluster production \
            --service myapp \
            --force-new-deployment

      - name: 验证部署
        run: |
          aws ecs wait services-stable \
            --cluster production \
            --services myapp

2. GitLab CI 流水线

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

variables:
  DOCKER_DRIVER: overlay2
  DOCKER_TLS_CERTDIR: ""
  IMAGE_TAG: $CI_COMMIT_SHA

test:
  stage: test
  image: node:20
  cache:
    paths:
      - node_modules/
  script:
    - npm ci
    - npm run lint
    - npm run test:coverage
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml
  coverage: '/Lines\s*:\s*(\d+.\d+)%/'

security:
  stage: test
  image: aquasec/trivy:latest
  script:
    - trivy fs --exit-code 0 --severity HIGH,CRITICAL .

build:
  stage: build
  image: docker:latest
  services:
    - docker:dind
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$IMAGE_TAG .
    - docker push $CI_REGISTRY_IMAGE:$IMAGE_TAG
    - docker tag $CI_REGISTRY_IMAGE:$IMAGE_TAG $CI_REGISTRY_IMAGE:latest
    - docker push $CI_REGISTRY_IMAGE:latest
  only:
    - main
    - develop

deploy_staging:
  stage: deploy
  image: alpine:latest
  before_script:
    - apk add --no-cache aws-cli
  script:
    - aws ecs update-service --cluster staging --service myapp --force-new-deployment
  environment:
    name: staging
    url: https://staging.myapp.com
  only:
    - develop

deploy_production:
  stage: deploy
  image: alpine:latest
  before_script:
    - apk add --no-cache aws-cli
  script:
    - aws ecs update-service --cluster production --service myapp --force-new-deployment
  environment:
    name: production
    url: https://myapp.com
  when: manual
  only:
    - main

3. Jenkins 流水线

// Jenkinsfile
pipeline {
    agent any

    options {
        buildDiscarder(logRotator(numToKeepStr: '10'))
        timeout(time: 1, unit: 'HOURS')
        timestamps()
    }

    environment {
        REGISTRY = 'gcr.io'
        PROJECT_ID = 'my-project'
        IMAGE_NAME = 'myapp'
        IMAGE_TAG = "${BUILD_NUMBER}-${GIT_COMMIT.take(7)}"
    }

    stages {
        stage('Checkout') {
            steps {
                checkout scm
                script {
                    GIT_COMMIT_MSG = sh(
                        script: "git log -1 --pretty=%B",
                        returnStdout: true
                    ).trim()
                }
            }
        }

        stage('Install') {
            steps {
                sh 'npm ci'
            }
        }

        stage('Lint') {
            steps {
                sh 'npm run lint'
            }
        }

        stage('Test') {
            steps {
                sh 'npm run test:coverage'
                publishHTML([
                    reportDir: 'coverage',
                    reportFiles: 'index.html',
                    reportName: 'Coverage Report'
                ])
            }
        }

        stage('Build Image') {
            when {
                branch 'main'
            }
            steps {
                script {
                    sh '''
                        docker build -t ${REGISTRY}/${PROJECT_ID}/${IMAGE_NAME}:${IMAGE_TAG} .
                        docker tag ${REGISTRY}/${PROJECT_ID}/${IMAGE_NAME}:${IMAGE_TAG} \
                                   ${REGISTRY}/${PROJECT_ID}/${IMAGE_NAME}:latest
                    '''
                }
            }
        }

        stage('Push Image') {
            when {
                branch 'main'
            }
            steps {
                sh '''
                    docker push ${REGISTRY}/${PROJECT_ID}/${IMAGE_NAME}:${IMAGE_TAG}
                    docker push ${REGISTRY}/${PROJECT_ID}/${IMAGE_NAME}:latest
                '''
            }
        }

        stage('Deploy Staging') {
            when {
                branch 'develop'
            }
            steps {
                sh '''
                    kubectl set image deployment/myapp myapp=${REGISTRY}/${PROJECT_ID}/${IMAGE_NAME}:${IMAGE_TAG} \
                        -n staging --record
                    kubectl rollout status deployment/myapp -n staging
                '''
            }
        }

        stage('Deploy Production') {
            when {
                branch 'main'
            }
            input {
                message "Deploy to production?"
                ok "Deploy"
            }
            steps {
                sh '''
                    kubectl set image deployment/myapp myapp=${REGISTRY}/${PROJECT_ID}/${IMAGE_NAME}:${IMAGE_TAG} \
                        -n production --record
                    kubectl rollout status deployment/myapp -n production
                '''
            }
        }
    }

    post {
        always {
            cleanWs()
        }
        success {
            slackSend(
                channel: '#deployments',
                message: "Build ${BUILD_NUMBER} succeeded on ${BRANCH_NAME}"
            )
        }
        failure {
            slackSend(
                channel: '#deployments',
                message: "Build ${BUILD_NUMBER} failed on ${BRANCH_NAME}"
            )
        }
    }
}

4. CI/CD 脚本

#!/bin/bash
# ci-pipeline.sh - 本地流水线验证

set -euo pipefail

echo "Starting CI/CD pipeline..."

# 代码质量
echo "Running code quality checks..."
npm run lint
npm run type-check

# 测试
echo "Running tests..."
npm run test:coverage

# 构建
echo "Building application..."
npm run build

# Docker 构建
echo "Building Docker image..."
docker build -t myapp:latest .

# 安全扫描
echo "Running security scans..."
trivy image myapp:latest --exit-code 0 --severity HIGH

echo "All pipeline stages completed successfully!"

最佳实践

✅ DO

  • 早期验证快速失败
  • 可能时并行运行测试
  • 使用依赖缓存
  • 实施适当的密钥管理
  • 通过审批控制生产部署
  • 监控并警告流水线失败
  • 使用一致的环境配置
  • 实施基础设施即代码

❌ DON’T

  • 在流水线配置中存储凭证
  • 无自动化测试即部署
  • 跳过安全扫描
  • 允许长时间运行的流水线
  • 混合使用暂存和生产流水线
  • 忽略测试失败
  • 直接在主分支上部署
  • 部署后跳过健康检查

资源