GKE でのサービス アカウントのトラブルシューティング


このページでは、Google Kubernetes Engine(GKE)サービス アカウントに関する問題のトラブルシューティングを行う方法について説明します。

GKE に必要なロールをノード サービス アカウントに付与する

GKE ノードが使用する IAM サービス アカウントには、Kubernetes Engine デフォルト ノード サービス アカウントroles/container.defaultNodeServiceAccount)IAM ロールに含まれるすべての権限が必要です。GKE ノード サービス アカウントにこれらの権限の 1 つ以上がない場合、GKE は次のようなシステムタスクを実行できません。

ノードのサービス アカウントには、次のような理由で、特定の必要な権限がない場合があります。

  • 組織で 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)のロールを付与します。次のオプションのいずれかを選択します。

コンソール

ノードが使用するサービス アカウントの名前を確認する手順は次のとおりです。

  1. [Kubernetes クラスタ] ページに移動します。

    Kubernetes クラスタに移動

  2. クラスタのリストで、調査するクラスタの名前をクリックします。

  3. ノード サービス アカウントの名前を確認します。この名前は後で必要になります。

    • Autopilot モードのクラスタの場合は、[セキュリティ] セクションで [サービス アカウント] フィールドを見つけます。
    • Standard モードのクラスタの場合は、次の操作を行います。
    1. [ノード] タブをクリックします。
    2. [ノードプール] テーブルで、ノードプールの名前をクリックします。[ノードプールの詳細] ページが開きます。
    3. [セキュリティ] セクションで、[サービス アカウント] フィールドを見つけます。

    [サービス アカウント] フィールドの値が default の場合、ノードは Compute Engine のデフォルトのサービス アカウントを使用します。このフィールドの値が default 以外の場合、ノードはカスタム サービス アカウントを使用します。

サービス アカウントに Kubernetes Engine Default Node Service Account ロールを付与する手順は、次のとおりです。

  1. [ようこそ] ページに移動します。

    [ようこそ] に移動

  2. [プロジェクト番号] フィールドで、クリップボードにコピー)をクリックします。

  3. [IAM] ページに移動します。

    [IAM] に移動

  4. [アクセスを許可] をクリックします。

  5. [新しいプリンシパル] フィールドに、ノードサービス アカウントの名前を指定します。ノードがデフォルトの Compute Engine サービス アカウントを使用している場合は、次の値を指定します。

    PROJECT_NUMBER-compute@developer.gserviceaccount.com
    

    PROJECT_NUMBER は、コピーしたプロジェクト番号に置き換えます。

  6. [ロールを選択] メニューで、[Kubernetes Engine デフォルト ノード サービス アカウント] ロールを選択します。

  7. [保存] をクリックします。

ロールが付与されたことを確認するには、次の操作を行います。

  1. [IAM] ページで、[ロール別に表示] タブをクリックします。
  2. [Kubernetes Engine デフォルト ノード サービス アカウント] セクションを開きます。このロールを持つプリンシパルのリストが表示されます。
  3. プリンシパルのリストでノード サービス アカウントを探します。

gcloud

  1. ノードが使用するサービス アカウントの名前を確認します。

    • 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 でない場合、ノードはカスタム サービス アカウントを使用しています。

  2. Google Cloud プロジェクト番号を確認します。

    gcloud projects describe PROJECT_ID \
        --format="value(projectNumber)"
    

    PROJECT_ID は、実際のプロジェクト ID に置き換えます。

    出力は次のようになります。

    12345678901
    
  3. サービス アカウントに 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 は、前の手順のプロジェクト番号に置き換えます。

  4. ロールが正常に付与されたことを確認します。

    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 で不足している権限を見つけて修正するには、次の操作を行います。

  1. プロジェクトで、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 時間ほどかかることがあります。詳細な手順については、分析情報と推奨事項を表示するをご覧ください。

  2. 前の手順の出力にあるすべてのクラスタについて、関連するノード サービス アカウントを見つけて、それらのサービス アカウントに必要なロールを付与します。詳細については、ノード サービス アカウントに 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 を使用します。

コンソール

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 を使用します。

コンソール

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 カスタマーケアにお問い合わせください。