GKE Pod 身份:访问 Bucket 与 Secret Manager

验证:静态检查 · 核验于 2026-10-14
适用版本
GKE Autopilot / VPC-native Standard;目标云环境未实测
验证范围
官方行为核对;本地构建、链接、Bash、YAML 和 Python 语法检查。未创建云资源或部署 Pod。

先注意:同 Project 的跨集群身份共享#

同一 GCP Project 的 GKE 集群共享 Workload Identity Pool。使用本页按名称授权的 Principal 时,相同 namespace + Kubernetes ServiceAccount(KSA)名称会被识别为同一身份,不能默认只授权了某一个集群。

例如,集群 A 和集群 B 都有 orders/reader。给 …/subject/ns/orders/sa/reader 授予 Bucket / Secret 权限时,两个集群中使用该 KSA 的 Pod 都可能获得权限,因为这个标识不包含 cluster 名称。

  • 互不信任的团队或需要独立身份池的环境:使用不同 Project 隔离。
  • 必须共用 Project:按官方支持的 IAM Conditions 限制具体集群,并检查其他 binding 是否仍授予宽泛权限。
  • 不同 namespace/KSA 命名可减少误用,但不能隔离有权创建同名 KSA 的其他集群管理员。官方 identity sameness 说明

WIF(Workload Identity Federation) 让工作负载用已有身份换取短期云凭据,无须保存长期 Service Account key。本文使用 Workload Identity Federation for GKE:Pod 以 KSA 身份访问 Google Cloud API;GKE 管理 identity pool/provider,无须自行搭建集群的外部 OIDC 信任。

推荐做法#

  • 业务使用独立 Kubernetes ServiceAccount(KSA);在目标 Bucket / Secret 上授权,不把业务权限授给 Node SA。
  • 优先直接授权 KSA 对应的 IAM Principal。遇到 Cloud Run 调用、Storage signed URL 等限制时,再使用 Google IAM Service Account 身份。
  • 应用使用 ADC(Application Default Credentials) 自动取凭据,不挂载 SA JSON key,不在 Pod 中执行用户登录。
  • Secret 读取固定数字版本,轮换时明确刷新方式;不把 Secret 内容或 token 打到日志。
方式 权限授予谁 需要 Google Service Account 吗
本页主方案:直接授权 目标资源上的 KSA Principal 不需要
备选:Service Account impersonation 资源授权给 Google SA;Google SA 再允许 KSA impersonate 需要,且 IAM binding 与 KSA annotation 都要配置
Pod 使用 KSA → GKE metadata server → STS 交换短期凭据 → Google Cloud API

STS(Security Token Service)负责交换短期凭据。KSA 是 Kubernetes 对象,Google SA 是云身份,两者不同;启用 WIF 不自动授予业务权限。身份机制

检查启用状态 · 资源级授权 · Pod 读取示例 · SDK · 排障

检查集群与执行身份#

已有集群、gcloud、kubectl 和可达的控制面;私有 endpoint 的访问方式见 集群入口。以下在获授权的操作终端执行,不是在 Pod 内登录管理员账号。

操作者需要启用 API、创建测试资源及修改其 IAM policy 的权限、Kubernetes namespace/KSA/Pod 管理和 pods/exec 权限;示例 KSA 只获得资源读取权限。

GKE_PROJECT_ID='REPLACE_GKE_PROJECT_ID'
RESOURCE_PROJECT_ID="$GKE_PROJECT_ID"  # Bucket / Secret 可属于另一项目
CLUSTER='REPLACE_CLUSTER'
LOCATION='REPLACE_REGION_OR_ZONE'
KUBE_CONTEXT='REPLACE_VERIFIED_KUBE_CONTEXT'
BUCKET_LOCATION='asia-east1'  # 按实际数据位置要求调整

gcloud auth list --filter=status:ACTIVE

