本頁說明如何排解 Google Kubernetes Engine (GKE) 服務帳戶的問題。
將 GKE 的必要角色授予節點服務帳戶
GKE 節點使用的 IAM 服務帳戶必須具備 Kubernetes Engine 預設節點服務帳戶 (roles/container.defaultNodeServiceAccount
) IAM 角色中的所有權限。如果 GKE 節點服務帳戶缺少一或多項權限,GKE 就無法執行下列系統工作:
- 將節點的系統和應用程式記錄檔傳送至 Cloud Logging。
- 將節點的系統和應用程式指標傳送至 Cloud Monitoring。
- 操作水平 Pod 自動配置器的效能設定檔。
節點服務帳戶可能沒有特定必要權限,原因如下:
- 機構強制執行
iam.automaticIamGrantsForDefaultServiceAccounts
機構政策限制,因此 Google Cloud 無法自動將 IAM 角色授予預設 IAM 服務帳戶。 - 您授予自訂節點服務帳戶的 IAM 角色,未包含
roles/container.defaultNodeServiceAccount
角色中的所有必要權限。
如果節點服務帳戶缺少 GKE 要求的權限,您可能會看到類似下列內容的錯誤和通知:
- 在 Google Cloud 控制台的「Kubernetes clusters」(Kubernetes 叢集) 頁面中,特定叢集的「Notifications」(通知) 欄會顯示「Grant critical permissions」(授予重要權限) 錯誤訊息。
在 Google Cloud 控制台中,特定叢集的叢集詳細資料頁面會顯示下列錯誤訊息:
Grant roles/container.defaultNodeServiceAccount role to Node service account to allow for non-degraded operations.
在 Cloud 稽核記錄中,如果節點服務帳戶缺少存取 Google Cloud API (例如
monitoring.googleapis.com
) 的相應權限,這些 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 clusters」(Kubernetes 叢集) 頁面:
在叢集清單中,按一下要檢查的叢集名稱。
找出節點服務帳戶的名稱。稍後會需要用到這個名稱。
- 如為 Autopilot 模式叢集,請在「安全性」部分中,找出「服務帳戶」欄位。
- 如果是 Standard 模式叢集,請執行下列操作:
- 按一下「Nodes」(節點) 分頁標籤。
- 在「節點集區」表格中,按一下節點集區名稱。「節點集區詳細資料」頁面隨即開啟。
- 在「安全性」部分,找到「服務帳戶」欄位。
如果「服務帳戶」欄位中的值為
default
,節點就會使用 Compute Engine 預設服務帳戶。如果這個欄位的值「不是」default
,表示節點使用自訂服務帳戶。
如要將 Kubernetes Engine Default Node Service Account
角色授予服務帳戶,請按照下列步驟操作:
前往「歡迎」頁面:
在「專案編號」欄位中,按一下
「複製到剪貼簿」。前往「IAM」(身分與存取權管理) 頁面:
按一下「授予存取權」
。在「新增主體」欄位中,指定節點服務帳戶的名稱。如果節點使用預設的 Compute Engine 服務帳戶,請指定下列值:
PROJECT_NUMBER-compute@developer.gserviceaccount.com
將
PROJECT_NUMBER
替換為您複製的專案編號。在「Select a role」(選取角色) 選單中,選取「Kubernetes Engine Default Node Service Account」(Kubernetes Engine 預設節點服務帳戶) 角色。
按一下 [儲存]。
如要確認角色已授予,請按照下列步驟操作:
- 在「IAM」頁面中,按一下「View by roles」(依角色檢視) 分頁標籤。
- 展開「Kubernetes Engine 預設節點服務帳戶」區段。 畫面上會顯示具備這個角色的主體清單。
- 在主體清單中找出節點服務帳戶。
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
,表示節點使用自訂服務帳戶。找出 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)'
輸出內容為服務帳戶名稱。
找出節點服務帳戶缺少權限的叢集
使用 NODE_SA_MISSING_PERMISSIONS
recommender 子類型的 GKE 建議,找出節點服務帳戶缺少權限的 Autopilot 和 Standard 叢集。Recommender 只會識別 2024 年 1 月 1 日當天或之後建立的叢集。如要使用 Recommender 找出並修正缺少的權限,請按照下列步驟操作:
在專案中找出下列
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"
更改下列內容:
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
) 是一種身分與存取權管理 (IAM) 角色,可授予服務帳戶管理叢集資源的權限。如果您從服務帳戶移除了這個角色繫結,預設服務帳戶就會解除與專案之間繫結,導致您無法部署應用程式及執行其他叢集作業。
如要確認服務帳戶是否已從專案中移除,可以使用 Google Cloud 控制台或 Google Cloud CLI。
控制台
前往 Google Cloud 控制台的「IAM & Admin」(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 & Admin」(IAM 與管理) 頁面。
gcloud
- 執行下列指令:
gcloud iam service-accounts list --filter="NAME~'compute' AND disabled=true"
如果服務帳戶已停用,請執行下列指令來啟用服務帳戶:
找出 Google Cloud 專案編號:
gcloud projects describe PROJECT_ID \ --format="value(projectNumber)"
將
PROJECT_ID
替換為您的專案 ID。輸出結果會與下列內容相似:
12345678901
啟用服務帳戶:
gcloud iam service-accounts enable PROJECT_NUMBER-compute@developer.gserviceaccount.com
將
PROJECT_NUMBER
替換為上一個步驟輸出內容中的專案編號。
詳情請參閱「排解節點註冊問題」。
錯誤 400/403:缺少帳戶的編輯權限
如果服務帳戶遭到刪除,您可能會看到編輯權限遺失的錯誤訊息。如要瞭解如何排解這項錯誤,請參閱「錯誤 400/403:缺少帳戶的編輯權限」。
後續步驟
如果無法在說明文件中找到問題的解決方法,請參閱「取得支援」一文,尋求進一步的協助, 包括下列主題的建議:
- 與 Cloud 客戶服務聯絡,建立支援案件。
- 在 StackOverflow 上提問,並使用
google-kubernetes-engine
標記搜尋類似問題,向社群尋求支援。你也可以加入#kubernetes-engine
Slack 頻道,取得更多社群支援。 - 使用公開問題追蹤工具回報錯誤或提出功能要求。