# MOMO Pro System - CI/CD 機制評估報告

## 執行摘要

本文件評估 MOMO Pro System 導入 CI/CD（持續整合/持續部署）機制的建議方案。

**現況分析**：
- 部署方式：手動執行 `deploy.sh` 腳本（使用 gcloud 命令）
- 版本控制：本地 Git 倉庫，尚未設定遠端倉庫
- 測試覆蓋：無自動化測試套件
- 部署環境：GCP VM (asia-east1-a), e2-standard-2

**建議方案**：GitHub Actions（推薦）或 GitLab CI

---

## 1. 平台選擇評估

### 1.1 GitHub Actions（推薦）✅

**優點**：
- 與 GitHub 原生整合，無需額外設定
- 每月 2,000 分鐘免費額度（私有倉庫）
- 公有倉庫完全免費
- 豐富的 Marketplace Actions 可重用
- 文件齊全，社群活躍
- 支援 Self-hosted Runners（可在 GCP VM 上運行，節省成本）

**缺點**：
- 需要將程式碼推送到 GitHub
- 超過免費額度後需付費（$0.008/分鐘）
- 相對 GitLab CI，功能較精簡

**適用場景**：
- 開源或小型團隊專案
- 需要快速上手的 CI/CD 解決方案
- 預算有限，希望最大化利用免費額度

**成本估算**：
- 免費額度：2,000 分鐘/月（私有倉庫）
- 每次 CI/CD 執行約 5-10 分鐘
- 預計每月約 100-200 次部署 → **完全免費**（使用 Self-hosted Runner）

---

### 1.2 GitLab CI

**優點**：
- 可以 Self-hosted（完全控制）
- 內建 Container Registry
- 更強大的 Pipeline 配置功能
- 免費版功能豐富
- 每月 400 分鐘免費額度（私有倉庫，GitLab.com）

**缺點**：
- Self-hosted 需要額外維護
- GitLab.com 免費額度較少（400 分鐘）
- 學習曲線較陡峭
- 如果使用 GitLab.com，需要遷移程式碼

**適用場景**：
- 大型企業專案
- 需要更複雜的 Pipeline 流程
- 已有 GitLab 基礎設施

**成本估算**：
- GitLab.com 免費額度：400 分鐘/月
- Self-hosted：需額外 VM 資源（約 $20-50/月）

---

### 1.3 建議結論

**推薦 GitHub Actions**，理由：
1. 免費額度充足（2,000 分鐘 + Self-hosted Runner 無限制）
2. 與 GitHub 生態系統整合度高
3. 學習成本低，快速上手
4. 可在現有 GCP VM 上運行 Self-hosted Runner，完全免費

---

## 2. CI/CD Pipeline 設計

### 2.1 Pipeline 階段規劃

```
┌─────────────┐    ┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│   Trigger   │ -> │    Build    │ -> │    Test     │ -> │   Deploy    │
│ (Git Push)  │    │  (安裝依賴)  │    │  (自動測試)  │    │ (部署到GCP) │
└─────────────┘    └─────────────┘    └─────────────┘    └─────────────┘
```

### 2.2 建議的 Workflow

#### 階段 1：程式碼檢查（Linting & Security）
- **觸發條件**：Pull Request 或 Push 到 main/develop
- **執行內容**：
  - Python 語法檢查（flake8 / pylint）
  - 安全漏洞掃描（bandit）
  - 程式碼格式檢查（black）
- **目標**：確保程式碼品質和安全性
- **執行時間**：~1-2 分鐘

#### 階段 2：自動化測試（Testing）
- **觸發條件**：通過階段 1
- **執行內容**：
  - 單元測試（pytest）
  - 整合測試（測試資料庫操作）
  - API 端點測試（測試 Flask routes）
- **目標**：確保功能正確性
- **執行時間**：~2-3 分鐘

#### 階段 3：建置與打包（Build）
- **觸發條件**：通過階段 2
- **執行內容**：
  - 安裝 Python 依賴（pip install -r requirements.txt）
  - 檢查依賴版本衝突
  - 建置文件版本號
