Integration Github Actions with Google Compute Engine
记录一下通过 Github Actions 把项目输出通过 SSH 发布到 Google GCE 的配置过程.
- 主要步骤:
- 创建新的身份池:先建一个 Workload Identity Pool,它像一个“外层容器”,用来容纳来自 GitHub 的外部身份。
- 配置映射并添加条件:在这个池里建一个 OIDC Provider,把 GitHub 发来的 OIDC claims 映射成 Google 可识别的属性,并加一个准入条件,限制只有你指定的 GitHub 组织/仓库/分支能进来。
- 将新池连接到服务帐户:这一步不是点一个“连接”按钮,而是给目标 Service Account 加一条 IAM policy binding,让来自这个池中、满足条件的 GitHub principal 能以 roles/iam.workloadIdentityUser 身份冒充这个服务账号。
对于“GitHub Actions 操作 GCE”,建议直接走 Workload Identity Federation through a Service Account,不要用长期 JSON key。Google 的 auth Action 文档把它列为推荐路径之一;而且如果要生成 OAuth 2.0 access token 或用 gcloud 操作资源,通常都要提供 service account。直接给 pool/resource 绑定权限的方式虽然可行,但并不是所有资源都支持 principalSet,并且直接 WIF 的 token 生命周期还有更严格限制。
- 前置条件: 你至少要有这些权限:
- 配置 Workload Identity Pool / Provider:roles/iam.workloadIdentityPoolAdmin
- 创建 Service Account:roles/iam.serviceAccountCreator
- 修改 Service Account 的 IAM 访问策略:roles/iam.serviceAccountAdmin (docs.cloud.google.com) 另外,如果你的项目还没开相关 API,至少要确保 IAM API 和 Compute Engine API 已启用;Compute Engine API 的服务名是 compute.googleapis.com,IAM API 会处理 service account 相关能力。
- 资源准备: 先假设已有或需要准备这些资源值:
- Github Organization or User, Github 组织或用户名. 比如 myorg
- Github Repository, Github 仓库地址. 比如 myorg/myrepo
- GCP Project ID, Google Cloud 项目ID. 比如 my-gcp-project
- GCP Project Number, Google Cloud 项目编号. 比如 123456789012
- GCP Workload Identity Pool ID, 需要命名并创建. 比如 github-actions
- GCP Workload Identity Provider ID, 需要命名并创建. 比如 github-myorg-myrepo
- GCP Service Account, 现有 GCE 实例关联的 Service Account. 比如自动生成的 123456789012-compute
- GCP Service Account Email, 现有 GCE 实例关联的 Service Account Email. 比如自动生成的 [email protected]
- 安全优势 使用 OIDC Workload Identity Federation 相比传统 SSH 密钥方式有以下优势:
- 无需长期密钥:不再需要在 GitHub Secrets 中存储 SSH 私钥,认证令牌是短期的,自动过期
- 细粒度权限控制:可以通过 GCP IAM 精细控制部署权限
- 审计追踪:所有认证和操作都会记录在 GCP Cloud Audit Logs 中
- 条件约束:可以限制只允许特定仓库、分支触发部署
- 权限分离:SA 用户仅负责 SSH 登录和文件传输,实际部署以 OS 用户身份执行,遵循最小权限原则
登录 Google Cloud -> IAM & Admin / Wordload Identity Federation
创建和配置 Workload Identity Pool 和 Provider
- 如果没有 Workload Identity Pool, 点击
Create pool根据需要创建一个
输入 Pool 名称, Pool ID 会自动生成, 也可以修改, 点击 Continue 继续.
对 gcloud cli 熟悉也可以通过 cli 创建. 示例:
gcloud iam workload-identity-pools create "github-actions" \
--project="${GCP_PROJECT_ID}" \
--location="global" \
--display-name="GitHub Actions"
-
继续创建 Provider, Provider 类型选择
OpenID Connect (OIDC), 输入 Provider 名称, Issuer 填入 GitHub 官方 OIDC issuerhttps://token.actions.githubusercontent.com, 其它不变
-
创建 Provider 属性映射, 映射/mapping 是"把 claim 变成可引用属性"

