ci-cd

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

CI/CD 流水线配置

CI/CD 流水线配置

概述

概述

Jenkins、GitLab CI、GitHub Actions 等 CI/CD 工具配置技能。
Jenkins、GitLab CI、GitHub Actions 等 CI/CD 工具配置技能。

GitHub Actions

GitHub Actions

基础工作流

基础工作流

yaml
undefined
yaml
undefined

.github/workflows/ci.yml

.github/workflows/ci.yml

name: CI
on: push: branches: [main, develop] pull_request: branches: [main]
jobs: build: runs-on: ubuntu-latest
steps:
  - uses: actions/checkout@v4
  
  - name: Setup Node.js
    uses: actions/setup-node@v4
    with:
      node-version: '18'
      cache: 'npm'
  
  - name: Install dependencies
    run: npm ci
  
  - name: Run tests
    run: npm test
  
  - name: Build
    run: npm run build
undefined
name: CI
on: push: branches: [main, develop] pull_request: branches: [main]
jobs: build: runs-on: ubuntu-latest
steps:
  - uses: actions/checkout@v4
  
  - name: Setup Node.js
    uses: actions/setup-node@v4
    with:
      node-version: '18'
      cache: 'npm'
  
  - name: Install dependencies
    run: npm ci
  
  - name: Run tests
    run: npm test
  
  - name: Build
    run: npm run build
undefined

矩阵构建

矩阵构建

yaml
jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [16, 18, 20]
    
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm test
yaml
jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [16, 18, 20]
    
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm test

Docker 构建与推送

Docker 构建与推送

yaml
jobs:
  docker:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}
      
      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: user/app:${{ github.sha }}
yaml
jobs:
  docker:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}
      
      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: user/app:${{ github.sha }}

部署到 Kubernetes

部署到 Kubernetes

yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    needs: build
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Configure kubectl
        uses: azure/k8s-set-context@v3
        with:
          kubeconfig: ${{ secrets.KUBE_CONFIG }}
      
      - name: Deploy
        run: |
          kubectl set image deployment/app app=user/app:${{ github.sha }}
          kubectl rollout status deployment/app
yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    needs: build
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Configure kubectl
        uses: azure/k8s-set-context@v3
        with:
          kubeconfig: ${{ secrets.KUBE_CONFIG }}
      
      - name: Deploy
        run: |
          kubectl set image deployment/app app=user/app:${{ github.sha }}
          kubectl rollout status deployment/app

GitLab CI

GitLab CI

基础配置

基础配置

yaml
undefined
yaml
undefined

.gitlab-ci.yml

.gitlab-ci.yml

stages:
  • build
  • test
  • deploy
variables: DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
build: stage: build image: docker:latest services: - docker:dind script: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker build -t $DOCKER_IMAGE . - docker push $DOCKER_IMAGE
test: stage: test image: node:18 script: - npm ci - npm test coverage: '/Coverage: \d+.\d+%/'
deploy: stage: deploy image: bitnami/kubectl:latest script: - kubectl set image deployment/app app=$DOCKER_IMAGE only: - main environment: name: production url: https://app.example.com
undefined
stages:
  • build
  • test
  • deploy
variables: DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
build: stage: build image: docker:latest services: - docker:dind script: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker build -t $DOCKER_IMAGE . - docker push $DOCKER_IMAGE
test: stage: test image: node:18 script: - npm ci - npm test coverage: '/Coverage: \d+.\d+%/'
deploy: stage: deploy image: bitnami/kubectl:latest script: - kubectl set image deployment/app app=$DOCKER_IMAGE only: - main environment: name: production url: https://app.example.com
undefined

多环境部署

多环境部署

yaml
.deploy_template: &deploy_template
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - kubectl config use-context $KUBE_CONTEXT
    - kubectl set image deployment/app app=$DOCKER_IMAGE

deploy_staging:
  <<: *deploy_template
  variables:
    KUBE_CONTEXT: staging
  environment:
    name: staging
  only:
    - develop

deploy_production:
  <<: *deploy_template
  variables:
    KUBE_CONTEXT: production
  environment:
    name: production
  only:
    - main
  when: manual