- **目標**：確保可部署性
- **執行時間**：~1-2 分鐘

#### 階段 4：部署（Deployment）
- **觸發條件**：
  - **自動觸發**：Push 到 main 分支
  - **手動觸發**：透過 GitHub Actions UI 手動執行
- **執行內容**：
  - 備份遠端資料庫
  - 上傳程式碼到 GCP VM
  - 重啟服務（systemctl restart momo）
  - 健康檢查（確認服務正常運行）
  - 失敗時自動回滾
- **目標**：零停機部署
- **執行時間**：~3-5 分鐘

---

## 3. 具體實作方案

### 3.1 GitHub Actions 實作

#### 目錄結構
```
.github/
└── workflows/
    ├── ci.yml           # 持續整合（測試、檢查）
    ├── deploy.yml       # 持續部署（部署到 GCP）
    └── backup.yml       # 定期備份任務（每日）
```

#### 範例：`.github/workflows/ci.yml`
```yaml
name: CI - Continuous Integration

on:
  pull_request:
    branches: [main, develop]
  push:
    branches: [develop]

jobs:
  lint:
    name: 程式碼檢查
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: 設定 Python 環境
        uses: actions/setup-python@v5
        with:
          python-version: '3.13'

      - name: 安裝檢查工具
        run: |
          pip install flake8 bandit black

      - name: Python 語法檢查
        run: flake8 app.py scheduler.py --max-line-length=120

      - name: 安全漏洞掃描
        run: bandit -r . -x ./venv,./lib

      - name: 程式碼格式檢查
        run: black --check app.py scheduler.py

  test:
    name: 自動化測試
    runs-on: ubuntu-latest
    needs: lint
    steps:
      - uses: actions/checkout@v4

      - name: 設定 Python 環境
        uses: actions/setup-python@v5
        with:
          python-version: '3.13'

      - name: 安裝依賴
        run: |
          pip install -r requirements.txt
          pip install pytest pytest-cov

      - name: 執行測試
        run: |
          pytest tests/ -v --cov=. --cov-report=html

      - name: 上傳測試覆蓋率報告
        uses: actions/upload-artifact@v4
        with:
          name: coverage-report
          path: htmlcov/
```

#### 範例：`.github/workflows/deploy.yml`
```yaml
name: CD - Deploy to GCP

on:
  push:
    branches: [main]
  workflow_dispatch:  # 允許手動觸發

jobs:
  deploy:
    name: 部署到 GCP Production
    runs-on: ubuntu-latest
    environment:
      name: production
      url: http://35.194.172.48

    steps:
      - uses: actions/checkout@v4

      - name: 設定 GCP 認證
        uses: google-github-actions/auth@v2
        with:
          credentials_json: ${{ secrets.GCP_SA_KEY }}

      - name: 設定 gcloud CLI
        uses: google-github-actions/setup-gcloud@v2

      - name: 備份遠端資料庫
        run: |
          gcloud compute ssh momo-server \
            --zone=asia-east1-a \
            --command="bash ~/momo_pro_system/backup_system.py"

      - name: 上傳程式碼
        run: |
          gcloud compute scp --recurse \
            --exclude='.git' --exclude='venv' --exclude='logs' \
            ./*.py ./*.html \
            momo-server:~/momo_pro_system/ \
            --zone=asia-east1-a

      - name: 重啟服務
        run: |
          gcloud compute ssh momo-server \
            --zone=asia-east1-a \
            --command="sudo systemctl restart momo"

      - name: 健康檢查
        run: |
          sleep 10
          gcloud compute ssh momo-server \
            --zone=asia-east1-a \
            --command="
              if sudo systemctl is-active --quiet momo; then
                echo '✓ 服務運行正常'
                exit 0
              else
                echo '✗ 服務啟動失敗'
                sudo journalctl -u momo -n 50 --no-pager
                exit 1
              fi
            "

      - name: 部署失敗時回滾
        if: failure()
        run: |
          echo "部署失敗，執行回滾..."
          gcloud compute ssh momo-server \
            --zone=asia-east1-a \
            --command="bash ~/momo_pro_system/rollback_latest.sh"
```

---

