gke-workload-identity について
Workload Identity Federation で Google Cloud API に認証できない GKE Pod を調べる、運用担当者向けの読み取り専用スキルです。エラー、クラスターやノードプールの設定、ID バインディング、IAM 権限、メタデータサーバーを確認し、人が適用する修正案を提示します。
主な機能
- 01クラスターとノードプールの ID 設定確認
- 02認証エラーとログの分析
- 03KSA の ID バインディング確認
- 04対象リソースの IAM 権限確認
- 05人が適用する修正案の提示
活用シーン
- 01ノードのデフォルトサービスアカウントを使う Pod の調査
- 02アクセストークン取得時の 403 エラーの診断
- 03デフォルト認証情報やメタデータサーバーのエラー調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ google/ skills --skill gke-workload-identityインストール後、Claude Code で/gke-workload-identityを入力して読み込まれたか確認します。
同梱ファイルreferences/attribution.md
詳細
GKE Workload Identity Federation のトラブルシューティングスキル
Workload Identity Federation for GKE を使用する Pod が Google Cloud API に認証できない原因を体系的に診断するには、このスキルを使用します。よくある症状は次のとおりです。
HTTP/403 ... Permission 'iam.serviceAccounts.getAccessToken' denied on resourcegoogle.auth.exceptions ... could not find default credentials/ComputeEngineCredentials cannot find the metadata server- ワークロードの ID ではなく、ノードのデフォルトの Compute Engine サービスアカウントを API 呼び出しが予期せず使用する。
帰属情報
必ずお読みください:gcloud、REST API、または MCP コマンドを実行する前に、必ず Usage Attribution をお読みください。ターミナルで直接実行するコマンド(生成されたスクリプト、Terraform、ユーザーコード内では実行しない)では、gcloud の前に
CLOUDSDK_METRICS_REQUEST_ATTRIBUTION="agent-skills/1.0 (skill:gke-workload-identity)"
を付け、User-Agent ヘッダーに agent-skills/1.0 (skill:gke-workload-identity) を追加してください。
読み取り専用の範囲
このスキルは、診断専用で、対話的な操作は行いません。クラスター、IAM、ログの状態を読み取り、人間が適用する修正をコマンドまたは GitOps マニフェストの変更として提案するだけです。IAM バインディング、KSA アノテーション、ノードプール、クラスターを自動的に作成または変更してはなりません。証拠が不足している場合、または修正に特権が必要な場合は、調査結果を要約して人間に引き継いでください(ステップ 7 を参照)。
出力に関するルール(すべての結論に適用)
診断を報告する際は、必ず次のことを行ってください。
- 最も可能性の高い単一の根本原因を明示します(可能性を列挙するだけにしないでください)。
- 証拠を挙げて、考えられる他の原因を明確に除外します。特に、原因がノードプールの設定である場合は、KSA アノテーションや IAM バインディングの問題ではないと明言してください。対象リソースのロール不足が原因である場合は、Workload Identity 自体の設定ミスではないと明言してください。
- 人間が適用する修正を具体的に提案し、実際の識別子を入れた具体的な
gcloudコマンド(または GitOps マニフェストの編集内容)を示します。自動的に適用してはなりません。
診断ワークフロー
ステップ 0:コンテキストの確認と時間範囲
ユーザーまたは障害が発生しているリソースから、PROJECT_ID、PROJECT_NUMBER、CLUSTER、クラスターの LOCATION、NAMESPACE、Pod が実行されている KSA、Pod がスケジュールされている ノードとノードプール(ステップ 2 で使用)、呼び出し対象の リソース/API、および正確なエラー文字列を収集します。ログを照会するため、最初に障害が観測された時刻を中心に時間範囲を定めます。
# Resolve the project number (used in the direct-binding principal identifier).
gcloud projects describe "{PROJECT_ID}" --format="value(projectNumber)"
# Confirm which KSA the workload runs as.
kubectl get pod "{pod_name}" -n "{namespace}" \
-o jsonpath='{.spec.serviceAccountName}'
# Identify the node the Pod runs on, then the node pool that node belongs to
# (Step 2 checks the node pool's Workload Identity mode).
NODE=$(kubectl get pod "{pod_name}" -n "{namespace}" -o jsonpath='{.spec.nodeName}')
kubectl get node "$NODE" \
-o jsonpath='{.metadata.labels.cloud\.google\.com/gke-nodepool}'
ステップ 1:エラーの正確なシグネチャを把握する
ワークロード自身のログと gke-metadata-server のログを確認して、
障害を分類します。
kubectl logs "{pod_name}" -n "{namespace}" --all-containers --prefix
kubectl describe pod "{pod_name}" -n "{namespace}"
Cloud Logging でも同様に確認できます(過去のイベントを調べる場合に推奨)。Logs Explorer の詳細リンクとして開きます。クエリを URL エンコードし、プロジェクトと時間範囲を追加してください。
https://console.cloud.google.com/logs/query;query={URL_ENCODED_QUERY};timeRange={start}%2F{end}?project={project_id}
(/ は %2F としてエンコードするか、直近 1 時間を指定する場合は ;duration=PT1H を使用します):
resource.type="k8s_container"
resource.labels.namespace_name="{namespace}"
resource.labels.pod_name="{pod_name}"
severity>=WARNING
シグネチャを分類します:
iam.serviceAccounts.getAccessTokenが拒否される/HTTP/403→ ワークロードは GSA の偽装経路を使用しています。これは従来の設定では想定どおりですが、直接バインディングを使う場合は、KSA に古いiam.gke.io/gcp-service-accountアノテーションが残っている(またはクライアント SDK が偽装するよう設定されている)ため、意図せず GSA を偽装していることを意味します。ステップ 3 に進みます。- 対象 API/リソースで
403 PERMISSION_DENIEDが発生する(エラーにgetAccessTokenが含まれない) → 解決された ID に、そのリソースで必要な IAM ロールがありません。ステップ 4 に進みます。 could not find default credentials/cannot find the metadata server→ メタデータサーバーへの接続に問題があるか、起動時の競合状態です。ステップ 5 に進みます。- 呼び出しは成功するものの、ノードのデフォルト サービス アカウントとして実行される → このノードプールでは Workload Identity が有効になっていません。ステップ 2 に進みます。
ステップ 2:Workload Identity が有効になっていることを確認する(クラスタとノードプール)
クラスタと、Pod が実行されるノードプールの両方で Workload
Identity が有効になっている必要があります。GKE_METADATA ではなく
GCE_METADATA が設定されたノードプールでは、Pod がノードのデフォルトの
Compute Engine サービス アカウントにフォールバックします。
# Cluster must have a workload identity pool (PROJECT_ID.svc.id.goog).
gcloud container clusters describe "{cluster}" --location "{location}" \
--format="value(workloadIdentityConfig.workloadPool)"
# Node pool must have workloadMetadataConfig.mode = GKE_METADATA.
gcloud container node-pools describe "{node_pool}" --cluster "{cluster}" \
--location "{location}" \
--format="value(config.workloadMetadataConfig.mode)"
- ワークロードプールが空 → クラスタで Workload Identity が有効になっていません。
- ノードプールのモードが
GCE_METADATA(または空) → ノードプールは GKE メタデータサーバーを使用していません。これが「ノードのデフォルト サービス アカウントとして実行される」場合の通常の原因です。対処方法:ノードプールで--workload-metadata=GKE_METADATAを有効にします(人に提案してください。ノードが再作成されます)。これはノードプールの設定に関する問題であり、KSA のアノテーションや IAM バインディングの問題ではありません。そのため、修正のために KSA のアノテーションや IAM バインディングを変更しないでください。症状が「ノードのデフォルト サービス アカウントとして実行される」である場合は、原因がノードプールのworkloadMetadataConfig.modeにあることを明示してください。また、KSA のアノテーションと IAM バインディングは原因として除外されます。次のように、具体的な修正方法を提案してください。
gcloud container node-pools update "{node_pool}" --cluster "{cluster}" \
--location "{location}" --workload-metadata=GKE_METADATA
ステップ 3:KSA → ID のバインディングを確認する
サポートされているモデルは 2 つあります。直接バインディング(現在のデフォルト)を優先し、GSA の偽装は従来の経路として扱います。
2 つのモデルで障害の現れ方が異なる点: 直接バインディングでは、KSA プリンシパルがリソースに直接アクセスするため、障害は対象 API で単純な
403 PERMISSION_DENIEDとして現れます(ステップ 4 で修正します)。一方、403 iam.serviceAccounts.getAccessTokenは偽装の試行を意味します。従来の経路では想定どおりですが、直接バインディングを使用するはずの KSA に古いiam.gke.io/gcp-service-accountアノテーションが残っている場合は、意図しない偽装です。
(a)KSA の直接バインディング(GSA の偽装なし)。 KSA プリンシパルにロールを直接付与します。プリンシパル識別子を構成し、そのバインディングを検索します。
principal://iam.googleapis.com/projects/{PROJECT_NUMBER}/locations/global/workloadIdentityPools/{PROJECT_ID}.svc.id.goog/subject/ns/{NAMESPACE}/sa/{KSA_NAME}
(b)従来の方式:KSA + GSA の偽装。 KSA に GSA を指すアノテーションを付け、KSA がその GSA 上で roles/iam.workloadIdentityUser を持つ必要があります。
# The KSA annotation must reference the intended GSA.
kubectl get serviceaccount "{ksa_name}" -n "{namespace}" \
-o jsonpath='{.metadata.annotations.iam\.gke\.io/gcp-service-account}'
# The GSA's IAM policy must bind the KSA member to workloadIdentityUser.
gcloud iam service-accounts get-iam-policy \
"{gsa_name}@{project_id}.iam.gserviceaccount.com" \
--format=json
# Expect a binding: role roles/iam.workloadIdentityUser,
# member serviceAccount:{PROJECT_ID}.svc.id.goog[{NAMESPACE}/{KSA_NAME}]
アノテーションが存在するのにバインディングが見つからない場合は、バインディングが削除された可能性があります。障害発生時刻の前後にある
setIamPolicy監査ログを確認し、該当するプリンシパルを特定してください。
ステップ 4:対象リソースの IAM 権限を確認する
バインディングが正しくても、ID(直接バインディングの場合は KSA プリンシパル、レガシーの場合は GSA)には、API 呼び出しで必要なロール(例:roles/storage.objectViewer)が付与されている必要があります。標準の IAM Policy Troubleshooter は Workload Identity プリンシパルのサポートが限られているため、Cloud Asset Inventory を使用して、プリンシパルに関するすべての IAM ポリシーを検索してください。
# Direct binding: search for the KSA principal's bindings across the project.
gcloud asset search-all-iam-policies \
--scope="projects/{PROJECT_ID}" \
--query='policy:"{PROJECT_ID}.svc.id.goog"'
# Legacy: search for the GSA's bindings on the target resource's project.
gcloud asset search-all-iam-policies \
--scope="projects/{TARGET_PROJECT_ID}" \
--query='policy:"{gsa_name}@{project_id}.iam.gserviceaccount.com"'
対象リソースで必要なロールを付与するバインディングがない場合、そのロールがないことが根本原因です(Workload Identity 自体の設定に問題はありません)。修正案は、人間が適用する変更案として提示してください。実際のプリンシパルとロールを指定した正確な add-iam-policy-binding を提示し、自動適用は決して行わないでください。たとえば、プロジェクトレベルのリソースに直接バインディングする場合は次のとおりです。
gcloud projects add-iam-policy-binding "{TARGET_PROJECT_ID}" \
--member="principal://iam.googleapis.com/projects/{PROJECT_NUMBER}/locations/global/workloadIdentityPools/{PROJECT_ID}.svc.id.goog/subject/ns/{NAMESPACE}/sa/{KSA_NAME}" \
--role="{REQUIRED_ROLE}" # e.g. roles/storage.objectViewer
ステップ 5:GKE メタデータサーバーと接続性
シグネチャが could not find the metadata server、cannot find the metadata server、または接続/タイムアウトエラーの場合に該当します。kube-system 内の gke-metadata-server DaemonSet は、各ノードでトークン交換を仲介します。169.254.169.254 へのリクエストはこの DaemonSet にリダイレクトされるため、Pod は同じノード上の正常なメタデータサーバー Pod に到達できないと、安全側に失敗します。
(a)ワークロードのノード上にある gke-metadata-server Pod の健全性を確認します。
# The DaemonSet Pods must be healthy on the workload's node.
kubectl get pods -n kube-system -l k8s-app=gke-metadata-server -o wide
# A gke-metadata-server Pod can be OOM-evicted when the cluster has many
# (>3,000) Kubernetes service accounts. Look for CrashLoopBackOff, then confirm
# the eviction was OOMKilled.
kubectl get pods -n kube-system | grep CrashLoopBackOff
kubectl describe pod {gke_metadata_server_pod} --namespace=kube-system | grep OOMKilled
(b)gke-metadata-server のログを調べます(Cloud Logging で過去のログを確認します。ログエクスプローラーへのディープリンクを開いてください。ステップ 1 を参照)。
resource.type="k8s_container"
resource.labels.namespace_name="kube-system"
labels."k8s-pod/k8s-app"="gke-metadata-server"
severity>=WARNING
(c)影響を受ける Pod から接続性をテストします。メタデータサーバーに到達できる必要があります。また、一部のクライアントライブラリでは DNS 名を解決できる必要があります。
# Token endpoint via the hardcoded metadata IP (a healthy path returns a token).
kubectl exec {pod_name} -n {namespace} -- \
curl -sS -H 'Metadata-Flavor: Google' \
'http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token'
考えられる原因:
- 起動時の競合:メタデータサーバーが利用可能になるまで、Pod の起動後に数秒かかる場合があります。すぐに認証するアプリケーションは失敗する可能性があります。修正方法は、アプリケーション側でリトライするか、メタデータサーバーを待機する
initContainerを設定することであり、クラスタを変更することではありません。 - DNS:一部のクライアントライブラリは
metadata.google.internalを解決します。クラスタ内の DNS が機能していないと、cannot find the metadata serverが発生します。回避策として、DNS 解決をスキップするためにGCE_METADATA_HOST=169.254.169.254を設定します。 - ネットワークポリシー/エグレス:クラスタのネットワークポリシーで、ポート
80(GKE Dataplane V2)上の169.254.169.254/32、またはポート988上の169.254.169.252/32へのエグレスを許可する必要があります。パブリック Security Token Service(sts.googleapis.com:443)への HTTPS をブロックするデフォルト拒否のエグレスルールがあると、504 Gateway Timeoutまたはcontext deadline exceededが発生します。 gke-metadata-serverのクラッシュ/再起動回数が多い:(a)を参照してください。機能を復旧するには、Kubernetes サービスアカウントの数を 3,000 未満に減らします。
ステップ 6:「Invalid form of account ID」の特殊ケース(レガシー GSA 偽装)
レガシー GSA 偽装セットアップにのみ該当します(
iam.gke.io/gcp-service-accountがアノテーションされた KSA)。直接プリンシパルをバインドする場合には該当しません。
シグネチャ:IAM サービスアカウントのメールアドレスを必要とする操作(例:Cloud Storage の署名付き URL を手動で作成する操作)が、次のエラーで失敗します。
ERROR: Invalid form of account ID SERVICEACCOUNT_NAME.svc.id.goog.
Should be [Gaia ID | Email | Unique ID | ] of the account
原因:アノテーションを介して KSA を GSA にリンクしている場合、GKE メタデータサーバーはデフォルトで識別子として SERVICEACCOUNT_NAME.svc.id.goog を返します。これは有効な IAM サービスアカウントのメールアドレスではありません。これは IAM バインディングの不足が原因ではなく、セットアップを元に戻す必要もありません。
修正方法(人間への提案):Pod の KSA に iam.gke.io/return-principal-id-as-email="true" アノテーションを追加し、メタデータサーバーが期待される形式で ID を返すようにします。
kubectl annotate serviceaccount "{ksa_name}" --namespace "{namespace}" \
iam.gke.io/return-principal-id-as-email="true"
ステップ 7:修正の境界とエスカレーション
- 根本原因と証拠(正確なエラー、欠落しているバインディング/アノテーション/ノードプールのモード、またはメタデータサーバーの状態)を提示します。裏付けとなるログエントリへの Cloud Logging の詳細リンク(ステップ 1 を参照)を含めてください。
- 修正案は、人間が適用する コマンドまたは GitOps マニフェストの変更として提案してください。IAM、KSA、ノードプールを自動的に変更してはいけません。
次のいずれかに該当する場合は、セルフサービス診断をさらに提案せず、エスカレーションしてください。
- 関連するログが利用できない(フィルターで除外されている、またはログバケットの保持期間を過ぎている)。
- ID/バインディングが正しく、対象リソースの IAM も正しく、メタデータサーバーも正常であるにもかかわらず、問題が続いている(根本原因が特定できない)。
その場合は、制約を明確に伝え、収集した調査結果(クラスター/ノードプールの構成、バインディング、アノテーション、メタデータサーバーの状態、および setIamPolicy の監査ログがあればその内容)を要約し、GKE サポート/エンジニアリングチームへのエスカレーションにつなげてください。証拠が不足している場合、診断結果を捏造してはいけません。
参考資料
このスキルは、Google Cloud の公開ドキュメントをもとにしています。
- GKE の認証に関する問題のトラブルシューティング
— Workload Identity Federation のセクション:WI が有効かどうかの確認、KSA のアノテーション、
roles/iam.workloadIdentityUser、iam.serviceAccounts.getAccessTokenの 403 エラー、iam.gke.io/return-principal-id-as-email、メタデータサーバーの DNS、およびgke-metadata-serverPod のクラッシュ に関するガイダンス。 - GKE の Workload Identity Federation について — ID、GKE メタデータサーバー、および Pod がノードの ID を継承しない理由。
- GKE の Workload Identity Federation を使用するようにアプリケーションを構成する
— ノードプールの
--workload-metadata=GKE_METADATA、直接バインディングのprincipal://...識別子、旧形式のserviceAccount:PROJECT_ID.svc.id.goog[NAMESPACE/KSA_NAME]メンバー形式。 - IAM ポリシーを検索する(Cloud Asset Inventory)
— プリンシパルのバインディングを監査するための
gcloud asset search-all-iam-policies。 - GKE ログを表示する — ワークロードログとシステムログの Cloud Logging クエリ。