Crafteo - Formation Kubernetes

Bienvenue sur la page d'exercice Kubernetes ! Utiliser le menu pour accéder aux différents modules.

Plus d'infos sur crafteo.io

Notre premier Pod !

Les Pods et la plupart des objets Kubernetes sont par crées dans un Namespace. Un Namespace est une sorte d'espace indépendant où notre Pod sera créé dans le cluster Kubernetes pour éviter les conflits avec d'autres objets :

kubectl create namespace <your_ns>

Ensuite, créez un Pod dans notre namespace (l'option -n spécifie le namespace à utiliser) :

kubectl -n <your_ns> run mypod --image us-docker.pkg.dev/google-samples/containers/gke/whereami:v1.2.21 --port 8080

C'est l'équivalent de docker run pour Kubernetes.

Pour accéder à votre Pod de l'extérieur, utilisez:

kubectl -n <your_ns> port-forward pod/mypod --address 0.0.0.0 8081:8080
  • port-forward créé un tunnel réseau directement depuis la machine depuis laquelle kubectl est lancé vers le container dans le cluster Kubernetes. Un équivalent de docker run -p 8081:80 [...]. - --address permet de binder l'écoute du port sur toutes les adresses (localhost) est utilisé par defaut pour permettre une connection à distance sur votre machine de TP

Votre Pod est maintenant accessible sur le port 8081. Essayez d'y accéder localement ou via un navigateur :

curl YOU.training.crafteo.io:8081

En réalité, kubectl run est rarement utilisé, sauf pour le debug. La plupart du temps, on utilise des manifests ou configurations YAML pour décrire les objets Kubernetes.

Créer un Pod en utilisant un manifest YAML avec

kubectl -n <your_ns> apply -f intro/pod.yml

Maintenant, utiliser les commandes kubectl pour :

  • Lister tous les Pods
  • Faire un port-forward du pod créé via intro/pod.yml
  • Supprimer les deux Pods avec kubectl delete

Par défaut, kubectl récupère les Pods (et autres objets) dans le namespace default. Vous pouvez changer ce comportement avec :

kubectl config set-context --current --namespace pierre

Deployment : gérer plusieurs Pods

Un Deployment gère un ensemble de Pods.

  • Créer un Deployment avec kubectl à partir du manifest intro/deployment.yml.
    • Utiliser kubectl --help, un moteur de recherche ou une IA si besoin
    • La commande ressemble à kubectl apply [...]
  • Lister les Deployments avec kubectl get
  • Redémarrer le Pod créé par notre Deployment
    • Il n'y a pas de commande restart, trouver une autre méthode
  • Mettre à jour votre Deployment pour avoir 3 replicas de notre Pod

Service: diriger les flux réseaux vers nos Pods

Manager des Services avec des manifests et accéder à un Pod via un Service:

  • Créer un Service avec kubectl en utilisant le manifest intro/service.yml
    • Rappel : kubectl apply -f ...
  • Utiliser kubectl port-forward pour exposer le service localement et tester l'accès
    • Le port forwarding va rediriger un port local vers votre Pod. Utilisez curl localhost:PORT ou équivalent.
    • kubectl port-forward --help ou consulter la documentation officielle
  • Comment un Service identifie-t-il les Pods à servir ?

  • Supprimer Pods, Deployment et Service avec une seule commande kubectl
  • Ré-appliquer notre Deployment, Service et Pod avec une seule commande

logs, exec et autres opérations

De nombreuses commandes kubectl sont l'équivalent direct de la CLI docker / podman sur Kubernetes.

  • Afficher les logs d'un de vos Pods en cours d'exécution
    • Équivalent de docker logs
    • Vous pouvez aussi cibler un Deployment ou un Service
  • Lancer un shell sh dans le conteneur d'un Pod
    • Équivalent de docker exec -it [container] sh
    • Depuis votre nouveau shell, essayez d'atteindre un autre Pod via son le nom de domaine associé à son Service (ex : curl <service>.<namespace>.svc.cluster.local) en spécifiant le port approprié.
  • Afficher un Deployment (ou autre object) au format YAML
    • Utilisez une option de kubectl get
  • Décrire un Deployment
    • Équivalent de docker inspect

Deployments

Modifier le nombre de replicas

  • Lister les Deployments avec kubectl. Identifier le ReplicaSet associé au Deployment Vote.
  • Mettre à jour le Deployment Vote à 3 replicas. Observer l'état du ReplicaSet.
    • Appliquer directement ou utiliser kubectl edit
  • Repasser le Deployment Vote à 1 replica. Observer l'état du ReplicaSet.
  • Mettre à jour l'image du Deployment Vote avec un tag inexistant. Observer l'état du ReplicaSet.

Deployment et Pods

Comment un Deployment identifie-t-il les Pods qu'il manage ?

Rollout et rollback d'un Deployment

Mettre à jour l'image du Deployment Vote avec une image inexistante (provoquer un échec volontairement).

  • Utiliser kubectl rollout status pour observer le déploiement en cours.
  • Observer l'état du ReplicaSet.
  • Utiliser une autre commande de kubectl rollout pour rollback le déploiement en échec.

Stratégie

Par défaut, la stratégie de mise à jour d'un Deployment est RollingUpdate.

  • Trouver d'autres stratégies de mise à jour pour les Deployments
  • Mettre à jour le Deployment Vote pour utiliser cette stratégie et la tester

Jobs et CronJobs

Utiliser un CronJob pour voter chaque minute (oui, c'est de la triche)

Déployer un CronJob

  • Déployer le CronJob resources/cronjob.yml
  • Analyser le contenu du template CronJob. Pourquoi spec est-il spécifié plusieurs fois ?
  • Quand sera executé le CronJob ?

Parallélisme

Mettre à jour le CronJob pour lancer 3 instances de Jobs au lieu d'une seule lors du trigger du CronJob.

Déclencher un Job manuellement à la demande

Lancer une commande kubectl pour déclencher manuellement un Job à partir du CronJob sans attendre la prochaine exécution planifiée.

  • Utiliser kubectl create [...]

DaemonSets

StatefulSets

Déployer une base de données Postgres en StatefulSet avec la commande :

helm install my-postgres oci://registry-1.docker.io/bitnamicharts/postgresql -f resources/postgres-sts-values.yml

Cette commande crée plusieurs StatefulSets. Note : Helm est un package manager pour Kubernetes, l'équivalent de apt ou yum pour Linux. On reviendra sur Helm plus tard.

  • Utiliser kubectl pour décrire les Pods et PersistentVolumes. Observer le nommage des pods.
  • Supprimer un Pod et observer le résultat.
  • Supprimer le release Helm Postgres avec helm delete my-postgres. Que se passe-t-il pour les volumes ?

NodePort

L'application Example Voting App propose plusieurs services pour accéder à chaque composant.

  • Modifier les services vote et result pour utiliser le type de service NodePort:
    spec:
      type: NodePort
      ports:
      - name: "result-service"
        port: 5001
        targetPort: 80
        # Attention aux conflits de ports !
        # Comme tout le monde est sur le même cluster,
        # chacun doit utiliser des ports différents.
        # Utiliser un port entre 31000 et 36000
        nodePort: 31001
    
  • Accéder à Vote et Result via un navigateur web en utilisant l'adresse IP publique ou le nom d'hôte d'un Node.
    • Utiliser kubectl get node et kubectl describe node <node-name> pour identifier l'IP publique des Nodes
    • Même si le port Node est ouvert et à l'écoute, il faut aussi des règles réseau et firewall pour accepter le trafic entrant
  • À quoi correspondent port, targetPort et nodePort ?

LoadBalancer

Un service LoadBalancer va automatiquement provisionner un Load Balancer:

  • dans le Provider Cloud correspondant à notre cluster si il est déployé dans le Cloud
  • Directement via des configurations internes pour un cluster on-prem

Configurer un Service de type Load Balancer:

  • Modifier le service Vote pour qu'il soit de type LoadBalancer
    • Optionnel: retirer nodePort: xxx de la définition du Service qui n'est plus nécéssaire
  • Observer le changement de comportement du service
    • Utiliser kubectl describe|get -o yaml pour observer le nouveau status
  • Repasser le type à ClusterIP et observer la suppression du Cloud Load Balancer - Bien faire cette étape car les Load Balancers coûtent $$$, merci :-)

Ingresses

Déployer un Ingress avec la configuration Vote :

kubectl apply -f resources/ingress.yml
  • Comment l'Ingress fait-il le lien entre le nom de domaine vote.<YOUR_NAME>.k8s.crafteo.io et le Service Vote ?
  • Ajouter une configuration similaire pour result.<YOUR_NAME>.k8s.crafteo.io et le Service Result

Ingress Controller

Un Ingress ne fonctionne pas tout seul – il a besoin d'un Ingress Controller.

Traefik est actuellement déployé et agit comme Ingress Controller:

  • Identifier le service LoadBalancer utilisé par Traefik
  • Est-il possible de déployer plusieurs Ingress Controllers ?

ExternalName Services

Créer un service ExternalName pointant vers google.com:

kubectl apply -f resources/externalname-service.yml 
  • Lancer un shell dans le conteneur Vote avec kubectl exec
  • Essayer de curl https://external-test et observer le résultat

Concepts avancés sur les Services

Endpoints et EndpointSlices

Les Services utilisent des EndpointSlices (et Endpoints) en interne pour rediriger le trafic.

  • Scaler le déploiement Vote à 3 replicas
  • Identifier les EndpointSlices et Endpoints créés pour le service Vote

Historiquement, les Endpoints étaient utilisés pour lier les Services aux Pods. Aujourd'hui les EndpointSlices fournissent une API plus complète et remplaceront progressivement les Endpoints. Voir la doc Kubernetes pour plus de détails

Headless Services

Les Headless Services sont utilisés quand le load balancing n'est pas nécessaire. Les Headless services n'ont pas d'IP.

En utilisant des selectors, une requête DNS interne retournera directement toutes les IPs des pods existants.

  • Scaler les déploiements Vote et Service à 3 replicas
  • Supprimer le service Vote
  • Mettre à jour le template du service Vote avec spec.clusterIP: "None"
  • Re-déployer le service Vote
  • Lancer un shell dans le Pod Vote et observer le comportement DNS entre les services vote et result.
    • Lancer un shell dans un container kubectl exec -it ... sh et ces commandes pour installer les outils DNS et checker la résolution:
# Installer des outils DNS à la volée pour tester
apt update && apt install dns-utils

nslookup vote
nslookup result

Exposition des services dans les Pods

Les services sont exposés aux Pods via des variables d'environnement.

  • Lancer un shell dans le pod Vote et explorer les services disponibles
    • Utiliser env pour afficher toutes les variables d'environnement, et env | grep pour filtrer
  • Trouver les variables d'environnement du service Result

L'approche la plus courante est d'utiliser les enregistrements DNS pointant vers les Services.

ConfigMap

Les ConfigMap sont utilisées pour monter des configurations ou des variables d'environnement dans les containers.

Variables d'environnement

Le déploiement Database définit des variables d'environnement pour l'utilisateur et le mot de passe Postgres. Remplacer ces variables par celles définies dans la ConfigMap resources/config/configmap-postgres-env.yml

  • Créer la ConfigMap avec kubectl apply
  • Mettre à jour le Deployment pour utiliser la ConfigMap afin de charger les variables d'environnement. Utiliser quelque chose comme ceci dans le template du Pod :
    envFrom:
    - configMapRef:
        name: db-env

Fichiers de configs

Utiliser la ConfigMap resources/config/configmap-postgres-config.yml pour monter une configuration Postgres personnalisée dans le container à /etc/postgresql/postgresql.conf

  • Équivalent de docker run -v "$PWD/my-postgres.conf":/etc/postgresql/postgresql.conf

Utiliser quelque chose comme ceci dans le spec du Pod :

    spec:
      # [...]
      containers:
      - name: postgres
        # [...]
        # Utiliser un fichier de config personnalisé
        args: [ "-c", "config_file=/etc/postgresql/postgresql.conf"]
        # Monter la config personnalisée dans le container
        volumeMounts:
        - mountPath: /etc/postgresql
          name: config-vol
      volumes:
      - name: config-vol
        configMap:
          name: db-config

Secret

Les Secrets sont similaires aux ConfigMaps, sauf qu'ils sont (théoriquement) chiffrés dans le Control Plane. Leur accès et utilisation doivent être protégés avec une autorisation appropriée dans le cluster.

Les Secrets nécessitent que leur configuration soit encodée en Base64. Utiliser une commande pour encoder une chaîne en Base64 :

echo -n "mypassword" | base64

Note : Base64 n'est PAS un chiffrement. Il ne doit pas être utilisé pour protéger un secret. Base64 est utilisé car les secrets peuvent contenir des données binaires et leur représentation base64 peut être utilisée comme chaîne de caractères.

Utiliser une chaîne en clair dans un Secret ?

Par défaut, les valeurs des secrets doivent être encodées en base64, ex :

data:
    # "secretPassword" en base 64
    password: c2VjcmV0UGFzc3dvcmQ=

Trouver un moyen d'utiliser des chaînes en clair au lieu de données encodées en base64 dans un manifest YAML Secret.

Variables d'environnement depuis un Secret

Utiliser le Secret resources/config/secret-postgres-env.yml pour définir des variables d'environnement dans le Deployment Postgres

Fichiers depuis un Secret

Copier la ConfigMap resources/config/configmap-postgres-config.yml et la transformer en Secret pour monter une configuration Postgres personnalisée dans le container à /etc/postgresql/postgresql.conf

Persistent Volume Claims

Un Persistent Volume Claim (PVC) est une demande d'espace de stockage. En créant un PVC, tu indiques au cluster que tu as besoin d'un espace de stockage et le cluster va essayer de le fournir.

  • Créer un PVC à partir de resources/volumes/pvc.yml
  • Mettre à jour le déploiement Database pour attacher le PVC créé:
apiVersion: apps/v1
kind: Deployment
# [...]
spec:
  template:
    spec:
      containers:
      - name: postgres
        # [...]
        env:
        # postgres requiert que le dossier où les données sont créées soit vide
        # Utiliser le chemin par défaut /var/lib/postgresql/data provoquerait une erreur
        # car il contient un dossier "lost.found", empêchant le serveur de s'initialiser
        # Utiliser cette astuce pour stocker les données dans un sous-dossier du volume créé
        - name: PGDATA
          value: "/var/lib/postgresql/data/pg"
          
        volumeMounts:
        - mountPath: /var/lib/postgresql/data
          name: db-data
      volumes:
      - name: db-data
        persistentVolumeClaim:
          claimName: db-data-pvc # Le nom doit correspondre au nom du PVC
  • Trouver le Persistent Volume (PV) créé suite à ta demande de stockage via le PVC
  • Détruire et recréer le déploiement Database, vérifier que le PVC est resté intact

emptyDir volume

emptyDir peut être utilisé dans différentes situations, comme partager des données entre les containers d'un même Pod. Par exemple pour créer une sauvegarde d'une base de données:

  • Configurer un Pod avec 2 containers, postgres (dump database) et aws (upload de données)
  • Partager un volume /backup entre les 2 containers d'un même Pod pour "passer" le dump de postgres à aws

Un CronJob de backup est présent dans resources/volumes/cronjob.yml mais il manque la configuration des Volumes. Adapter le template pour :

  • Déclarer un volume emptyDir (utiliser volumes: au bon endroit)
    • Le volume doit être déclaré au niveau du Pod puis un point de montage effectué au niveau de chaque container
  • Monter le volume sur le path /backup pour les deux containers
  • Créer le CronJob et le déclencher (créer un Job à partir du CronJob) avec
    kubectl create job --from=cronjob/postgres-backup manual-backup
    
  • Observer le résultat

Helm : package manager pour Kubernetes

Helm est un "package manager" pour Kubernetes. Il utilise des templates YAML pour gérer les ressources Kubernetes et permet de partager des "Charts" publiques ou privées.

On trouve des charts Helm sur Artifact Hub ou via n'importe quel moteur de recherche.

Exemple d'utilisation :

helm --help

# Avant d'installer un chart, il faut souvent ajouter un Repository
# Ajouter le repository "bitnami"
helm repo add bitnami https://charts.bitnami.com/bitnami

# On peut aussi chercher un chart Helm
# Chercher les charts "redis"
helm search repo redis

# Installer un chart Redis
# 'myredis' est le nom du Release (comme un nom de container pour une image)
# 'bitnami/redis' est le chart à installer
helm install myredis bitnami/redis

Ajouter un repository n'est pas toujours nécessaire, de nombreuses charts sont aujourd'hui installables via OCI comme:

helm install my-redis oci://registry-1.docker.io/bitnamicharts/redis

Installer un chart Wordpress

Trouver un chart Wordpress et l'installer. Essayer d'y accéder en externe avec un port-forward.

  • Wordpress est un système de blog avec une base de données. Il fournit un frontend simple à atteindre depuis internet.
  • Chercher un chart Wordpress par n'importe quelle méthode

Une fois terminé, désinstaller le release du chart Wordpress

Helm avancé: écrire un chart Helm

Le dossier resources/helm/example-voting-app contient un chart Helm avec des manifests YAML templatisés.

Déploiement, mise à jour et values

Utiliser la commande helm install pour déployer un release du chart Example Voting App.

  • Vérifier le fonctionnement via un port-forward
  • Explorer les templates YAML pour comprendre le mécanisme de templating et le lien avec values.yml

Il est possible d'override values.yml avec des fichiers de configuration externes, typiquement par environnement.

  • Mettre à jour le release pour surcharger les valeurs par défaut avec resources/helm/values/dev.yml

Gestion des Secrets

Helm ne fournit pas de mécanisme natif pour gérer les secrets. Un pattern courant est de référencer un secret externe (non géré par le chart).

Mettre à jour le release Helm en utilisant les valeurs de resources/helm/values/prod.yml.

  • Cette configuration référence un secret externe, il faut le créer soi-même
  • Explorer le contenu du chart pour comprendre le mécanisme sous-jacent

Il existe aussi des plugins Helm permettant la gestion des secrets comme helm-secrets

Kustomize

Kustomize est un outil intégré à kubectl pour du déploiement multi-environnements et la gestion de configuration.

Voir la documentation officielle de Kustomize et la documentation de référence

Kustomize Example Voting App

Un déploiement d'application avec Kustomize nécessite:

  • Une base avec la config YAML (Deployment, Services, etc.) et un kustomization.yml listant les ressources et options souhaitées
  • Un ou plusieurs overrides (typiquement par environnement, mais pas forcément)

Pour Kustomizer Example Voting App :

  • Copier resources/kustomize/base-kustomization.yml dans base/kustomization.yml. Ce fichier référence toutes les ressources de l'Example Voting App.
  • Utiliser l'un des dossiers resources/kustomize/dev ou resources/kustomize/prod avec kubectl. Chacun référence sa base via un chemin relatif, ex : resources: [ "../../../base" ]
#
# Attention au -k
#
# Comme kubectl apply -f mais va lire depuis dev et toutes les ressources référencées (y compris la base)
kubectl apply -n <YOU> -k resources/kustomize/dev

Observer le résultat selon le contenu des fichiers kustomization.yml de dev et base, en particulier commonLabels

Définir le namespace

Créer un namespace avec votre nom préfixé par -kustomize, ex : YOU-kustomize.

Mettre à jour le kustomization.yml de dev pour définir le namespace YOU-kustomize et appliquer sans flag -n xxx, ex :

kubectl apply -k resources/kustomize/dev

Que sont devenues les ressources dans l'ancien namespace ?

Patches et générateurs ConfigMap/Secret

Utiliser un ConfigMap generator pour créer une ConfigMap à partir du fichier postgresql.conf

Utiliser un Secret generator pour créer un Secret à partir du fichier postgres-secret.properties

  • Il faut des options supplémentaires pour considérer ce fichier comme des variables d'environnement

Surcharger le Deployment avec un patch pour s'assurer que les valeurs du Secret et de la ConfigMap sont utilisées à la place de celles définies dans la base.

  • Utiliser db-deployment-patch.yml et mettre à jour kustomization.yml

Adapter une autre Kustomization

Reproduire les changements de la Kustomization dev dans la Kustomization prod et déployer les deux dans le même namespace.

Ingress avec TLS (HTTPS)

resources/https contient une ressource Ingress configurée avec TLS (HTTPS) et un Certificate Cert Manager.

Déployer cet Ingress et le Certificate avec Example Voting App

  • Vérifier que Example Voting App est déployée
  • Déployer les ressources dans resources/https. Remplacer <YOUR_NAME> dans les YAML par votre nom.
  • Observer la création des ressources Ingress et Certificate
  • Tester la fonctionnalité. L'Ingress doit être accessible de l'extérieur.

Pod Resources / Requests

Les Pods peuvent avoir des requests et limits de ressources, par exemple:

spec:
  template:
    spec:
      containers:
      - name: # ...
        resources:
          requests:
            cpu: 1
            memory: "1Gi"
          limits:
            cpu: 2
            memory: 2Gi

Affecter des ressources à vos Pods :

  • Mettre à jour les Pods Vote pour demander 1 CPU et 1024Mi de mémoire
  • Quelles unités peut-on utiliser pour spécifier les ressources ? Quelle différence entre 128M et 128Mi ?

Requests et limits affectent le scheduling différemment. Mettre les ressources des Pods Vote comme ci-dessous et scaler à 10 replicas. Observer le résultat.

resources:
  requests:
    cpu: 1m
    memory: 2Mi
  limits:
    cpu: 1
    memory: 1Gi

Mettre ensuite les ressources comme ci-dessous, scaler à 10 replicas et observer le résultat.

resources:
  requests:
    cpu: 250m
    memory: 256Mi
  limits:
    cpu: 1
    memory: 1Gi

Scalabilité horizontale et Horizontal Pod Autoscaler (HPA)

Configurer un Horizontal Pod Autoscaler (HPA) pour scaler automatiquement les Pods Vote lorsque leur charge CPU est élevée. On utilisera un Pod Auto Voter pour simuler une charge CPU (beaucoup de votes !)

Mettre à jour le Deployment Vote pour spécifier les ressources et requests:

        resources:
          requests:
            cpu: 50m
            memory: 128Mi
          limits:
            cpu: 100m
            memory: 256Mi

Déployer Auto Voter : kubectl apply -f resources/scaling/auto-voter.yml

  • C'est une simple boucle bash qui envoie des requêtes POST au service Vote.
  • Observer la charge CPU de Vote avec la commande kubectl top (CPU idle ~2m, devrait passer à ~50m). Exemple :
watch kubectl top pod vote-xxx

Déployer le HPA dans resources/scaling/hpa.yml et observer la scalabilité

  • Comment le HPA sait-il quand scaler un pod ?

Supprimer Auto Voter et observer le HPA réduire le nombre de pods.

Scalabilité verticale et Cluster Autoscaler

Le Cluster Autoscaler observe en continu l'état du cluster et ajoute ou retire des Nodes selon le besoin. Il utilise les requests de ressources et la capacité de scheduling des Pods pour décider d'ajouter ou retirer des Nodes.

Configurer le deployment Vote pour avoir les ressources suivantes :

        resources:
          requests:
            cpu: 500m
            memory: 512Mi
          limits:
            cpu: 1000m
            memory: 1024Mi

Scaler ensuite le deployment Vote à 20 Pods

kubectl scale deployment vote --replicas 20

Observer le comportement des Pods :

  • Les Pods sont en Pending
  • De nouveaux nodes sont ajoutés pour accueillir les nouveaux Pods (jusqu'à un certain maximum)

Revenir à 1 replica

  • Observer la suppression des nodes

Identifier le Cluster Autoscaler dans kube-system responsable de l'autoscaling.

Liveness, Readiness et Startup Probes

Liveness, Readiness et Startup Probes définissent des comportements pour gérer le routage du traffic réseau et auto-réparer les Pods et containers en cas de problèmes (comme une boucle infini ou un blocage d'une application qui n'a pas fait crasher le pod)

Liveness probe

La liveness probe vérifie régulièrement l'état du container et le redémarre en cas d'échec. Note : c'est le container qui est redémarré (tué et recréé), pas le Pod.

Ajouter une Liveness Probe sur le container Vote, par exemple :

        livenessProbe:
         httpGet:
           path: /
           port: 80
         periodSeconds: 2 # Normalement ~10s, ici plus court pour observer le comportement

Appliquer les changements.

Pour tester le comportement, overrider la commande du container Vote:

        command: ["sleep", "infinity"]

Cela va faire échouer la Liveness probe car le serveur Vote ne démarre pas, GET / ne fonctionnera donc pas. Appliquer et observer le comportement (kubectl describe pour voir les events).

Mettre à jour la liveness probe pour démarrer 10s après le démarrage du container, pour fail après 8 tentatives.

Readiness probe

Ajouter une Readiness Probe sur le container Vote (garder la Liveness Probe). Utiliser une méthode similaire pour tester le comportement.

Décrire le service Vote et vérifier qu'aucun trafic n'est routé vers un Pod non-Ready (dont la Readiness probe échoue).

Startup probe

Ajouter une Startup Probe sur le container Vote (garder Liveness et Readiness). Utiliser une méthode similaire pour tester le comportement.

Pod Disruption Budget (PDB)

Les Pod Disruption Budget (PDB) servent à spécifier un nombre de Pods pouvant devenir indisponibles lors d'une mise à jour. Si le PDB ne peut pas être respecté, la mise à jour d'un Deployment ou autre object échouera ou restera bloqué. C'est un méchanisme de sécurité pour empêcher les downtimes involontaires. Voir la doc officielle

Mettre à jour le Deployment Vote pour avoir 5 replicas, une Readiness Probe et préférer déployer les Pods sur un seul node, ex :

apiVersion: apps/v1
kind: Deployment
# ...
spec:
  replicas: 5
  template:
    spec:
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              preference:
                matchExpressions:
                  - key: "kubernetes.io/hostname"
                    operator: In
                    values:
                      - "ip-192-168-1-4.eu-west-3.compute.internal"
      containers:
      - name: vote
        # ...
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 5

Créer un PDB pour Vote pour permettre max 1 Pod indisponible, par exemple :

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: vote-pdb
spec:
  minAvailable: 4
  selector:
    matchLabels:
      app: vote

Drainer ensuite le Node sur lequel les Pods sont déployés pour observer le comportement.

# Exemple de commande pour drainer un node
# Remplacer par le nom de votre node
kubectl drain ip-192-168-1-160.eu-west-3.compute.internal --delete-emptydir-data=true --ignore-daemonsets=true

Reproduire avec maxUnavailable: 2 au lieu de minAvailable: 1

Que se passe-t-il si on met une valeur en pourcentage et que le nombre de Pods n'est pas rond ? (ex : minAvailable à 50% sur 5 replicas)

Contraintes de Scheduling: introduction

Les contraintes de scheduling des Pods permettent de spécifier comment un Pod sera scheduler sur un Node. Voir la documentation officielle

Node selector: affecter des Pods via les labels Node

Les Nodes Kubernetes ont des labels comme les autres objets Kubernetes. On peut obtenir les labels d'un Node avec get ou describe.

kubectl get node some-node-name -o yaml
apiVersion: v1
kind: Node
metadata:
  name: some-node-name

  # Ces labels peuvent être utilisés par les Pods pour sélectionner les Nodes
  # Comme les Services utilisent labelSelector pour sélectionner les Pods
  labels:
    beta.kubernetes.io/arch: amd64
    beta.kubernetes.io/os: linux
    kubernetes.io/hostname: some-node-name
    topology.kubernetes.io/region: us-central1
    topology.kubernetes.io/zone: us-central1-a

# ...

Utiliser nodeSelector pour affecter les Pods du Deployment Vote à un Node donné via le label topology.kubernetes.io/zone.

  • Utiliser kubectl describe node ou kubectl get node -o yaml pour identifier les labels des Nodes
  • Mettre à jour le Deployment Vote pour définir nodeSelector et appliquer

Affecter un Pod à un Node spécifique

Utiliser le champ nodeName d'un Pod pour l'affecter à un Node précis.

Topology Spread Constraint

Topology Spread Constraints permettent de répartir la charge sur plusieurs domaines (zones, régions, etc.) topologiques. C'est utile pour améliorer la haute disponibilité et la résilience en répartissant les Pods sur plusieurs zones, régions, etc.

topologySpreadConstraints utilise topologyKey pour répartir les Pods sur les domaines correspondant à la clé, par exemple :

    topologySpreadConstraints:
      # Différence maximale entre deux domaines,
      # ex. un domaine ne peut pas avoir plus d'1 Pod qu'un autre
      - maxSkew: 1

        # Clé de topologique utilisée pour définir le domaine
        # Les Pods seront répartis sur différentes zones
        topologyKey: topology.kubernetes.io/zone
        
        # Comportement si la contrainte ne peut pas être respectée
        whenUnsatisfiable: DoNotSchedule

        # Seuls les Pods correspondant à ces labels seront contraints
        labelSelector:
          matchLabels:
            app: vote

Mettre à jour le Deployment Vote pour équilibrer la charge dans la région courante.

Quel est le comportement de topologySpreadConstraints par rapport à Node/Pod Affinity ?

Node Affinity et Anti-Affinity

Affinity et Anti-affinity existent en deux variantes:

requiredDuringSchedulingIgnoredDuringExecution : le scheduler ne peut pas programmer le Pod si la règle n'est pas respectée. Fonctionne comme nodeSelector, mais avec une syntaxe plus expressive. preferredDuringSchedulingIgnoredDuringExecution : le scheduler essaie de trouver un node qui respecte la règle. Si aucun node ne correspond, le Pod est quand même programmé.

Source : doc officielle

En résumé :

  • requiredDuringSchedulingIgnoredDuringExecution = contrainte forte : le Pod ne sera pas programmé si la contrainte n'est pas respectée
  • preferredDuringSchedulingIgnoredDuringExecution = contrainte souple : le Pod est préféré sur les nodes qui respectent la contrainte, mais sera programmé ailleurs si besoin

On peut définir les deux en même temps pour des comportements complexes.

Exemple: Node Affinity qui exige qu'un Pod soit schedulé dans une zone spécifique, comme un Node Selector:

spec:
  template:
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
             - matchExpressions:
                 - key: "topology.kubernetes.io/zone"
                   operator: In
                   values:
                     - "eu-west-3b"

Mettre à jour le Deployment Vote pour :

  • Préférer programmer les Pods dans la zone eu-west-3b (utiliser preferred au lieu de required)
  • Si le Deployment ou les Pods sont bloqués, supprimer le Deployment et le recréer

Appliquer les changements et observer le comportement du scheduling. Supprimer tous les Pods Vote et observer leur scheduling.

Ajouter une seconde règle pour que le Pod :

  • Préfère la zone eu-west-3b avec un weight de 100
  • Préfère la zone eu-west-3a avec un weight de 50
  • Observer le résultat

Pod Affinity et Anti-affinity

Pod affinity et Anti-affinity permettent de contraindre sur quels nodes vos Pods peuvent être schedulés en fonction des labels des Pods déjà présents sur ce node, au lieu des labels du node. Bien que complexe à utiliser, ils permettent des schémas de scheduling avancés.

Source : doc officielle

Répartir les Pods sur les Nodes

Contexte: on souhaite répartir les Pods Vote sur les nodes pour équilibrer la charge et améliorer la redondance et la résilience.

Exemple: scheduler les Pods Vote sur un Node qui n'a pas déjà un Pod Vote via une Pod Anti-affinity:

      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          # Groupe les Nodes par Hostname (donc 1 noeud par groupe)
          # Selectionne un groupe (donc un Node) qui n'a pas de Pod matchant le label "app=vote"
          - topologyKey: "kubernetes.io/hostname"
            labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - vote
            namespaceSelector:
              matchExpressions:
              - key: kubernetes.io/metadata.name
                operator: In
                values:
                  - <YOUR NAME>

Appliquer au Deployment Vote et observer le résultat.

Cependant, si chaque Node a déjà un Pod Vote, cette contrainte empêchera de programmer de nouveaux Pods Vote. Modifier la contrainte pour utiliser preferredDuringSchedulingIgnoredDuringExecution afin de répartir les Pods Vote sur les Nodes sans bloquer: preferred vs. required

Déployer des Pods sur un même Node

Contexte: pour de meilleures performances, on veut que les Pods Redis soient au plus proche des Pods Vote, en préférant répartir les Pods Redis sur les Nodes et en préférant que ces Pods Vote soient déployés sur un Node qui a déjà un Pod Redis (pour réduire la latence réseau en contactant Redis).

Pod anti-affinity :

  • Configurer le Deployment Redis pour préférer être programmé sur un Node sans Pod Redis déjà présent.

Pod affinity :

  • Configurer le Deployment Vote pour exiger d'être programmé sur un Node qui a déjà un Pod Redis (pour optimiser la communication) et préférer un Node qui n'a pas déjà un Pod Vote.

Jouer avec les replicas sur les Deployments Redis et Vote pour observer les résultats.