cicd-pipeline-setup

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

CI/CD Pipeline Setup

CI/CD流水线搭建

Overview

概述

Build automated continuous integration and deployment pipelines that test code, build artifacts, run security checks, and deploy to multiple environments with minimal manual intervention.
构建自动化的持续集成与部署流水线,可测试代码、构建制品、运行安全检查,并在极少人工干预的情况下部署到多环境中。

When to Use

适用场景

  • Automated code testing and quality checks
  • Containerized application builds
  • Multi-environment deployments
  • Release management and versioning
  • Automated security scanning
  • Performance testing integration
  • Artifact management and registry
  • 自动化代码测试与质量检查
  • 容器化应用构建
  • 多环境部署
  • 版本发布管理
  • 自动化安全扫描
  • 性能测试集成
  • 制品管理与仓库

Implementation Examples

实现示例

1. GitHub Actions Workflow

1. GitHub Actions 工作流

yaml
undefined
yaml
undefined

.github/workflows/deploy.yml

.github/workflows/deploy.yml

name: Build and Deploy
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: Setup Node.js ${{ matrix.node-version }}
    uses: actions/setup-node@v4
    with:
      node-version: ${{ matrix.node-version }}
      cache: npm

  - name: Install dependencies
    run: npm ci

  - name: Run linting
    run: npm run lint

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

  - name: Upload coverage to 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: Run Snyk Security Scan
    uses: snyk/actions/node@master
    env:
      SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
    with:
      args: --severity-threshold=high

  - name: Run Trivy vulnerability scan
    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: 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=semver,pattern={{version}}
        type=sha

  - name: Build and push Docker image
    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: Configure AWS credentials
    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: Deploy to ECS
    run: |
      aws ecs update-service \
        --cluster production \
        --service myapp \
        --force-new-deployment

  - name: Verify deployment
    run: |
      aws ecs wait services-stable \
        --cluster production \
        --services myapp
undefined
name: Build and Deploy
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: Setup Node.js ${{ matrix.node-version }}
    uses: actions/setup-node@v4
    with:
      node-version: ${{ matrix.node-version }}
      cache: npm

  - name: Install dependencies
    run: npm ci

  - name: Run linting
    run: npm run lint

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

  - name: Upload coverage to 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: Run Snyk Security Scan
    uses: snyk/actions/node@master
    env:
      SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
    with:
      args: --severity-threshold=high

  - name: Run Trivy vulnerability scan
    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: 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=semver,pattern={{version}}
        type=sha

  - name: Build and push Docker image
    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: Configure AWS credentials
    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: Deploy to ECS
    run: |
      aws ecs update-service \
        --cluster production \
        --service myapp \
        --force-new-deployment

  - name: Verify deployment
    run: |
      aws ecs wait services-stable \
        --cluster production \
        --services myapp
undefined

2. GitLab CI Pipeline

2. GitLab CI 流水线

yaml
undefined
yaml
undefined

.gitlab-ci.yml

.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
undefined
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
undefined

3. Jenkins Pipeline

3. Jenkins 流水线

groovy
// 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}"
            )
        }
    }
}
groovy
// 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 Script

4. CI/CD 脚本

bash
#!/bin/bash
bash
#!/bin/bash

ci-pipeline.sh - Local pipeline validation

ci-pipeline.sh - Local pipeline validation

set -euo pipefail
echo "Starting CI/CD pipeline..."
set -euo pipefail
echo "Starting CI/CD pipeline..."

Code quality

Code quality

echo "Running code quality checks..." npm run lint npm run type-check
echo "Running code quality checks..." npm run lint npm run type-check

Testing

Testing

echo "Running tests..." npm run test:coverage
echo "Running tests..." npm run test:coverage

Build

Build

echo "Building application..." npm run build
echo "Building application..." npm run build

Docker build

Docker build

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

Security scanning

Security scanning

echo "Running security scans..." trivy image myapp:latest --exit-code 0 --severity HIGH
echo "All pipeline stages completed successfully!"
undefined
echo "Running security scans..." trivy image myapp:latest --exit-code 0 --severity HIGH
echo "All pipeline stages completed successfully!"
undefined

Best Practices

最佳实践

✅ DO

✅ 建议做法

  • Fail fast with early validation
  • Run tests in parallel when possible
  • Use caching for dependencies
  • Implement proper secret management
  • Gate production deployments with approval
  • Monitor and alert on pipeline failures
  • Use consistent environment configuration
  • Implement infrastructure as code
  • 尽早验证,快速失败
  • 尽可能并行运行测试
  • 对依赖项使用缓存
  • 实施完善的密钥管理
  • 为生产部署设置审批关卡
  • 监控流水线失败情况并发送告警
  • 使用一致的环境配置
  • 实施基础设施即代码

❌ DON'T

❌ 避免做法

  • Store credentials in pipeline configuration
  • Deploy without automated tests
  • Skip security scanning
  • Allow long-running pipelines
  • Mix staging and production pipelines
  • Ignore test failures
  • Deploy directly to main branch
  • Skip health checks after deployment
  • 在流水线配置中存储凭证
  • 未经过自动化测试就部署
  • 跳过安全扫描
  • 允许长时间运行的流水线
  • 混合 staging 与生产流水线
  • 忽略测试失败
  • 直接部署到主分支
  • 部署后跳过健康检查

Resources

参考资源