### 3.2 Self-hosted Runner 設定（節省成本）

在 GCP VM 上安裝 GitHub Actions Runner，讓 CI/CD 在自己的伺服器上執行（完全免費）。

#### 安裝步驟（在 GCP VM 上執行）
```bash
# 1. 建立 Runner 目錄
mkdir -p ~/actions-runner && cd ~/actions-runner

# 2. 下載 Runner
curl -o actions-runner-linux-x64-2.313.0.tar.gz \
  -L https://github.com/actions/runner/releases/download/v2.313.0/actions-runner-linux-x64-2.313.0.tar.gz

# 3. 解壓縮
tar xzf ./actions-runner-linux-x64-2.313.0.tar.gz

# 4. 配置 Runner（需要 GitHub Token）
./config.sh --url https://github.com/YOUR_USERNAME/momo_pro_system \
  --token YOUR_TOKEN

# 5. 安裝為系統服務
sudo ./svc.sh install
sudo ./svc.sh start
```

使用 Self-hosted Runner 後，所有 CI/CD 任務都在您的 GCP VM 上執行，**完全免費**。

---

## 4. 自動化測試策略

### 4.1 測試框架選擇：pytest

建議使用 `pytest` 作為主要測試框架，理由：
- Python 最流行的測試框架
- 語法簡潔，易於編寫和維護
- 豐富的插件生態系統
- 支援測試覆蓋率報告

### 4.2 測試架構規劃

```
tests/
├── __init__.py
├── conftest.py              # pytest 配置和 fixtures
├── test_app.py              # Flask 路由測試
├── test_database.py         # 資料庫操作測試
├── test_scheduler.py        # 排程器測試
├── test_crawler.py          # 爬蟲測試
└── test_email.py            # 郵件發送測試
```

### 4.3 測試範例

#### `tests/conftest.py`（測試配置）
```python
import pytest
from app import app, db

@pytest.fixture
def client():
    """Flask 測試客戶端"""
    app.config['TESTING'] = True
    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'

    with app.test_client() as client:
        with app.app_context():
            db.create_all()
        yield client

@pytest.fixture
def auth_client(client):
    """已登入的測試客戶端"""
    client.post('/login', data={
        'username': 'admin',
        'password': 'test_password'
    })
    return client
```

#### `tests/test_app.py`（路由測試）
```python
def test_index_redirect(client):
    """測試首頁重定向到登入頁面"""
    response = client.get('/')
    assert response.status_code == 302
    assert '/login' in response.location

def test_login_success(client):
    """測試登入成功"""
    response = client.post('/login', data={
        'username': 'admin',
        'password': 'correct_password'
    })
    assert response.status_code == 302
    assert '/' in response.location

def test_dashboard_access(auth_client):
    """測試已登入用戶可訪問儀表板"""
    response = auth_client.get('/dashboard')
    assert response.status_code == 200
    assert b'dashboard' in response.data.lower()
```

#### `tests/test_database.py`（資料庫測試）
```python
from datetime import date

def test_sales_query(auth_client):
    """測試銷售數據查詢"""
    # 插入測試數據
    from database.models import RealtimeSalesMonthly
    test_data = RealtimeSalesMonthly(
        日期=date.today(),
        商品ID='TEST001',
        商品名稱='測試商品',
        銷售數量=100,
        銷售金額=5000
    )
    db.session.add(test_data)
    db.session.commit()

    # 查詢測試
    result = RealtimeSalesMonthly.query.filter_by(商品ID='TEST001').first()
    assert result is not None
    assert result.銷售數量 == 100
```

### 4.4 測試執行

```bash
# 執行所有測試
pytest tests/ -v

# 執行測試並生成覆蓋率報告
pytest tests/ --cov=. --cov-report=html

# 執行特定測試檔案
pytest tests/test_app.py -v

# 執行特定測試函數
pytest tests/test_app.py::test_login_success -v
```

---

## 5. 零停機部署策略

### 5.1 Blue-Green 部署（推薦）

**概念**：維護兩個相同的生產環境（Blue 和 Green），部署時切換流量。

