🚀 什麼是 CI/CD?
📖 定義
CI/CD 是持續整合(Continuous Integration)和持續部署(Continuous Deployment)的縮寫,是一種自 動測試和部署程式碼變更的開發方法論。CI 在每次合併程式碼時自動建置和測試,CD 則自動將通過測試的程式碼部署到正式環境。這使得能夠快速發現錯誤並縮短部署週期。
🎯 透過比喻理解
工廠自動化生產線
傳統開發 (手動)
開發者 1 → 撰寫程式碼
開發者 2 → 撰寫程式碼
開發者 3 → 撰寫程式碼
↓
一週後
↓
團隊負責人手動合併
↓
發生衝突! 😱
↓
手動測試
↓
發現錯誤! 😱
↓
手動部署
↓
部署失敗! 😱
CI/CD (自動化)
開發者 1 → 推送程式碼 → 自動測試 ✅ → 自動部署 🚀
開發者 2 → 推送程式碼 → 自動測試 ✅ → 自動部署 🚀
開發者 3 → 推送程式碼 → 自動測試 ❌ → 立即通知!
(立即發現並修復問題)
食品配送
手動部署 = 直接配送
1. 製作食品 (開發)
2. 檢查包裝 (測試)
3. 確認地址 (設定)
4. 直接開車配送 (部署)
5. 客戶確認 (驗證)
→ 耗時長,容易出錯
CI/CD = 自動配送系統
1. 製作食品 (開發)
2. 自動包裝 (自動建置)
3. 自動品質檢查 (自動測試)
4. 機器人自動配送 (自動部署)
5. 即時追蹤 (監控)
→ 快速準確
⚙️ 運作原理
1. CI/CD 流水線
程式碼推送 (Push)
↓
┌─────────────────────────────┐
│ CI: 持續整合 │
│ │
│ 1. 檢出程式碼 │
│ 2. 安裝相依套件 │
│ 3. 程式碼檢查 │
│ 4. 建置 │
│ 5. 單元測試 │
│ 6. 整合測試 │
│ │
│ ✅ 全部成功 │
│ ❌ 任一失敗 → 停止 │
└─────────────────────────────┘
↓
┌─────────────────────────────┐
│ CD: 持續部署 │
│ │
│ 1. 部署到預發環境 │
│ 2. E2E 測試 │
│ 3. 部署到正式環境 │
│ 4. 健康檢查 │
│ 5. 發送通知 │
└─────────────────────────────┘
↓
🎉 部署完成!
2. 傳統方式 vs CI/CD
傳統方式
┌─────────────────────────────────────┐
│ 星期一 │
│ - 開發者 A: 開發功能 1 │
│ - 開發者 B: 開發功能 2 │
│ - 開發者 C: 修復錯誤 │
├─────────────────────────────────────┤
│ 星期二-星期四 │
│ - 各自繼續開發 │
│ - 衝突可能性增加 😰 │
├─────────────────────────────────────┤
│ 星期五 (整合日) │
│ - 合併所有程式碼 │
│ - 花一整天解決衝突 😱 │
│ - 執行測試 → 發現很多錯誤 │
│ - 加班到週末 😭 │
└─────────────────────────────────────┘
CI/CD 方式
┌─────────────────────────────────────┐
│ 每天,每次提交 │
│ │
│ 開發者 A: 推送程式碼 │
│ → 自動測試 ✅ │
│ → 自動部署 🚀 │
│ → 3分鐘後反映到正式環境 │
│ │
│ 開發者 B: 推送程式碼 │
│ → 自動測試 ❌ │
│ → 立即通知 │
│ → 立即修復 (5分鐘) │
│ │
│ - 最小化 衝突 │
│ - 快速發現錯誤 │
│ - 準時下班 😊 │
└─────────────────────────────────────┘
3. CI/CD 組成要素
┌──────────────────────────────────────┐
│ Version Control (版本控制) │
│ Git, GitHub, GitLab │
└───────────────┬──────────────────────┘
↓ Push
┌──────────────────────────────────────┐
│ CI Server (持續整合伺服器) │
│ GitHub Actions, Jenkins, CircleCI │
│ - 檢出程式碼 │
│ - 自動建置 │
│ - 自動測試 │
└───────────────┬──────────────────────┘
↓ Pass
┌──────────────────────────────────────┐
│ Artifact Repository (建置倉庫) │
│ Docker Hub, npm Registry │
└───────────────┬──────────────────────┘
↓ Deploy
┌──────────────────────────────────────┐
│ Deployment (部署) │
│ AWS, Heroku, Vercel, Netlify │
└───────────────┬──────────────────────┘
↓ Monitor
┌──────────────────────────────────────┐
│ Monitoring (監控) │
│ Sentry, DataDog, CloudWatch │
└──────────────────────────────────────┘
💡 實際範例
GitHub Actions 基本範例
# .github/workflows/ci.yml
# Node.js 專案 CI/CD 流水線
name: CI/CD Pipeline
# 觸發器: 推送到 main 分支或建立 PR 時
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
# 環境變數
env:
NODE_VERSION: '18'
# 任務定義
jobs:
# ========== Job 1: 測試 ==========
test:
name: 測試和程式碼檢查
runs-on: ubuntu-latest # 在 Ubuntu 環境執行
steps:
# 1. 檢出程式碼
- name: 檢出程式碼
uses: actions/checkout@v3
# 2. 設定 Node.js
- name: 設定 Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm' # 使用 npm 快取
# 3. 安裝相依套件
- name: 安裝相依套件
run: npm ci # 比 npm install 更快更穩定
# 4. 程式碼檢查
- name: 執行程式碼檢查
run: npm run lint
# 5. 單元測試
- name: 執行測試
run: npm test
# 6. 程式碼覆蓋率
- name: 上傳覆蓋率
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
# ========== Job 2: 建置 ==========
build:
name: 建置
needs: test # test 成功後才執行
runs-on: ubuntu-latest
steps:
- name: 檢出程式碼
uses: actions/checkout@v3
- name: 設定 Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: 安裝相依套件
run: npm ci
# 正式建置
- name: 正式建置
run: npm run build
env:
NODE_ENV: production
# 上傳建置產物
- name: 上傳建置產物
uses: actions/upload-artifact@v3
with:
name: build-files
path: dist/
retention-days: 7
# ========== Job 3: 部署 ==========
deploy:
name: 正式部署
needs: build # build 成功後才執行
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' # 只部署 main 分支
steps:
- name: 檢出程式碼
uses: actions/checkout@v3
# 下載建置產物
- name: 下載建置產物
uses: actions/download-artifact@v3
with:
name: build-files
path: dist/
# 部署到 Vercel
- name: 部署到 Vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--prod'
# Slack 通知
- name: Slack 通知
if: always() # 無論成功/失敗都執行
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: '部署${{ job.status }}!'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
Docker 建置和部署
# .github/workflows/docker.yml
# Docker 映像建置和部署
name: Docker CI/CD
on:
push:
branches: [ main ]
tags:
- 'v*' # 推送 v1.0.0 這樣的標籤時執行
env:
DOCKER_IMAGE: myapp
DOCKER_REGISTRY: docker.io
jobs:
docker:
name: Docker 建置和推送
runs-on: ubuntu-latest
steps:
- name: 檢出程式碼
uses: actions/checkout@v3
# 提取 Docker 中繼資料
- name: 提取 Docker 中繼資料
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha
# 設定 Docker Buildx (多平台建置)
- name: 設定 Docker Buildx
uses: docker/setup-buildx-action@v2
# 登入 Docker Hub
- name: 登入 Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
# 建置和推送 Docker 映像
- name: 建置和推送 Docker 映像
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha # 使用 GitHub Actions 快取
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64 # 多平台
# 部署 (SSH 連線伺服器並重啟容器)
- name: 部署到伺服器
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USERNAME }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: |
docker pull ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:latest
docker stop myapp || true
docker rm myapp || true
docker run -d \
--name myapp \
-p 3000:3000 \
--restart unless-stopped \
${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE }}:latest
docker system prune -af
Pull Request 自動驗證
# .github/workflows/pr.yml
# PR 建立時自動驗證
name: Pull Request Check
on:
pull_request:
types: [ opened, synchronize, reopened ]
jobs:
# ========== 程式碼品質檢查 ==========
lint:
name: 程式碼檢查和格式檢查
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: 設定 Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: 安裝相依套件
run: npm ci
# ESLint
- name: 執行 ESLint
run: npm run lint
# Prettier
- name: Prettier 檢查
run: npm run format:check
# TypeScript 型別檢查
- name: TypeScript 型別檢查
run: npm run type-check
# ========== 測試 ==========
test:
name: 測試
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16, 18, 20] # 在多個 Node 版本上測試
steps:
- uses: actions/checkout@v3
- name: 設定 Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: 安裝相依套件
run: npm ci
- name: 執行測試
run: npm test -- --coverage
- name: 上傳測試結果
uses: actions/upload-artifact@v3
if: always()
with:
name: test-results-${{ matrix.node-version }}
path: coverage/
# ========== 安全檢查 ==========
security:
name: 安全檢 查
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: 設定 Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
# npm audit
- name: npm 安全檢查
run: npm audit --audit-level=moderate
# Snyk 安全掃描
- name: Snyk 安全掃描
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
# ========== 效能測試 ==========
performance:
name: 效能測試
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: 設定 Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: 安裝相依套件
run: npm ci
- name: 建置
run: npm run build
# Lighthouse CI
- name: Lighthouse CI
uses: treosh/lighthouse-ci-action@v9
with:
urls: |
http://localhost:3000
uploadArtifacts: true
# ========== PR 留言 ==========
comment:
name: PR 留言
needs: [lint, test, security]
runs-on: ubuntu-latest
if: always()
steps:
- name: 建立 PR 留言
uses: actions/github-script@v6
with:
script: |
const statuses = {
lint: '${{ needs.lint.result }}',
test: '${{ needs.test.result }}',
security: '${{ needs.security.result }}'
};
let message = '## 🤖 CI 檢查結果\n\n';
for (const [job, status] of Object.entries(statuses)) {
const emoji = status === 'success' ? '✅' : '❌';
message += `${emoji} **${job}**: ${status}\n`;
}
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: message
});
排程任務
# .github/workflows/scheduled.yml
# 定期執行的任務
name: Scheduled Tasks
on:
schedule:
# 每天上午 9 點 (UTC 0 點)
- cron: '0 0 * * *'
workflow_dispatch: # 也可以手動執行
jobs:
# ========== 相依套件更新檢查 ==========
dependency-check:
name: 檢查相依套件更新
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: 設定 Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
# 檢查 npm outdated
- name: 檢查過期套件
run: npm outdated || true
# Dependabot 替代方案
- name: 檢查可更新套件
run: |
npx npm-check-updates
# ========== 備份 ==========
backup:
name: 資料庫備份
runs-on: ubuntu-latest
steps:
- name: 備份資料庫
run: |
# 實際上執行資料庫備份腳本
echo "正在備份資料庫..."
# 上傳到 S3
- name: 上傳備份到 S3
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: 上傳到 S3
run: |
aws s3 cp backup.sql s3://my-backups/$(date +%Y%m%d).sql
# ========== 效能監控 ==========
performance-monitor:
name: 效能監控
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: 執行 Lighthouse
uses: treosh/lighthouse-ci-action@v9
with:
urls: |
https://myapp.com
uploadArtifacts: true
# 效能下降時發送 Slack 通知
- name: 效能下降通知
if: failure()
uses: 8398a7/action-slack@v3
with:
status: failure
text: '⚠️ 網站效能下降了!'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
多環境部署
# .github/workflows/multi-env.yml
# 依環境部署: 開發、預發、正式
name: Multi-Environment Deployment
on:
push:
branches:
- develop # 開發環境
- staging # 預發環境
- main # 正式環境
jobs:
deploy:
name: 部署
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: 設定 Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: 安裝相依套件
run: npm ci
# 依環境建置
- name: 建置
run: npm run build
env:
NODE_ENV: ${{
github.ref == 'refs/heads/main' && 'production' ||
github.ref == 'refs/heads/staging' && 'staging' ||
'development'
}}
# 開發環境部署
- name: 開發環境部署
if: github.ref == 'refs/heads/develop'
run: |
echo "正在部署到開發環境..."
# 開發伺服器部署命令
env:
API_URL: https://api-dev.myapp.com
# 預發環境部署
- name: 預發環境部署
if: github.ref == 'refs/heads/staging'
run: |
echo "正在部署到預發環境..."
# 預發伺服器部署命令
env:
API_URL: https://api-staging.myapp.com
# 正式環境部署
- name: 正式環境部署
if: github.ref == 'refs/heads/main'
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: task-definition.json
service: myapp-service
cluster: production-cluster
wait-for-service-stability: true
env:
API_URL: https://api.myapp.com
# 部署後健康檢查
- name: 健康檢查
run: |
sleep 30 # 等待 30 秒
URL=${{
github.ref == 'refs/heads/main' && 'https://myapp.com' ||
github.ref == 'refs/heads/staging' && 'https://staging.myapp.com' ||
'https://dev.myapp.com'
}}
STATUS=$(curl -s -o /dev/null -w "%{http_code}" $URL/health)
if [ $STATUS -eq 200 ]; then
echo "✅ 健康檢查成功"
else
echo "❌ 健康檢查失敗: $STATUS"
exit 1
fi
# 部署成功通知
- name: 部署通知
if: success()
uses: 8398a7/action-slack@v3
with:
status: success
text: |
🚀 部署完成!
環境: ${{ github.ref_name }}
提交: ${{ github.sha }}
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
🤔 常見問題
Q1. 採用 CI/CD 有什麼好處?
A:
✅ 主要好處:
1. 快速發現錯誤
傳統: 一週後整合時發現
CI/CD: 提交後 5 分鐘內發現
→ 修復成本降低 90%
2. 縮短部署時間
傳統: 手動部署需要 2 小時
CI/CD: 自動部署需要 5 分鐘
→ 每天可以多次部署
3. 提高程式碼品質
- 自動程式碼檢查、測試
- 自動化程式碼審查
- 減少錯誤
4. 提高開發者生產力
- 消除手動任務
- 自動化重複工作
- 只專注於開發
5. 提高穩定性
- 自動測試
- 自動回滾
- 一致的部署流程
6. 改善協作
- 最小化程式碼衝突
- 快速回饋
- 透明的流程
實際案例:
A 公司: CI/CD 之前
- 部署: 每月 1 次
- 部署時間: 4 小時
- 部署失敗率: 30%
- 錯誤發現時間: 平均 3 天
A 公司: CI/CD 之後
- 部署: 每天 10 次
- 部署時間: 5 分鐘
- 部署失敗率: 5%
- 錯誤發現時間: 平均 10 分鐘
Q2. GitHub Actions vs Jenkins vs CircleCI?
A:
// ========== GitHub Actions ==========
優點:
- 與 GitHub 完美整合
- 免費 (公開儲存庫,每月 2000 分鐘免費)
- 設定簡單 (YAML 檔案)
- Marketplace 中有各種 Actions
缺點:
- 依賴 GitHub
- 複雜工作流程受限
適用情境:
- 使用 GitHub
- 簡單的 CI/CD
- 需要快速開始
範例:
name: CI
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: npm test
// ========== Jenkins ==========
優點:
- 最古老且最成熟
- 龐大的外掛生態系統
- 完全自訂
- 自主託管 (無成本)
缺點:
- 設定複雜
- UI 老舊
- 需要維護
- 學習曲線陡峭
適用情境:
- 本地部署環境
- 複雜流水線
- 舊系統整合
// ========== CircleCI ==========
優點:
- 建置速度快
- Docker 支援優秀
- 強大的平行處理
- UI 直觀
缺點:
- 付費 (免費層受限)
- 僅支援 GitHub/GitLab
適用情境:
- 效能至關重要
- 大量使用 Docker
- 需要平行測試
// ========== 比較表 ==========
特性 | GitHub Actions | Jenkins | CircleCI
--------------|----------------|---------|----------
價格 | 免費(受限) | 免費 | 付費
設定難度 | 簡單 | 困難 | 中等
效能 | 中等 | 高 | 高
整合性 | GitHub 完美 | 通用 | 通用
維護 | 不需要 | 需要 | 不需要
Docker 支援 | 中等 | 好 | 非常好
建議:
- 小型團隊,使用 GitHub → GitHub Actions
- 大型團隊,複雜需求 → Jenkins
- 效能至關重要,有預算 → CircleCI
Q3. 如何管理秘密金鑰(Secrets)?
A:
# ========== GitHub Secrets 設定 ==========
# Repository → Settings → Secrets and variables → Actions
# 使用方式
- name: 部署
env:
API_KEY: ${{ secrets.API_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: npm run deploy
# ========== 環境別 Secrets ==========
# Repository → Settings → Environments
# 正式環境
environment: production
env:
API_URL: ${{ secrets.PROD_API_URL }}
# 預發環境
environment: staging
env:
API_URL: ${{ secrets.STAGING_API_URL }}
# ========== 安全最佳實務 ==========
# ✅ 良好範例
- name: 部署
env:
# 從 Secrets 取得
AWS_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY }}
AWS_SECRET_KEY: ${{ secrets.AWS_SECRET_KEY }}
run: |
# 使用環境變數
aws s3 sync dist/ s3://my-bucket
# ❌ 不良範例
- name: 部署
run: |
# 寫死在程式碼中 (絕對禁止!)
export AWS_ACCESS_KEY="AKIAIOSFODNN7EXAMPLE"
export AWS_SECRET_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
# ========== 建立 .env 檔案 ==========
- name: 建立 .env 檔案
run: |
cat << EOF > .env
API_URL=${{ secrets.API_URL }}
DATABASE_URL=${{ secrets.DATABASE_URL }}
JWT_SECRET=${{ secrets.JWT_SECRET }}
EOF
# ========== Docker Secrets ==========
- name: Docker 建置
env:
DOCKER_BUILDKIT: 1
run: |
docker build \
--secret id=api_key,env=API_KEY \
--secret id=db_url,env=DATABASE_URL \
-t myapp .
# Dockerfile
# syntax=docker/dockerfile:1
FROM node:18
RUN --mount=type=secret,id=api_key \
API_KEY=$(cat /run/secrets/api_key) npm install
# ========== 使用 Vault (進階) ==========
- name: 從 HashiCorp Vault 取得 Secrets
uses: hashicorp/vault-action@v2
with:
url: https://vault.mycompany.com
token: ${{ secrets.VAULT_TOKEN }}
secrets: |
secret/data/production api_key | API_KEY ;
secret/data/production db_url | DATABASE_URL
Q4. 測試失敗時會發生什麼?
A:
# ========== 預設行為: 停止流水線 ==========
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: 執行測試
run: npm test
# 測試失敗 → 在此停止
# 後續步驟不會執行
deploy:
needs: test # 只有 test 成功才執行
runs-on: ubuntu-latest
steps:
- name: 部署
run: npm run deploy
# test 失敗時此步驟不會執行 ✅
# ========== 失敗仍繼續 ==========
- name: 執行測試
run: npm test
continue-on-error: true # 即使失敗也繼續
# ⚠️ 不建議
# ========== 條件執行 ==========
- name: 失敗時通知
if: failure() # 只在前一步驟失敗時執行
uses: 8398a7/action-slack@v3
with:
status: failure
text: '❌ 測試失敗!'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
- name: 成功時部署
if: success() # 只在所有步驟成功時執行
run: npm run deploy
- name: 總是執行
if: always() # 無論成功/失敗都執行
run: npm run cleanup
# ========== 重試 ==========
- name: 不穩定的測試 (重試)
uses: nick-invision/retry@v2
with:
timeout_minutes: 10
max_attempts: 3
command: npm run test:e2e
# ========== 錯誤處理範例 ==========
- name: 測試
id: test
run: npm test
- name: 測試失敗時建立 Issue
if: failure() && steps.test.outcome == 'failure'
uses: actions/github-script@v6
with:
script: |
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: '❌ 測試失敗',
body: '測試失敗了,需要檢查。',
labels: ['bug', 'ci']
});
# ========== 回滾 ==========
- name: 部署
id: deploy
run: npm run deploy
- name: 部署失敗時回滾
if: failure() && steps.deploy.outcome == 'failure'
run: |
echo "部署失敗! 正在回滾到前一版本..."
npm run rollback
Q5. 如何降低成本?
A:
# ========== 使用快取 ==========
- name: 設定 Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm' # npm 快取 (建置時間縮短 50%)
- name: 快取相依套件
uses: actions/cache@v3
with:
path: |
~/.npm
node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
# ========== 條件執行 ==========
on:
push:
branches: [ main ]
paths: # 只在特定檔案變更時執行
- 'src/**'
- 'package.json'
# README.md 變更時不執行 → 節省成本
# ========== 限制並行執行 ==========
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true # 取消之前的執行
# → 防止不必要的重複執行
# ========== Self-hosted Runner ==========
# 使用自己的伺服器 (免費)
jobs:
build:
runs-on: self-hosted # 不消耗 GitHub Actions 分鐘數
steps:
- run: npm run build
# ========== 只執行必要的任務 ==========
- name: 檢查變更的檔案
id: changes
uses: dorny/paths-filter@v2
with:
filters: |
frontend:
- 'frontend/**'
backend:
- 'backend/**'
- name: 前端測試
if: steps.changes.outputs.frontend == 'true'
run: npm run test:frontend
# 只在前端變更時執行 → 節省時間
- name: 後端測試
if: steps.changes.outputs.backend == 'true'
run: npm run test:backend
# ========== 成本範例 ==========
# GitHub Actions 免費方案:
# - 公開儲存庫: 無限制
# - 私有儲存庫: 每月 2000 分鐘
# 最佳化前:
# - 每次提交執行完整測試 (20 分鐘)
# - 每天推送 10 次
# - 每月使用量: 10 × 20 × 30 = 6000 分鐘
# - 超出費用: (6000 - 2000) × $0.008 = $32
# 最佳化後:
# - 透過快取節省 10 分鐘
# - 只測試變更的部分
# - 平均建置時間 5 分鐘
# - 每月使用量: 10 × 5 × 30 = 1500 分鐘
# - 費用: $0 (在免費額度內)
# → 每月節省 $32!
🎓 下一步
了解 CI/CD 後,學習以下主題:
- 什麼是 Git? (待撰寫) - 版本控制基礎
- 什麼是 Docker? (待撰寫) - 容器化部署
- 什麼是 TDD? (待撰寫) - 測試驅動開發
實作練習
# ========== 1. 開始使用 GitHub Actions ==========
# 1) 在儲存庫中建立工作流程檔案
mkdir -p .github/workflows
cat > .github/workflows/ci.yml << 'EOF'
name: CI
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: 設定 Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm ci
- run: npm test
EOF
# 2) 提交並推送
git add .github/workflows/ci.yml
git commit -m "Add CI workflow"
git push
# 3) 在 GitHub 上查看 Actions 標籤
# → 查看自動執行的工作流程!
# ========== 2. 本地測試 (act) ==========
# 使用 act 可以在本地測試 GitHub Actions
# 安裝 act (macOS)
brew install act
# 執行工作流程
act push
# 僅執行特定 job
act -j test
# ========== 3. 部署自動化範例 ==========
# Vercel 部署
npm install -g vercel
vercel login
vercel --prod
# Netlify 部署
npm install -g netlify-cli
netlify login
netlify deploy --prod
# Heroku 部署
heroku login
git push heroku main
🎬 總結
CI/CD 是現代開發的必備工具:
- CI: 自動測試和整合程式碼變更
- CD: 自動部署通過測試的程式碼
- 好處: 快速回饋、提高穩定性、增加生產力
- 工具: GitHub Actions、Jenkins、CircleCI 等
透過自動化專注於開發並更快地部署! 🚀