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