**實作方式**：
```bash
# 1. 部署到 Green 環境（新版本）
gcloud compute ssh momo-server --command="
  # 複製當前版本到 blue 目錄
  cp -r ~/momo_pro_system ~/momo_pro_system_blue

  # 部署新版本到 green 目錄
  cd ~/momo_pro_system_green
  git pull origin main

  # 啟動 Green 服務（Port 5001）
  sudo systemctl restart momo-green
"

# 2. 健康檢查 Green 環境
curl http://localhost:5001/health

# 3. 切換 Nginx 流量到 Green
# 修改 /etc/nginx/sites-available/momo
# proxy_pass http://127.0.0.1:5001;  # 從 5000 切換到 5001

# 4. 重新載入 Nginx（無停機）
sudo nginx -s reload

# 5. 停止 Blue 環境
sudo systemctl stop momo-blue
```

**優點**：
- 完全零停機
- 可快速回滾（切換回 Blue）
- 可進行 A/B 測試

**缺點**：
- 需要雙倍資源（兩個服務同時運行）
- 配置較複雜

---

### 5.2 Rolling 更新（簡化版，推薦用於小型專案）

**概念**：先更新程式碼，然後快速重啟服務。

**實作方式**（現有 deploy.sh 已實現）：
```bash
# 1. 備份資料庫
bash ~/momo_pro_system/backup_system.py

# 2. 上傳新程式碼
gcloud compute scp *.py *.html momo-server:~/momo_pro_system/

# 3. 重啟服務（Gunicorn graceful reload）
sudo systemctl reload momo  # 使用 reload 而非 restart

# 4. 健康檢查
sleep 5
curl http://localhost:5000/health || {
  echo "部署失敗，回滾..."
  # 從備份還原
  bash ~/momo_pro_system/rollback_latest.sh
}
```

**優點**：
- 簡單易實作
- 資源消耗少
- 適合小型專案

**缺點**：
- 有短暫停機（約 2-5 秒）
- 回滾較慢

---

### 5.3 建議實作

**階段 1（立即可行）**：使用 Rolling 更新 + Gunicorn Graceful Reload
- 修改 `momo.service`，使用 `ExecReload=/bin/kill -HUP $MAINPID`
- 部署時執行 `systemctl reload momo` 而非 `restart`

**階段 2（長期目標）**：Blue-Green 部署
- 當業務量增長後，再實作完整的 Blue-Green 部署

---

## 6. 密鑰管理

### 6.1 GitHub Secrets 配置

需要在 GitHub Repository Settings > Secrets 中新增：

| Secret 名稱 | 說明 | 取得方式 |
|------------|------|----------|
| `GCP_SA_KEY` | GCP Service Account JSON 金鑰 | 在 GCP Console 建立 Service Account 並下載 JSON |
| `GCP_PROJECT_ID` | GCP 專案 ID | GCP Console 查看 |
| `GCP_ZONE` | GCP VM 區域 | `asia-east1-a` |
| `GCP_INSTANCE` | GCP VM 實例名稱 | `momo-server` |
| `ADMIN_PASSWORD_HASH` | 管理員密碼雜湊 | 現有 `generate_password_hash.py` 生成 |

### 6.2 GCP Service Account 權限

需要賦予 Service Account 以下權限：
- `Compute Instance Admin (v1)` - 管理 VM
- `Service Account User` - 使用 Service Account
- `Storage Object Viewer` - 讀取 Cloud Storage（如果使用）

---

## 7. 實作階段規劃

### 階段 1：基礎設施準備（1-2 天）
- [ ] 將專案推送到 GitHub 私有倉庫
- [ ] 建立 GCP Service Account 並配置權限
- [ ] 在 GitHub 設定 Secrets
- [ ] 安裝 Self-hosted Runner（可選，節省成本）

### 階段 2：建立測試框架（3-5 天）
- [ ] 安裝 pytest 和相關插件
- [ ] 撰寫基本測試（路由測試、資料庫測試）
- [ ] 建立測試數據 fixtures
- [ ] 達到 >60% 測試覆蓋率