gcloud container clusters describe "$CLUSTER" \
  --project="$GKE_PROJECT_ID" --location="$LOCATION" \
  --format='yaml(autopilot.enabled,workloadIdentityConfig,nodePools.name,nodePools.config.workloadMetadataConfig,nodePools.config.oauthScopes)'

kubectl config get-contexts "$KUBE_CONTEXT"
  • Autopilot:始终启用 WIF。
  • Standard:集群的 pool 应为 GKE_PROJECT_ID.svc.id.goog;承载 Pod 的 node pool 应使用 GKE_METADATA。

只启用集群 WIF,不会自动更新已有 node pool。 将已有 pool 切到 GKE_METADATA 前,先为依赖 Node SA 权限的应用配置自己的身份和授权,否则应用可能失去访问权限。

  • Secret Manager 访问还须核对节点的 cloud-platform OAuth scope;授权与 API 网络路径仍分别检查。
Standard 未启用时的配置命令

仅对计划迁移的 Standard 集群/node pool 执行。已有工作负载若依赖 Node SA,切到 GKE metadata server 后可能失去原权限;先完成应用授权,必要时用新 pool 迁移。

gcloud container clusters update "$CLUSTER" \
  --project="$GKE_PROJECT_ID" --location="$LOCATION" \
  --workload-pool="${GKE_PROJECT_ID}.svc.id.goog"

NODE_POOL='REPLACE_NODE_POOL'

gcloud container node-pools update "$NODE_POOL" \
  --project="$GKE_PROJECT_ID" --location="$LOCATION" \
  --cluster="$CLUSTER" --workload-metadata=GKE_METADATA

这两条分别设置集群身份池和 node pool 凭据来源,不会代替 Bucket / Secret IAM 授权。启用与迁移

示例资源与 KSA#

本例新建专用 namespace、Bucket 和 Secret,只写入合成内容,不依赖 lab.env。下方折叠区是准备步骤;已有资源的操作只需按自己的资源值使用后续授权方式,不执行本例清理。

准备专用 Bucket、Secret 和 KSA

保留本终端变量供后续测试与清理使用。创建报错时先处理原因,不继续使用同名现有资源。

GKE_PROJECT_NUMBER=$(gcloud projects describe "$GKE_PROJECT_ID" --format='value(projectNumber)')
RUN_ID=$(date -u +%Y%m%d%H%M%S)
NAMESPACE="wi-demo-${RUN_ID}"
KSA='reader'
BUCKET="gke-wi-${GKE_PROJECT_NUMBER}-${RUN_ID}"
SECRET="gke-wi-${RUN_ID}"

gcloud services enable iamcredentials.googleapis.com sts.googleapis.com --project="$GKE_PROJECT_ID"

gcloud services enable storage.googleapis.com secretmanager.googleapis.com --project="$RESOURCE_PROJECT_ID"

kubectl --context="$KUBE_CONTEXT" create namespace "$NAMESPACE"
kubectl --context="$KUBE_CONTEXT" -n "$NAMESPACE" create serviceaccount "$KSA"

gcloud storage buckets create "gs://$BUCKET" \
  --project="$RESOURCE_PROJECT_ID" --location="$BUCKET_LOCATION" \
  --uniform-bucket-level-access --public-access-prevention

printf 'hello from GKE workload identity\n' | gcloud storage cp - "gs://$BUCKET/hello.txt" --project="$RESOURCE_PROJECT_ID"

printf 'synthetic-demo-value' | \
  gcloud secrets create "$SECRET" --project="$RESOURCE_PROJECT_ID" \
  --replication-policy=automatic --data-file=-

新 Secret 的首次版本为 1。本例使用 global Secret resource;regional Secret 的资源名和调用方式需按对应 API 调整。

在 Bucket 和 Secret 上授权#

以下变量使用上一节的示例值;复用现有资源时明确填写 NAMESPACE、KSA、BUCKET、SECRET。

Principal 中的 project number 与 pool ID 使用 GKE 所属 Project;IAM binding 配置在目标资源上。 跨 Project 访问时,不要把 Principal 中的项目改成 Bucket / Secret 所属项目。

