Résoudre les problèmes de journalisation dans GKE


Cette page vous aide à résoudre les problèmes liés au pipeline de journalisation Google Kubernetes Engine (GKE) lui-même, par exemple lorsque les journaux n'apparaissent pas dans Cloud Logging. Pour en savoir plus sur l'utilisation des journaux pour résoudre les problèmes liés à vos charges de travail et clusters, consultez Présentation du dépannage GKE.

Journaux de clusters manquants dans Cloud Logging

Vérifiez que la journalisation est activée dans le projet

  1. Répertoriez les services activés :

    gcloud services list --enabled --filter="NAME=logging.googleapis.com"
    

    Le résultat suivant indique que la journalisation est activée pour le projet :

    NAME                    TITLE
    logging.googleapis.com  Cloud Logging API
    

    Facultatif: Consultez les journaux dans la visionneuse de journaux pour déterminer qui a désactivé l'API et quand :

    protoPayload.methodName="google.api.serviceusage.v1.ServiceUsage.DisableService"
    protoPayload.response.services="logging.googleapis.com"
    
  2. Si la journalisation est désactivée, activez-là :

    gcloud services enable logging.googleapis.com
    

Vérifiez que la journalisation est activée sur le cluster

  1. Répertoriez les clusters :

    gcloud container clusters list \
        --project=PROJECT_ID \
        '--format=value(name,loggingConfig.componentConfig.enableComponents)' \
        --sort-by=name | column -t
    

    Remplacez les éléments suivants :

    • PROJECT_ID : ID de votre projet Trusted Cloud by S3NS .

    Le résultat ressemble à ce qui suit :

    cluster-1              SYSTEM_COMPONENTS
    cluster-2              SYSTEM_COMPONENTS;WORKLOADS
    cluster-3
    

    Si la valeur correspondant à votre cluster est vide, la journalisation est désactivée. Par exemple, cluster-3 dans cette sortie a la journalisation désactivée.

  2. Activez la journalisation des clusters si elle est définie sur NONE :

    gcloud container clusters update CLUSTER_NAME  \
        --logging=SYSTEM,WORKLOAD \
        --location=COMPUTE_LOCATION
    

    Remplacez les éléments suivants :

Après avoir vérifié que vous avez activé la journalisation dans votre projet et sur le cluster, envisagez d'utiliser les enquêtes Gemini Cloud Assist pour obtenir des informations supplémentaires sur vos journaux et résoudre les problèmes. Pour en savoir plus sur les différentes façons de lancer une investigation à l'aide de l'explorateur de journaux, consultez Résoudre les problèmes liés aux investigations Gemini Cloud Assist dans la documentation Gemini.

Vérifier que les nœuds des pools de nœuds disposent du niveau d'accès à Cloud Logging

L'un des champs d'application suivants est nécessaire pour que les nœuds écrivent des journaux dans Cloud Logging :

  • https://www.googleapis.com/auth/logging.write
  • https://www.googleapis.com/auth/cloud-platform
  • https://www.googleapis.com/auth/logging.admin
  1. Vérifiez les champs d'application configurés sur chaque pool de nœuds du cluster :

    gcloud container node-pools list --cluster=CLUSTER_NAME \
        --format="table(name,config.oauthScopes)" \
        --location COMPUTE_LOCATION
    

    Remplacez les éléments suivants :

    Migrez vos charges de travail de l'ancien pool de nœuds vers le nouveau pool de nœuds et surveillez la progression.

  2. Créez des pools de nœuds avec le champ d'application de journalisation approprié :

    gcloud container node-pools create NODE_POOL_NAME \
        --cluster=CLUSTER_NAME \
        --location=COMPUTE_LOCATION \
        --scopes="gke-default"
    

    Remplacez les éléments suivants :

Identifier les clusters dont les comptes de service de nœud ne disposent pas d'autorisations critiques

Pour identifier les clusters dont les comptes de service de nœuds ne disposent pas des autorisations critiques, utilisez les recommandations GKE du sous-type de recommandation NODE_SA_MISSING_PERMISSIONS :

  • Utilisez la console Trusted Cloud . Accédez à la page Clusters Kubernetes. Dans la colonne Notifications pour des clusters spécifiques, recherchez la recommandation Accorder des autorisations critiques.
  • Utilisez la gcloud CLI ou l'API Recommender en spécifiant le sous-type de recommandation NODE_SA_MISSING_PERMISSIONS.

    Pour interroger cette recommandation, exécutez la commande suivante :

    gcloud recommender recommendations list \
        --recommender=google.container.DiagnosisRecommender \
        --location LOCATION \
        --project PROJECT_ID \
        --format yaml \
        --filter="recommenderSubtype:NODE_SA_MISSING_PERMISSIONS"
    

Pour appliquer cette recommandation, attribuez le rôle roles/container.defaultNodeServiceAccount au compte de service du nœud.

Vous pouvez exécuter un script qui recherche les comptes de service de nœud dans les clusters Standard et Autopilot de votre projet qui ne disposent pas des autorisations requises pour GKE. Ce script utilise gcloud CLI et l'utilitaire jq. Pour afficher le script, développez la section suivante :

Afficher le script

#!/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.s3ns-system.iam.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.s3ns-system.iam.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

Identifier les comptes de service de nœud qui ne disposent pas d'autorisations critiques dans un cluster

