Exécuter des applications Web sur GKE à l'aide de VM préemptives économiques

Ce tutoriel explique comment gérer les préemptions lors de l'exécution de VM préemptives sur Google Kubernetes Engine (GKE) diffusant une application Web. Les VM préemptives sont des instances de calcul abordables et éphémères, adaptées aux charges de travail tolérantes aux pannes. Elles offrent les mêmes options et types de machines que les instances de calcul standards, et peuvent durer jusqu'à 24 heures.

Ce tutoriel est destiné aux développeurs d'applications, aux architectes système et aux ingénieurs DevOps qui définissent, mettent en œuvre et déploient des applications Web et souhaitent utiliser des VM préemptives dans les déploiements en production. Dans ce tutoriel, nous partons du principe que vous comprenez les concepts Kubernetes fondamentaux et les différents composants de l'équilibrage de charge HTTP(S).

Contexte

Une VM préemptive est limitée à un environnement d'exécution de 24 heures et reçoit un avertissement de 30 secondes d'arrêt lorsque l'instance est sur le point d'être préemptée. La VM préemptive envoie initialement une notification de préemption à l'instance sous la forme d'un signal ACPI G2 Soft Off (SIGTERM). Au bout de 30 secondes, un signal ACPI G3 Mechanical Off (SIGKILL) est envoyé au système d'exploitation de l'instance. La VM fait ensuite passer l'instance à l'état TERMINATED.

Les VM préemptives sont idéales pour les charges de travail distribuées et tolérantes aux pannes, qui n'ont pas besoin de la disponibilité continue d'une seule instance. Voici des exemples de ce type de charge de travail : encodage vidéo, rendu pour les effets visuels, analyse de données, simulation et génomique. Toutefois, en raison de limites de disponibilité et d'interruptions potentiellement fréquentes causées par les préemptions, les VM préemptives sont généralement déconseillées pour les applications Web et destinées aux utilisateurs.

Ce tutoriel vous explique comment configurer un déploiement utilisant une combinaison de VM préemptives et de VM standards sur GKE afin de diffuser de manière fiable le trafic des applications Web sans aucune interruption.

Défis liés à l'utilisation de VM préemptives

La principale difficulté liée à l'utilisation de VM préemptives pour la diffusion de trafic utilisateur est de s'assurer que les requêtes des utilisateurs ne soient pas interrompues. Lors de la préemption, vous devez considérer les points suivants :

  • Comment garantir la disponibilité d'une application lorsqu'elle s'exécute sur des VM préemptives ? Les VM préemptives n'ont pas de disponibilité garantie et sont exclues explicitement des contrats de niveau de service de Compute Engine.
  • Comment gérer l'arrêt optimal de l'application de sorte que les conditions suivantes soient remplies :
    • L'équilibreur de charge arrête de transférer des requêtes aux pods qui s'exécutent sur une instance en cours de préemption.
    • Les requêtes en cours de transfert sont gérées correctement : elles se terminent ou elles sont arrêtées.
    • Les connexions à vos bases de données et à l'application sont fermées ou drainées avant l'arrêt de l'instance.
  • Comment traiter les requêtes, telles que les transactions critiques, qui peuvent nécessiter des garanties de disponibilité ou qui ne sont pas tolérantes aux pannes ?

Considérez les défis liés à l'arrêt optimal des conteneurs exécutés sur les VM préemptives. Du point de vue de la mise en œuvre, le moyen le plus simple d'écrire une logique de nettoyage lorsqu'une instance est en cours d'arrêt est de le faire via un script d'arrêt. Toutefois, les scripts d'arrêt ne sont pas compatibles si vous exécutez des charges de travail conteneurisées dans GKE.

L'utilisation d'un gestionnaire SIGTERM dans votre application peut également vous permettre d'écrire une logique de nettoyage. Les conteneurs fournissent également des hooks de cycle de vie tels que preStop, qui est déclenché juste avant l'arrêt du conteneur. Dans Kubernetes, Kubelet est responsable de l'exécution des événements de cycle de vie des conteneurs. Dans un cluster Kubernetes, Kubelet s'exécute sur la VM et surveille les spécifications des pods via le serveur d'API Kubernetes.

Lorsque vous expulsez un pod à l'aide d'une ligne de commande ou d'une API, Kubelet voit que le pod a été marqué comme en cours d'arrêt et démarre le processus d'arrêt. La durée du processus est limitée par le "délai de grâce", qui est défini comme le nombre défini de secondes après lequel Kubelet envoie un signal SIGKILL aux conteneurs. Dans le cadre de l'arrêt optimal, si un conteneur qui s'exécute dans le pod a défini un hook preStop, Kubelet exécute ce hook à l'intérieur du conteneur. Ensuite, Kubelet déclenche un signal SIGTERM sur process-ID 1 à l'intérieur de chaque conteneur. Si l'application utilise un gestionnaire SIGTERM, il est exécuté. Une fois que le gestionnaire a terminé, Kubelet envoie un signal SIGKILL à tous les processus toujours en cours d'exécution dans le pod.

