ci-cd
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCI/CD
CI/CD
Core Principles
核心原则
- Pipeline as code — YAML pipelines committed to the repo. No click-ops in the UI.
- Fast feedback — Build and test on every push. Cache NuGet packages. Fail fast.
- Build once, deploy many — Build the artifact once, promote it through environments (dev → staging → production).
- Never skip tests — Tests gate the pipeline. No deployment without passing tests.
- 流水线即代码 — 将YAML流水线提交到代码仓库。不在UI中进行点击式操作。
- 快速反馈 — 每次推送代码时执行构建和测试。缓存NuGet包。快速失败。
- 一次构建,多次部署 — 仅构建一次工件,然后在各个环境中推广部署(开发 → 预发布 → 生产)。
- 绝不跳过测试 — 测试是流水线的关卡。未通过测试则无法部署。
Patterns
模式
GitHub Actions — Build + Test
GitHub Actions — 构建 + 测试
yaml
undefinedyaml
undefined.github/workflows/ci.yml
.github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
DOTNET_VERSION: '10.0.x'
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
jobs:
build-and-test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:18
env:
POSTGRES_DB: testdb
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v5
- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Restore
run: dotnet restore
- name: Build
run: dotnet build --no-restore --configuration Release
- name: Format check
run: dotnet format --verify-no-changes --no-restore
- name: Test
run: dotnet test --no-build --configuration Release --logger trx --results-directory TestResults
env:
ConnectionStrings__Default: "Host=localhost;Database=testdb;Username=postgres;Password=postgres"
- name: Publish test results
uses: actions/upload-artifact@v5
if: always()
with:
name: test-results
path: TestResults/*.trxundefinedname: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
DOTNET_VERSION: '10.0.x'
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
jobs:
build-and-test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:18
env:
POSTGRES_DB: testdb
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v5
- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Restore
run: dotnet restore
- name: Build
run: dotnet build --no-restore --configuration Release
- name: Format check
run: dotnet format --verify-no-changes --no-restore
- name: Test
run: dotnet test --no-build --configuration Release --logger trx --results-directory TestResults
env:
ConnectionStrings__Default: "Host=localhost;Database=testdb;Username=postgres;Password=postgres"
- name: Publish test results
uses: actions/upload-artifact@v5
if: always()
with:
name: test-results
path: TestResults/*.trxundefinedGitHub Actions — Build + Publish Docker Image
GitHub Actions — 构建 + 发布Docker镜像
yaml
undefinedyaml
undefined.github/workflows/publish.yml
.github/workflows/publish.yml
name: Publish
on:
push:
tags: ['v*']
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v5
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract version from tag
id: version
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:${{ steps.version.outputs.VERSION }}
ghcr.io/${{ github.repository }}:latestundefinedname: Publish
on:
push:
tags: ['v*']
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v5
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract version from tag
id: version
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:${{ steps.version.outputs.VERSION }}
ghcr.io/${{ github.repository }}:latestundefinedAzure DevOps — Build + Test
Azure DevOps — 构建 + 测试
Same restore → build → format → test flow as GitHub Actions. Key differences:
yaml
undefined流程与GitHub Actions一致:还原 → 构建 → 格式检查 → 测试。主要差异:
yaml
undefinedazure-pipelines.yml
azure-pipelines.yml
trigger:
branches:
include: [main]
paths:
exclude: ['*.md', docs/]
pool:
vmImage: 'ubuntu-latest' # vs runs-on: ubuntu-latest
variables:
dotnetVersion: '10.0.x'
trigger:
branches:
include: [main]
paths:
exclude: ['*.md', docs/]
pool:
vmImage: 'ubuntu-latest' # 对应GitHub Actions的runs-on: ubuntu-latest
variables:
dotnetVersion: '10.0.x'
Key task differences from GitHub Actions:
与GitHub Actions的主要任务差异:
Setup .NET: task: UseDotNet@2 (inputs: version: $(dotnetVersion))
Setup .NET: task: UseDotNet@2 (inputs: version: $(dotnetVersion))
Test results: task: PublishTestResults@2 (testResultsFormat: VSTest)
测试结果: task: PublishTestResults@2 (testResultsFormat: VSTest)
Steps use script:
+ displayName:
instead of - name:
+ run:
script:displayName:- name:run:步骤使用script:
+ displayName:
替代- name:
+ run:
script:displayName:- name:run:Services (e.g., Postgres) require a separate Docker task or pipeline service connection
服务(如Postgres)需要单独的Docker任务或流水线服务连接
undefinedundefinedNuGet Package Publishing
NuGet包发布
yaml
undefinedyaml
undefinedPart of GitHub Actions workflow
GitHub Actions工作流的一部分
-
name: Pack run: dotnet pack src/MyLibrary -c Release -o ./nupkg --no-build
-
name: Push to NuGet run: dotnet nuget push ./nupkg/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json
undefined-
name: Pack run: dotnet pack src/MyLibrary -c Release -o ./nupkg --no-build
-
name: Push to NuGet run: dotnet nuget push ./nupkg/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json
undefinedAnti-patterns
反模式
Don't Build Different Artifacts per Environment
不要为每个环境构建不同的工件
yaml
undefinedyaml
undefinedBAD — building separately for each environment
错误示例 — 为每个环境单独构建
- script: dotnet publish -c Debug # for dev
- script: dotnet publish -c Release # for prod
- script: dotnet publish -c Debug # 开发环境
- script: dotnet publish -c Release # 生产环境
GOOD — build once, deploy everywhere
正确示例 — 一次构建,全环境部署
- script: dotnet publish -c Release -o ./publish
- script: dotnet publish -c Release -o ./publish
Then deploy the same ./publish artifact to dev, staging, prod
然后将同一个./publish工件部署到开发、预发布、生产环境
undefinedundefinedDon't Skip Format Checks in CI
不要在CI中跳过格式检查
yaml
undefinedyaml
undefinedBAD — no format enforcement
错误示例 — 没有格式校验
steps:
- run: dotnet build
- run: dotnet test
steps:
- run: dotnet build
- run: dotnet test
GOOD — format check catches style issues early
正确示例 — 格式检查提前发现风格问题
steps:
- run: dotnet build
- run: dotnet format --verify-no-changes
- run: dotnet test
undefinedsteps:
- run: dotnet build
- run: dotnet format --verify-no-changes
- run: dotnet test
undefinedDon't Hardcode Secrets in Pipelines
不要在流水线中硬编码密钥
yaml
undefinedyaml
undefinedBAD — secret in pipeline YAML
错误示例 — 密钥写在流水线YAML中
env:
DB_PASSWORD: "my-secret-password"
env:
DB_PASSWORD: "my-secret-password"
GOOD — use pipeline secrets
正确示例 — 使用流水线密钥
env:
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
undefinedenv:
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
undefinedDecision Guide
决策指南
| Scenario | Recommendation |
|---|---|
| Open source project | GitHub Actions |
| Enterprise with Azure | Azure DevOps Pipelines |
| Docker deployment | Multi-stage build in CI, push to container registry |
| NuGet library | Build → Test → Pack → Push on tag |
| Database migrations | Run in CI test stage, script for production |
| Environment promotion | Same artifact, different configuration |
| 场景 | 推荐方案 |
|---|---|
| 开源项目 | GitHub Actions |
| 使用Azure的企业 | Azure DevOps Pipelines |
| Docker部署 | CI中使用多阶段构建,推送到容器注册表 |
| NuGet类库 | 打标签时执行构建 → 测试 → 打包 → 推送 |
| 数据库迁移 | 在CI测试阶段运行,生产环境使用脚本执行 |
| 环境推广 | 使用同一工件,不同配置 |