本页面介绍了如何排查 Google Kubernetes Engine (GKE) 服务账号的问题。
向节点服务账号授予 GKE 所需的角色
GKE 节点使用的 IAM 服务账号必须具有 Kubernetes Engine Default Node Service Account (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 集群页面上,对于特定集群,通知列中显示 授予关键权限错误消息。
在 Google Cloud 控制台中,特定集群的集群详情页面上显示以下错误消息:
Grant roles/container.defaultNodeServiceAccount role to Node service account to allow for non-degraded operations.
在 Cloud Audit Logs 中,如果节点服务账号缺少访问
monitoring.googleapis.com
等 Google Cloud API 的相应权限,则这些 API 的管理员活动日志会具有以下值:- 严重程度:
ERROR
- 消息:
Permission denied (or the resource may not exist)
- 严重程度:
Cloud Logging 中缺少特定节点的日志,并且这些节点上 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 Default Node Service Account (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 Default Node Service Account 角色。
点击保存。
如需验证是否已授予角色,请执行以下操作:
- 在 IAM 页面中,点击按角色查看标签页。
- 展开 Kubernetes Engine Default Node Service Account 部分。此时会显示具有此角色的主账号的列表。
- 在主账号列表中找到您的节点服务账号。
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)'
输出是服务账号的名称。
识别节点服务账号缺少权限的集群
使用属于 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 Service Agent 角色 (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 Service Agent 角色 (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"
如果服务账号已停用,请运行以下命令以启用服务账号:
找到您的 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 Customer Care 联系,以提交支持请求。
- 通过在 StackOverflow 上提问并使用
google-kubernetes-engine
标记搜索类似问题,从社区获得支持。您还可以加入#kubernetes-engine
Slack 频道,以获得更多社区支持。 - 使用公开问题跟踪器提交 bug 或功能请求。