重新定義部署流程:
更安全、更自動
這份文件整合了 Google Cloud (GCE)、GitHub Actions 與 Cloudflare 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 的身分代理人。
- 前往 IAM 與管理 > 服務帳戶。
- 建立帳戶:
github-deploy。 - 賦予以下角色:
- ● 角色 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
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 查看日誌。