CI/CD 持續整合部署
什麼是 CI/CD?
💡 比喻:工廠生產線 傳統做法:工人手工組裝每一件產品,品質不穩定,速度慢。 CI/CD:建立自動化生產線,每個步驟都自動檢查,有問題立刻停下來。
- CI(Continuous Integration)持續整合 = 自動檢查零件品質
- CD(Continuous Deployment)持續部署 = 自動包裝出貨
CI/CD 流程
開發者推 Code
↓
CI 自動觸發
├── 1. 還原套件(dotnet restore)
├── 2. 編譯(dotnet build)
├── 3. 執行測試(dotnet test)
├── 4. 程式碼品質檢查
└── 5. 建置 Docker Image
↓
CD 自動部署
├── 6. 推送 Image 到 Registry
├── 7. 部署到測試環境
├── 8. 部署到正式環境
└── 9. 通知團隊
GitHub Actions 基礎
Workflow 設定檔位置
你的專案/
├── .github/
│ └── workflows/
│ ├── ci.yml ← CI 工作流程
│ └── deploy.yml ← 部署工作流程
├── src/
├── tests/
└── Dockerfile
基本 CI Workflow
# .github/workflows/ci.yml
# 工作流程名稱
name: CI Pipeline
# 觸發條件
on:
# 推送到 main 或 develop 分支時觸發
push:
branches: [ main, develop ]
# Pull Request 到 main 時觸發
pull_request:
branches: [ main ]
# 定義工作
jobs:
# 工作名稱:build-and-test
build-and-test:
# 執行環境:最新版 Ubuntu
runs-on: ubuntu-latest
# 步驟
steps:
# 步驟 1:拉取程式碼
- name: Checkout code
uses: actions/checkout@v4
# 步驟 2:安裝 .NET SDK
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
# 步驟 3:還原 NuGet 套件
- name: Restore dependencies
run: dotnet restore
# 步驟 4:編譯專案
- name: Build
run: dotnet build --configuration Release --no-restore
# 步驟 5:執行單元測試
- name: Test
run: dotnet test --configuration Release --no-build --verbosity normal
# 步驟 6:發佈應用程式
- name: Publish
run: dotnet publish --configuration Release --no-build -o ./publish
加入 Docker Build
# .github/workflows/docker.yml
# Docker 建置和推送工作流程
name: Docker Build & Push
on:
push:
branches: [ main ]
jobs:
docker:
runs-on: ubuntu-latest
steps:
# 拉取程式碼
- name: Checkout
uses: actions/checkout@v4
# 登入 Docker Hub
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
# 從 GitHub Secrets 讀取帳號密碼
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
# 建置並推送 Docker Image
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
# 標記 Image 名稱和版本
tags: |
myapp:latest
myapp:${{ github.sha }}
Build → Test → Deploy Pipeline
完整的 CI/CD Pipeline
# .github/workflows/pipeline.yml
# 完整的 CI/CD Pipeline
name: Full Pipeline
on:
push:
branches: [ main ]
jobs:
# === 階段 1:Build & Test ===
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
# 還原、編譯、測試一氣呵成
- name: Build and Test
run: |
dotnet restore
dotnet build -c Release --no-restore
dotnet test -c Release --no-build
# 上傳建置產物(給下一個 Job 使用)
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: publish
path: ./publish/
# === 階段 2:Deploy to Staging ===
deploy-staging:
# 依賴 build 工作完成後才執行
needs: build
runs-on: ubuntu-latest
# 只有 main 分支才部署
if: github.ref == 'refs/heads/main'
# 指定環境(GitHub 環境保護規則)
environment: staging
steps:
# 下載上一階段的建置產物
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: publish
# 部署到 Staging 環境
- name: Deploy to Staging
run: echo "部署到測試環境..."
# === 階段 3:Deploy to Production ===
deploy-production:
# 依賴 staging 部署完成
needs: deploy-staging
runs-on: ubuntu-latest
# 需要手動審核的環境
environment: production
steps:
- name: Download artifact
uses: actions/download-artifact@v4
with:
name: publish
# 部署到正式環境
- name: Deploy to Production
run: echo "部署到正式環境..."
Branch 策略
推薦的分支策略:
main(正式環境)
↑ merge(需要 PR + Code Review)
develop(開發環境)
↑ merge(需要 PR)
feature/add-login(功能分支)
流程:
1. 從 develop 切出 feature 分支
2. 在 feature 分支開發、commit
3. 推上 GitHub,建立 PR 到 develop
4. CI 自動跑測試 ← 測試失敗就不能 merge!
5. Code Review 通過 → merge 到 develop
6. develop 累積足夠功能 → merge 到 main
7. main 自動部署到正式環境
分支命名規範:
feature/add-login → 新功能
feature/user-profile → 新功能
bugfix/fix-login-error → 修 Bug
hotfix/security-patch → 緊急修復(直接從 main 切)
release/v1.2.0 → 發版準備
GitHub Branch Protection
建議的 Branch Protection 設定:
main 分支:
├── ✅ Require pull request(必須 PR,不能直接 push)
├── ✅ Require approvals(至少 1 人 Code Review)
├── ✅ Require status checks(CI 必須通過)
├── ✅ Require branches to be up to date(必須是最新的)
└── ❌ Allow force push(禁止 force push)
develop 分支:
├── ✅ Require pull request
├── ✅ Require status checks
└── ⬜ Require approvals(可選)
GitHub Secrets 管理
GitHub Secrets 用於存儲機密資料:
設定路徑:
Repository → Settings → Secrets and variables → Actions
常見的 Secrets:
├── DOCKER_USERNAME → Docker Hub 帳號
├── DOCKER_PASSWORD → Docker Hub 密碼
├── DEPLOY_KEY → 部署用的 SSH Key
├── DATABASE_URL → 資料庫連線字串
└── JWT_SECRET → JWT 簽章金鑰
# 在 Workflow 中使用 Secrets
env:
# 用 ${{ secrets.名稱 }} 讀取 Secrets
DATABASE_URL: ${{ secrets.DATABASE_URL }}
steps:
- name: Deploy
# Secrets 不會出現在 Log 中(自動遮罩)
run: echo "部署中..." && deploy --db $DATABASE_URL
🤔 我這樣寫為什麼會錯?
❌ 錯誤 1:CI 沒有跑測試
# ❌ 錯誤:CI 只有 build,沒有 test
steps:
- run: dotnet restore
- run: dotnet build
# 沒有 dotnet test!
# 這樣就算程式碼有 Bug,CI 還是會通過
# ✅ 正確:CI 一定要包含測試
steps:
- run: dotnet restore
- run: dotnet build -c Release --no-restore
# 一定要跑測試!測試失敗就停止 Pipeline
- run: dotnet test -c Release --no-build
❌ 錯誤 2:從本機直接部署
❌ 錯誤流程:
開發者在自己電腦 → dotnet publish → 手動複製到伺服器
問題:
├── 「在我電腦上明明可以跑啊!」(環境不一致)
├── 忘記跑測試就部署了
├── 不知道是誰部署的、部署了什麼版本
└── 無法回滾到上一版
✅ 正確流程:
開發者 push code → GitHub Actions 自動 build + test → 自動部署
好處:
├── 環境一致(都在 CI 的 Ubuntu 上 build)
├── 自動跑測試(測試失敗不會部署)
├── 有完整的部署紀錄
└── 可以隨時回滾
❌ 錯誤 3:把 Secrets 寫在 Workflow 檔案中
# ❌ 錯誤:密碼直接寫在 YAML 中(推上 Git 就洩漏了)
env:
DOCKER_PASSWORD: "my-real-password-123"
# ✅ 正確:使用 GitHub Secrets
env:
# ${{ secrets.DOCKER_PASSWORD }} 會從 GitHub Secrets 讀取
# 而且不會出現在 CI 的 Log 中
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
💡 重點整理
| 概念 | 說明 |
|---|---|
| CI(持續整合) | 自動 build + test,確保程式碼品質 |
| CD(持續部署) | 自動部署到測試/正式環境 |
| GitHub Actions | GitHub 內建的 CI/CD 工具 |
| Workflow | YAML 格式的自動化流程定義 |
| Branch Protection | 保護重要分支,強制 PR 和 Code Review |
| GitHub Secrets | 安全儲存機密資料(密碼、Key) |