GKE에서 서비스 계정 문제 해결


이 페이지에서는 Google Kubernetes Engine(GKE) 서비스 계정 문제를 해결하는 방법을 보여줍니다.

노드 서비스 계정에 GKE에 필요한 역할 부여

GKE 노드에서 사용하는 IAM 서비스 계정에는 Kubernetes Engine 기본 노드 서비스 계정(roles/container.defaultNodeServiceAccount) IAM 역할에 포함된 모든 권한이 있어야 합니다. GKE 노드 서비스 계정에 이러한 권한 중 하나 이상이 없으면 GKE는 다음과 같은 시스템 작업을 실행할 수 없습니다.

노드 서비스 계정에 다음과 같은 이유로 특정한 필수 권한이 누락되었을 수 있습니다.

  • 조직에서 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) 역할을 오류를 일으키는 서비스 계정에 부여합니다. 다음 옵션 중 하나를 선택합니다.

콘솔

노드에서 사용하는 서비스 계정의 이름을 찾으려면 다음 단계를 따르세요.

  1. Kubernetes 클러스터 페이지로 이동합니다.

    Kubernetes 클러스터로 이동

  2. 클러스터 목록에서 조사할 클러스터 이름을 클릭합니다.

  3. 노드 서비스 계정의 이름을 찾으세요. 이 이름은 나중에 필요합니다.

    • Autopilot 모드 클러스터의 경우 보안 섹션에서 서비스 계정 필드를 찾습니다.
    • Standard 모드 클러스터의 경우 다음을 실행합니다.
    1. 노드 탭을 클릭합니다.
    2. 노드 풀 표에서 노드 풀 이름을 클릭합니다. 노드 풀 세부정보 페이지가 열립니다.
    3. 보안 섹션에서 서비스 계정 필드를 찾습니다.

    서비스 계정 필드의 값이 default인 경우 노드는 Compute Engine 기본 서비스 계정을 사용합니다. 이 필드의 값이 default아닌 경우 노드에서 커스텀 서비스 계정을 사용합니다.

서비스 계정에 Kubernetes Engine Default Node Service Account 역할을 부여하려면 다음 단계를 따르세요.

  1. 시작 페이지로 이동합니다.

    시작 페이지로 이동

  2. 프로젝트 번호 필드에서 클립보드에 복사를 클릭합니다.

  3. IAM 페이지로 이동합니다.

    IAM으로 이동

  4. 액세스 권한 부여를 클릭합니다.

  5. 새 주 구성원 필드에서 노드 서비스 계정의 이름을 지정합니다. 노드에서 기본 Compute Engine 서비스 계정을 사용하는 경우 다음 값을 지정합니다.

    PROJECT_NUMBER[email protected]
    

    PROJECT_NUMBER를 복사한 프로젝트 번호로 바꿉니다.

  6. 역할 선택 메뉴에서 Kubernetes Engine 기본 노드 서비스 계정 역할을 선택합니다.

  7. 저장을 클릭합니다.

역할이 부여되었는지 확인하려면 다음 단계를 따르세요.

  1. IAM 페이지에서 역할별 보기 탭을 클릭합니다.
  2. Kubernetes Engine 기본 노드 서비스 계정 섹션을 펼칩니다. 이 역할이 있는 주 구성원 목록이 표시됩니다.
  3. 주 구성원 목록에서 노드 서비스 계정을 찾습니다.

gcloud

  1. 노드에서 사용하는 서비스 계정의 이름을 찾습니다.

    • 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아닌 경우 노드에서 커스텀 서비스 계정을 사용합니다.

  2. Google Cloud 프로젝트 번호를 찾습니다.

    gcloud projects describe PROJECT_ID \
        --format="value(projectNumber)"
    

    PROJECT_ID를 프로젝트 ID로 바꿉니다.

    출력은 다음과 비슷합니다.

    12345678901
    
  3. 서비스 계정에 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를 이전 단계의 프로젝트 번호로 바꿉니다.

  4. 역할이 부여되었는지 확인합니다.

    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일 이후에 생성된 클러스터만 식별합니다. 추천 도구를 사용하여 누락된 권한을 찾아 수정하려면 다음 단계를 따르세요.

  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시간이 소요될 수 있습니다. 상세 안내는 통계 및 권장사항 보기를 참조하세요.

  2. 이전 단계의 출력에 있는 모든 클러스터에 대해 연결된 노드 서비스 계정을 찾아 해당 서비스 계정에 필요한 역할을 부여합니다. 자세한 내용은 노드 서비스 계정에 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를 사용하면 됩니다.

콘솔

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를 사용하면 됩니다.

콘솔

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: 계정에서 수정 권한 누락을 참조하세요.

다음 단계

  • 문서에서 문제의 해결 방법을 찾을 수 없는 경우 지원 받기에서 다음 주제에 관한 조언을 포함한 추가 도움을 받으세요.