이 페이지에서는 Google Kubernetes Engine(GKE) 서비스 계정 문제를 해결하는 방법을 보여줍니다.
노드 서비스 계정에 GKE에 필요한 역할 부여
GKE 노드에서 사용하는 IAM 서비스 계정에는 Kubernetes Engine 기본 노드 서비스 계정(roles/container.defaultNodeServiceAccount
) IAM 역할에 포함된 모든 권한이 있어야 합니다. GKE 노드 서비스 계정에 이러한 권한 중 하나 이상이 없으면 GKE는 다음과 같은 시스템 작업을 실행할 수 없습니다.
- 노드에서 Cloud Logging으로 시스템 및 애플리케이션 로그 전송하기
- 노드에서 Cloud Monitoring으로 시스템 및 애플리케이션 측정항목 전송하기
- 수평형 포드 자동 확장 처리의 성능 프로필 운영하기
노드 서비스 계정에 다음과 같은 이유로 특정한 필수 권한이 누락되었을 수 있습니다.
- 조직에서
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 감사 로그에서
monitoring.googleapis.com
같은 Google Cloud API의 관리자 활동 로그는 노드 서비스 계정에서 해당 API에 액세스할 수 있는 권한이 누락된 경우 다음 값을 갖습니다.- 심각도:
ERROR
- 메시지:
Permission denied (or the resource may not exist)
- 심각도:
Cloud Logging에서 특정 노드의 로그가 누락되고 해당 노드의 로깅 에이전트의 포드 로그에
401
오류가 표시됩니다. 이러한 포드 로그를 가져오려면 다음 명령어를 실행합니다.[[ $(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 클러스터 페이지로 이동합니다.
클러스터 목록에서 조사할 클러스터 이름을 클릭합니다.
노드 서비스 계정의 이름을 찾으세요. 이 이름은 나중에 필요합니다.
- Autopilot 모드 클러스터의 경우 보안 섹션에서 서비스 계정 필드를 찾습니다.
- Standard 모드 클러스터의 경우 다음을 실행합니다.
- 노드 탭을 클릭합니다.
- 노드 풀 표에서 노드 풀 이름을 클릭합니다. 노드 풀 세부정보 페이지가 열립니다.
- 보안 섹션에서 서비스 계정 필드를 찾습니다.
서비스 계정 필드의 값이
default
인 경우 노드는 Compute Engine 기본 서비스 계정을 사용합니다. 이 필드의 값이default
가 아닌 경우 노드에서 커스텀 서비스 계정을 사용합니다.
서비스 계정에 Kubernetes Engine Default Node Service Account
역할을 부여하려면 다음 단계를 따르세요.
시작 페이지로 이동합니다.
프로젝트 번호 필드에서
클립보드에 복사를 클릭합니다.IAM 페이지로 이동합니다.
액세스 권한 부여를 클릭합니다.
새 주 구성원 필드에서 노드 서비스 계정의 이름을 지정합니다. 노드에서 기본 Compute Engine 서비스 계정을 사용하는 경우 다음 값을 지정합니다.
PROJECT_NUMBER[email protected]
PROJECT_NUMBER
를 복사한 프로젝트 번호로 바꿉니다.역할 선택 메뉴에서 Kubernetes Engine 기본 노드 서비스 계정 역할을 선택합니다.
저장을 클릭합니다.
역할이 부여되었는지 확인하려면 다음 단계를 따르세요.
- IAM 페이지에서 역할별 보기 탭을 클릭합니다.
- Kubernetes Engine 기본 노드 서비스 계정 섹션을 펼칩니다. 이 역할이 있는 주 구성원 목록이 표시됩니다.
- 주 구성원 목록에서 노드 서비스 계정을 찾습니다.
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[email protected]
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
추천자 하위유형의 GKE 추천을 사용하여 권한이 누락된 노드 서비스 계정이 있는 Autopilot 및 Standard 클러스터를 식별합니다. 추천 도구는 2024년 1월 1일 이후에 생성된 클러스터만 식별합니다. 추천 도구를 사용하여 누락된 권한을 찾아 수정하려면 다음 단계를 따르세요.
프로젝트에서
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}[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 클러스터에 적용됩니다.
권한이 누락된 서비스 계정의 이름을 확인한 후 필요한 역할을 부여합니다. 자세한 내용은 노드 서비스 계정에 GKE에 필요한 역할 부여 섹션의 안내를 참고하세요.
Google Cloud 프로젝트에 기본 서비스 계정 복원
GKE의 기본 서비스 계정인 container-engine-robot
이 실수로 프로젝트에서 결합 해제될 수 있습니다. Kubernetes Engine 서비스 에이전트 역할(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 서비스 에이전트 역할(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"
서비스 계정이 비활성화된 경우 다음 명령어를 실행하여 서비스 계정을 사용 설정합니다.
gcloud iam service-accounts enable PROJECT_ID[email protected]
PROJECT_ID
를 프로젝트 ID로 바꿉니다.
자세한 내용은 노드 등록 문제 해결을 참조하세요.
오류 400/403: 계정에 수정 권한 없음
서비스 계정이 삭제되면 수정 권한 누락 오류가 표시될 수 있습니다. 이 오류를 해결하는 방법은 오류 400/403: 계정에서 수정 권한 누락을 참조하세요.
다음 단계
문서에서 문제의 해결 방법을 찾을 수 없는 경우 지원 받기에서 다음 주제에 관한 조언을 포함한 추가 도움을 받으세요.
- Cloud Customer Care에 문의하여 지원 케이스를 엽니다.
- StackOverflow에서 질문하고
google-kubernetes-engine
태그를 사용하여 유사한 문제를 검색해 커뮤니티의 지원을 받습니다.#kubernetes-engine
Slack 채널에 조인하여 더 많은 커뮤니티 지원을 받을 수도 있습니다. - 공개 Issue Tracker를 사용하여 버그나 기능 요청을 엽니다.