🌟 ABP VNext + GitHub Actions:CI/CD 全流程自动化
📚 目录
- 🌟 ABP VNext + GitHub Actions:CI/CD 全流程自动化
- 🤩 TL;DR
- 🔄 全局流程概览
- 1️⃣ 准备工作与项目结构
- 1.1 🛠️ 工具链与 Secrets
- 1.2 📁 项目目录示例
- 2️⃣ 🔨 Build & Test(并行编译与单测)
- 🔄 子流程图
- 3️⃣ 🕵️ Static Analysis(SonarCloud & CodeQL)
- 🔄 子流程图
- 4️⃣ 📦 Package & Publish(NuGet 与 Docker)
- 5️⃣ 🚀 Deploy to Staging(预发布环境)
- 6️⃣ 🏭 Deploy to Production(生产环境)
- 7️⃣ ⏪ Rollback & Alert(自动回滚与告警)
- Rollback Staging
- Rollback Production
- 🔧 ABP VNext 专属集成示例
- Program.cs 示例
- Helm Chart 探针示例 (`charts/myapp/values.yaml`)
- 📦 附录:配置文件
- sonar-project.properties
- CodeQL 配置 (`.github/codeql/codeql-config.yml`)
🤩 TL;DR
- 🚀 端到端流水线:Push → 并行编译/测试 → 静态扫描 (SonarCloud/CodeQL) → NuGet/Docker 打包 → 分环境部署 → 自动回滚
- 🔒 严格审批:在 GitHub Environments 中分别为
staging
与production
配置 Required Reviewers - ⚡ 性能优化:NuGet 缓存、actions/cache、Docker Layer 缓存、并行 Jobs、Concurrency 控制
- 🛠️ 深度契合 ABP VNext:自动执行 EF Core 迁移、Swagger/UI、Health Checks 与 AKS 探针
🔄 全局流程概览
1️⃣ 准备工作与项目结构
1.1 🛠️ 工具链与 Secrets
在仓库 Settings → Secrets 添加以下凭据:
AZURE_CREDENTIALS
:Azure Service Principal JSON(az ad sp create-for-rbac … --sdk-auth
)NUGET_API_KEY
:NuGet.org 发布 KeySONAR_TOKEN
:SonarCloud Access TokenSLACK_WEBHOOK_URL
:Slack Incoming Webhook URLGITHUB_TOKEN
(Actions 内置,用于 GHCR)
🎯 示例 CLI:
az ad sp create-for-rbac \--name "abp-ci-sp" \--role contributor \--scopes /subscriptions/<SUB_ID>/resourceGroups/<RG_NAME> \--sdk-auth > azure-credentials.json
1.2 📁 项目目录示例
.
├─ .github/workflows/ci-cd.yml
├─ src/
│ ├─ MyApp.Domain/
│ ├─ MyApp.Application/
│ ├─ MyApp.EntityFrameworkCore/
│ └─ MyApp.HttpApi.Host/
└─ tests/└─ MyApp.Tests/
2️⃣ 🔨 Build & Test(并行编译与单测)
📝 本 Job 目标:并行 Restore/Build/Test,上传测试报告
build-test:name: 🔨 Build & Testruns-on: ubuntu-lateststrategy:matrix:dotnet-version: ['8.0.x']steps:- name: Checkout Codeuses: actions/checkout@v3- name: Cache NuGet packagesuses: actions/cache@v3with:path: ~/.nuget/packageskey: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}restore-keys: ${{ runner.os }}-nuget-- name: Setup .NET SDKuses: actions/setup-dotnet@v3with:dotnet-version: ${{ matrix.dotnet-version }}cache: true- name: Restore Dependenciesrun: dotnet restore src/MyApp.sln --locked-mode- name: Build Solutionrun: dotnet build src/MyApp.sln --no-restore --configuration Release- name: Run Unit Testsrun: dotnet test tests/MyApp.Tests/MyApp.Tests.csproj \--no-build --configuration Release --logger "trx"- name: Upload Test Resultsuses: actions/upload-artifact@v3with:name: test-resultspath: '**/*.trx'retention-days: 7
🔄 子流程图
3️⃣ 🕵️ Static Analysis(SonarCloud & CodeQL)
📝 本 Job 目标:Shift‐Left 质量与安全保障
static-scan:name: 🕵️ Static Analysisruns-on: ubuntu-latestneeds: build-teststrategy:matrix:tool: ['sonarcloud','codeql']steps:- name: Checkout Full Historyuses: actions/checkout@v3with:fetch-depth: 0# SonarCloud- if: matrix.tool == 'sonarcloud'name: SonarCloud Prepareuses: SonarSource/sonarcloud-github-action@v1.9.0- if: matrix.tool == 'sonarcloud'name: Build for SonarCloudrun: dotnet build src/MyApp.sln --configuration Release- if: matrix.tool == 'sonarcloud'name: SonarCloud Publishuses: SonarSource/sonarcloud-github-action@v1.9.0# CodeQL- if: matrix.tool == 'codeql'name: Initialize CodeQLuses: github/codeql-action/init@v2with:languages: csharpconfig-file: .github/codeql/codeql-config.yml- if: matrix.tool == 'codeql'name: Autobuilduses: github/codeql-action/autobuild@v2- if: matrix.tool == 'codeql'name: Perform CodeQL Analysisuses: github/codeql-action/analyze@v2
🔄 子流程图
4️⃣ 📦 Package & Publish(NuGet 与 Docker)
📝 本 Job 目标:仅在 Push 时执行包与镜像发布
package-publish:name: 📦 Package & Publishif: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop')runs-on: ubuntu-latestneeds: static-scansteps:- name: Checkout Codeuses: actions/checkout@v3# NuGet- name: Pack NuGet Packagerun: dotnet pack src/MyApp.Application/MyApp.Application.csproj \--no-build -o ./artifacts- uses: NuGet/setup-nuget@v2- name: Push to NuGet.orgrun: dotnet nuget push ./artifacts/*.nupkg \--api-key ${{ secrets.NUGET_API_KEY }} \--source https://api.nuget.org/v3/index.json# Docker (GHCR)- name: Login to GHCRuses: docker/login-action@v3with:registry: ghcr.iousername: ${{ github.repository_owner }}password: ${{ secrets.GITHUB_TOKEN }}- name: Build & Push Docker Imageuses: docker/build-push-action@v4with:context: src/MyApp.HttpApi.Hostfile: src/MyApp.HttpApi.Host/Dockerfilepush: truetags: |ghcr.io/${{ github.repository_owner }}/myapp:${{ github.sha }}ghcr.io/${{ github.repository_owner }}/myapp:latestcache-from: type=gha,scope=src-MyApp.HttpApi.Hostcache-to: type=gha,mode=max,scope=src-MyApp.HttpApi.Host
5️⃣ 🚀 Deploy to Staging(预发布环境)
📝 本 Job 目标:在 develop
分支推送时执行,需审批
deploy-staging:name: 🚀 Deploy to Stagingruns-on: ubuntu-latestneeds: package-publishif: github.ref == 'refs/heads/develop'environment: stagingsteps:- name: Azure Loginuses: azure/login@v1with:creds: ${{ secrets.AZURE_CREDENTIALS }}- name: Set AKS Contextuses: azure/aks-set-context@v2with:creds: ${{ secrets.AZURE_CREDENTIALS }}cluster-name: myClusterresource-group: myRG- name: Install EF CLIrun: |dotnet tool install --global dotnet-ef --version 8.* echo "$HOME/.dotnet/tools" >> $GITHUB_PATH- name: Run EF Core Migrationsrun: dotnet ef database update \--project src/MyApp.EntityFrameworkCore/MyApp.EntityFrameworkCore.csproj \--startup-project src/MyApp.HttpApi.Host/MyApp.HttpApi.Host.csproj \--configuration Release- name: Helm Upgrade (Staging)run: |helm upgrade myapp-staging ./charts/myapp \--namespace staging --install \--set image.tag=${{ github.sha }}
6️⃣ 🏭 Deploy to Production(生产环境)
📝 本 Job 目标:在 main
分支推送时执行,需审批
deploy-prod:name: 🏭 Deploy to Productionruns-on: ubuntu-latestneeds: deploy-stagingif: github.ref == 'refs/heads/main'environment: productionsteps:- name: Azure Loginuses: azure/login@v1with:creds: ${{ secrets.AZURE_CREDENTIALS }}- name: Set AKS Contextuses: azure/aks-set-context@v2with:creds: ${{ secrets.AZURE_CREDENTIALS }}cluster-name: myClusterresource-group: myRG- name: Helm Upgrade (Production)run: |helm upgrade myapp-prod ./charts/myapp \--namespace prod --install \--set image.tag=${{ github.sha }}
7️⃣ ⏪ Rollback & Alert(自动回滚与告警)
Rollback Staging
rollback-staging:name: ⏪ Rollback & Notify (Staging)if: failure() && github.ref == 'refs/heads/develop'runs-on: ubuntu-latestneeds: deploy-stagingsteps:- name: Determine Last Successful Revisionid: histrun: |rev=$(helm history myapp-staging -n staging --max 5 \--output json | jq -r '.[] | select(.status=="DEPLOYED") | .revision' | tail -1)if [[ -z "$rev" || "$rev" -le 0 ]]; thenecho "No valid revision to rollback"; exit 1fiecho "::set-output name=revision::$rev"- name: Helm Rollback (Staging)run: helm rollback myapp-staging ${{ steps.hist.outputs.revision }} -n staging- name: Slack Notificationuses: rtCamp/action-slack-notify@v2with:webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}message: ":warning: Staging 部署失败,已回滚到 revision ${{ steps.hist.outputs.revision }}"
Rollback Production
rollback-prod:name: ⏪ Rollback & Notify (Production)if: failure() && github.ref == 'refs/heads/main'runs-on: ubuntu-latestneeds: deploy-prodsteps:- name: Determine Last Successful Revisionid: histrun: |rev=$(helm history myapp-prod -n prod --max 5 \--output json | jq -r '.[] | select(.status=="DEPLOYED") | .revision' | tail -1)if [[ -z "$rev" || "$rev" -le 0 ]]; thenecho "No valid revision to rollback"; exit 1fiecho "::set-output name=revision::$rev"- name: Helm Rollback (Production)run: helm rollback myapp-prod ${{ steps.hist.outputs.revision }} -n prod- name: Slack Notificationuses: rtCamp/action-slack-notify@v2with:webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}message: ":x: Production 部署失败,已回滚到 revision ${{ steps.hist.outputs.revision }}"
🔧 ABP VNext 专属集成示例
Program.cs 示例
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddApplication<MyAppHttpApiHostModule>();
builder.Host.UseAutofac();var app = builder.Build();
app.UseAbpRequestLocalization();
app.UseAbpSwaggerUI(options =>
{options.SwaggerEndpoint("/swagger/v1/swagger.json", "MyApp API V1");
});
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapHealthChecks("/health");
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{Predicate = reg => reg.Name.Contains("ready")
});
app.Run();
Helm Chart 探针示例 (charts/myapp/values.yaml
)
livenessProbe:httpGet:path: /healthport: httpinitialDelaySeconds: 30readinessProbe:httpGet:path: /health/readyport: httpinitialDelaySeconds: 10
📦 附录:配置文件
sonar-project.properties
sonar.projectKey=<YOUR_PROJECT_KEY>
sonar.organization=<YOUR_ORGANIZATION>
sonar.sources=src
sonar.tests=tests
sonar.dotnet.visualstudio.solution.file=src/MyApp.sln
CodeQL 配置 (.github/codeql/codeql-config.yml
)
queries:- security-and-quality- security-and-performance