GKE utilise des comptes de service IAM associés à vos nœuds pour exécuter des tâches système telles que la journalisation et la surveillance. Au minimum, ces comptes de service de nœud doivent disposer du rôle Compte de service de nœud par défaut Kubernetes Engine (roles/container.defaultNodeServiceAccount) sur votre projet. Par défaut, GKE utilise le compte de service Compute Engine par défaut, qui est créé automatiquement dans votre projet, en tant que compte de service de nœud.

Si votre organisation applique la contrainte de règle d'administration iam.automaticIamGrantsForDefaultServiceAccounts, il est possible que le compte de service Compute Engine par défaut de votre projet n'obtienne pas automatiquement les autorisations requises pour GKE.

  • Pour vérifier si des autorisations de journalisation sont manquantes, recherchez les erreurs 401 dans la journalisation de votre cluster :

    [[ $(kubectl logs -l k8s-app=fluentbit-gke -n kube-system -c fluentbit-gke | grep -cw "Received 401") -gt 0 ]] && echo "true" || echo "false"
    

    Si le résultat est true, cela signifie que la charge de travail du système rencontre des erreurs 401, qui indiquent un manque d'autorisations. Si le résultat est false, ignorez le reste de ces étapes et essayez une autre procédure de dépannage.Pour identifier toutes les autorisations critiques manquantes, consultez le script.

  1. Recherchez le nom du compte de service utilisé par vos nœuds :

    Console

    1. Accédez à la page Clusters Kubernetes :

      Accéder à la page "Clusters Kubernetes"

    2. Dans la liste des clusters, cliquez sur le nom du cluster que vous souhaitez inspecter.
    3. En fonction du mode de fonctionnement du cluster, effectuez l'une des opérations suivantes :
      • Pour les clusters en mode Autopilot, dans la section Sécurité, recherchez le champ Compte de service.
      • Pour les clusters en mode Standard, procédez comme suit :
        1. Cliquez sur l'onglet Nœuds.
        2. Dans le tableau Pools de nœuds, cliquez sur le nom d'un pool de nœuds. La page Détails du pool de nœuds s'affiche.
        3. Dans la section Sécurité, recherchez le champ Compte de service.

    Si la valeur du champ Compte de service est default, vos nœuds utilisent le compte de service Compute Engine par défaut. Si la valeur de ce champ n'est pas default, vos nœuds utilisent un compte de service personnalisé. Pour attribuer le rôle requis à un compte de service personnalisé, consultez Utiliser le principe du moindre privilège pour les comptes de service IAM.

    gcloud

    Pour les clusters en mode Autopilot, exécutez la commande suivante :

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

    Pour les clusters en mode Standard, exécutez la commande suivante :

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

    Si le résultat est default, vos nœuds utilisent le compte de service Compute Engine par défaut. Si le résultat n'est pas default, vos nœuds utilisent un compte de service personnalisé. Pour attribuer le rôle requis à un compte de service personnalisé, consultez Utiliser le principe du moindre privilège pour les comptes de service IAM.

  2. Pour attribuer le rôle roles/container.defaultNodeServiceAccount au compte de service Compute Engine par défaut, procédez comme suit :

    Console

    1. Accédez à la page Bienvenue :

      Accéder à la page d'accueil

    2. Dans le champ Numéro du projet, cliquez sur Copier dans le presse-papiers.
    3. Accédez à la page IAM :

      Accéder à IAM

    4. Cliquez sur Accorder l'accès.
    5. Dans le champ Nouveaux comptes principaux, spécifiez la valeur suivante :
      PROJECT_NUMBER-compute@developer.s3ns-system.iam.gserviceaccount.com
      Remplacez PROJECT_NUMBER par le numéro de projet que vous avez copié.
    6. Dans le menu Sélectionner un rôle, sélectionnez le rôle Compte de service de nœud par défaut Kubernetes Engine.
    7. Cliquez sur Enregistrer.

    gcloud

    1. Recherchez le numéro de votre projet Trusted Cloud  :
      gcloud projects describe PROJECT_ID \
          --format="value(projectNumber)"

      Remplacez PROJECT_ID par l'ID du projet.

      Le résultat ressemble à ce qui suit :

      12345678901
      
    2. Attribuez le rôle roles/container.defaultNodeServiceAccount au compte de service Compute Engine par défaut :
      gcloud projects add-iam-policy-binding PROJECT_ID \
          --member="serviceAccount:PROJECT_NUMBER-compute@developer.s3ns-system.iam.gserviceaccount.com" \
          --role="roles/container.defaultNodeServiceAccount"

      Remplacez PROJECT_NUMBER par le numéro de projet de l'étape précédente.

  • Vérifiez que les comptes de service des nœuds disposent des autorisations requises. Vérifiez le script pour le confirmer.

Script permettant d'identifier les autorisations manquantes pour le compte de service des nœuds GKE

Vous pouvez exécuter un script qui recherche les comptes de service de nœud dans les clusters Standard et Autopilot de votre projet qui ne disposent pas des autorisations requises pour GKE. Ce script utilise gcloud CLI et l'utilitaire jq. Pour afficher le script, développez la section suivante :

Afficher le script

#!/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.s3ns-system.iam.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.s3ns-system.iam.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

Vérifier que les quotas d'écriture de l'API Cloud Logging n'ont pas été atteints

Vérifiez que vous n'avez pas atteint les quotas d'écriture de l'API Cloud Logging.

  1. Accédez à la page Quotas de la console Trusted Cloud .

    Accéder à "Quotas"

  2. Filtrez le tableau avec l'expression "Cloud Logging API".

  3. Vérifiez que vous n'avez atteint aucun quota.