重新定義部署流程:
更安全、更自動

這份文件整合了 Google Cloud (GCE)GitHub ActionsCloudflare Zero Trust。我們解決了傳統部署中的 IP 暴露、SSH 金鑰管理與權限不對稱等問題。

無密鑰身份驗證

不再手動上傳 SSH Key 或 Service Account JSON。透過 WIF OIDC 技術,讓 GitHub 直接與 GCP 溝通。

零入站防火牆

Cloudflare Tunnel 建立由內而外的連線,你的 GCE 實例可以對外關閉 5000 埠,只允許 localhost 存取。

2 GCP 基礎環境建立

2.1 建立服務帳戶 (Service Account)

這是 GitHub Actions 的身分代理人。

  1. 前往 IAM 與管理 > 服務帳戶
  2. 建立帳戶:github-deploy
  3. 賦予以下角色:
    • ● 角色 1:Compute Instance Admin (v1)
    • ● 角色 2:Service Account User
    • ● 角色 3:Compute OS Admin Login (若需 sudo 權限)

2.2 建立 GCE 執行個體

建立 VM 時,請注意以下設定:

  • 服務帳戶:選擇剛建立的 github-deploy
  • 防火牆:勾選「允許 HTTP/HTTPS」流量 (Cloudflare 代理時仍需 80 埠或直接用 Tunnel)。
  • 標籤:建議設定 http-server 方便防火牆管理。

3 Workload Identity Federation (WIF)

第一步:建立 Pool & Provider

  • 1. 進入 Workload Identity Federation
  • 2. 建立 Pool: github-pool
  • 3. 建立 Provider: github-provider (選擇 OIDC)。
  • 4. Issuer URL: https://token.actions.githubusercontent.com

第二步:屬性映射 (Mapping)

這是讓 GCP 認出你 GitHub 帳號的橋樑。

google.subject = assertion.sub
attribute.repository = assertion.repository
attribute.repository_owner = assertion.repository_owner

第三步:將 SA 權限開放給 WIF

這一步解決 getAccessToken denied 報錯。將你的 GitHub 身分加入該 Service Account 的 PrincipalSet

principalSet://iam.googleapis.com/projects/865505184616/locations/global/workloadIdentityPools/github-pool/attribute.repository_owner/WangWilly

4 Cloudflare Tunnel 設定

零信任安全連線

Cloudflare Tunnel 就像是在你的 GCE 與 Cloudflare 之間接了一根專線,黑客在網路上看不到你的伺服器 IP。

Service Type HTTP
Local URL http://localhost:5000

GCE 安裝指令

# 安裝套件
curl -L --output cf.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cf.deb

# 登入與啟動
sudo cloudflared service install [YOUR_TOKEN]

5 GitHub Actions 配置

.github/workflows/deploy.yml

name: Deploy to GCE
on: [workflow_dispatch] # 手動觸發
jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: 'write' # WIF 必備
      contents: 'read'
    steps:
      - uses: actions/checkout@v4
      - id: 'auth'
        uses: 'google-github-actions/auth@v2'
        with:
          workload_identity_provider: '${{ secrets.WIF_PROVIDER }}'
          service_account: '${{ secrets.GCP_SA_EMAIL }}'
      - uses: 'google-github-actions/setup-gcloud@v2'
      - name: 'Execute Deploy Script'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          DEPLOY_ENV_CONTENT: ${{ secrets.DEPLOY_ENV }}
          GCE_SSH_USER: iammodeltaiwan
        run: bash scripts/github_deploy.sh

6 核心部署腳本 (github_deploy.sh)

#!/bin/bash
set -e
PROJECT_DIR="~/linebot-iammodel"
REMOTE_USER=$GCE_SSH_USER 

# 1. 建立遠端指令
cat <<'REMOTE_EOF' > deploy_script.sh
set -e
PROJECT_DIR="$HOME/linebot-iammodel"
export GH_TOKEN="${REMOTE_GH_TOKEN}"

# 設定 Git 憑證小幫手 (解決 private repo 認證)
gh auth setup-git

# 檢查目錄並同步
if [ ! -d "$PROJECT_DIR/.git" ]; then
  rm -rf "$PROJECT_DIR"
  gh repo clone WangWilly/linebot-immodel "$PROJECT_DIR"
fi
cd "$PROJECT_DIR"
git fetch origin main && git reset --hard origin/main && git clean -fd

# 環境建置
export PATH="$HOME/.local/bin:$PATH"
if ! command -v uv &> /dev/null; then
  curl -LsSf https://astral.sh/uv/install.sh | sh
fi
uv sync

# 重啟程序
sudo fuser -k 5000/tcp || true
nohup uv run uvicorn app:app --host 0.0.0.0 --port 5000 > app.log 2>&1 &
REMOTE_EOF

# 2. 準備 .env 並傳輸
printf "%s" "${DEPLOY_ENV_CONTENT}" > .env.tmp
gcloud compute scp deploy_script.sh ${REMOTE_USER}@${GCE_INSTANCE_NAME}:~/ --zone=${GCE_INSTANCE_ZONE}
gcloud compute scp .env.tmp ${REMOTE_USER}@${GCE_INSTANCE_NAME}:~/env.tmp --zone=${GCE_INSTANCE_ZONE}

# 3. 執行
gcloud compute ssh ${REMOTE_USER}@${GCE_INSTANCE_NAME} --zone=${GCE_INSTANCE_ZONE} --command="mkdir -p $PROJECT_DIR && mv ~/env.tmp $PROJECT_DIR/.env && REMOTE_GH_TOKEN='${GH_TOKEN}' bash ~/deploy_script.sh"

FAQ: 踩坑筆記

Q: 為什麼 SSH 一直噴 403 Access Denied?

A: 兩大原因:1. SA 沒開 Service Account Token Creator。 2. 你沒把 GitHub 的 PrincipalSet 加入 SA 的存取清單中。請回頭檢查 Phase 3 的路徑。

Q: 為什麼 git fetch 失敗?

A: CI/CD 執行時是非互動式終端機,git 預設不讀取環境變數。務必執行 gh auth setup-git 讓 GitHub CLI 接手憑證管理。

Q: 為什麼 Cloudflare Tunnel 顯示 Down?

A: 請檢查 GCE 是否有安裝 cloudflared 並啟動為 systemd 服務。可用 sudo journalctl -u cloudflared 查看日誌。