Principal 为什么这样写#

Principal 是 IAM 用来识别“给谁授权”的身份标识。 本例按 KSA 名称选身份,Google 规定的格式是:

principal://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/PROJECT_ID.svc.id.goog/subject/ns/NAMESPACE/sa/KSA_NAME
部分 规则与来源
principal://iam.googleapis.com 固定前缀,表示 IAM Principal 标识,不是浏览器访问地址
projects/PROJECT_NUMBER GKE 所属项目的数字编号,由下方 gcloud projects describe 查询;不是 Project ID
locations/global 身份池的固定位置,不填集群所在 Region
workloadIdentityPools/PROJECT_ID.svc.id.goog GKE 托管的项目身份池;.svc.id.goog 是固定后缀,goog 没有拼错
subject/ns/NAMESPACE/sa/KSA_NAME 固定路径关键字加实际 namespace、KSA 名称;ns 和 sa 不替换

例如 GKE 项目 ID 为 example-gke、编号为 123456789012,namespace 为 orders,KSA 为 reader,标识就是:

principal://iam.googleapis.com/projects/123456789012/locations/global/workloadIdentityPools/example-gke.svc.id.goog/subject/ns/orders/sa/reader

在已启用 WIF 的 GKE 中创建 orders/reader 后,就可按此格式在 IAM 中引用它,不需要再创建一个 Principal 资源。仅创建 namespace 不会创建自定义的 reader KSA。

下方 KSA_PRINCIPAL="..." 只是给 shell 变量赋值;${变量名} 会替换成前面设置的值。真正授予权限的是后面的 add-iam-policy-binding --member="$KSA_PRINCIPAL"。Pod 再通过 serviceAccountName: reader 使用这个身份。官方 Principal 格式

GKE_PROJECT_NUMBER=$(gcloud projects describe "$GKE_PROJECT_ID" --format='value(projectNumber)')
KSA_PRINCIPAL="principal://iam.googleapis.com/projects/${GKE_PROJECT_NUMBER}/locations/global/workloadIdentityPools/${GKE_PROJECT_ID}.svc.id.goog/subject/ns/${NAMESPACE}/sa/${KSA}"

gcloud storage buckets add-iam-policy-binding "gs://$BUCKET" \
  --project="$RESOURCE_PROJECT_ID" \
  --member="$KSA_PRINCIPAL" --role=roles/storage.objectViewer

gcloud secrets add-iam-policy-binding "$SECRET" \
  --project="$RESOURCE_PROJECT_ID" \
  --member="$KSA_PRINCIPAL" --role=roles/secretmanager.secretAccessor
Role 授权范围 允许的操作
roles/storage.objectViewer 这个 Bucket 读取、列出对象,不写入或删除
roles/secretmanager.secretAccessor 这个 Secret 读取其可访问版本的 payload,不管理 Secret

Bucket 直接授权的前提:启用 uniform bucket-level access。 本例创建命令已启用;复用已有 Bucket 时先检查,不能只添加 IAM binding 就认为可用。

不在 Project 上授这两个 role,可避免把权限扩大到项目全部 Bucket / Secret。首次 IAM 变更需等待传播。Storage roles、Secret Manager IAM

Pod 读取 Bucket 和 Secret#

使用 Cloud CLI 镜像只为验证身份,无须在业务镜像安装 gcloud。把 Pod 的 serviceAccountName 指定为获授权 KSA;省略时会使用 namespace 的 default KSA。

创建测试 Pod:显式 KSA,不挂 key

生成本地清单;环境变量都是资源标识,没有 Secret 值。stable 仅作测试镜像,正式交付固定审核过的 digest。

cat > wi-check.yaml <<EOF_POD
apiVersion: v1
kind: Pod
metadata:
  name: wi-check
  namespace: ${NAMESPACE}
