排解 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 指令成功,您會看到命名空間清單。請按照「在 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 二進位檔為必要項目,因此您需要使用 1.24 以下版本的 kubectl 指令列工具。

按照下列步驟操作,即可存取叢集,不必使用外掛程式:

  1. 使用 curl 安裝 1.24 版或更早版本的 kubectl 指令列工具。以下範例會安裝 1.24 版的工具:

    curl -LO https://dl.k8s.io/release/v1.24.0/bin/linux/amd64/kubectl 
  2. 在文字編輯器中開啟 Shell 啟動指令碼檔案。例如,開啟 Bash 殼層的 .bashrc

    vi ~/.bashrc 

    如果您使用 macOS,請在這些操作說明中以 ~/.bash_profile 取代 .bashrc

  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 尚未在叢集中重建一或多個節點集區,作業會失敗。

如要解決這個問題,請選擇下列任一解決方案:

  • 等待重新建立完成。視現有維護期間和排除時段等因素而定,這項作業可能需要數小時、數天或數週。
  • 手動啟動版本升級,升級至與控制層相同的版本,重新建立受影響的節點集區。

    如要開始重建,請執行下列指令:

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

    升級完成後,請再次嘗試操作。

錯誤 401:未獲授權

找出節點服務帳戶缺少重要權限的叢集

如要找出缺少重要權限的節點服務帳戶叢集,請使用 NODE_SA_MISSING_PERMISSIONS recommender 子類型GKE 建議

  • 使用 Google Cloud 控制台。前往「Kubernetes clusters」(Kubernetes 叢集) 頁面,並在特定叢集的「Notifications」(通知) 欄中,查看「Grant critical permissions」(授予重要權限) 建議。
  • 使用 gcloud CLI 或 Recommender API,並指定NODE_SA_MISSING_PERMISSIONS 推薦子類型。

    如要查詢建議,請執行下列指令:

    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}[email protected]"       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}[email protected]"       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 機構政策限制,專案中的預設 Compute Engine 服務帳戶可能不會自動取得 GKE 的必要權限。

  1. 找出節點使用的服務帳戶名稱:

    控制台

    1. 前往「Kubernetes clusters」(Kubernetes 叢集) 頁面:

      前往 Kubernetes 叢集

    2. 在叢集清單中,按一下要檢查的叢集名稱。
    3. 視叢集運作模式而定,請執行下列其中一項操作:
      • 如為 Autopilot 模式叢集,請在「安全性」部分中,找出「服務帳戶」欄位。
      • 如果是 Standard 模式叢集,請執行下列操作:
        1. 按一下「Nodes」(節點) 分頁標籤。
        2. 在「節點集區」表格中,按一下節點集區名稱。「節點集區詳細資料」頁面隨即開啟。
        3. 在「安全性」部分,找到「服務帳戶」欄位。

    如果「服務帳戶」欄位中的值為 default,節點就會使用 Compute Engine 預設服務帳戶。如果這個欄位的值不是 default,節點就會使用自訂服務帳戶。如要將必要角色授予自訂服務帳戶,請參閱「使用最低權限的 IAM 服務帳戶」。

    gcloud

    如果是 Autopilot 模式叢集,請執行下列指令:

    gcloud container clusters describe CLUSTER_NAME \     --location=LOCATION \     --flatten=autoscaling.autoprovisioningNodePoolDefaults.serviceAccount

    如果是標準模式叢集,請執行下列指令:

    gcloud container clusters describe CLUSTER_NAME \     --location=LOCATION \     --format="table(nodePools.name,nodePools.config.serviceAccount)"

    如果輸出為 default,表示節點使用 Compute Engine 預設服務帳戶。如果輸出不是 default,表示節點使用自訂服務帳戶。如要將必要角色授予自訂服務帳戶,請參閱「使用最低權限的 IAM 服務帳戶」。

  2. 如要將 roles/container.defaultNodeServiceAccount 角色授予 Compute Engine 預設服務帳戶,請完成下列步驟:

    主控台

    1. 前往「歡迎」頁面:

      前往「歡迎」

    2. 在「專案編號」欄位中,按一下「複製到剪貼簿」
    3. 前往「IAM」(身分與存取權管理)IAM 頁面:

      前往「身分與存取權管理」頁面

    4. 按一下「授予存取權」
    5. 在「New principals」(新增主體) 欄位中,指定下列值:
      PROJECT_NUMBER[email protected]
      PROJECT_NUMBER 替換為您複製的專案編號。
    6. 在「Select a role」(選取角色) 選單中,選取「Kubernetes Engine Default Node Service Account」(Kubernetes Engine 預設節點服務帳戶) 角色。
    7. 按一下 [儲存]

    gcloud

    1. 找出 Google Cloud 專案編號:
      gcloud projects describe PROJECT_ID \     --format="value(projectNumber)"

      PROJECT_ID 替換為您的專案 ID。

      輸出結果會與下列內容相似:

       12345678901 
    2. roles/container.defaultNodeServiceAccount 角色指派給 Compute Engine 預設服務帳戶:
      gcloud projects add-iam-policy-binding PROJECT_ID \     --member="serviceAccount:PROJECT_NUMBER[email protected]" \     --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. 建立或修改 IAM 服務帳戶,使其具備執行 GKE 叢集所需的所有權限。如需操作說明,請參閱「使用最低權限的 IAM 服務帳戶」。
  2. 使用 --service-account 標記,在叢集建立指令中指定更新後的 IAM 服務帳戶。如需操作說明,請參閱「建立 Autopilot 叢集」。