Imaginez qu'un nœud Kubernetes soit en cours de préemption. Vous avez besoin d'un mécanisme pour intercepter la notification de préemption et lancer le processus d'expulsion du pod. Supposons que vous exécutiez un programme qui écoute les notifications de préemption et expulse les pods en cours d'exécution à la réception d'un événement. En cas d'expulsion, la séquence d'arrêt du pod décrite précédemment est déclenchée. Toutefois, dans ce cas, le nœud subit également un arrêt, qui est géré par le système d'exploitation (OS, operating system) du nœud. Cet arrêt peut interférer avec la gestion du cycle de vie des conteneurs par Kubelet, ce qui signifie qu'un conteneur peut être arrêté soudainement, même s'il est en train d'exécuter un hook preStop.

En outre, du point de vue de la disponibilité et de la gestion du trafic, l'exécution exclusive de votre application Web sur des VM préemptives peut également poser plusieurs problèmes. Avant d'utiliser des VM préemptives, posez-vous les questions suivantes :

  • Que se passe-t-il si la plupart des VM préemptives sont préemptées en même temps ? Pour les applications diffusant des milliers de requêtes par seconde, comment le basculement des requêtes se poursuit-il sans interruption ?
  • Que se passe-t-il si la capacité des VM préemptives n'est pas disponible ? Dans ce cas, comment effectuer un scaling horizontal ou maintenir un état de déploiement stable pour votre application ?

Architecture

Pour résoudre les difficultés liées à l'utilisation de VM préemptives, vous devez effectuer toutes les opérations suivantes :

  1. Exécutez un cluster GKE avec deux pools de nœuds : l'un pour exécuter des VM préemptives et l'autre pour exécuter des VM standards. Cela vous permet de répartir le trafic et de disposer d'un basculement actif pour traiter les requêtes nouvelles et en cours de transfert en cas de préemption. Cette approche vous permet également de répartir le trafic entre des VM standards et des VM préemptives en fonction des exigences de garantie de temps d'activité et de tolérance aux pannes. Pour savoir comment choisir la taille des pools de nœuds, consultez la section sur les éléments à prendre en compte.
  2. Écoutez les notifications de préemption sur les VM préemptives et expulsez les pods qui s'exécutent sur le nœud.
  3. Utilisez un hook preStop ou un gestionnaire SIGTERM pour exécuter la logique de nettoyage.
  4. Assurez-vous que Kubelet est autorisé à gérer le cycle de vie d'arrêt des pods et n'est pas arrêté soudainement.
  5. Rejetez le nœud de sorte qu'aucun nouveau pod n'y soit programmé lorsqu'il est en cours de préemption.

Le schéma suivant présente une vue d'ensemble de l'architecture que vous allez déployer dans ce tutoriel.

Architecture de haut niveau

Comme le montre le schéma, vous créez deux pools de nœuds : default-pool s'exécute sur les VM standards et pvm-pool s'exécute sur des VM préemptives. Le programmeur GKE par défaut tente de répartir uniformément les pods sur toutes les instances des pools de nœuds. Par exemple, si vous déployez quatre instances dupliquées et que deux nœuds s'exécutent dans chaque pool de nœuds, le programmeur provisionne un pod sur chacun des quatre nœuds. Toutefois, en cas d'utilisation de VM préemptives, vous pouvez diriger davantage de trafic vers pvm-pool pour obtenir une utilisation plus élevée des VM préemptives et donc réaliser des économies. Par exemple, vous pouvez provisionner trois pods sur pvm-pool et un pod sur default-pool pour le basculement, réduisant ainsi le nombre de VM standards du cluster.

Pour disposer de ce type de contrôle sur la planification, vous pouvez écrire votre propre programmeur ou répartir l'application en deux déploiements. Chaque déploiement sera épinglé à un pool de nœuds en fonction des règles d'affinité des nœuds. Dans cet exemple, vous allez créer deux déploiements, web-std et web-pvm, où web-std est épinglé à default-pool et web-pvm est épinglé à pvm-pool.

Pour les nœuds pvm-pool, vous devez écouter une notification de préemption et, lorsque vous la recevez, vous devez commencer à expulser les pods. Vous pouvez créer votre propre logique permettant d'écouter la notification de préemption et d'expulser les pods qui s'exécutent sur le nœud. Vous pouvez également, comme dans ce tutoriel, créer un agent à l'aide du gestionnaire d'événements k8s-node-termination-handler.