spec:
  serviceAccountName: ${KSA}
  restartPolicy: Never
  containers:
    - name: cli
      image: gcr.io/google.com/cloudsdktool/google-cloud-cli:stable
      command: ["sleep", "3600"]
      env:
        - name: RESOURCE_PROJECT_ID
          value: "${RESOURCE_PROJECT_ID}"
        - name: BUCKET
          value: "${BUCKET}"
        - name: SECRET
          value: "${SECRET}"
      resources:
        requests:
          cpu: "250m"
          memory: "512Mi"
          ephemeral-storage: "256Mi"
        limits:
          cpu: "250m"
          memory: "512Mi"
          ephemeral-storage: "256Mi"
EOF_POD

Standard 在上述 Pod spec 下补充以下字段,保证调度到启用 WIF 的节点;Autopilot 不添加该 selector:

spec:
  nodeSelector:
    iam.gke.io/gke-metadata-server-enabled: "true"

确认清单后创建 Pod:

kubectl --context="$KUBE_CONTEXT" apply -f wi-check.yaml

kubectl --context="$KUBE_CONTEXT" -n "$NAMESPACE" wait --for=condition=Ready pod/wi-check --timeout=600s

Ready 只证明容器启动,下面两个实际 API 调用才验证 IAM。首次节点供应或镜像拉取失败时先看 Pod Events,不把它当授权拒绝。

读取 Bucket 对象,预期输出合成文本 hello from GKE workload identity:

kubectl --context="$KUBE_CONTEXT" -n "$NAMESPACE" exec wi-check -- \
  gcloud storage cat "gs://$BUCKET/hello.txt" --project="$RESOURCE_PROJECT_ID"

读取 Secret version 1,只输出成功标记,不打印 payload;临时文件由退出时的 trap 清理:

kubectl --context="$KUBE_CONTEXT" -n "$NAMESPACE" exec wi-check -- sh -c '
  set -eu
  umask 077
  trap "rm -f /tmp/wi-secret" EXIT
  gcloud secrets versions access 1 --project="$RESOURCE_PROJECT_ID" --secret="$SECRET" --out-file=/tmp/wi-secret
  test -s /tmp/wi-secret
  echo "PASS: Secret version 1 readable"
'

Pod 内不执行 gcloud auth login,也不复制本机 ADC。读取来自 GKE metadata server 提供的短期身份;Secret 的 --out-file 将原始字节写入文件。Secret version 读取

应用代码使用 ADC#

应用代码如何读取:Python ADC 示例

在业务镜像构建阶段安装 google-cloud-storage、google-cloud-secret-manager 并锁定依赖;Pod 使用同一 KSA 和上述三个环境变量。下面代码通过 ADC 获取凭据,不传 credentials= 或 key 文件。

import os
from google.cloud import storage, secretmanager

project = os.environ["RESOURCE_PROJECT_ID"]
blob = storage.Client(project=project).bucket(os.environ["BUCKET"]).blob("hello.txt")
object_bytes = blob.download_as_bytes()

client = secretmanager.SecretManagerServiceClient()
version = f"projects/{project}/secrets/{os.environ['SECRET']}/versions/1"
secret_bytes = client.access_secret_version(
    request={"name": version}
).payload.data

# 将内容交给业务逻辑;不打印 Secret、token 或对象的敏感内容。
if not object_bytes or not secret_bytes:
    raise RuntimeError("Unexpected empty demo payload")
print("PASS: Bucket object and Secret version readable")

代码放在你的应用容器中运行;前面的 CLI 测试镜像不保证已安装这些 Python libraries。Storage client、Secret Manager client

备选:KSA impersonate Google Service Account#

本页读取 Bucket 对象和 Secret version,优先直接授权 KSA,不需要额外创建 Google SA。 需要 Google SA 身份的具体场景如下;限制针对操作,不代表整个产品都不支持 WIF。

