このページでは、Google Kubernetes Engine(GKE)サービス アカウントに関する問題のトラブルシューティングを行う方法について説明します。
GKE に必要なロールをノード サービス アカウントに付与する
GKE ノードが使用する IAM サービス アカウントには、Kubernetes Engine デフォルト ノード サービス アカウント(roles/container.defaultNodeServiceAccount
)IAM ロールに含まれるすべての権限が必要です。GKE ノード サービス アカウントにこれらの権限の 1 つ以上がない場合、GKE は次のようなシステムタスクを実行できません。
- システムログとアプリケーション ログをノードから Cloud Logging に送信する。
- ノードから Cloud Monitoring にシステム指標とアプリケーション指標を送信する。
- HorizontalPodAutoscaler のパフォーマンス プロファイルを処理する。
ノードのサービス アカウントには、次のような理由で、特定の必要な権限がない場合があります。
- 組織で
iam.automaticIamGrantsForDefaultServiceAccounts
組織のポリシーの制約が適用されているため、 Google Cloud がデフォルトの IAM サービス アカウントに IAM ロールを自動的に付与できない。 - カスタムノード サービス アカウントに付与する IAM ロールに、
roles/container.defaultNodeServiceAccount
ロールに含まれる必要な権限がすべて含まれていない。
ノードのサービス アカウントに GKE に必要な権限がない場合、次のようなエラーと通知が表示されることがあります。
- Google Cloud コンソールの [Kubernetes クラスタ] ページで、特定のクラスタの [通知] 列に [重要な権限を付与します] エラー メッセージが表示される。
Google Cloud コンソールの特定のクラスタの詳細ページに、次のエラー メッセージが表示される。
Grant roles/container.defaultNodeServiceAccount role to Node service account to allow for non-degraded operations.
Cloud Audit Logs では、API にアクセスするための対応する権限がノード サービス アカウントにない場合、
monitoring.googleapis.com
などの Google Cloud API 管理アクティビティ ログに次の値が設定されます。- 重要度:
ERROR
- メッセージ:
Permission denied (or the resource may not exist)
- 重要度:
特定のノードのログが Cloud Logging に存在せず、これらのノードのロギング エージェントの Pod ログに
401
エラーが表示されます。これらの Pod ログを取得するには、次のコマンドを実行します。[[ $(kubectl logs -l k8s-app=fluentbit-gke -n kube-system -c fluentbit-gke | grep -cw "Received 401") -gt 0 ]] && echo "true" || echo "false"
出力が
true
の場合、システム ワークロードで401
エラーが発生しています。これは、権限がないことを示します。
この問題を解決するには、エラーの原因となっているサービス アカウントに、プロジェクトに対する Kubernetes Engine デフォルト ノード サービス アカウント(roles/container.defaultNodeServiceAccount
)のロールを付与します。次のオプションのいずれかを選択します。
コンソール
ノードが使用するサービス アカウントの名前を確認する手順は次のとおりです。
[Kubernetes クラスタ] ページに移動します。
クラスタのリストで、調査するクラスタの名前をクリックします。
ノード サービス アカウントの名前を確認します。この名前は後で必要になります。
- Autopilot モードのクラスタの場合は、[セキュリティ] セクションで [サービス アカウント] フィールドを見つけます。
- Standard モードのクラスタの場合は、次の操作を行います。
- [ノード] タブをクリックします。
- [ノードプール] テーブルで、ノードプールの名前をクリックします。[ノードプールの詳細] ページが開きます。
- [セキュリティ] セクションで、[サービス アカウント] フィールドを見つけます。
[サービス アカウント] フィールドの値が
default
の場合、ノードは Compute Engine のデフォルトのサービス アカウントを使用します。このフィールドの値がdefault
以外の場合、ノードはカスタム サービス アカウントを使用します。
サービス アカウントに Kubernetes Engine Default Node Service Account
ロールを付与する手順は、次のとおりです。
[ようこそ] ページに移動します。
[プロジェクト番号] フィールドで、
(クリップボードにコピー)をクリックします。[IAM] ページに移動します。
[
アクセスを許可] をクリックします。[新しいプリンシパル] フィールドに、ノードサービス アカウントの名前を指定します。ノードがデフォルトの Compute Engine サービス アカウントを使用している場合は、次の値を指定します。
PROJECT_NUMBER-compute@developer.gserviceaccount.com
PROJECT_NUMBER
は、コピーしたプロジェクト番号に置き換えます。[ロールを選択] メニューで、[Kubernetes Engine デフォルト ノード サービス アカウント] ロールを選択します。
[保存] をクリックします。
ロールが付与されたことを確認するには、次の操作を行います。
- [IAM] ページで、[ロール別に表示] タブをクリックします。
- [Kubernetes Engine デフォルト ノード サービス アカウント] セクションを開きます。このロールを持つプリンシパルのリストが表示されます。
- プリンシパルのリストでノード サービス アカウントを探します。
gcloud
ノードが使用するサービス アカウントの名前を確認します。
- Autopilot モードのクラスタの場合は、次のコマンドを実行します。
gcloud container clusters describe CLUSTER_NAME \ --location=LOCATION \ --flatten=autoscaling.autoprovisioningNodePoolDefaults.serviceAccount
- Standard モードのクラスタの場合は、次のコマンドを実行します。
gcloud container clusters describe CLUSTER_NAME \ --location=LOCATION \ --format="table(nodePools.name,nodePools.config.serviceAccount)"
出力が
default
の場合、ノードは Compute Engine のデフォルトのサービス アカウントを使用します。出力がdefault
でない場合、ノードはカスタム サービス アカウントを使用しています。Google Cloud プロジェクト番号を確認します。
gcloud projects describe PROJECT_ID \ --format="value(projectNumber)"
PROJECT_ID
は、実際のプロジェクト ID に置き換えます。出力は次のようになります。
12345678901
サービス アカウントに
roles/container.defaultNodeServiceAccount
ロールを付与します。gcloud projects add-iam-policy-binding PROJECT_ID \ --member="SERVICE_ACCOUNT_NAME" \ --role="roles/container.defaultNodeServiceAccount"
SERVICE_ACCOUNT_NAME
は、前の手順で確認したサービス アカウントの名前に置き換えます。ノードが Compute Engine のデフォルトのサービス アカウントを使用する場合は、次の値を指定します。serviceAccount:PROJECT_NUMBER-compute@developer.gserviceaccount.com
PROJECT_NUMBER
は、前の手順のプロジェクト番号に置き換えます。ロールが正常に付与されたことを確認します。
gcloud projects get-iam-policy PROJECT_ID \ --flatten="bindings[].members" --filter=bindings.role:roles/container.defaultNodeServiceAccount \ --format='value(bindings.members)'
出力はサービス アカウントの名前です。
必要な権限がないノード サービス アカウントを特定する
以降のセクションでは、GKE に必要な権限がないノード サービス アカウントを特定する方法について説明します。
権限を持たないノード サービス アカウントのあるクラスタを特定する
NODE_SA_MISSING_PERMISSIONS
recommender サブタイプの GKE 推奨事項を使用して、権限を持たないノード サービス アカウントが存在する Autopilot クラスタと Standard クラスタを特定します。Recommender は、2024 年 1 月 1 日以降に作成されたクラスタのみを識別します。Recommender で不足している権限を見つけて修正するには、次の操作を行います。
プロジェクトで、
NODE_SA_MISSING_PERMISSIONS
Recommender のサブタイプに関するアクティブな推奨事項を探します。gcloud recommender recommendations list \ --recommender=google.container.DiagnosisRecommender \ --location LOCATION \ --project PROJECT_ID \ --format yaml \ --filter="recommenderSubtype:NODE_SA_MISSING_PERMISSIONS"
次のように置き換えます。
LOCATION
: 推奨事項を検索するロケーション。PROJECT_ID
: 実際の Google Cloud プロジェクト ID。
出力は次のようになります。これは、クラスタに権限のないノード サービス アカウントが存在することを示しています。
associatedInsights: # lines omitted for clarity recommenderSubtype: NODE_SA_MISSING_PERMISSIONS stateInfo: state: ACTIVE targetResources: - //container.googleapis.com/projects/12345678901/locations/us-central1/clusters/cluster-1
推奨事項が表示されるまでに最長で 24 時間ほどかかることがあります。詳細な手順については、分析情報と推奨事項を表示するをご覧ください。
前の手順の出力にあるすべてのクラスタについて、関連するノード サービス アカウントを見つけて、それらのサービス アカウントに必要なロールを付与します。詳細については、ノード サービス アカウントに GKE に必要なロールを付与するの手順をご覧ください。
特定されたノードのサービス アカウントに必要なロールを付与した後、手動で閉じない限り、推奨事項は最大 24 時間保持されます。
権限のないノード サービス アカウントをすべて特定する
プロジェクトの Standard クラスタと Autopilot クラスタのノードプールで、GKE に必要な権限のないノード サービス アカウントを検索するスクリプトを実行できます。このスクリプトでは、gcloud CLI と jq
ユーティリティを使用します。スクリプトを表示するには、次のセクションを開きます。
スクリプトを表示する
#!/bin/bash
# Set your project ID
project_id=PROJECT_ID
project_number=$(gcloud projects describe "$project_id" --format="value(projectNumber)")
declare -a all_service_accounts
declare -a sa_missing_permissions
# Function to check if a service account has a specific permission
# $1: project_id
# $2: service_account
# $3: permission
service_account_has_permission() {
local project_id="$1"
local service_account="$2"
local permission="$3"
local roles=$(gcloud projects get-iam-policy "$project_id" \
--flatten="bindings[].members" \
--format="table[no-heading](bindings.role)" \
--filter="bindings.members:\"$service_account\"")
for role in $roles; do
if role_has_permission "$role" "$permission"; then
echo "Yes" # Has permission
return
fi
done
echo "No" # Does not have permission
}
# Function to check if a role has the specific permission
# $1: role
# $2: permission
role_has_permission() {
local role="$1"
local permission="$2"
gcloud iam roles describe "$role" --format="json" | \
jq -r ".includedPermissions" | \
grep -q "$permission"
}
# Function to add $1 into the service account array all_service_accounts
# $1: service account
add_service_account() {
local service_account="$1"
all_service_accounts+=( ${service_account} )
}
# Function to add service accounts into the global array all_service_accounts for a Standard GKE cluster
# $1: project_id
# $2: location
# $3: cluster_name
add_service_accounts_for_standard() {
local project_id="$1"
local cluster_location="$2"
local cluster_name="$3"
while read nodepool; do
nodepool_name=$(echo "$nodepool" | awk '{print $1}')
if [[ "$nodepool_name" == "" ]]; then
# skip the empty line which is from running `gcloud container node-pools list` in GCP console
continue
fi
while read nodepool_details; do
service_account=$(echo "$nodepool_details" | awk '{print $1}')
if [[ "$service_account" == "default" ]]; then
service_account="${project_number}-compute@developer.gserviceaccount.com"
fi
if [[ -n "$service_account" ]]; then
printf "%-60s| %-40s| %-40s| %-10s| %-20s\n" $service_account $project_id $cluster_name $cluster_location $nodepool_name
add_service_account "${service_account}"
else
echo "cannot find service account for node pool $project_id\t$cluster_name\t$cluster_location\t$nodepool_details"
fi
done <<< "$(gcloud container node-pools describe "$nodepool_name" --cluster "$cluster_name" --zone "$cluster_location" --project "$project_id" --format="table[no-heading](config.serviceAccount)")"
done <<< "$(gcloud container node-pools list --cluster "$cluster_name" --zone "$cluster_location" --project "$project_id" --format="table[no-heading](name)")"
}
# Function to add service accounts into the global array all_service_accounts for an Autopilot GKE cluster
# Autopilot cluster only has one node service account.
# $1: project_id
# $2: location
# $3: cluster_name
add_service_account_for_autopilot(){
local project_id="$1"
local cluster_location="$2"
local cluster_name="$3"
while read service_account; do
if [[ "$service_account" == "default" ]]; then
service_account="${project_number}-compute@developer.gserviceaccount.com"
fi
if [[ -n "$service_account" ]]; then
printf "%-60s| %-40s| %-40s| %-10s| %-20s\n" $service_account $project_id $cluster_name $cluster_location $nodepool_name
add_service_account "${service_account}"
else
echo "cannot find service account" for cluster "$project_id\t$cluster_name\t$cluster_location\t"
fi
done <<< "$(gcloud container clusters describe "$cluster_name" --location "$cluster_location" --project "$project_id" --format="table[no-heading](autoscaling.autoprovisioningNodePoolDefaults.serviceAccount)")"
}
# Function to check whether the cluster is an Autopilot cluster or not
# $1: project_id
# $2: location
# $3: cluster_name
is_autopilot_cluster() {
local project_id="$1"
local cluster_location="$2"
local cluster_name="$3"
autopilot=$(gcloud container clusters describe "$cluster_name" --location "$cluster_location" --format="table[no-heading](autopilot.enabled)")
echo "$autopilot"
}
echo "--- 1. List all service accounts in all GKE node pools"
printf "%-60s| %-40s| %-40s| %-10s| %-20s\n" "service_account" "project_id" "cluster_name" "cluster_location" "nodepool_name"
while read cluster; do
cluster_name=$(echo "$cluster" | awk '{print $1}')
cluster_location=$(echo "$cluster" | awk '{print $2}')
# how to find a cluster is a Standard cluster or an Autopilot cluster
autopilot=$(is_autopilot_cluster "$project_id" "$cluster_location" "$cluster_name")
if [[ "$autopilot" == "True" ]]; then
add_service_account_for_autopilot "$project_id" "$cluster_location" "$cluster_name"
else
add_service_accounts_for_standard "$project_id" "$cluster_location" "$cluster_name"
fi
done <<< "$(gcloud container clusters list --project "$project_id" --format="value(name,location)")"
echo "--- 2. Check if service accounts have permissions"
unique_service_accounts=($(echo "${all_service_accounts[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '))
echo "Service accounts: ${unique_service_accounts[@]}"
printf "%-60s| %-40s| %-40s| %-20s\n" "service_account" "has_logging_permission" "has_monitoring_permission" "has_performance_hpa_metric_write_permission"
for sa in "${unique_service_accounts[@]}"; do
logging_permission=$(service_account_has_permission "$project_id" "$sa" "logging.logEntries.create")
time_series_create_permission=$(service_account_has_permission "$project_id" "$sa" "monitoring.timeSeries.create")
metric_descriptors_create_permission=$(service_account_has_permission "$project_id" "$sa" "monitoring.metricDescriptors.create")
if [[ "$time_series_create_permission" == "No" || "$metric_descriptors_create_permission" == "No" ]]; then
monitoring_permission="No"
else
monitoring_permission="Yes"
fi
performance_hpa_metric_write_permission=$(service_account_has_permission "$project_id" "$sa" "autoscaling.sites.writeMetrics")
printf "%-60s| %-40s| %-40s| %-20s\n" $sa $logging_permission $monitoring_permission $performance_hpa_metric_write_permission
if [[ "$logging_permission" == "No" || "$monitoring_permission" == "No" || "$performance_hpa_metric_write_permission" == "No" ]]; then
sa_missing_permissions+=( ${sa} )
fi
done
echo "--- 3. List all service accounts that don't have the above permissions"
if [[ "${#sa_missing_permissions[@]}" -gt 0 ]]; then
printf "Grant roles/container.defaultNodeServiceAccount to the following service accounts: %s\n" "${sa_missing_permissions[@]}"
else
echo "All service accounts have the above permissions"
fi
このスクリプトは、プロジェクト内のすべての GKE クラスタに適用されます。
権限のないサービス アカウントの名前を特定したら、必要なロールを付与します。詳細については、ノード サービス アカウントに GKE に必要なロールを付与するの手順をご覧ください。
デフォルトのサービス アカウントを Google Cloud プロジェクトに復元する
GKE のデフォルトのサービス アカウント、container-engine-robot
が、プロジェクトから誤ってバインド解除されることがあります。Kubernetes Engine サービス エージェント ロール(roles/container.serviceAgent
)は、クラスタ リソースを管理する権限をサービス アカウントに付与する Identity and Access Management(IAM)ロールです。このロール バインディングをサービス アカウントから削除すると、デフォルト サービス アカウントはプロジェクトからバインド解除され、アプリケーションのデプロイや他のクラスタ操作の実行をユーザーができなくなる可能性があります。
サービス アカウントがプロジェクトから削除されているかどうかを確認するには、 Google Cloud コンソールまたは Google Cloud CLI を使用します。
コンソール
Google Cloud コンソールで、[IAM と管理] ページに移動します。
gcloud
次のコマンドを実行します。
gcloud projects get-iam-policy PROJECT_ID
PROJECT_ID
は、実際のプロジェクト ID に置き換えます。
ダッシュボードまたはコマンドで、サービス アカウントの中に container-engine-robot
が表示されない場合、ロールはバインドされていません。
Kubernetes Engine サービス エージェントのロール(roles/container.serviceAgent
)バインディングを復元するには、次のコマンドを実行します。
PROJECT_NUMBER=$(gcloud projects describe "PROJECT_ID" \
--format 'get(projectNumber)') \
gcloud projects add-iam-policy-binding PROJECT_ID \
--member "serviceAccount:service-${PROJECT_NUMBER?}@container-engine-robot.iam.gserviceaccount.com" \
--role roles/container.serviceAgent
ロール バインディングが復元されたことを確認します。
gcloud projects get-iam-policy $PROJECT_ID
サービス アカウント名と container.serviceAgent
ロールが表示されている場合、ロール バインディングが復元されています。例:
- members:
- serviceAccount:service-1234567890@container-engine-robot.iam.gserviceaccount.com
role: roles/container.serviceAgent
Compute Engine のデフォルトのサービス アカウントを有効にする
ノードプールに使用されるサービス アカウントは通常、Compute Engine のデフォルトのサービス アカウントです。このデフォルトのサービス アカウントが無効になっていると、ノードがクラスタに登録できないことがあります。
プロジェクトでサービス アカウントが無効になっているかどうかを確認するには、Google Cloud コンソールまたは gcloud CLI を使用します。
コンソール
Google Cloud コンソールで、[IAM と管理] ページに移動します。
gcloud
- 次のコマンドを実行します。
gcloud iam service-accounts list --filter="NAME~'compute' AND disabled=true"
サービス アカウントが無効になっている場合は、次のコマンドを実行してサービス アカウントを有効にします。
gcloud iam service-accounts enable PROJECT_ID-compute@developer.gserviceaccount.com
PROJECT_ID
は、実際のプロジェクト ID に置き換えます。
詳細については、ノードの登録に関するトラブルシューティングをご覧ください。
エラー 400/403: Missing edit permissions on account
サービス アカウントが削除されている場合は、編集権限がないことを示すエラーが表示されることがあります。このエラーのトラブルシューティング方法については、エラー 400/403: アカウントに対する編集権限がないをご覧ください。
次のステップ
さらにサポートが必要な場合は、Cloud カスタマーケアにお問い合わせください。