k8s-node-termination-handler utilise un DaemonSet Kubernetes pour créer un agent sur chaque instance de pvm-pool. Cet agent recherche un événement d'arrêt de nœud à l'aide des API de métadonnées Compute Engine. Chaque fois qu'un événement d'arrêt est observé, l'agent démarre le processus d'expulsion du pod. Dans cet exemple, un délai de grâce de 20 secondes est alloué pour les pods standards et de 10 secondes pour les pods système. Cela signifie que si un hook preStop ou un gestionnaire SIGTERM est configuré pour le pod, celui-ci peut s'exécuter pendant 20 secondes au maximum avant que SIGKILL ne l'arrête. Le délai de grâce est un paramètre configurable dans k8s-node-termination-handler. Le délai de grâce total pour les pods standards et système ne peut pas dépasser le délai de notification de préemption, soit 30 secondes.

L'agent rejette également le nœud pour éviter la planification de nouveaux pods.

Lorsque vous expulsez des pods, le hook preStop est déclenché. Dans cet exemple, preStop est configuré pour effectuer deux tâches :

  • Faire échouer la vérification de l'état de l'application. Cela permet de signaler à l'équilibreur de charge qu'il doit supprimer le pod du chemin de diffusion des requêtes.
  • Être en veille pendant la durée du délai de grâce (20 secondes) allouée par k8s-node-termination-handler. Le pod reste actif pendant 20 secondes pour traiter les requêtes en cours de transfert.

Pendant que le hook preStop s'exécute, pour vous assurer que Kubelet ne soit pas arrêté soudainement par l'OS du nœud, vous devez créer un service "systemd" qui bloque l'arrêt. Cette approche permet de s'assurer que Kubelet peut gérer le cycle de vie des pods lors de l'arrêt sans interférence de l'OS du nœud. Vous utiliserez un DaemonSet Kubernetes pour créer le service. Ce DaemonSet sera exécuté sur chaque instance de pvm-pool.

Cet exemple utilise Traffic Director pour gérer le trafic. Notez que vous pouvez utiliser n'importe quelle solution de proxy, telle que OSS Envoy, Istio, Nginx ou HAProxy, pour gérer le trafic, à condition de respecter les consignes données dans cet exemple concernant le sous-système, où Traffic Director est configuré pour effectuer les opérations suivantes :

  • Activer le routage pondéré des requêtes. Dans cet exemple, vous allez créer trois instances dupliquées d'application sur pvm-pool et une sur default-pool. Traffic Director est configuré pour répartir le trafic (75 %-25 %) entre pvm-pool et default-pool. En cas de préemption, toutes les requêtes basculent automatiquement vers default-pool.

    Ce tutoriel fournit un exemple simplifié de répartition du trafic. Vous pouvez également définir des conditions de correspondance pour les ports de trafic, les champs d'en-tête, les URI, etc. pour acheminer la requête vers un pool de nœuds spécifique. Pour en savoir plus, consultez la section concernant les techniques avancées de routage du trafic à l'aide de Traffic Director.

  • En cas de préemption, si la requête génère un code d'état 5xx (par exemple en raison de l'indisponibilité d'une passerelle ou du dépassement du délai d'une connexion en amont), Traffic Director tente d'exécuter à nouveau la requête jusqu'à trois fois.

  • Utiliser un disjoncteur pour limiter le nombre maximal de nouvelles tentatives pouvant être en attente à tout moment.

  • Utiliser la détection d'anomalies pour expulser les points de terminaison non opérationnels du chemin de diffusion de l'équilibreur de charge.

Traffic Director utilise le modèle de proxy side-car. Dans ce modèle, les événements suivants se produisent :

  1. Les clients envoient des requêtes à un équilibreur de charge géré par Google Cloud.
  2. Cet équilibreur de charge envoie le trafic vers un proxy de périphérie configuré par Traffic Director.
  3. Ce proxy de périphérie applique des règles prédéfinies (telles que la distribution des requêtes entre différents points de terminaison), ainsi que des règles de nouvelle tentative et de rupture de circuit, et équilibre la charge des requêtes vers les services du cluster GKE.
  4. Le proxy side-car intercepte le trafic et le transfère à l'application.

Pour en savoir plus, consultez la section Fonctionnement de l'interception et du transfert du trafic avec Traffic Director.

Objectifs

  • Déployer un cluster GKE avec deux pools de nœuds à l'aide de VM standards et de VM préemptives
  • Déployer un exemple d'application
  • Configurer les ressources du cluster pour gérer correctement la préemption
  • Configurer Traffic Director de manière à contrôler le trafic vers les services GKE
  • Simuler la préemption d'une VM préemptive
  • Vérifier que les pods sont correctement arrêtés
  • Vérifier que les nouvelles requêtes ne sont pas transférées aux pods en cours de préemption
  • Vérifier que les requêtes nouvelles et en cours de transfert sont diffusées sans aucune interruption

Coûts