或者,您也可以省略 --service-account 旗標,讓 GKE 使用專案中的 Compute Engine 預設服務帳戶,該帳戶預設具有必要權限。

錯誤 404:找不到資源

如果呼叫gcloud container指令時收到錯誤 404 (找不到資源),請重新向 Google Cloud CLI 驗證,解決這個問題:

gcloud auth login 

錯誤 400/403:缺少帳戶的編輯權限

如果發生缺少帳戶編輯權限的錯誤 (錯誤 400 或 403),表示下列其中一項已遭刪除或手動編輯:

啟用 Compute Engine 或 GKE API 時, Google Cloud會建立下列服務帳戶和代理程式:

  • 專案中的 Compute Engine 預設服務帳戶。GKE 預設會將這個服務帳戶附加至節點,用於記錄和監控等系統工作。
  • Google 代管專案中的 Google API 服務代理程式,且具備專案的編輯權限。
  • Google 管理的專案中的 Google Kubernetes Engine 服務代理人,且在專案中具有 Kubernetes Engine 服務代理人角色。

如果有人編輯這些權限、移除專案的角色繫結、完全移除服務帳戶,或停用 API,叢集建立和所有管理功能都會失效。

確認 GKE 服務代理的權限

如要確認 Google Kubernetes Engine 服務帳戶是否已獲派專案的 Kubernetes Engine 服務代理人角色,請完成下列步驟:

  1. 找出 Google Kubernetes Engine 服務帳戶的名稱。所有服務帳戶都採用下列格式:

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

    PROJECT_NUMBER 換成您的專案編號

  2. 確認 Google Kubernetes Engine 服務帳戶未獲派專案的 Kubernetes Engine 服務代理人角色:

    gcloud projects get-iam-policy PROJECT_ID 

    PROJECT_ID 替換為您的專案 ID。

如要修正問題,請檢查是否有人從 Google Kubernetes Engine 服務帳戶移除了 Kubernetes Engine 服務代理人角色,如果是,請重新新增。否則,請按照下列操作說明重新啟用 Kubernetes Engine API,還原服務帳戶和權限:

控制台

  1. 前往 Google Cloud 控制台的「APIs & Services」(API 和服務) 頁面

    前往「API 與服務」頁面

  2. 選取專案。

  3. 點選「啟用 API 和服務」

  4. 搜尋 Kubernetes,然後從搜尋結果中選取 API。

  5. 點選「Enable」(啟用)。如果您之前已啟用過 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 

後續步驟