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.