google.subject->assertion.subattribute.repository->assertion.repositoryattribute.repository_owner->assertion.repository_ownerattribute.actor->assertion.actorattribute.ref->assertion.ref
具体字段细节请参考: https://docs.cloud.google.com/iam/docs/workload-identity-federation#mapping
- 设置 Attribute conditions, condition 是"准入规则", 以限制能通过认证的范围. 这里我们设置只有指定的 Owner 和指定的仓库才能通过认证触发相应动作. 最后点击
Save完成 Provider Pool 创建assertion.repository_owner == 'GITHUB_ORG_OR_USER' && attribute.repository == 'GITHUB_ORG_OR_USER/GITHUB_REPO_NAME'
合并上述 #2/#3/#4 步骤的 gcloud cli 示例:
gcloud iam workload-identity-pools providers create-oidc "github-actions-provider" \
--project="${GCP_PROJECT_ID}" \
--location="global" \
--workload-identity-pool="github-actions-pool" \
--display-name="GitHub Actions Provider" \
--attribute-mapping="googled.subject=assertion.sub,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner,attribute.actor=assertion.actor,attribute.ref=assertion.ref" \
--attribute-condition="assertion.repository_owner == 'GITHUB_ORG_OR_USER'" \
--issuer-uri="https://token.actions.githubusercontent.com"
创建和配置 Github Actions 的 Service Account
-
如果你还没有专门给 GitHub Actions 用的 Service Account,先创建一个: IAM & Admin/IAM 和管理 -> Service Accounts/服务账号 -> Create service account/创建服务账号, 创建时只填服务账号名称即可, 权限稍后配置.
-
为 Service Account 授于角色
- 进入 IAM & Admin → IAM ->
Allowtab - 找到你刚创建的 service account(邮箱类似 gha-gce@PROJECT_ID.iam.gserviceaccount.com)
- 点击铅笔 Edit principal
- 根据需要在 Assign roles 里增加 相应的Role. 在些我需要让这个 SA 可以 SSH 到 Compute Engine 中执行 sudo, 因此给这个SA 授予
Compute Viewer和Compute OS Admin Login - 保存。Google Cloud 的 IAM 策略把角色绑定到 principal 上,项目级角色会影响该项目内资源。
- 把 Pool “连接”到 Service Account
- 进入 IAM & Admin -> Service accounts
- 找到你刚才创建的 Service Account, 点击进入详细页并切换到
Principals with accesstab - 点击
Grant access把指定 GitHub repo 的 principal 授权为可以使用该 Service Account:New principals字段中填写principalSet://iam.googleapis.com/projects/<GCP_PROJECT_NUMBER>/locations/global/workloadIdentityPools/<IDENTITY_POOL_ID>/attribute.repository/<GITHUB_ORG_OR_USER>/<GITHUB_REPO_NAME>(这背后的意思是: “允许来自这个 pool 且 attribute.repository == <GITHUB_ORG_OR_USER>/<GITHUB_REPO_NAME> 的主体使用这个 service account。”, 这种 principal identifier 格式是 Google IAM 支持的做法)Role选择Workload Identity User. Workload Identity User 允许工作负载身份代表该 service account 获取 access token / OIDC token- 点击
Save完成授权
检查是否启用 Compute Engine API
如果你的项目从没用过 Compute / IAM 相关能力,建议顺手检查 API 是否启用。控制台操作:
- 进入
APIs & Services-> 点击Enabled APIs & services - 搜索并启用
Compute Engine API - 与 IAM / Service Account 相关 API(如果页面提示缺失,一并启用)
授权 GitHub Actions 的 SA 使用 VM 的 SA
如果 Github Actions 使用的 Service Account 与 GCP 服务器绑定的 Service Account 不是同一个, 则需要授权 GitHub Actions 的 Service Account 可以使用 VM 绑定的 SA,
- IAM & Admin → Service Accounts, 找到 VM 绑定的 service account, 点击进入详情页
- 切换到
Principals with accesstab, 点击Grant access New principals搜索并选择 Github Actions 使用的 Service account- 在
Assign roles中添加Service Account Userrole
开启 OS Login
- 进入 GCP
Comoute Engine控制台并找到需要操作的VM - 点击
Edit修改该VM, 在Metadata中添加 keyenable-oslogin, 设置 value 为TRUE后保存
Github Actions 设置
在项目的 .github/workflows 目录下创建 deploy.yml
一个配置示例:
name: Build and Deploy
on:
push:
branches:
- release
workflow_dispatch:
# Required for requesting the OIDC JWT token
permissions:
contents: read
id-token: write
jobs:
build-and-deploy:
runs-on: ubuntu-latest
env:
DEPLOY_BASE: ${{ secrets.GCP_VM_DEPLOY_BASE }}
DEPLOY_TAR: /tmp/mywebsite-com-build.tar.gz
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
lfs: true
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'yarn'
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Build static files
run: yarn build
- name: Compress build output
run: tar -czf build.tar.gz -C build .
- name: Authenticate to Google Cloud via OIDC
id: auth
uses: google-github-actions/auth@v3
with:
workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v2
- name: Generate release timestamp
id: release
run: echo "timestamp=$(TZ=Asia/Shanghai date +%Y%m%d%H%M%S)" >> "$GITHUB_OUTPUT"
- name: Upload archive to GCP VM
run: |
gcloud compute scp build.tar.gz \
${{ secrets.GCP_VM_SA_USER }}@${{ secrets.GCP_VM_INSTANCE_NAME }}:$DEPLOY_TAR \
--zone=${{ secrets.GCP_VM_ZONE }} \
--project=${{ secrets.GCP_PROJECT_ID }}
- name: Deploy, switch release, and cleanup
env:
RELEASE_TS: ${{ steps.release.outputs.timestamp }}
run: |
gcloud compute ssh ${{ secrets.GCP_VM_SA_USER }}@${{ secrets.GCP_VM_INSTANCE_NAME }} \
--zone=${{ secrets.GCP_VM_ZONE }} \
--project=${{ secrets.GCP_PROJECT_ID }} \
--command="sudo chown ${{ secrets.GCP_VM_OS_USER }}:${{ secrets.GCP_VM_OS_USER }} $DEPLOY_TAR && sudo -u ${{ secrets.GCP_VM_OS_USER }} $DEPLOY_BASE/deploy.sh '$DEPLOY_BASE' '$RELEASE_TS' '$DEPLOY_TAR'"
在 github 项目 -> Settings -> Secrets and veriables 下创建相应的 secrets
配合 deploy.yml 示例, 提供如下 secrets:
| Secret 名称 | 说明 | 示例 |
|---|---|---|
| GCP_WORKLOAD_IDENTITY_PROVIDER | Workload Identity Provider 完整资源名 | projects/<GCP_PROJECT_NUMBER>/locations/global/workloadIdentityPools/<IDENTITY_POOL_ID>/providers/<IDENTITY_PROVIDER_ID> |
| GCP_SERVICE_ACCOUNT | 部署用 GCP Service Account 邮箱 | <SERVICE_ACCOUNT_ID>@<GCP_PROJECT_ID>.iam.gserviceaccount.com |
| GCP_PROJECT_ID | GCP 项目 ID | my-gcp-project |
| GCP_VM_INSTANCE_NAME | 目标 GCP VM 实例名称 | or01 |
| GCP_VM_ZONE | VM 实例所在的 GCP 区域 | us-west1-a |
| GCP_VM_SA_USER | Service Account 的 OS Login 用户名(用于 SSH/SCP) | sa_111929993936969721118 |
| GCP_VM_OS_USER | VM 上实际执行部署的用户名(deploy.sh 的运行身份) | zhangsan |
| GCP_VM_DEPLOY_BASE | VM 上的部署基础路径 | /var/www/mywebsite.com |
说明:
GCP_VM_SA_USER是 GCP OS Login 为 Service Account 自动生成的用户名(格式为sa_<OAuth2 Client ID>),用于gcloud compute scp/ssh登录。GCP_VM_OS_USER是服务器上拥有部署目录权限的实际用户,deploy.sh会通过sudo -u以该用户身份执行。
GCP 服务器端一次性配置
1. 创建部署目录结构
sudo mkdir -p /var/www/mywebsite.com/releases
sudo chown -R <deploy_user>:<deploy_user> /var/www/mywebsite.com
2. 部署 deploy.sh 脚本
创建脚本 $GCP_VM_DEPLOY_BASE/deploy.sh:
#!/usr/bin/env bash
set -euo pipefail
DEPLOY_BASE="$1"
RELEASE_TS="$2"
DEPLOY_TAR="$3"
RELEASE_DIR=${DEPLOY_BASE}/releases/${RELEASE_TS}
CURRENT_LINK=${DEPLOY_BASE}/current
# Create release directory and extract archive
mkdir -p ${RELEASE_DIR}
tar -xzf $DEPLOY_TAR -C ${RELEASE_DIR}
rm -f $DEPLOY_TAR
# Atomically switch the 'current' symlink to the new release
# ln -sfn is atomic on Linux (single rename syscall)
ln -sfn ${RELEASE_DIR} ${CURRENT_LINK}
# Test nginx config
sudo /usr/sbin/nginx -t
# Reload nginx to pick up any potential config changes
sudo systemctl reload nginx
# Clean up old releases, keeping only the current and previous
cd ${DEPLOY_BASE}/releases
ls -1dt */ | tail -n +3 | xargs -r rm -rf
echo '✅ Deployment completed successfully'
echo "📦 Release: ${RELEASE_TS}"
echo "📂 Path: ${RELEASE_DIR}"
ls -la ${CURRENT_LINK}
设置脚本权限:
chmod +x /var/www/mywebsite.com/deploy.sh
3. 配置 sudoers
在/etc/sudoers.d目录下为 deploy user 创建 sudoer配置文件. 比如 zhangsan_sudoers
zhangsan ALL=(root) NOPASSWD: /usr/sbin/nginx -t
zhangsan ALL=(root) NOPASSWD: /usr/sbin/nginx -s reload
zhangsan ALL=(root) NOPASSWD: /usr/bin/systemctl reload nginx
将 suders 文件的权限修改为 440
sudo chmod 440 /etc/sudoers.d/zhangsan_sudoers
---全文完---
