Skip to content

Instantly share code, notes, and snippets.

@jpetazzo
Created January 26, 2024 17:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jpetazzo/439b9c0e06ee6611e0f9c201ac10f5a5 to your computer and use it in GitHub Desktop.
Save jpetazzo/439b9c0e06ee6611e0f9c201ac10f5a5 to your computer and use it in GitHub Desktop.

Le partage de volume se fait-il uniquement au sein d'un pod ?

Non, on peut aussi partager un volume entre plusieurs pods !

S'il s'agit de plusieurs pods sur la même machine, on peut utiliser un volume de type hostPath. Il y a un exemple dans le manifest hacktheplanet.yaml, qui permet à un pod d'accéder au répertoire /root du node. (Ce qui permet alors d'y injecter une clé SSH pour prendre la main en root sur le node... Et oui, côté sécurité, ça craint ! Pour s'en prémunir, on peut utiliser les mécanismes de contrôle d'admission, comme par exemple les Pod Security Settings ou bien un policy engine comme Kyverno - on verra ça dans "Kubernetes Avancé".)

Si plusieurs pods utilisent un volume hostPath avec le même chemin (par exemple /data/models) alors ces pods se retrouvent à partager les mêmes données.

Ensuite, s'il s'agit de pods se trouvant sur des machines différentes, il faudra utiliser un système de fichiers réseau tel que NFS (par exemple). Voici un exemple de volume NFS dans un manifest de pod :

  volumes:
  - name: my-nfs-volume
    nfs:
      server: 192.168.0.55
      path: "/exports/assets"

S'il y a plusieurs initContainers, l'exécutation est-elle séquentielle ?

L'exécution sera séquentielle. Les initContainers sont pris dans l'ordre, et il faut qu'un conteneur termine correctement (avec un code de retour égal à zéro) avant que Kubernetes ne passe au suivant.

Et il faut que tous les initContainers s'exécutent avec succès pour les containers "principaux" démarrent. Par contre, les containers principaux, eux, sont tous lancés en même temps, en parallèle.

Y a-t-il moyen de paralléliser les initContainers ?

Malheureusement non ! Mais si on veut paralléliser les initContainers, c'est probablement parce qu'ils prennent longtemps à s'exécuter. Peut-être, par exemple, qu'il y en a un qui charge un dump SQL de 100 giga-octets, pendant qu'un autre convertit des assets (images et vidéos) et que ça prend du temps ... Dans ce cas là, il sera probablement préférable de "sortir" ces tâches et les mettre (par exemple) dans un job (s'il y a plusieurs tâches, plusieurs jobs). Puis dans notre pod principal, les initContainers se contenteront d'attendre que les jobs soient terminés.

Suggestion dans le cadre du redémarrage du processus dans un conteneur: kill -9 -1

