跳至正文

🚀 什麼是 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 支援 | 中等 || 非常好

建議:
- 小型團隊,使用 GitHubGitHub 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 後,學習以下主題:

  1. 什麼是 Git? (待撰寫) - 版本控制基礎
  2. 什麼是 Docker? (待撰寫) - 容器化部署
  3. 什麼是 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 等

透過自動化專注於開發並更快地部署! 🚀