yaml
.deploy_template: &deploy_template
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - kubectl config use-context $KUBE_CONTEXT
    - kubectl set image deployment/app app=$DOCKER_IMAGE

deploy_staging:
  <<: *deploy_template
  variables:
    KUBE_CONTEXT: staging
  environment:
    name: staging
  only:
    - develop

deploy_production:
  <<: *deploy_template
  variables:
    KUBE_CONTEXT: production
  environment:
    name: production
  only:
    - main
  when: manual

Jenkins

Jenkins

Jenkinsfile(声明式)

Jenkinsfile(声明式)

groovy
// Jenkinsfile
pipeline {
    agent any
    
    environment {
        DOCKER_IMAGE = "user/app:${BUILD_NUMBER}"
        DOCKER_CREDENTIALS = credentials('docker-hub')
    }
    
    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }
        
        stage('Build') {
            steps {
                sh 'npm ci'
                sh 'npm run build'
            }
        }
        
        stage('Test') {
            steps {
                sh 'npm test'
            }
            post {
                always {
                    junit 'test-results/*.xml'
                }
            }
        }
        
        stage('Docker Build') {
            steps {
                sh "docker build -t ${DOCKER_IMAGE} ."
            }
        }
        
        stage('Docker Push') {
            steps {
                sh "echo ${DOCKER_CREDENTIALS_PSW} | docker login -u ${DOCKER_CREDENTIALS_USR} --password-stdin"
                sh "docker push ${DOCKER_IMAGE}"
            }
        }
        
        stage('Deploy') {
            when {
                branch 'main'
            }
            steps {
                sh "kubectl set image deployment/app app=${DOCKER_IMAGE}"
            }
        }
    }
    
    post {
        always {
            cleanWs()
        }
        success {
            slackSend channel: '#deployments', message: "Build ${BUILD_NUMBER} succeeded"
        }
        failure {
            slackSend channel: '#deployments', message: "Build ${BUILD_NUMBER} failed"
        }
    }
}
groovy
// Jenkinsfile
pipeline {
    agent any
    
    environment {
        DOCKER_IMAGE = "user/app:${BUILD_NUMBER}"
        DOCKER_CREDENTIALS = credentials('docker-hub')
    }
    
    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }
        
        stage('Build') {
            steps {
                sh 'npm ci'
                sh 'npm run build'
            }
        }
        
        stage('Test') {
            steps {
                sh 'npm test'
            }
            post {
                always {
                    junit 'test-results/*.xml'
                }
            }
        }
        
        stage('Docker Build') {
            steps {
                sh "docker build -t ${DOCKER_IMAGE} ."
            }
        }
        
        stage('Docker Push') {
            steps {
                sh "echo ${DOCKER_CREDENTIALS_PSW} | docker login -u ${DOCKER_CREDENTIALS_USR} --password-stdin"
                sh "docker push ${DOCKER_IMAGE}"
            }
        }
        
        stage('Deploy') {
            when {
                branch 'main'
            }
            steps {
                sh "kubectl set image deployment/app app=${DOCKER_IMAGE}"
            }
        }
    }
    
    post {
        always {
            cleanWs()
        }
        success {
            slackSend channel: '#deployments', message: "Build ${BUILD_NUMBER} succeeded"
        }
        failure {
            slackSend channel: '#deployments', message: "Build ${BUILD_NUMBER} failed"
        }
    }
}

Jenkinsfile(脚本式)

Jenkinsfile(脚本式)

groovy
node {
    stage('Checkout') {
        checkout scm
    }
    
    stage('Build') {
        sh 'npm ci'
        sh 'npm run build'
    }
    
    stage('Test') {
        try {
            sh 'npm test'
        } finally {
            junit 'test-results/*.xml'
        }
    }
    
    if (env.BRANCH_NAME == 'main') {
        stage('Deploy') {
            sh 'kubectl apply -f k8s/'
        }
    }
}
groovy
node {
    stage('Checkout') {
        checkout scm
    }
    
    stage('Build') {
        sh 'npm ci'
        sh 'npm run build'
    }
    
    stage('Test') {
        try {
            sh 'npm test'
        } finally {
            junit 'test-results/*.xml'
        }
    }
    
    if (env.BRANCH_NAME == 'main') {
        stage('Deploy') {
            sh 'kubectl apply -f k8s/'
        }
    }
}