Malheureusement, kill -9 -1 ne va pas tuer tous les processus dans un conteneur, car le -1 veut dire "tous les processus avec un PID plus grand que 1" et très souvent, le processus qui nous intéresse dans un conteneur (celui qu'on veut kill) c'est précisément le PID 1 !

Quant à kill -9 1 (que j'ai essayé pendant la formation) ça ne marche pas, mais j'avoue ne pas être certain de pouvoir expliquer pourquoi. Je suppose qu'il y a un mécanisme dans le noyau qui empêche tout simplement d'envoyer SIGKILL (-9) au PID 1.

En revanche, on peut envoyer ce SIGKILL d'une autre façon !

Par exemple, depuis l'extérieur du conteneur. (C'est comme ça que Kubernetes arrête le conteneur quand il y a besoin.)

Démo !

Avec un cluster k3s qui tourne sur ma machine Linux, je peux démarrer un pod NGINX :

$ kubectl run web --image=nginx

On attend quelques instants que le pod tourne correctement :

$ kubectl get pod web
NAME   READY   STATUS    RESTARTS   AGE
web    1/1     Running   0          13s

Puis, on retrouve le PID 1 de ce conteneur en allant fouiner dans le conteneur k3s :

$ docker exec k3d-k3s-default-server-0 ps faux | grep nginx
68873 0        nginx: master process nginx -g daemon off;
68906 101      nginx: worker process
68907 101      nginx: worker process
...

Ensuite, on envoie SIGKILL à ce premier processus NGINX :

$ docker exec k3d-k3s-default-server-0 kill -9 68873

On constate que le conteneur a bien été "tué" car le compteur de RESTARTS a augmenté :

$ kubectl get pod web
NAME   READY   STATUS    RESTARTS     AGE
web    1/1     Running   1 (3s ago)   2m46s

Peut-on faire du load balancing en fonction de la charge des nodes et des pods ?

On peut, mais ça n'est pas très facile.

Parlons d'abord du load balancing en fonction de la charge des nodes. Kubernetes fonctionne sur un principe de réservation des ressources. C'est-à-dire que quand on lance nos applications, on va déclarer "cette application a besoin de X CPU et Y RAM" (via les resource requests) et Kubernetes va nous garantir que ces ressources sont disponibles. Si elles ne sont pas disponibles, le pod va rester en Pending. Autrement dit, pas d'overcommit. Dans cette logique, ce n'est pas pertinent de répartir les requêtes en fonction de la charge des nodes, car Kubernetes suppose que les nodes ont la capacité de traiter les requêtes.

Bien sûr, on pourrait dire : "mais je veux optimiser le temps de réponse de mon service, et s'il y a un node moins chargé qu'un autre, autant envoyer les requêtes vers ce node!"

À ce moment là, ce qui va être le plus important, c'est d'envoyer les requêtes vers le pod le moins chargé - peu importe le node. Et en fait, ce n'est pas tellement le pod le moins chargé qui est important, mais plutôt le pod qui répond le plus vite.

Cela peut se faire avec un load balancer interne "custom" (un peu comme ce qu'on a montré avec HAProxy, mais avec une configuration un peu plus poussée) ou bien avec certains service mesh.

Mais pour récapituler : ça serait assez complexe à mettre en place. La "philosophie" Kubernetes serait plutôt de s'assurer que les pods ont bien les ressources nécessaires, autrement dit, si je veux répondre à X requêtes par seconde, je fais une campagne de benchmark en allouant une quantité exacte de ressources (en utilisant requests + limits) et une fois que j'ai déterminé que la charge X pouvait être gérée par les ressources Y, j'alloue ces ressources à mon pod, et voilà !


Sur des gros clusters, est-ce qu'on ne risque pas d'atteindre une limite etcd, étant donné tout ce qui est stocké dedans ? (ConfigMap, Events, etc.)

Oui, c'est possible, mais en pratique, ce qui va nous limiter, c'est surtout les I/O (vitesse de lecture/écriture) sur le disque utilisé par etcd.

Autrement dit, ce n'est pas tellement le nombre d'objets (pods etc.) mais plutôt combien on en crée / modifie / supprime. Pour caricaturer un peu le tableau, un cluster avec un million de pods qui tournent en permanence va probablement moins solliciter etcd qu'un cluster avec un millier de pods mais qui font sans arrêt des redémarrages, des rolling updates, dont la readiness change en permanence ...

Il y a des métriques intéressantes dans la documentation d'OpenShift.

Le passage intéressant :

In terms of latency, run etcd on top of a block device that can write at least 50 IOPS of 8000 bytes long sequentially. That is, with a latency of 10ms, keep in mind that uses fdatasync to synchronize each write in the WAL. For heavy loaded clusters, sequential 500 IOPS of 8000 bytes (2 ms) are recommended. To measure those numbers, you can use a benchmarking tool, such as fio.

Bien sûr ces nombres vont dépendre énormément de la charge du cluster !

Enfin, c'est très pertinent d'avoir mentionné les events, car en effet, ceux-ci sollicitent beaucoup etcd. En fait, il est possible de configurer le control plane pour séparer les objets sur plusieurs clusters etcd. Et le cas le plus typique (pour des clusters très actifs) est de séparer les events sur un cluster etcd distinct. On peut voir ça sur ce schéma d'architecture.


Peux-tu nous montrer le manifest qui contient le lien entre le volume et la ConfigMap ?

Voilà le manifest du pod haproxy qu'on a utilisé.


Les "office hours" de la semaine prochaine sont ils déjà planifiés (en terme d'horaire) ?

  • Lundi: 14h00-15h00
  • Mardi: 14h30-15h30
  • Mercredi: 15h00-16h00

Au sujet du message d'erreur qu'on a rencontré quelques fois (fsnotify watcher: too many open files), tu as dit qu'on pouvait régler ça en modifiant un paramètre système via un conteneur privilégié. Mais je pensais qu'un conteneur était complètement isolé du système, alors, comment ça marche ?

Il y a plusieurs éléments dans la réponse.

Tout d'abord, le problème vient d'une limite dans le système "inotify", qui est un mécanisme du noyau Linux permettant de "surveiller" un ensemble de fichiers ou de répertoires afin d'être informé quand il y a une modification. C'est plus efficace d'utiliser inotify, plutôt que de faire (par exemple) une boucle active qui regarde en permanence la date de modification ou le contenu d'un répertoire.

Mais ce système inotify a des limites (une sorte de quota) afin d'éviter qu'une application (ou un utilisateur / utilisatrice) ne consomme toutes les ressources du système. Ces limites sont paramétrées via des "sysctl" - il s'agit des fichiers qu'on trouve dans /proc/sys. Dans le cas d'inotify, ce sont les fichiers dans /proc/sys/fs/inotify.

Les conteneurs sont isolés du systèmes, mais pas complètement isolés. Par exemple, le système inotify est commun à l'ensemble de la machine (il n'y en a pas un différent pour chaque conteneur). C'est architecturé comme ça, car il y a des scénarios dans lesquels un fichier peut être "surveillé" par plusieurs programmes qui seraient dans des conteneurs différents ; donc il faut que le système inotify soit "transverse" en quelque sorte.

Cela veut dire que les limites (ou quotas) d'inotify sont globaux pour tout le système.

De manière générale, les droits d'un conteneur sont relativement limités ; et notamment, un conteneur ne peut pas modifier ces limites d'inotify. C'est là qu'interviennent les conteneurs privilégiés ! Un conteneur privilégié, comme son nom l'indique, peut tout faire. C'est un peu l'équivalent (en dehors du monde des conteneurs) d'un programme qui tourne un root ! Notre conteneur privilégié pourra donc modifier ces quotas sans problème.

En l'occurrence, nous avions le message d'erreur lors de l'affiche des logs de nos pods. Pour corriger le problème, il faut modifier les quotas en questions sur la machine qui fait tourner le pod dont on regarde les logs. Autrement dit, si je regarde les logs du pod webui-6c9dcc77dc-dc59w il faut que j'aille augmenter les quotas inotify sur le node qui fait tourner webui-6c9dcc77dc-dc59w.

Comment faire ?

D'abord, on récupère le nom du node en question :

$ kubectl get pods webui-6c9dcc77dc-dc59w -o jsonpath={.spec.nodeName}
scw-awtt-101-x86-ae7df0929d5d495f9f5ac207515ca

Ensuite, on lance un conteneur privilégié sur ce node. On notera le --overrides permettant de forcer le placement du pod sur le bon node (ce n'est pas la seule méthode, on aurait pu aussi utiliser un podAffinity pour forcer le placement sur le même node que l'autre pod) :

$ kubectl run --privileged --rm -it --image alpine fixfsnotify \
  --overrides '{"spec": {"nodeName": "scw-awtt-101-x86-ae7df0929d5d495f9f5ac207515ca"}}'
If you don't see a command prompt, try pressing enter.
/ #

Maintenant qu'on est dans la place, on peut consulter la valeur des paramètres :

/ # grep . /proc/sys/fs/inotify/*
/proc/sys/fs/inotify/max_queued_events:16384
/proc/sys/fs/inotify/max_user_instances:128
/proc/sys/fs/inotify/max_user_watches:124330

Et les modifier généreusement :

/ # echo 100000 > /proc/sys/fs/inotify/max_queued_events
/ # echo 1000 > /proc/sys/fs/inotify/max_user_instances
/ # echo 1000000 > /proc/sys/fs/inotify/max_user_watches

Enfin, on ressort du pod. Et quand on teste kubectl logs webui-6c9dcc77dc-dc59w --follow, cette fois-ci, ça fonctionne correctement !

Bien sûr, cela n'a modifié les limites / quotas inotify que sur un seul node. Idéalement, il faudrait faire la modification sur tout le reste du cluster. Cela peut se faire avec un daemon set. Cela garantira que si on ajoute un nouveau node au cluster, celui-ci va aussi être configuré correctement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment