4xx エラーをトラブルシューティングする


このページでは、Google Kubernetes Engine(GKE)の使用時に発生する可能性のある 400、401、403、404 エラーを解決する方法について説明します。

問題: 認証 / 認可エラー

GKE クラスタに接続すると、HTTP ステータス コード 401 (Unauthorized) の認証 / 認可エラーが発生することがあります。この問題は、ローカル環境から GKE クラスタで kubectl コマンドを実行しようとしたときに発生することがあります。

この問題の原因は、次のいずれかである可能性があります。

  • gke-gcloud-auth-plugin 認証プラグインが正しくインストールされていないか、構成されていません。
  • クラスタ API サーバーに接続し、kubectl コマンドを実行するための権限がありません。

原因を診断するには、次のセクションの手順を完了します。

  1. curl を使用してクラスタに接続する
  2. kubeconfig でプラグインを構成する

curl を使用してクラスタに接続する

認証 / 認可エラーの原因を診断するには、curl を使用してクラスタに接続します。curl を使用すると、kubectl コマンドライン ツールと gke-gcloud-auth-plugin プラグインがバイパスされます。

  1. 環境変数を設定します。

    APISERVER=https://$(gcloud container clusters describe CLUSTER_NAME \
        --location=COMPUTE_LOCATION --format "value(endpoint)")
    TOKEN=$(gcloud auth print-access-token)
    
  2. アクセス トークンが有効であることを確認します。

    curl https://oauth2.googleapis.com/tokeninfo?access_token=$TOKEN
    

    有効なアクセス トークンがある場合、このコマンドによって Google の OAuth 2.0 サーバーにリクエストが送信され、サーバーからトークンに関する情報が返されます。

  3. API サーバーのコア API エンドポイントに接続してみます。

    # Get cluster CA certificate
    gcloud container clusters describe CLUSTER_NAME \
        --location=COMPUTE_LOCATION \
        --format "value(masterAuth.clusterCaCertificate)" | \
        base64 -d > /tmp/ca.crt
    
    # Make API call with authentication and CA certificate
    curl -s -X GET "${APISERVER}/api/v1/namespaces" \
        --header "Authorization: Bearer $TOKEN" \
        --cacert /tmp/ca.crt
    

    curl コマンドが成功すると、Namespace のリストが表示されます。kubeconfig でプラグインを構成するセクションの手順に沿って、プラグインが原因かどうかを確認します。

    curl コマンドが次のような出力で失敗した場合は、クラスタにアクセスするための適切な権限がありません。

    {
    "kind": "Status",
    "apiVersion": "v1",
    "metadata": {},
    "status": "Failure",
    "message": "Unauthorized",
    "reason": "Unauthorized",
    "code": 401
    }
    

    この問題を解決するには、管理者に問い合わせて、クラスタにアクセスするための適切な権限を取得してください。

kubeconfig でプラグインの使用を構成する

クラスタへの接続時に認証 / 認可エラーが発生した場合に、curl を使用してクラスタに接続できたときは、gke-gcloud-auth-plugin プラグインを使用せずにクラスタにアクセスできるかどうかを確認します。

この問題を解決するには、クラスタに対する認証時に gke-gcloud-auth-plugin バイナリを無視するようにローカル環境を構成します。バージョン 1.25 以降を実行している Kubernetes クライアントでは、gke-gcloud-auth-plugin バイナリが必須であるため、kubectl コマンドライン ツールにはバージョン 1.24 以前を使用する必要があります。

プラグインを使用せずにクラスタにアクセスする手順は次のとおりです。

  1. curl を使用して、バージョン 1.24 以前の kubectl コマンドライン ツールをインストールします。次の例では、バージョン 1.24 のツールをインストールします。

    curl -LO https://dl.k8s.io/release/v1.24.0/bin/linux/amd64/kubectl
    
  2. テキスト エディタでシェル起動スクリプト ファイルを開きます。たとえば、Bash シェルの .bashrc を開きます。

    vi ~/.bashrc
    

    macOS を使用している場合は、これらの手順で .bashrc ではなく ~/.bash_profile を使用します。

  3. 起動スクリプト ファイルに次の行を追加して保存します。

    export USE_GKE_GCLOUD_AUTH_PLUGIN=False
    
  4. 起動スクリプトを実行します。

    source ~/.bashrc
    
  5. クラスタの認証情報を取得します。これにより、.kube/config ファイルが設定されます。

    gcloud container clusters get-credentials CLUSTER_NAME \
        --location=COMPUTE_LOCATION
    

    次のように置き換えます。

  6. kubectl コマンドを実行します。例:

    kubectl cluster-info
    

    これらのコマンドを実行した後に 401 エラーまたは同様の認可エラーが発生した場合は、適切な権限があることを確認し、エラーを返した手順を再実行します。

エラー 400: ノードプールの再作成が必要

コントロール プレーンとノードを再作成するアクションを実行しようとすると、次のエラーが発生することがあります。

ERROR: (gcloud.container.clusters.update) ResponseError: code=400, message=Node pool "test-pool-1" requires recreation.

たとえば、進行中の認証情報のローテーションを完了したときに、このエラーが発生することがあります。

バックエンドでは、ノードプールが再作成対象としてマークされていますが、実際の再作成オペレーションの開始には時間がかかることがあります。これが原因で、GKE がクラスタ内の 1 つ以上のノードプールをまだ再作成していない場合、オペレーションは失敗します。

この問題を解決するには、次のいずれかの解決策を選択します。

  • 再作成が完了するまで待ちます。既存のメンテナンスの時間枠や除外などの要因に応じて、完了までに数時間、数日、あるいは数週間かかることがあります。
  • コントロール プレーンと同じバージョンへのアップグレードを開始して、影響を受けるノードプールの再作成を手動で開始します。

    再作成を開始するには、次のコマンドを実行します。

    gcloud container clusters upgrade CLUSTER_NAME \
        --node-pool=POOL_NAME
    

    アップグレードが完了したら、操作をやり直してください。

エラー 401: Unauthorized

重要な権限のノード サービス アカウントが存在するクラスタを特定する

ノード サービス アカウントに重要な権限がないクラスタを特定するには、NODE_SA_MISSING_PERMISSIONS recommender サブタイプGKE 推奨事項を使用します。

  • Google Cloud コンソールを使用します。[Kubernetes クラスタ] ページに移動し、特定のクラスタの [通知] 列で「重要な権限を付与する」の推奨事項を確認します。
  • gcloud CLI または Recommender API を使用して、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"
    

推奨事項が表示されるまでに、最長で 24 時間ほどかかることがあります。手順については、分析情報と推奨事項を表示する方法をご覧ください。

この推奨事項を実装するには、ノード サービス アカウントに roles/container.defaultNodeServiceAccount ロールを付与します。

プロジェクトの 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 は、ノードに接続されている IAM サービス アカウントを使用して、ロギングやモニタリングなどのシステムタスクを実行します。これらのノード サービス アカウントには、プロジェクトに対する Kubernetes Engine デフォルト ノード サービス アカウントroles/container.defaultNodeServiceAccount)ロールが最低限必要です。デフォルトでは、GKE はプロジェクトに自動的に作成される Compute Engine のデフォルトのサービス アカウントをノード サービス アカウントとして使用します。

組織で iam.automaticIamGrantsForDefaultServiceAccounts 組織のポリシー制約が適用されている場合、GKE に必要な権限がプロジェクトのデフォルトの Compute Engine サービス アカウントに自動的に付与されないことがあります。

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

    コンソール

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

      Kubernetes クラスタに移動

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

    [サービス アカウント] フィールドの値が default の場合、ノードは Compute Engine のデフォルトのサービス アカウントを使用します。このフィールドの値が default 以外の場合、ノードはカスタム サービス アカウントを使用します。必要なロールをカスタム サービス アカウントに付与するには、最小権限の IAM サービス アカウントを使用するをご覧ください。

    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 でない場合、ノードはカスタム サービス アカウントを使用しています。必要なロールをカスタム サービス アカウントに付与するには、最小権限の IAM サービス アカウントを使用するをご覧ください。

  2. Compute Engine のデフォルト サービス アカウントに roles/container.defaultNodeServiceAccount ロールを付与する手順は次のとおりです。

    コンソール

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

      [ようこそ] に移動

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

      [IAM] に移動

    4. [アクセスを許可] をクリックします。
    5. [新しいプリンシパル] フィールドに次の値を指定します。
      PROJECT_NUMBER-compute@developer.gserviceaccount.com
      PROJECT_NUMBER は、コピーしたプロジェクト番号に置き換えます。
    6. [ロールを選択] メニューで、[Kubernetes Engine デフォルト ノード サービス アカウント] ロールを選択します。
    7. [保存] をクリックします。

    gcloud

    1. Google Cloud プロジェクト番号を確認します。
      gcloud projects describe PROJECT_ID \
          --format="value(projectNumber)"

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

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

      12345678901
      
    2. Compute Engine のデフォルト サービス アカウントに roles/container.defaultNodeServiceAccount ロールを付与します。
      gcloud projects add-iam-policy-binding PROJECT_ID \
          --member="serviceAccount:PROJECT_NUMBER-compute@developer.gserviceaccount.com" \
          --role="roles/container.defaultNodeServiceAccount"

      PROJECT_NUMBER は、前の手順のプロジェクト番号に置き換えます。

エラー 403: 十分な権限がない

gcloud container clusters get-credentials を使用して GKE クラスタに接続しようとしたときに、アカウントに Kubernetes API サーバーにアクセスする権限がないと、次のエラーが発生します。

ERROR: (gcloud.container.clusters.get-credentials) ResponseError: code=403, message=Required "container.clusters.get" permission(s) for "projects/<your-project>/locations/<region>/clusters/<your-cluster>".

この問題を解決するには、次の操作を行います。

  1. アクセスの問題が発生しているアカウントを特定します。

    gcloud auth list
    
  2. Kubernetes API サーバーの認証の手順に沿って、アカウントに必要なアクセス権を付与します。

エラー 403: リトライ バジェットが残っていない

GKE クラスタを作成しようとすると、次のエラーが発生することがあります。

Error: googleapi: Error 403: Retry budget exhausted: Google Compute Engine:
Required permission 'PERMISSION_NAME' for 'RESOURCE_NAME'.

このエラー メッセージでは、次の変数が適用されます。

  • PERMISSION_NAME: 権限の名前(compute.regions.get など)。
  • RESOURCE_NAME: アクセスしようとした Google Cloudリソースのパス(Compute Engine リージョンなど)。

このエラーは、クラスタに関連付けられている IAM サービス アカウントに、クラスタの作成に最低限必要な権限がない場合に発生します。

この問題を解決するには、次の操作を行います。

  1. GKE クラスタを実行するために必要なすべての権限を持つ IAM サービス アカウントを作成または変更します。手順については、最小権限の IAM サービス アカウントを使用するをご覧ください。
  2. クラスタ作成コマンドで --service-account フラグを使用して、更新した IAM サービス アカウントを指定します。手順については、Autopilot クラスタを作成するをご覧ください。

または、--service-account フラグを省略して、プロジェクトの Compute Engine のデフォルトのサービス アカウントを GKE で使用するようにします。このアカウントには、必要な権限がデフォルトで付与されています。

エラー 404: リソースが見つからない

gcloud container コマンドの呼び出し時にエラー 404(リソースが見つからない)が発生した場合は、Google Cloud CLI に対して再認証することで問題を解決します。

gcloud auth login

エラー 400 / 403: アカウントに編集権限がありません

アカウントに対する編集権限がないというエラー(エラー 400 または 403)は、次のいずれかが手動で削除または編集されたことを示します。

Compute Engine または Kubernetes Engine API を有効にすると、 Google Cloudは次のサービス アカウントとエージェントを作成します。

  • プロジェクトの Compute Engine のデフォルトのサービス アカウント。GKE は、ロギングやモニタリングなどのシステムタスク用に、このサービス アカウントをデフォルトでノードに接続します。
  • Google 管理のプロジェクト内の Google API サービス エージェント。プロジェクトに対する編集権限が付与されています。
  • Google 管理のプロジェクト内の Google Kubernetes Engine サービス エージェント。プロジェクトに対する Kubernetes Engine サービス エージェント ロールが付与されています。

いずれかの時点で、これらの権限の編集、プロジェクトのロール バインディングの削除、サービス アカウントの完全削除、API の無効化が行われると、クラスタの作成とすべての管理機能が失敗します。

GKE サービス エージェントの権限を確認する

プロジェクトに対する Kubernetes Engine サービス エージェント ロールが Google Kubernetes Engine サービス アカウントに割り当てられているかどうかを確認するには、次の手順を完了します。

  1. Google Kubernetes Engine サービス アカウントの名前を確認します。すべてのサービス アカウントの形式は次のとおりです。

    service-PROJECT_NUMBER@container-engine-robot.iam.gserviceaccount.com
    

    PROJECT_NUMBER は、使用するプロジェクト番号に置き換えます。

  2. プロジェクトに対する Kubernetes Engine サービス エージェント ロールが Google Kubernetes Engine サービス アカウントに割り当てられていないことを確認します。

    gcloud projects get-iam-policy PROJECT_ID
    

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

この問題を解決するには、Google Kubernetes Engine サービス アカウントから Kubernetes Engine サービス エージェント ロールが削除されている場合は、このロールを追加し直します。それ以外の場合は、次の手順に沿って Kubernetes Engine API を再度有効にします。これにより、サービス アカウントと権限が復元されます。

コンソール

  1. Google Cloud コンソールの [API とサービス] ページに移動します。

    [API とサービス] に移動

  2. プロジェクトを選択します。

  3. [API とサービスを有効化] をクリックします。

  4. Kubernetes を検索して、検索結果から API を選択します。

  5. [有効にする] をクリックします。API をすでに有効にしている場合は、まずそれを無効にしてから再度有効にする必要があります。API と関連サービスが有効になるには、数分かかることがあります。

gcloud

gcloud CLI で次のコマンドを実行します。

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

次のステップ

さらにサポートが必要な場合は、Cloud カスタマーケアにお問い合わせください。