GitHub Actions CI/CD cho .NET 2026 — OIDC, Egress Firewall và Immutable Actions cho Pipeline Production
Posted on: 4/17/2026 3:11:11 PM
Table of contents
- Kiến trúc CI/CD Pipeline cho .NET 10
- Chiến lược Caching — Giảm 80% Thời gian Build
- Workflow YAML hoàn chỉnh cho .NET 10
- OIDC — Deploy Zero-Credential lên Azure
- Bảo mật Supply Chain — Security Roadmap 2026
- Parallel Steps — Tính năng được yêu cầu nhiều nhất
- Reusable Workflows — DRY cho Multi-Repo
- Matrix Builds — Test song song trên nhiều phiên bản
- Runner Pricing 2026 — Mạnh hơn, rẻ hơn
- Checklist Bảo mật cho CI/CD Production
- Roadmap GitHub Actions 2026
- Kết luận
Mỗi lần developer push code, một chuỗi sự kiện phức tạp cần xảy ra: build, test, phân tích tĩnh, đóng gói container, deploy lên staging, chạy smoke test, rồi mới promote lên production. Làm thủ công? Mất 45 phút và dễ sai. Tự động hoá với GitHub Actions? Dưới 8 phút và không bao giờ quên bước nào. Năm 2026, GitHub Actions không chỉ là CI/CD runner mà đã trở thành nền tảng DevOps toàn diện với hệ thống bảo mật cấp enterprise, parallel steps, OIDC authentication, và egress firewall — tất cả tích hợp native trong GitHub.
Bài viết này đi sâu vào kiến trúc CI/CD pipeline cho .NET 10 trên GitHub Actions: từ caching NuGet/Docker giảm 80% thời gian build, reusable workflows cho multi-repo, đến bảo mật supply chain với immutable actions và triển khai zero-credential lên Azure qua OIDC.
Kiến trúc CI/CD Pipeline cho .NET 10
Một pipeline .NET production-grade cần tách biệt rõ ràng CI (Continuous Integration — mỗi push) và CD (Continuous Deployment — chỉ merge vào main). Ranh giới này ngăn việc deploy nhầm và giữ feedback loop nhanh cho developer.
graph LR
subgraph CI["CI — Mỗi Push/PR"]
direction TB
A["Checkout + Cache Restore"] --> B["dotnet restore"]
B --> C["dotnet build"]
C --> D["dotnet test"]
D --> E["Code Analysis"]
end
subgraph CD["CD — Merge vào main"]
direction TB
F["Docker Build + Push"] --> G["Deploy Staging"]
G --> H["Smoke Test"]
H --> I["Deploy Production"]
end
CI --> CD
style CI fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style CD fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50
style A fill:#e94560,stroke:#fff,color:#fff
style I fill:#4CAF50,stroke:#fff,color:#fff
Hình 1: Tổng quan pipeline CI/CD tách biệt cho .NET 10
CI trên mọi push, CD chỉ trên main
Quy tắc vàng: CI chạy trên mỗi push và pull request để phát hiện lỗi sớm. CD chỉ trigger khi merge vào nhánh main — ngăn chặn triệt để việc deploy nhầm từ feature branch. Pattern này được GitHub khuyến nghị chính thức cho mọi dự án production.
Chiến lược Caching — Giảm 80% Thời gian Build
Cache là yếu tố quyết định tốc độ pipeline. Trên GitHub Actions, có hai loại cache quan trọng cho .NET: NuGet package cache và Docker BuildKit layer cache.
NuGet Package Caching
Không có cache, dotnet restore phải download lại toàn bộ NuGet packages mỗi lần chạy — thường mất 60–90 giây cho project vừa. Với actions/cache keyed trên hash các file .csproj, cache hit giảm restore xuống dưới 5 giây.
- name: Cache NuGet packages
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj') }}
restore-keys: |
nuget-${{ runner.os }}-Cache key sử dụng hash của tất cả file .csproj — bất kỳ thay đổi nào trong dependencies đều invalidate cache đúng cách. restore-keys cho phép fallback về cache cũ nhất của cùng OS, vẫn tiết kiệm đáng kể so với cold download.
Docker BuildKit Layer Caching
Đối với pipeline đóng gói container, Docker BuildKit layer cache là game-changer. Sử dụng GitHub Actions cache backend (type=gha), build time giảm từ 4–5 phút xuống dưới 60 giây khi cache hit.
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=maxTuỳ chọn mode=max cache toàn bộ layers kể cả intermediate — tối ưu cho project nhiều stages trong Dockerfile. Kết hợp với docker/metadata-action để tự động generate tags: sha-abc1234 cho traceability và latest cho main branch.
| Kỹ thuật Cache | Không Cache | Có Cache (hit) | Tiết kiệm |
|---|---|---|---|
| NuGet Restore | 60–90s | <5s | ~93% |
| Docker Build | 240–300s | 30–60s | ~80% |
| dotnet build (incremental) | 45–60s | 10–15s | ~75% |
| Tổng pipeline | ~8–10 phút | ~2–3 phút | ~70% |
Workflow YAML hoàn chỉnh cho .NET 10
Dưới đây là workflow production-ready kết hợp CI + CD, NuGet cache, Docker build, và deploy qua OIDC — không lưu bất kỳ credential nào dạng secret dài hạn.
name: CI/CD .NET 10
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
permissions:
contents: read
id-token: write # Bắt buộc cho OIDC
packages: write # Push container lên GHCR
env:
DOTNET_VERSION: '10.0.x'
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Cache NuGet
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj') }}
restore-keys: nuget-${{ runner.os }}-
- name: Restore
run: dotnet restore
- name: Build
run: dotnet build --no-restore -c Release
- name: Test
run: dotnet test --no-build -c Release --logger trx --results-directory results
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results
path: results/*.trx
deploy:
needs: build-and-test
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Login to Azure (OIDC)
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Deploy to Azure Container Apps
uses: azure/container-apps-deploy-action@v2
with:
containerAppName: my-app
resourceGroup: my-rg
imageToDeploy: ${{ steps.meta.outputs.tags }}Về permission id-token: write
Permission id-token: write cho phép workflow yêu cầu OIDC token từ GitHub. Token này có thời hạn ngắn (thường 10 phút), chỉ hợp lệ cho đúng repository và workflow đang chạy. Azure Entra ID verify token này qua federated credential — không cần client secret hay certificate. Đây là cách duy nhất nên deploy lên cloud năm 2026.
OIDC — Deploy Zero-Credential lên Azure
Truyền thống, CI/CD pipeline cần lưu service principal password dạng GitHub Secret. Vấn đề: secret có thể bị leak qua log, fork, hoặc compromised action. OIDC (OpenID Connect) loại bỏ hoàn toàn long-lived credentials.
sequenceDiagram
participant GH as GitHub Actions
participant IDP as GitHub OIDC Provider
participant AZ as Azure Entra ID
participant RES as Azure Resources
GH->>IDP: Yêu cầu OIDC token
IDP-->>GH: JWT token (repo, branch, workflow)
GH->>AZ: Trình diện JWT token
AZ->>AZ: Verify issuer + subject claims
AZ-->>GH: Access token (ngắn hạn)
GH->>RES: Deploy với access token
Note over AZ,RES: Không secret nào được lưu trữ
Hình 2: Luồng xác thực OIDC giữa GitHub Actions và Azure
Thiết lập OIDC trên Azure
Cấu hình phía Azure gồm 3 bước: tạo App Registration, thêm Federated Credential, và gán RBAC role.
# 1. Tạo App Registration
az ad app create --display-name "github-actions-deploy"
APP_ID=$(az ad app list --display-name "github-actions-deploy" --query "[0].appId" -o tsv)
# 2. Tạo Service Principal
az ad sp create --id $APP_ID
# 3. Thêm Federated Credential
az ad app federated-credential create --id $APP_ID --parameters '{
"name": "github-main-branch",
"issuer": "https://token.actions.githubusercontent.com",
"subject": "repo:myorg/myrepo:ref:refs/heads/main",
"audiences": ["api://AzureADTokenExchange"]
}'
# 4. Gán role cho resource group
az role assignment create \
--assignee $APP_ID \
--role "Contributor" \
--scope "/subscriptions/{sub-id}/resourceGroups/my-rg"Claim subject trong federated credential giới hạn chính xác repo và branch nào được phép deploy. Nghĩa là dù attacker fork repo, OIDC token từ fork sẽ có subject khác và bị Azure reject — bảo mật hơn static secret nhiều lần.
Custom Claims mới 2026
GitHub vừa bổ sung repository custom properties vào OIDC token claims. Cho phép tạo trust policy dựa trên metadata tuỳ chỉnh của repo — ví dụ chỉ cho deploy khi repo có property environment: production. Attribute-based access control (ABAC) này linh hoạt hơn nhiều so với per-repo subject matching.
Bảo mật Supply Chain — Security Roadmap 2026
Năm 2026, GitHub đầu tư mạnh vào bảo mật CI/CD với ba trụ cột: Immutable Actions, Egress Firewall, và Actions Data Stream. Đây là phản hồi trực tiếp từ các sự cố supply chain attack nghiêm trọng (như Trivy supply chain compromise tháng 3/2026).
Immutable Actions — Khoá chặt Dependencies
Vấn đề cốt lõi: action dependencies được resolve tại runtime qua mutable references (tags, branches). Tag v4 hôm nay có thể trỏ đến commit khác hôm mai nếu maintainer bị compromise. Điều này nghĩa là CI/CD pipeline không deterministic.
Giải pháp của GitHub: dependencies: section mới trong workflow YAML, lock toàn bộ direct và transitive dependencies bằng commit SHA. Mọi workflow chạy đúng code đã được review — hash mismatch sẽ halt execution trước khi job bắt đầu.
# Pattern hiện tại (KHÔNG an toàn)
- uses: actions/checkout@v4 # Tag có thể bị move
# Pattern an toàn (Pin SHA)
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.7
# Pattern tương lai (dependencies lock)
dependencies:
actions/checkout:
version: v4.1.7
sha: b4ffde65f46336ab88eb53be808477a3936bae11
# Transitive deps cũng bị lockBài học từ Trivy Supply Chain Attack (03/2026)
Tháng 3/2026, action aquasecurity/trivy-action bị compromise — attacker inject malicious code vào tag mutable, ảnh hưởng hàng nghìn workflow. Pipeline nào pin SHA thì an toàn hoàn toàn. Pipeline dùng tag @master hoặc @latest thì bị khai thác. Đây là lý do buộc phải pin SHA cho mọi third-party action.
Egress Firewall — Kiểm soát kết nối ra ngoài
GitHub đang xây dựng native egress firewall hoạt động tại Layer 7, nằm bên ngoài runner VM. Dù attacker giành quyền root bên trong runner, firewall vẫn bất biến và không thể bypass.
graph TB
subgraph Runner["GitHub-hosted Runner VM"]
W["Workflow Step"] --> NET["Outbound Request"]
end
subgraph FW["Egress Firewall (Layer 7)"]
NET --> CHECK["Kiểm tra Allowlist"]
CHECK -->|Cho phép| EXT["External Service"]
CHECK -->|Từ chối| BLOCK["Blocked + Logged"]
end
subgraph AUDIT["Audit Trail"]
CHECK --> LOG["Request → Workflow → Job → Step"]
end
style FW fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style BLOCK fill:#ff9800,stroke:#fff,color:#fff
style EXT fill:#4CAF50,stroke:#fff,color:#fff
Hình 3: Egress Firewall kiểm soát outbound traffic từ runner
Tổ chức có thể:
- Monitor: Xem toàn bộ outbound traffic, tự động audit mỗi request với workflow/job/step tương ứng
- Allowlist: Chỉ cho phép kết nối đến domains cần thiết (nuget.org, ghcr.io, azure endpoints)
- Block: Chặn hoàn toàn kết nối không mong muốn — ngăn data exfiltration từ compromised step
Parallel Steps — Tính năng được yêu cầu nhiều nhất
Từ 2020, GitHub Community Discussion #14484 về parallel steps đã thu hút hàng nghìn upvotes. Năm 2026, GitHub chính thức phát triển tính năng này với mục tiêu ship trước giữa năm.
Hiện tại, các steps trong một job chạy tuần tự. Để song song hoá, phải tách thành nhiều jobs — phức tạp và tốn overhead do mỗi job cần runner riêng. Parallel steps cho phép chạy các bước độc lập đồng thời trong cùng một job.
graph LR
subgraph Before["Hiện tại: Tuần tự"]
direction TB
S1["Restore (30s)"] --> S2["Build (45s)"]
S2 --> S3["Unit Test (60s)"]
S3 --> S4["Integration Test (90s)"]
S4 --> S5["Lint (20s)"]
end
subgraph After["Parallel Steps"]
direction TB
P1["Restore + Build (50s)"] --> P2["Unit Test (60s)"]
P1 --> P3["Integration Test (90s)"]
P1 --> P4["Lint (20s)"]
end
style Before fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
style After fill:#f8f9fa,stroke:#4CAF50,color:#2c3e50
Hình 4: Parallel steps giảm tổng thời gian pipeline từ 245s xuống ~140s
Với parallel steps, pipeline .NET có thể chạy unit test, integration test, và linting đồng thời sau khi build xong — giảm thời gian tổng từ tổng các bước (tuần tự) xuống bước dài nhất (song song).
Reusable Workflows — DRY cho Multi-Repo
Khi tổ chức quản lý nhiều .NET microservices, duplicate workflow YAML là nightmare. Reusable Workflows cho phép định nghĩa pipeline một lần trong repo trung tâm, gọi lại từ mọi repo service.
# .github/workflows/dotnet-ci.yml (repo trung tâm: myorg/ci-templates)
name: .NET CI Template
on:
workflow_call:
inputs:
dotnet-version:
type: string
default: '10.0.x'
project-path:
type: string
required: true
secrets:
AZURE_CLIENT_ID:
required: true
AZURE_TENANT_ID:
required: true
jobs:
build-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ inputs.dotnet-version }}
- run: dotnet restore ${{ inputs.project-path }}
- run: dotnet build ${{ inputs.project-path }} --no-restore -c Release
- run: dotnet test ${{ inputs.project-path }} --no-build -c Release# .github/workflows/ci.yml (repo service)
name: CI
on: [push, pull_request]
jobs:
ci:
uses: myorg/ci-templates/.github/workflows/dotnet-ci.yml@main
with:
project-path: src/MyService.sln
secrets:
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}Reusable Workflows + OIDC: Lưu ý quan trọng
Khi reusable workflow yêu cầu OIDC token, caller workflow phải khai báo permissions: id-token: write. OIDC subject claim sẽ chứa thông tin của caller repo (nơi gọi), không phải template repo. Điều này đảm bảo federated credential trên Azure vẫn match đúng repo đang deploy.
Matrix Builds — Test song song trên nhiều phiên bản
Matrix strategy cho phép chạy cùng một bộ test trên nhiều phiên bản .NET, OS, hoặc database đồng thời. Đặc biệt hữu ích cho thư viện NuGet cần hỗ trợ multi-target.
jobs:
test:
strategy:
matrix:
dotnet: ['8.0.x', '9.0.x', '10.0.x']
os: [ubuntu-latest, windows-latest]
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
- uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ matrix.dotnet }}
- run: dotnet test --configuration ReleaseVới fail-fast: false, tất cả 6 combinations (3 .NET × 2 OS) chạy đến hoàn tất dù một job fail — giúp phát hiện toàn bộ lỗi compatibility trong một lần chạy thay vì phải fix từng cái.
Runner Pricing 2026 — Mạnh hơn, rẻ hơn
Tháng 1/2026, GitHub giảm giá runner đáng kể: runner 4-vCPU "Standard" mới có giá bằng runner 2-vCPU cũ năm 2024. Tức gấp đôi compute với cùng chi phí.
| Runner Type | vCPU | RAM | Giá ($/phút) | So với 2024 |
|---|---|---|---|---|
| ubuntu-latest (cũ 2024) | 2 | 7 GB | $0.008 | — |
| Standard Runner 2026 | 4 | 16 GB | $0.008 | -39% per vCPU |
| Large Runner | 8 | 32 GB | $0.016 | Giữ nguyên |
| GPU Runner (Linux) | 4 | 28 GB + T4 | $0.07 | Mới 2025 |
Free tier vẫn rất hào phóng: 2,000 phút/tháng cho public repo (không giới hạn) và repo private trên GitHub Free plan. Với caching tốt, đủ cho team nhỏ 3–5 developer chạy CI/CD hàng ngày.
Checklist Bảo mật cho CI/CD Production
Dựa trên GitHub Security Roadmap 2026 và bài học từ các sự cố supply chain, dưới đây là checklist bảo mật cần thiết cho mọi pipeline .NET.
| Hạng mục | Thực hành | Mức độ |
|---|---|---|
| Pin Actions | Pin mọi third-party action bằng full SHA, không dùng tag | Bắt buộc |
| OIDC | Dùng OIDC thay static credentials cho cloud deploy | Bắt buộc |
| Permissions | Khai báo permissions tối thiểu, không dùng default write-all | Bắt buộc |
| Branch Protection | Require status checks + review trước merge vào main | Bắt buộc |
| Egress Firewall | Bật monitor mode, xây dựng allowlist, sau đó enforce | Khuyến nghị |
| Dependabot | Tự động update action versions + NuGet packages | Khuyến nghị |
| CODEOWNERS | Bảo vệ .github/workflows/ bằng CODEOWNERS | Khuyến nghị |
| Secret Scanning | Bật push protection để chặn commit chứa secrets | Bắt buộc |
Roadmap GitHub Actions 2026
Kết luận
GitHub Actions năm 2026 không chỉ là "CI/CD tool" mà đã trở thành security-first DevOps platform. Với egress firewall, immutable actions, và OIDC — lần đầu tiên pipeline CI/CD có thể đạt mức bảo mật ngang với production infrastructure. Kết hợp caching NuGet + Docker BuildKit giảm 80% thời gian build, reusable workflows chuẩn hoá pipeline cho toàn tổ chức, và parallel steps sắp tới sẽ giảm thêm 40% thời gian nữa.
Đối với team .NET 10, GitHub Actions là lựa chọn tự nhiên nhất: tích hợp sâu với GitHub, free tier hào phóng, runner mạnh hơn với giá rẻ hơn, và hệ sinh thái action đồ sộ. Quan trọng nhất — pin SHA cho mọi action, dùng OIDC cho mọi cloud deploy.
Bắt đầu ngay
Nếu đang dùng Azure DevOps Pipelines hoặc Jenkins, migration sang GitHub Actions không phức tạp như tưởng. GitHub cung cấp hướng dẫn migration chính thức cho từng CI system. Bước đầu tiên: tạo một workflow đơn giản chỉ chạy dotnet test trên mỗi PR — sau đó mở rộng dần.
Nguồn tham khảo:
Cloudflare Workers 2026 — Xây dựng Full-Stack trên Edge với Zero Cold Start và Zero Egress
AWS Lambda Serverless 2026: Kiến trúc, SnapStart, Event-Driven Patterns và Free Tier cho Production
Disclaimer: The opinions expressed in this blog are solely my own and do not reflect the views or opinions of my employer or any affiliated organizations. The content provided is for informational and educational purposes only and should not be taken as professional advice. While I strive to provide accurate and up-to-date information, I make no warranties or guarantees about the completeness, reliability, or accuracy of the content. Readers are encouraged to verify the information and seek independent advice as needed. I disclaim any liability for decisions or actions taken based on the content of this blog.