### 階段 3：設定 CI Pipeline（1-2 天）
- [ ] 建立 `.github/workflows/ci.yml`
- [ ] 配置程式碼檢查（flake8, bandit）
- [ ] 配置自動化測試
- [ ] 測試 PR 觸發 CI

### 階段 4：設定 CD Pipeline（2-3 天）
- [ ] 建立 `.github/workflows/deploy.yml`
- [ ] 整合現有 `deploy.sh` 腳本
- [ ] 實作自動備份機制
- [ ] 實作健康檢查和回滾
- [ ] 測試自動部署流程

### 階段 5：優化與監控（持續進行）
- [ ] 整合 Prometheus 監控到 CI/CD
- [ ] 設定部署失敗通知（Slack / Email）
- [ ] 優化部署速度
- [ ] 建立部署文件和 Runbook

**總計時間**：約 1-2 週（根據團隊經驗調整）

---

## 8. 成本分析

### 8.1 GitHub Actions（使用 Self-hosted Runner）
- **費用**：$0/月（完全免費）
- **說明**：所有 CI/CD 任務在 GCP VM 上執行，不消耗 GitHub 提供的分鐘數

### 8.2 GitHub Actions（使用 GitHub-hosted Runner）
- **免費額度**：2,000 分鐘/月（私有倉庫）
- **預估使用**：
  - 每次 CI: 5 分鐘
  - 每次 CD: 5 分鐘
  - 每月約 100 次部署 = 1,000 分鐘
- **費用**：$0/月（在免費額度內）

### 8.3 GitLab CI（Self-hosted）
- **額外 VM 成本**：$20-50/月（需要額外的 Runner VM）
- **維護成本**：需要額外的系統維護時間

### 8.4 總結
**建議使用 GitHub Actions + Self-hosted Runner**，完全免費且功能完整。

---

## 9. 風險評估

| 風險 | 影響 | 機率 | 緩解措施 |
|-----|------|------|----------|
| 部署失敗導致服務中斷 | 高 | 中 | 實作自動回滾機制 + 健康檢查 |
| 測試覆蓋不足，未發現 Bug | 中 | 高 | 逐步提升測試覆蓋率至 >80% |
| GCP Service Account 金鑰洩漏 | 高 | 低 | 使用 GitHub Secrets + 定期輪換金鑰 |
| CI/CD Pipeline 執行時間過長 | 低 | 中 | 使用 Self-hosted Runner + 快取依賴 |
| 資料庫遷移失敗 | 高 | 低 | 部署前自動備份 + 測試環境驗證 |

---

## 10. 建議與結論

### 10.1 核心建議
1. **採用 GitHub Actions** 作為 CI/CD 平台
2. **使用 Self-hosted Runner** 節省成本（完全免費）
3. **分階段實作**，先建立 CI，再建立 CD
4. **優先建立測試框架**，確保程式碼品質
5. **實作自動回滾機制**，降低部署風險

### 10.2 立即可執行的行動
1. 將專案推送到 GitHub（私有倉庫）
2. 建立 `tests/` 目錄並撰寫基本測試
3. 建立 `.github/workflows/ci.yml`（先實作 CI）
4. 驗證 CI 正常運作後，再建立 `deploy.yml`（實作 CD）

### 10.3 長期目標
- 測試覆蓋率 >80%
- 平均部署時間 <5 分鐘
- 自動化部署成功率 >95%
- 零停機部署（Blue-Green）

---

## 附錄

### A. 相關文件
- [GitHub Actions 官方文件](https://docs.github.com/en/actions)
- [pytest 官方文件](https://docs.pytest.org/)
- [Gunicorn Graceful Reload](https://docs.gunicorn.org/en/stable/signals.html)
- [GCP Service Account 設定](https://cloud.google.com/iam/docs/service-accounts)

### B. 參考專案
- [Flask CI/CD 範例](https://github.com/actions/starter-workflows/blob/main/deployments/azure-webapps-python.yml)
- [Python 測試最佳實踐](https://realpython.com/pytest-python-testing/)

---

**文件版本**：1.0
**撰寫日期**：2026-01-14
**下次更新**：實作完成後更新實際成果和經驗
