CI/CD 最佳实践
大约 9 分钟约 2702 字
CI/CD 最佳实践
简介
持续集成和持续交付(CI/CD)是现代 DevOps 实践的核心支柱,它通过自动化构建、测试和部署流程,实现代码从提交到生产的快速、可靠交付。本文将从流水线设计、质量门禁、制品管理和多环境管理四个维度,深入讲解企业级 CI/CD 的最佳实践,帮助团队构建高效、安全的自动化交付流水线,缩短发布周期、降低发布风险、提升交付质量。
特点
流水线设计
设计合理的流水线结构是 CI/CD 成功的基础,应遵循阶段化、并行化和幂等性原则。
# .github/workflows/ci-cd.yml - 完整 CI/CD 流水线
name: CI/CD Pipeline
on:
push:
branches: [main, develop, 'release/**']
pull_request:
branches: [main, develop]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# ========== 阶段 1: 代码质量检查 ==========
code-quality:
name: 代码质量检查
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 安装依赖
run: npm ci --cache .npm
- name: ESLint 代码检查
run: npx eslint . --format json --output-file eslint-report.json
- name: Prettier 格式检查
run: npx prettier --check .
- name: TypeScript 类型检查
run: npx tsc --noEmit
# ========== 阶段 2: 测试 ==========
test:
name: 测试套件
runs-on: ubuntu-latest
needs: code-quality
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: test_db
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7-alpine
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
- name: 运行单元测试
run: npm run test:unit -- --coverage --coverageReporters=lcov
env:
NODE_ENV: test
- name: 运行集成测试
run: npm run test:integration
env:
DATABASE_URL: postgresql://test:test@localhost:5432/test_db
REDIS_URL: redis://localhost:6379
- name: 上传测试覆盖率报告
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
fail_ci_if_error: false
# ========== 阶段 3: 安全扫描 ==========
security-scan:
name: 安全扫描
runs-on: ubuntu-latest
needs: code-quality
steps:
- uses: actions/checkout@v4
- name: 依赖漏洞扫描
run: npm audit --audit-level=high
continue-on-error: true
- name: SAST 代码安全扫描
uses: SonarSource/sonarcloud-github-action@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
with:
args: >
-Dsonar.exclusions=**/*.test.ts,**/*.spec.ts
-Dsonar.javascript.lcov.reportPaths=coverage/lcov.info
- name: 容器镜像安全扫描
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
severity: 'HIGH,CRITICAL'
exit-code: '1'
# ========== 阶段 4: 构建与推送 ==========
build:
name: 构建镜像
runs-on: ubuntu-latest
needs: [test, security-scan]
permissions:
contents: read
packages: write
outputs:
image_digest: ${{ steps.build.outputs.digest }}
image_tag: ${{ steps.meta.outputs.version }}
steps:
- uses: actions/checkout@v4
- name: 登录容器注册表
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: 提取 Docker 元数据
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha,prefix=
type=ref,event=branch
type=semver,pattern={{version}}
type=raw,value=latest,enable={{is_default_branch}}
- name: 构建并推送镜像
id: build
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
build-args: |
BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
VCS_REF=${{ github.sha }}质量门禁
质量门禁是保障代码质量的关键机制,在每个阶段设置明确的质量标准。
# ========== 质量门禁检查 ==========
quality-gate:
name: 质量门禁
runs-on: ubuntu-latest
needs: [test, security-scan]
if: always()
steps:
- name: 检查所有前置任务状态
run: |
echo "检查质量门禁..."
# 检查测试结果
if [ "${{ needs.test.result }}" != "success" ]; then
echo "::error::测试未通过"
exit 1
fi
# 检查安全扫描结果
if [ "${{ needs.security-scan.result }}" != "success" ]; then
echo "::error::安全扫描未通过"
exit 1
fi
echo "所有质量门禁检查通过"
# ========== 阶段 5: 部署到预发环境 ==========
deploy-staging:
name: 部署到预发环境
runs-on: ubuntu-latest
needs: [build, quality-gate]
if: github.ref == 'refs/heads/develop'
environment:
name: staging
url: https://staging.example.com
steps:
- uses: actions/checkout@v4
- name: 部署到预发环境
run: |
echo "部署镜像 ${{ needs.build.outputs.image_tag }} 到预发环境"
helm upgrade --install web-platform ./k8s-deployment \
--namespace staging \
-f ./k8s-deployment/values-staging.yaml \
--set image.tag=${{ needs.build.outputs.image_tag }} \
--wait --timeout 5m --atomic
- name: 运行端到端测试
run: |
npm run test:e2e -- --base-url=https://staging.example.com
- name: 运行冒烟测试
run: |
sleep 30
curl -sf https://staging.example.com/healthz || exit 1制品管理
规范的制品管理确保构建产物的一致性、可追溯性和不可变性。
# ========== 阶段 6: 制品发布 ==========
release:
name: 发布制品
runs-on: ubuntu-latest
needs: [build]
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write
packages: write
steps:
- uses: actions/checkout@v4
- name: 生成变更日志
id: changelog
uses: mikepenz/release-changelog-builder-action@v4
with:
configuration: "./changelog-config.json"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: 创建 GitHub Release
uses: softprops/action-gh-release@v1
with:
body: ${{ steps.changelog.outputs.changelog }}
draft: false
prerelease: ${{ contains(github.ref, '-rc') }}
generate_release_notes: true# 制品版本管理脚本
#!/bin/bash
set -euo pipefail
# 语义化版本管理
VERSION_FILE="VERSION"
get_current_version() {
cat "$VERSION_FILE"
}
bump_version() {
local bump_type="$1"
local current
current=$(get_current_version)
IFS='.' read -r major minor patch <<< "$current"
case "$bump_type" in
major)
major=$((major + 1))
minor=0
patch=0
;;
minor)
minor=$((minor + 1))
patch=0
;;
patch)
patch=$((patch + 1))
;;
*)
echo "用法: $0 {major|minor|patch}"
exit 1
;;
esac
local new_version="${major}.${minor}.${patch}"
echo "$new_version" > "$VERSION_FILE"
echo "版本更新: $current -> $new_version"
# 创建 Git 标签
git add "$VERSION_FILE"
git commit -m "chore: bump version to $new_version"
git tag -a "v$new_version" -m "Release v$new_version"
git push origin main --tags
}
# 制品签名与验证
sign_artifact() {
local artifact_path="$1"
local checksum_file="${artifact_path}.sha256"
sha256sum "$artifact_path" > "$checksum_file"
gpg --detach-sign --armor "$checksum_file"
echo "制品已签名: $artifact_path"
}
# 制品推送与归档
push_artifact() {
local image_tag="$1"
local registry="ghcr.io/myorg"
# 推送镜像到多个注册表(灾备)
docker push "${registry}/web-platform:${image_tag}"
docker push "registry.example.com/web-platform:${image_tag}"
# 归档 Helm Chart
helm package ./k8s-deployment --version "${image_tag}" --app-version "${image_tag}"
helm push "web-platform-${image_tag}.tgz" oci://"registry.example.com/charts"
}多环境管理
# env-config/production.yaml - 生产环境配置
apiVersion: v1
kind: ConfigMap
metadata:
name: env-config
namespace: production
data:
ENVIRONMENT: "production"
LOG_LEVEL: "info"
FEATURE_FLAGS: |
{
"new_dashboard": true,
"api_v2": true,
"dark_mode": true
}
---
# k8s-deployment/values-production.yaml
replicaCount: 5
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: "2"
memory: 2Gi
autoscaling:
enabled: true
minReplicas: 5
maxReplicas: 30
targetCPUUtilizationPercentage: 70
targetMemoryUtilizationPercentage: 80
podDisruptionBudget:
enabled: true
minAvailable: "80%"
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- web-platform
topologyKey: topology.kubernetes.io/zone
# 环境差异对照表
# | 配置项 | 开发环境 | 测试环境 | 预发环境 | 生产环境 |
# |--------------|---------|---------|---------|------------|
# | replicas | 1 | 2 | 2 | 5+ |
# | CPU request | 100m | 200m | 250m | 500m |
# | Memory limit | 256Mi | 512Mi | 1Gi | 2Gi |
# | HPA | 关闭 | 关闭 | 开启 | 开启 |
# | PDB | 关闭 | 关闭 | 开启 | 开启 |
# | 亲和性 | 关闭 | 关闭 | 开启 | 开启(多AZ) | # ========== 部署到生产环境 ==========
deploy-production:
name: 部署到生产环境
runs-on: ubuntu-latest
needs: [build, quality-gate]
if: github.ref == 'refs/heads/main'
environment:
name: production
url: https://app.example.com
steps:
- uses: actions/checkout@v4
- name: 金丝雀部署(Canary Release)
run: |
# 首先部署金丝雀版本(1个副本)
helm upgrade --install web-platform-canary ./k8s-deployment \
--namespace production \
-f ./k8s-deployment/values-production.yaml \
--set replicaCount=1 \
--set image.tag=${{ needs.build.outputs.image_tag }} \
--set nameSuffix="-canary" \
--wait --timeout 5m
- name: 金丝雀健康检查
run: |
# 等待金丝雀版本稳定运行
sleep 60
# 检查金丝雀版本错误率
ERROR_RATE=$(curl -s http://prometheus:9090/api/v1/query \
--data-urlencode 'query=rate(http_requests_total{job="web-platform-canary",status=~"5.."}[5m]) / rate(http_requests_total{job="web-platform-canary"}[5m])' \
| jq -r '.data.result[0].value[1]')
echo "金丝雀版本错误率: ${ERROR_RATE}"
if (( $(echo "$ERROR_RATE > 0.01" | bc -l) )); then
echo "金丝雀版本错误率过高,终止发布"
helm uninstall web-platform-canary -n production
exit 1
fi
- name: 全量发布
run: |
# 全量更新
helm upgrade --install web-platform ./k8s-deployment \
--namespace production \
-f ./k8s-deployment/values-production.yaml \
--set image.tag=${{ needs.build.outputs.image_tag }} \
--wait --timeout 10m --atomic
# 清理金丝雀资源
helm uninstall web-platform-canary -n production 2>/dev/null || true优点
缺点
总结
CI/CD 最佳实践涵盖了流水线设计、质量门禁、制品管理和多环境管理四大核心领域。通过阶段化的流水线设计确保构建过程可重复可追溯,自动化的质量门禁从代码质量、测试覆盖到安全扫描全方位保障交付质量,规范化的制品管理实现版本的精确控制和快速回滚,渐进式的多环境部署策略通过金丝雀发布等手段有效降低发布风险。建立一套完善的 CI/CD 体系需要持续的投入和优化,但它带来的效率提升和风险降低回报是企业数字化转型中不可或缺的一环。
关键知识点
- DevOps 主题的核心是让交付更快、更稳、更可审计。
- 自动化不是把命令脚本化,而是把失败、回滚、权限和观测一起设计进去。
- 生产链路必须明确制品、环境、凭据、配置和责任边界。
项目落地视角
- 把流水线拆成构建、测试、制品、部署、验证和回滚几个阶段。
- 为关键步骤补齐日志、指标、通知和人工兜底点。
- 定期演练扩容、回滚、故障注入和灾备切换。
常见误区
- 只关注部署成功,不关注失败恢复和审计追踪。
- 把环境差异藏在临时脚本或人工操作里。
- 上线频率高了以后,没有标准化制品和配置管理。
进阶路线
- 继续补齐 GitOps、可观测性、平台工程和成本治理。
- 把主题和应用架构、安全、权限、备份恢复联动起来理解。
- 形成团队级平台能力,而不是每个项目重复造轮子。
适用场景
- 当你准备把《CI/CD 最佳实践》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合构建自动化交付、基础设施治理、监控告警和生产发布体系。
- 当团队规模扩大、发布频率提升或环境变多时,这类主题会显著影响交付效率。
落地建议
- 所有自动化流程尽量做到幂等、可审计、可回滚。
- 把制品、变量、凭据和执行权限分层管理。
- 定期演练扩容、回滚、密钥轮换和灾备恢复。
排错清单
- 先定位失败发生在代码、构建、制品、环境还是权限层。
- 检查流水线变量、凭据、镜像标签和目标环境配置是否一致。
- 如果问题偶发,重点看并发发布、资源争抢和外部依赖抖动。
复盘问题
- 如果把《CI/CD 最佳实践》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《CI/CD 最佳实践》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《CI/CD 最佳实践》最大的收益和代价分别是什么?