具体操作 限制 / 选择 需要补充的配置
调用需要 IAM authentication 的 Cloud Run 服务 URL 不支持直接以 federated identity 调用;使用 Google SA 身份 场景:Pod 请求你部署在 Cloud Run 上的业务 HTTP API,例如订单 Pod 调用报表服务的 POST /reports;这里不是创建或部署 Cloud Run 的管理 API。

配置:服务启用 Invoker IAM check 且未允许匿名访问时,给调用方 Google SA 授予目标服务的 roles/run.invoker。Pod 以该 SA 身份获取 ID token,通过 Authorization: Bearer ID_TOKEN 发送;audience 为服务基础 URL(不加 /reports)或已配置的 custom audience,不能拿普通 access token 代替。网络路径仍须符合 ingress。服务间调用
生成 Cloud Storage signed URL KSA 的直接对象读取权限不提供签名能力;使用 Google SA 作为 signer signer 必须有 URL 对应的对象权限;调用者还须能对 signer 执行 iam.serviceAccounts.signBlob。下方的 roles/iam.workloadIdentityUser 不包含该签名权限
访问仍使用 fine-grained ACL 的 Bucket 直接 federated access 要求 Bucket 启用 uniform bucket-level access 能迁移时先评估现有 ACL 再启用;必须保留 ACL 时使用 Google SA 身份并配置其对象访问权限
现有应用明确依赖某个 Google SA 身份 这是兼容现有身份设计的选择,不是 WIF API 限制 将 KSA 关联到该 Google SA,核对其已有权限,避免多个应用共用身份后权限扩大

以上限制核对于 2026-10-15:官方支持与限制、Cloud Run 调用认证、Storage signed URL 签名权限。查支持表时区分 Workload 与 Workforce Identity Federation;后者面向人员登录,不能把它的 Console 限制套到 Pod。

下方命令只演示切换身份后读取 Bucket / Secret;Cloud Run 的 ID token 和 signed URL 的签名配置不包含在这组命令中。

它是资源级直接授权的替代方式。 验证此方案时,不要同时保留 KSA 的直接授权,否则无法确认访问成功来自哪一种授权。

Google SA 授权与 KSA annotation

以下复用示例资源,要求没有执行上面的直接授权;若已经执行,先从两个资源撤销 KSA Principal 的直接 binding,再切换。本方式仍不创建 SA key。

GSA_NAME="wi-demo-${RUN_ID}"
GSA_EMAIL="${GSA_NAME}@${GKE_PROJECT_ID}.iam.gserviceaccount.com"

gcloud iam service-accounts create "$GSA_NAME" --project="$GKE_PROJECT_ID"

gcloud storage buckets add-iam-policy-binding "gs://$BUCKET" \
  --project="$RESOURCE_PROJECT_ID" \
  --member="serviceAccount:$GSA_EMAIL" --role=roles/storage.objectViewer

gcloud secrets add-iam-policy-binding "$SECRET" \
  --project="$RESOURCE_PROJECT_ID" \
  --member="serviceAccount:$GSA_EMAIL" --role=roles/secretmanager.secretAccessor

gcloud iam service-accounts add-iam-policy-binding "$GSA_EMAIL" \
  --project="$GKE_PROJECT_ID" --role=roles/iam.workloadIdentityUser \
  --member="serviceAccount:${GKE_PROJECT_ID}.svc.id.goog[${NAMESPACE}/${KSA}]"

# 在指定集群/namespace 的 KSA 上添加 annotation,指定使用哪个 Google SA 获取凭据;--overwrite 允许替换已有值,此命令本身不授予 IAM 权限。
kubectl --context="$KUBE_CONTEXT" -n "$NAMESPACE" annotate serviceaccount "$KSA" \
  "iam.gke.io/gcp-service-account=${GSA_EMAIL}" --overwrite

本例的 KSA → Google SA 关联方式,IAM binding 与 annotation 缺一不可;Google SA 还必须获得目标资源权限。 binding 允许 KSA impersonate,annotation 指定使用哪个 Google SA。