Ce tutoriel utilise les composants facturables suivants de Google Cloud :

Obtenez une estimation des coûts en fonction de votre utilisation prévue à l'aide du simulateur de coût. Les nouveaux utilisateurs de Google Cloud peuvent bénéficier d'un essai gratuit.

Une fois que vous avez terminé ce tutoriel, vous pouvez éviter de continuer à payer des frais en supprimant les ressources que vous avez créées. Pour en savoir plus, consultez la section Effectuer un nettoyage.

Avant de commencer

  1. Dans Google Cloud Console, accédez à la page de sélection du projet.

    Accéder au sélecteur de projet

  2. Sélectionnez ou créez un projet Google Cloud.

  3. Assurez-vous que la facturation est activée pour votre projet Cloud. Découvrez comment vérifier que la facturation est activée pour votre projet.

  4. Dans Cloud Console, activez Cloud Shell.

    Activer Cloud Shell

    En bas de la fenêtre de Cloud Console, une session Cloud Shell démarre et affiche une invite de ligne de commande. Cloud Shell est un environnement shell dans lequel le SDK Cloud est déjà installé (y compris l'outil de ligne de commande gcloud), et dans lequel des valeurs sont déjà définies pour votre projet actuel. L'initialisation de la session peut prendre quelques secondes.

  5. Activez l'API Compute Engine, GKE, Container Analysis, Cloud Build, Container Registry, and Traffic Director.

    Activer l'API

  6. Recherchez votre ID de projet et définissez-le dans Cloud Shell. Remplacez YOUR_PROJECT_ID par l'ID de votre projet.
    gcloud config set project YOUR_PROJECT_ID
    
  7. Exportez les variables d'environnement suivantes :
    export PROJECT=$(gcloud config get-value project)
    export CLUSTER=$PROJECT-gke
    export REGION="us-central1"
    export ZONE="us-central1-c"
    export TERMINATION_HANDLER="https://github.com/GoogleCloudPlatform/k8s-node-termination-handler"
    

Créer un cluster GKE

  1. Dans Cloud Shell, créez un cluster GKE qui présente un pool de nœuds par défaut (default-pool) avec des instances standards et un pool de nœuds personnalisé (pvm-pool) avec des VM préemptives :

    gcloud beta container clusters create $CLUSTER \
       --zone=$ZONE \
       --num-nodes="1" \
       --enable-ip-alias \
       --machine-type="n1-standard-4" \
       --scopes=https://www.googleapis.com/auth/cloud-platform
    gcloud beta container node-pools create "pvm-pool" \
       --cluster=$CLUSTER \
       --zone=$ZONE \
       --preemptible \
       --machine-type="n1-standard-4" \
       --scopes=https://www.googleapis.com/auth/cloud-platform \
       --num-nodes="1"
    
  2. Clonez le dépôt de code solutions-gke-pvm-preemption-handler que vous allez utiliser pour ce tutoriel :

    git clone https://github.com/GoogleCloudPlatform/solutions-gke-pvm-preemption-handler && \
    cd solutions-gke-pvm-preemption-handler
    
  3. Créez un daemonset qui s'exécute sur les instances de VM préemptives du cluster GKE, et créez un service "systemd" qui bloque l'arrêt du processus Kubelet :

    kubectl apply -f daemonset.yaml
    
  4. Obtenez le nom de l'instance VM préemptive déployée dans le pool de nœuds pvm-pool :

    PVM=$(kubectl get no \
        -o=jsonpath='{range .items[*]} \
        {.metadata.name}{"\n"}{end}' | grep pvm)
    
  5. Vérifiez que le service est correctement déployé :

    1. Créez une règle de pare-feu afin d'utiliser SSH pour vous connecter à la VM préemptive via le transfert IAP :

      gcloud compute firewall-rules create allow-ssh-ingress-from-iap \
          --direction=INGRESS \
          --action=allow \
          --rules=tcp:22 \
          --source-ranges=35.235.240.0/20
      
    2. Utilisez SSH pour vous connecter à la VM préemptive :

      gcloud compute ssh $PVM --tunnel-through-iap --zone=$ZONE
      
    3. Dans le terminal de la VM préemptive, vérifiez l'état du service déployé :

      systemctl status delay.service
      

      L'état du service est Active (exited).

      ...
      delay.service - Delay GKE shutdown
         Loaded: loaded (/etc/systemd/system/delay.service; enabled; vendor preset: disabled)
         Active: active (exited) since Tue 2020-07-21 04:48:33 UTC; 1h 17min ago
      ...
      
    4. Quittez le terminal de la VM préemptive en saisissant exit.

  6. Déployez k8s-node-termination-handler :

    1. Clonez le dépôt :

      git clone $TERMINATION_HANDLER
      
    2. Dans un éditeur de texte, ouvrez le fichier k8s-node-termination-handler/deploy/k8s.yaml et recherchez la ligne suivante :

      args: ["--logtostderr", "--exclude-pods=$(POD_NAME):$(POD_NAMESPACE)", "-v=10", "--taint=cloud.google.com/impending-node-termination::NoSchedule"]
      
    3. Remplacez la ligne précédente par la ligne ci-dessous, qui attribue un délai de grâce de 10 secondes pour arrêter les pods système. Les 20 secondes restantes du délai de grâce sont automatiquement allouées aux pods standards.

      args: ["--logtostderr", "--exclude-pods=$(POD_NAME):$(POD_NAMESPACE)", "-v=10", "--taint=cloud.google.com/impending-node-termination::NoSchedule", "--system-pod-grace-period=10s"]
      
    4. Déployez le gestionnaire :

      kubectl apply \
          -f k8s-node-termination-handler/deploy/k8s.yaml \
          -f k8s-node-termination-handler/deploy/rbac.yaml
      
  7. Vérifiez que le gestionnaire d'arrêt des nœuds a bien été déployé :

    kubectl get ds node-termination-handler -n kube-system
    

    Le résultat ressemble à ce qui suit :

    NAME                       DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
    node-termination-handler   1         1         1       1            1           <none>          101s
    

Déployer l'application

  1. Dans Cloud Shell, configurez les règles de pare-feu pour les vérifications de l'état :

    gcloud compute firewall-rules create fw-allow-health-checks \
        --action=ALLOW \
        --direction=INGRESS \
        --source-ranges=35.191.0.0/16,130.211.0.0/22 \
        --rules tcp
    
  2. Pour déployer l'application, procédez comme suit :

    kubectl apply -f deploy.yaml
    
  3. Vérifiez que deux déploiements différents sont exécutés sur default-pool et pvm-pool :

    1. Pour default-pool, exécutez la commande suivante :

      kubectl get po -l app=web-std \
          -o=custom-columns=NAME:.metadata.name,Node:.spec.nodeName
      

      Le résultat ressemble à ce qui suit :

      NAME                       Node
      web-std-695b5fb6c4-55gcc   gke-vital-octagon-109612-default-pool-dcdb8fe5-2tc7
      
    2. Pour pvm-pool, exécutez la commande suivante :

      kubectl get po -l app=web-pvm \
          -o=custom-columns=NAME:.metadata.name,Node:.spec.nodeName
      

      Le résultat renvoyé ressemble à ceci :

      NAME                       Node
      web-pvm-6f867bfc54-nm6fb   gke-vital-octagon-109612-gke-pvm-pool-664ec4ff-2cgc
      

      Un NEG autonome est créé pour chaque service. Il contient les points de terminaison correspondant aux adresses IP et aux ports du pod. Pour en savoir plus et obtenir des exemples, consultez la section Groupes de points de terminaison du réseau autonomes.

  4. Vérifiez que le NEG autonome a été créé :

    gcloud beta compute network-endpoint-groups list
    

    Le résultat renvoyé ressemble à ceci :

    NAME                                       LOCATION       ENDPOINT_TYPE   SIZE
    k8s1-be35f81e-default-web-pvm-80-7c99357f  us-central1-c  GCE_VM_IP_PORT  1
    k8s1-be35f81e-default-web-std-80-f16dfcec  us-central1-c  GCE_VM_IP_PORT  1
    

    Pour gérer l'application à l'aide de Traffic Director, vous devez effectuer les déploiements en tant que groupes de points de terminaison du réseau (NEG, network endpoint group). Comme indiqué dans la section Architecture, vous allez créer les déploiements à l'aide d'un proxy side-car.

  5. Enfin, vérifiez que les déploiements sont créés avec un proxy side-car :

    kubectl get pods -l app=web-std \
        -o jsonpath={.items[*].spec.containers[*].name}
    

    Le résultat renvoyé ressemble à ceci :

    hello-app istio-proxy
    

    Pour afficher des résultats semblables pour les pods qui s'exécutent dans pvm-pool, vous pouvez exécuter la commande suivante :

    kubectl get pods -l app=web-pvm \
        -o jsonpath={.items[*].spec.containers[*].name}
    

Créer le service Traffic Director

Traffic Director utilise une configuration semblable à d'autres produits Cloud Load Balancing. En d'autres termes, vous devez configurer les composants suivants pour Traffic Director :

  1. Dans Cloud Shell, recherchez les NEG que vous avez créés précédemment et stockez leurs noms dans une variable :

    1. Pour le service default-pool, utilisez les éléments suivants :

      NEG_NAME_STD=$(gcloud beta compute network-endpoint-groups list \
                     | grep web-std | awk '{print $1}')
      
    2. Pour le service pvm-pool, utilisez les éléments suivants :

      NEG_NAME_PVM=$(gcloud beta compute network-endpoint-groups list \
                     | grep web-pvm | awk '{print $1}')
      
  2. Créez la vérification de l'état :

      gcloud compute health-checks create http td-gke-health-check \
          --request-path=/health \
          --use-serving-port \
          --healthy-threshold=1 \
          --unhealthy-threshold=2 \
          --check-interval=2s \
          --timeout=2s
    
  3. Remplacez les espaces réservés dans les fichiers manifestes :

    sed -i -e "s/\[PROJECT_ID\]/$PROJECT/g" td-gke-service-config.yaml
    sed -i -e "s/\[ZONE\]/$ZONE/g" td-gke-service-config.yaml
    sed -i -e "s/\[NEG_NAME_STD\]/$NEG_NAME_STD/g" td-gke-service-config.yaml
    sed -i -e "s/\[NEG_NAME_PVM\]/$NEG_NAME_PVM/g" td-gke-service-config.yaml
    sed -i -e "s/\[PROJECT_ID\]/$PROJECT/g" td-urlmap.yaml
    
  4. Créez un service Traffic Director :

    gcloud compute backend-services import td-gke-service \
        --source=td-gke-service-config.yaml --global
    

    Le service est configuré pour répartir le trafic entre les deux NEG que vous avez créés précédemment à l'aide d'un scaler de capacité. Ce service est également configuré avec la détection des anomalies :

    • Pour la détection des anomalies, définissez le paramètre Erreurs consécutives (ou les échecs de passerelle avant qu'un hôte ne soit expulsé du service) sur 2.
    • Pour les ruptures de circuit, définissez le paramètre Nombre maximal de nouvelles tentatives sur 3.
  5. Vérifiez que le service Traffic Director est correctement déployé :

    gcloud compute backend-services get-health td-gke-service --global
    

    Le résultat ressemble à ce qui suit :

    ‐‐‐
    backend: default-pool-service-NEG
    status:
      healthStatus:
      ‐ healthState: HEALTHY
    ...
    ‐‐‐
    backend: pvm-pool-service-NEG
    status:
      healthStatus:
      ‐ healthState: HEALTHY
    ...
    

    Vous devrez peut-être attendre quelques minutes et exécuter la commande plusieurs fois avant que le backend n'affiche un état opérationnel (HEALTHY).

  6. Créez un mappage d'URL qui utilise le service que vous avez créé :

    gcloud compute url-maps import web-service-urlmap \
        --source=td-urlmap.yaml
    

    Le mappage d'URL permet de configurer le routage du trafic. Toutes les requêtes vers le chemin "/*" sont redirigées vers le service Traffic Director que vous avez créé. En outre, le mappage configure également une stratégie permettant de relancer les requêtes ayant entraîné un code d'état 5xx (3 fois au maximum).

  7. Créez le proxy HTTP cible :

    gcloud compute target-http-proxies create td-gke-proxy \
        --url-map=web-service-urlmap
    
  8. Créez la règle de transfert qui utilise l'adresse IP virtuelle "0.0.0.0" :

    gcloud compute forwarding-rules create td-gke-forwarding-rule \
        --global \
        --load-balancing-scheme=INTERNAL_SELF_MANAGED \
        --address=0.0.0.0 \
        --target-http-proxy=td-gke-proxy \
        --ports=80
    

    À ce stade, les services GKE de default-pool et pvm-pool sont accessibles sur l'adresse IP virtuelle de service dont l'équilibrage de charge est effectué par Traffic Director.

Créer l'équilibreur de charge

Dans cette section, vous allez configurer un équilibreur de charge et un proxy de périphérie pour le trafic utilisateur. L'équilibreur de charge fait office de passerelle vers la configuration que vous venez de créer.

  1. Dans Cloud Shell, créez un proxy de périphérie géré par Traffic Director :

    kubectl apply -f edge-proxy.yaml
    
  2. Vérifiez que l'équilibreur de charge est opérationnel et prêt à diffuser le trafic :

    kubectl describe ingress gateway-proxy-ingress
    

    Le résultat ressemble à ce qui suit :

    ...
      Host        Path  Backends
      ‐‐‐‐        ‐‐‐‐  ‐‐‐‐‐‐‐‐
      *           *     gateway-proxy-svc:80 (10.20.0.14:80)
    
    Annotations:  ingress.kubernetes.io/backends: {"k8s1-da0dd12b-default-gateway-proxy-svc-80-b3b7b808":"HEALTHY"}
    ...
    

    L'état du backend doit être HEALTHY. Il peut s'écouler plusieurs minutes, et plusieurs tentatives d'exécution de la commande peuvent être nécessaires, avant que l'équilibreur de charge ne soit prêt à accepter le trafic.

  3. Enregistrez l'adresse IP pour une utilisation ultérieure :

    IPAddress=$(kubectl get ingress gateway-proxy-ingress \
                -o jsonpath="{.status.loadBalancer.ingress[*].ip}")
    

Générer du trafic

Maintenant que la configuration est terminée, il vous faut la tester.

  1. Dans Cloud Shell, cliquez sur Ouvrir un nouvel onglet + pour démarrer une nouvelle session Cloud Shell.

  2. Dans la nouvelle interface système, définissez l'ID du projet :

    gcloud config set project YOUR_PROJECT_ID
    
  3. Installez Kubetail pour observer plusieurs journaux de pod d'application à la fois :

    sudo apt-get update
    sudo apt-get install kubetail
    kubetail web
    
  4. Dans l'interface système d'origine, simulez le trafic :

    seq 1 100 | xargs -I{} -n 1 -P 10 curl -I http://$IPAddress
    

    Cette commande génère 100 requêtes, 10 requêtes parallèles à la fois.

    Dans la nouvelle interface système, des journaux semblables à ce qui suit s'affichent :

    ---
    [web-pvm-6f867bfc54-nm6fb hello-app] Received request at: 2020-07-20 20:26:23.393
    [web-pvm-6f867bfc54-nm6fb hello-app] Received request at: 2020-07-20 20:26:23.399
    [web-std-6f867bfc54-55gcc hello-app] Received request at: 2020-07-20 20:26:24.001
    ...
    

    Vérifiez que les requêtes sont réparties entre les pools de nœuds selon la répartition que vous avez configurée précédemment (75 %-25 %).

  5. Revenez à l'interface système d'origine, puis générez du trafic à l'aide de httperf :

    sudo apt-get install httperf && \
    httperf --server=$IPAddress --port=80 --uri=/ \
            --num-conns=5000 --rate=20 --num-calls=1 \
            --print-reply=header
    

    Cette commande installe httperf et génère 5 000 requêtes sur l'exemple d'application à une fréquence de 20 requêtes par seconde. Ce test s'exécute pendant environ 250 secondes.

Simuler la préemption

  1. Dans la nouvelle interface système, quittez la commande Kubetail en appuyant sur Ctrl+C.
  2. Obtenez le nom de l'instance de VM préemptive déployée dans pvm-pool :

    PVM=$(kubectl get no \
          -o=jsonpath='{range .items[*]} \
          {.metadata.name}{"\n"}{end}' | grep pvm)
    
  3. Pendant que le test httperf est en cours, déclenchez un événement de maintenance sur l'instance de VM préemptive pour simuler la préemption :

    gcloud compute instances simulate-maintenance-event $PVM \
        --zone=us-central1-c
    
  4. Observez les journaux d'application dans la nouvelle interface système :

    kubectl logs -f deploy/web-pvm -c hello-app
    

    Lorsque le pod reçoit un signal SIGTERM, l'état de la vérification de l'état de l'application indique explicitement fail :

    ...
    Health status fail at: 2020-07-21 04:45:43.742
    ...
    

    Le pod continue de recevoir des requêtes jusqu'à ce qu'il soit supprimé du chemin de diffusion des requêtes en raison de l'échec des vérifications de l'état. La propagation de cette suppression peut prendre quelques secondes.

    ...
    Received request at: 2020-07-21 04:45:45.735
    Received request at: 2020-07-21 04:45:45.743
    Health status fail at: 2020-07-21 04:45:45.766
    ...
    

    Au bout de quelques secondes, le pod cesse de recevoir de nouvelles requêtes. Il reste actif pendant 20 secondes (le gestionnaire preStop est mis en veille pendant 20 secondes pour permettre les activités de nettoyage, y compris les requêtes en cours de transfert). Le pod est alors arrêté.

    ...
    Health status fail at: 2020-07-21 04:46:01.796
    2020-07-21 04:46:02.303  INFO 1 --- [       Thread-3] ConfigServletWebServerApplicationContext : Closing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@27ddd392: startup date [Tue Jul 21 04:39:44 UTC 2020]; root of context hierarchy
    Exiting PreStop hook
    ...
    
  5. Vous pouvez également exécuter la commande kubetail web pour suivre les journaux de tous les pods d'application exécutés sur les deux pools de nœuds. La sortie affiche les requêtes acheminées vers default-pool tandis que la VM préemptive est en cours de préemption :

    ...
    [web-pvm-6f867bfc54-nm6fb] Received request at: 2020-07-20 20:45:45.743
    [web-pvm-6f867bfc54-nm6fb] Health status fail at: 2020-07-21 04:45:45.766
    [web-std-6f867bfc54-55gcc] Received request at: 2020-07-20 04:45:45.780
    [web-std-6f867bfc54-55gcc] Received request at: 2020-07-20 04:45:45.782
    ...
    

Validations à la suite de la préemption

  1. Dans l'interface système d'origine, attendez la fin du test Httperf. À la fin de l'opération, le résultat ressemble à ce qui suit :

    ...
    Reply status: 1xx=0 2xx=5000 3xx=0 4xx=0 5xx=0
    ...
    Errors: total 0 client-timo 0 socket-timo 0 connrefused 0 connreset 0
    ...
    

    Le résultat indique qu'à la suite de la préemption, les requêtes ont été diffusées par default-pool, et que pvm-pool a été arrêté normalement.

  2. Vérifiez que la VM préemptive est à nouveau opérationnelle et que l'état d'origine du cluster est restauré :

    kubectl get po -l app=web-pvm \
        -o=custom-columns=NAME:.metadata.name,Node:.spec.nodeName
    

    Notez que deux instances dupliquées sont provisionnées sur pvm-pool :

    NAME                     Node
    web-pvm-6f867bfc54-9z2cp gke-vital-octagon-109612-gke-pvm-pool-664ec4ff-49lx
    

Remarques

Avant d'exécuter cette solution en production, tenez compte des éléments suivants :

  1. Ce tutoriel n'utilise pas de règles d'autoscaling des pods ou des clusters. Pour les environnements de production, assurez-vous de disposer d'un autoscaling approprié pour gérer les pics de trafic.
  2. Examinez attentivement la répartition entre les VM standards et les VM préemptives du cluster. Par exemple, supposons que vous exécutiez 100 VM préemptives et une seule VM standard, et que 50 % des VM préemptives subissent une préemption en même temps. Dans ce cas, le scaling horizontal du pool de VM standards prend un certain temps, car il doit compenser les ressources préemptées. En attendant, le trafic utilisateur est affecté. Afin de minimiser les préemptions à grande échelle, vous pouvez utiliser des applications tierces, telles que Spot et estafette-gke-preemptible-killer. Celles-ci peuvent vous permettre de répartir les préemptions pour éviter que plusieurs instances ne soient indisponibles en même temps.
  3. En fonction de votre cas d'utilisation, testez soigneusement le délai de grâce alloué aux pods standards et système à l'aide de k8s-node-termination-handler. Compte tenu de la criticité de l'application, vous devrez peut-être allouer plus de 20 secondes aux pods standards. Cette approche peut avoir l'inconvénient de ne pas laisser assez de temps aux pods système pour s'arrêter correctement. Cela peut entraîner une perte des journaux et des métriques de surveillance qui sont gérés par les pods système.

Nettoyer

Pour éviter que les ressources utilisées dans ce tutoriel ne soient facturées sur votre compte Google Cloud, vous pouvez supprimer le projet Cloud que vous avez créé pour ce tutoriel ou supprimer les ressources associées.

Supprimer le projet Cloud

Le moyen le plus simple d'empêcher la facturation est de supprimer le projet que vous avez créé pour ce tutoriel.

  1. Dans Cloud Console, accédez à la page Gérer les ressources.

    Accéder à la page Gérer les ressources

  2. Dans la liste des projets, sélectionnez le projet que vous souhaitez supprimer, puis cliquez sur Supprimer.
  3. Dans la boîte de dialogue, saisissez l'ID du projet, puis cliquez sur Arrêter pour supprimer le projet.

Supprimer les ressources

Si vous souhaitez conserver le projet que vous avez utilisé dans ce tutoriel, supprimez les différentes ressources.

  1. Dans Cloud Shell, supprimez le cluster GKE :

    gcloud container clusters delete $CLUSTER --zone=$ZONE --async
    
  2. Supprimez les règles de pare-feu :

    gcloud compute firewall-rules delete allow-ssh-ingress-from-iap && \
    gcloud compute firewall-rules delete fw-allow-health-checks
    
  3. Supprimez le service Traffic Director :

    gcloud compute forwarding-rules delete td-gke-forwarding-rule \
        --global && \
    gcloud compute target-http-proxies delete td-gke-proxy \
        --global && \
    gcloud compute url-maps delete web-service-urlmap \
        --global && \
    gcloud compute backend-services delete td-gke-service \
        --global && \
    gcloud compute health-checks delete td-gke-health-check \
        --global
    
  4. Supprimez tous les NEG :

    NEG_NAME_EDGE=$(gcloud beta compute network-endpoint-groups list \
                    | grep gateway-proxy | awk '{print $1}') && \
    gcloud beta compute network-endpoint-groups delete $NEG_NAME_EDGE \
        --zone=$ZONE && \
    gcloud beta compute network-endpoint-groups delete $NEG_NAME_STD \
        --zone=$ZONE && \
    gcloud beta compute network-endpoint-groups delete $NEG_NAME_PVM \
        --zone=$ZONE
    
  5. Supprimez le code, les artefacts et les autres dépendances que vous avez téléchargés :

    cd .. && rm -rf solutions-gke-pvm-preemption-handler
    

Étape suivante