通用模式

通用模式

语义化版本发布

语义化版本发布

yaml
undefined
yaml
undefined

GitHub Actions

GitHub Actions

name: Release
on: push: tags: - 'v*'
jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
  - name: Get version
    id: version
    run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
  
  - name: Build
    run: npm run build
  
  - name: Create Release
    uses: softprops/action-gh-release@v1
    with:
      files: dist/*
      generate_release_notes: true
undefined
name: Release
on: push: tags: - 'v*'
jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
  - name: Get version
    id: version
    run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
  
  - name: Build
    run: npm run build
  
  - name: Create Release
    uses: softprops/action-gh-release@v1
    with:
      files: dist/*
      generate_release_notes: true
undefined

缓存依赖

缓存依赖

yaml
undefined
yaml
undefined

GitHub Actions

GitHub Actions

  • name: Cache node modules uses: actions/cache@v3 with: path: ~/.npm key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node-
  • name: Cache node modules uses: actions/cache@v3 with: path: ~/.npm key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-node-

GitLab CI

GitLab CI

cache: key: ${CI_COMMIT_REF_SLUG} paths: - node_modules/
undefined
cache: key: ${CI_COMMIT_REF_SLUG} paths: - node_modules/
undefined

并行测试

并行测试

yaml
undefined
yaml
undefined

GitHub Actions

GitHub Actions

jobs: test: runs-on: ubuntu-latest strategy: matrix: shard: [1, 2, 3, 4] steps: - uses: actions/checkout@v4 - run: npm ci - run: npm test -- --shard=${{ matrix.shard }}/4
undefined
jobs: test: runs-on: ubuntu-latest strategy: matrix: shard: [1, 2, 3, 4] steps: - uses: actions/checkout@v4 - run: npm ci - run: npm test -- --shard=${{ matrix.shard }}/4
undefined

条件执行

条件执行

yaml
undefined
yaml
undefined

GitHub Actions

GitHub Actions

jobs: deploy: if: github.ref == 'refs/heads/main' && github.event_name == 'push' runs-on: ubuntu-latest steps: - run: echo "Deploying..."
jobs: deploy: if: github.ref == 'refs/heads/main' && github.event_name == 'push' runs-on: ubuntu-latest steps: - run: echo "Deploying..."

GitLab CI

GitLab CI

deploy: rules: - if: $CI_COMMIT_BRANCH == "main" when: manual - if: $CI_COMMIT_TAG when: always
undefined
deploy: rules: - if: $CI_COMMIT_BRANCH == "main" when: manual - if: $CI_COMMIT_TAG when: always
undefined

常见场景

常见场景

场景 1:PR 检查

场景 1:PR 检查

yaml
name: PR Check

on:
  pull_request:
    types: [opened, synchronize, reopened]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run lint
      
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm test
yaml
name: PR Check

on:
  pull_request:
    types: [opened, synchronize, reopened]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run lint
      
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm test

场景 2:定时任务

场景 2:定时任务

yaml
name: Scheduled Job

on:
  schedule:
    - cron: '0 2 * * *'  # 每天凌晨2点

jobs:
  cleanup:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Running cleanup..."
yaml
name: Scheduled Job

on:
  schedule:
    - cron: '0 2 * * *'  # 每天凌晨2点

jobs:
  cleanup:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Running cleanup..."

场景 3:手动触发

场景 3:手动触发

yaml
name: Manual Deploy

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

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Deploying to ${{ inputs.environment }}"
yaml
name: Manual Deploy

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

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Deploying to ${{ inputs.environment }}"

故障排查

故障排查

问题排查方法
构建失败查看日志、本地复现
权限问题检查 secrets、token
缓存失效检查 cache key
超时增加 timeout、优化步骤
问题排查方法
构建失败查看日志、本地复现
权限问题检查 secrets、token
缓存失效检查 cache key
超时增加 timeout、优化步骤