Skip to main content

Integration Github Actions with Google Compute Engine

· 9 min read

记录一下通过 Github Actions 把项目输出通过 SSH 发布到 Google GCE 的配置过程.

  1. 主要步骤:
  • 创建新的身份池:先建一个 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 生命周期还有更严格限制。

  1. 前置条件: 你至少要有这些权限:
  • 配置 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 相关能力。
  1. 资源准备: 先假设已有或需要准备这些资源值:
  • 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]
  1. 安全优势 使用 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

  1. 如果没有 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"
  1. 继续创建 Provider, Provider 类型选择 OpenID Connect (OIDC), 输入 Provider 名称, Issuer 填入 GitHub 官方 OIDC issuer https://token.actions.githubusercontent.com, 其它不变

  2. 创建 Provider 属性映射, 映射/mapping 是"把 claim 变成可引用属性"

  • google.subject -> assertion.sub
  • attribute.repository -> assertion.repository
  • attribute.repository_owner -> assertion.repository_owner
  • attribute.actor -> assertion.actor
  • attribute.ref -> assertion.ref

具体字段细节请参考: https://docs.cloud.google.com/iam/docs/workload-identity-federation#mapping

  1. 设置 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

  1. 如果你还没有专门给 GitHub Actions 用的 Service Account,先创建一个: IAM & Admin/IAM 和管理 -> Service Accounts/服务账号 -> Create service account/创建服务账号, 创建时只填服务账号名称即可, 权限稍后配置.

  2. 为 Service Account 授于角色

  • 进入 IAM & Admin → IAM -> Allow tab
  • 找到你刚创建的 service account(邮箱类似 gha-gce@PROJECT_ID.iam.gserviceaccount.com
  • 点击铅笔 Edit principal
  • 根据需要在 Assign roles 里增加 相应的Role. 在些我需要让这个 SA 可以 SSH 到 Compute Engine 中执行 sudo, 因此给这个SA 授予Compute ViewerCompute OS Admin Login
  • 保存。Google Cloud 的 IAM 策略把角色绑定到 principal 上,项目级角色会影响该项目内资源。
  1. 把 Pool “连接”到 Service Account
  • 进入 IAM & Admin -> Service accounts
  • 找到你刚才创建的 Service Account, 点击进入详细页并切换到Principals with access tab
  • 点击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 是否启用。控制台操作:

  1. 进入APIs & Services -> 点击 Enabled APIs & services
  2. 搜索并启用 Compute Engine API
  3. 与 IAM / Service Account 相关 API(如果页面提示缺失,一并启用)

授权 GitHub Actions 的 SA 使用 VM 的 SA

如果 Github Actions 使用的 Service Account 与 GCP 服务器绑定的 Service Account 不是同一个, 则需要授权 GitHub Actions 的 Service Account 可以使用 VM 绑定的 SA,

  1. IAM & Admin → Service Accounts, 找到 VM 绑定的 service account, 点击进入详情页
  2. 切换到 Principals with access tab, 点击Grant access
  3. New principals搜索并选择 Github Actions 使用的 Service account
  4. Assign roles中添加Service Account User role

开启 OS Login

  1. 进入 GCP Comoute Engine控制台并找到需要操作的VM
  2. 点击Edit修改该VM, 在Metadata中添加 key enable-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_PROVIDERWorkload 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_IDGCP 项目 IDmy-gcp-project
GCP_VM_INSTANCE_NAME目标 GCP VM 实例名称or01
GCP_VM_ZONEVM 实例所在的 GCP 区域us-west1-a
GCP_VM_SA_USERService Account 的 OS Login 用户名(用于 SSH/SCP)sa_111929993936969721118
GCP_VM_OS_USERVM 上实际执行部署的用户名(deploy.sh 的运行身份)zhangsan
GCP_VM_DEPLOY_BASEVM 上的部署基础路径/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

---全文完---

ClustrMaps