重建测试 Pod,再重复两个 API 读取,避免沿用旧凭据状态。官方替代方案

验证权限边界与清理#

主方案读取成功后,单独撤销 Secret binding,再测试同一 Pod。Bucket 应仍可读取;Secret 应因 secretmanager.versions.access 权限不足被拒绝。这样能区分两个资源的授权。

直接授权方案:撤权验证与专用资源清理

以下 Secret 读取预期失败,单独执行并核对错误。网络超时、无法获取 token、Secret/version 不存在都不算撤权通过。

gcloud secrets remove-iam-policy-binding "$SECRET" \
  --project="$RESOURCE_PROJECT_ID" \
  --member="$KSA_PRINCIPAL" --role=roles/secretmanager.secretAccessor

kubectl --context="$KUBE_CONTEXT" -n "$NAMESPACE" exec wi-check -- \
  gcloud storage cat "gs://$BUCKET/hello.txt" --project="$RESOURCE_PROJECT_ID"

kubectl --context="$KUBE_CONTEXT" -n "$NAMESPACE" exec wi-check -- \
  gcloud secrets versions access 1 \
  --project="$RESOURCE_PROJECT_ID" --secret="$SECRET" >/dev/null

等待 IAM 传播后再判断;仍成功时查其他继承 binding、KSA annotation 或显式凭据。备用 Google SA 方案需撤销 Google SA 的 Secret binding,而非直接 KSA binding。后续仍需读取 Secret 时,重新执行对应方案的资源授权。

下面会删除整个测试 namespace、Bucket 和 Secret,只适用于本页新建的专用资源。 复用现有资源时,仅移除本次测试添加的 binding,不执行下面的资源删除命令。

kubectl --context="$KUBE_CONTEXT" delete namespace "$NAMESPACE"

gcloud storage rm --recursive "gs://$BUCKET" --project="$RESOURCE_PROJECT_ID"

gcloud secrets delete "$SECRET" --project="$RESOURCE_PROJECT_ID"

# 仅执行过备选方案且该 SA 是本例专用时:
# gcloud iam service-accounts delete "$GSA_EMAIL" --project="$GKE_PROJECT_ID"

Bucket 删除仍受 soft delete / retention 设置影响,见 Storage 清理。不删除集群、Node SA 或共享 API。

常见坑与排障#

表现 优先检查
Pod 使用了错误身份 serviceAccountName、namespace、KSA annotation;不要只看 Node SA
403 / PERMISSION_DENIED 资源路径、role binding、Principal 内的 GKE project number / pool、IAM 传播及组织限制
能拿到 token,但 Secret 失败 Secret version 是否 enabled、资源级 Accessor、Standard 节点 cloud-platform scope
metadata / token 请求失败 集群与 node pool 的 WIF、GKE metadata server、NetworkPolicy;Pod 启动初期可有短暂未就绪
API 超时 DNS、Google API 出口路径、firewall/NetworkPolicy/VPC Service Controls;IAM 不建立网络路径
ImagePullBackOff 查 Node SA 的 Artifact Registry Reader 与仓库路径,业务 KSA 授权不解决镜像拉取
另一集群同名 KSA 也能访问 先检查页首的 跨集群身份共享及其他继承授权

普通 hostNetwork: true Pod 的 metadata 请求走 Compute Engine metadata server,不使用本页的 KSA WIF 路径。 不要把它的访问成功当作 KSA 授权生效;先核对实际 Node SA 身份和权限。

特例:Standard GKE 1.33.3-gke.1226000 起,Cloud Storage FUSE CSI driver 支持为 hostNetwork Pod 配置使用 KSA;这不是所有 hostNetwork 应用自动获得 WIF。不要挂载用户 ADC 或通过 GOOGLE_APPLICATION_CREDENTIALS 指向长期 key,否则也会改变实际凭据来源。WIF 限制与特例

搜索文档

输入关键词,搜索所有文档。

↑ ↓ 选择 · Enter 打开 · Esc 关闭