[k8s] Création d’un Volume Persistant via un serveur NFS

<<< Chap 10 | Sommaire | Chap 12 >>>

Chap 11. Création d’un Volume Persistant via un serveur NFS

I. Serveur NFS et de stockage de données Kubernetes

1. Stockage de données

Chaque conteneur peut utiliser l’une des méthodes ci-dessous, pour écrire et sauvegarder des données dans un espace disque qui lui est aloué.

Pour reprendre le résumé donné dans le chap 10 de cette série d’articles sur kubernetes, la persistance des données d’un conteneur dépend du type de système de stockage choisi:

  • Filesystem
    Un redémarrage du conteneur réinitialise l’état du disque
    Exemples: voir chap 4 et 10
  • Volume
    Les données sont préservées jusqu’au redémarrage ou à la destruction du Pod concerné
    Exemple de Volumes: emptyDir (voir chap 10 et 8), et hostPath
  • Persistent Volume
    Les données sont préservées sur un système de stockage externe (NAS, SAN), indépendement de l’état du conteneur ou du Pod/Node
    Exemple: serveur NFS associé au couple PV/PVC (persistentVolume/persistentVolumeClaim)

Dans cet article,

  1. Je vais utiliser le node Master comme serveur NFS
    Si vous avez du mal à configurer votre serveur NFS, déployez un conteneur NFS
  2. Je vais vous présenter deux types de volumes qui sont, hostPath et PV/PVC

2. Configuration du node Master comme serveur NFS (Option 1)

Je vous propose de suivre les instructions de cet article, pour configurer le cluster kubernetes comme suit:

  • Installer le serveur NFS sur le Master (Control Plane)
    • Adresse IP du serveur: 192.168.0.200
    • NFS export: /srv/nfs/k8sn01
  • Installer les clients NFS sur chacun des Workers
    • Adresse IP des client NFS: 192.168.0.201 et 192.168.0.202
    • Mount: /mnt/pvs/k8sn01 (ne monter le(s) disque(s) dans les clients NFS que dans le cas d’un Volume de type hostPath uniquement)

Placer ensuite un fichier de test dans /srv/nfs/k8sn01 pour vérifier que chacun des clients NFS y ont accès.

L’utilisation du Master comme serveur NFS ne devrait ête en aucun cas, appliquée en production. Pensez plutôt à deployer un vrai serveur de type, FreeNAS TrueNAS ou NAS4Free XigmaNAS.

Pour un environnement de lab, cette configuration est largement suffisante pour apprendre à utiliser les fonctionnalités de kubernetes.

3. Déployement d’un Serveur NFS via un conteneur (Option 2)

Commencer par appliquer le fichier nfs-server.yaml dans kubernetes.

k8sn01:~$ kubectl create -f https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/master/deploy/example/nfs-provisioner/nfs-server.yaml

Si vous examiner le contenu du fichier nfs-server.yaml, vous devriez constater qu’il est composé d’un Service (voir chap 9 pour plus d’information sur les services) puis d’un contrôleur de Déploiement.

Le service de  nfs-servernous permettra d’accéder de l’extérieur du clusteur, au serveur NFS créé par le contrôleur de Déploiement.

Je dois préciser que le chemin d’accès aux fichiers NFS est donné par /. C’est cette information qu’il faudra renseigner au niveau de « share: / » dans les fichiers YAMLs des Volumes de type Persitant (statique ou dynamique), pour pouvoir accéder aux fichiers du serveur NFS.

  • Pour un Volume Persistant Statique (obsolète)
apiVersion: v1
kind: PersistentVolume
...
  csi:
    driver: nfs.csi.k8s.io
...
    volumeAttributes:
      server: nfs-server.default.svc.cluster.local
      share: /
apiVersion: storage.k8s.io/v1
kind: StorageClass
...
provisioner: nfs.csi.k8s.io
parameters:
  server: nfs-server.default.svc.cluster.local
  share: /
...

Ensuite, installer le pilote CSI (Interface de stockage de conteneurs) pour système NFS. Ce pilote peut être utilisé pour provisionner les Volymes Persistants de type Statique ou Dynamique.

L’interface de stockage de conteneurs (CSI) définit une interface standard pour les systèmes d’orchestration de conteneurs (comme Kubernetes) pour exposer des systèmes de stockage arbitraires aux charges de travail de leurs conteneurs. (Documentation Kubernetes)

k8sn01:~$ curl -skSL https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/master/deploy/install-driver.sh | bash -s master --

La liste des pilotes installés peut être affichée avec la commande,

k8sn01:~$ kubectl get pod -n kube-system

Quand vous en aurez fini avec le lab, supprimer le conteneur NFS.

k8sn01:~$ kubectl delete -f https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/master/deploy/example/nfs-provisioner/nfs-server.yaml

Si vous n’en avez plus besoin, vous pouvez aussi désinstaller le pilote NFS CSI.

k8sn01:~$ curl -skSL https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/master/deploy/uninstall-driver.sh | bash -s master --

II. hostPath lié à un système de fichier NFS

Un volume de type hostPath permet de monter le système de fichier NFS partagé par un serveur NFS directement dans le pod concerné, pour le présenter comme un de ces disques locaux.

k8sn01:~$ cat << EOF >> hostpath.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    run: nfs-busybox
  name: nfs-busybox
spec:
  replicas: 2
  selector:
    matchLabels:
      run: nfs-busybox
  template:
    metadata:
      labels:
        run: nfs-busybox
    spec:
      containers:
      - image: busybox
        name: nfs-busybox
        command:
        - sh
        - -c
        - 'while true; do date > /mnt/index.html; hostname >> /mnt/index.html; \
          sleep $(($RANDOM % 5 + 5)); done'
        imagePullPolicy: Always
        volumeMounts:
        - name: nfs-volume
          mountPath: /mnt

      volumes:
      - name: nfs-volume
        hostPath:
          path: /mnt/pvs/k8sn01/
EOF

k8sn01:~$ kubectl create -f hostpath.yaml

Vérifier que vous avez accès aux contenus du répertoire partagé par le serveur NFS.

k8sn01:~$ kubectl get pod

k8sn01:~$ kubectl exec -it nfs-hostpath -- cat /mnt/test.txt

k8sn01:~$ kubectl exec -it nfs-hostpath -- touch /mnt/hostpath-test.txt


Les changements apportés devraient être visible aussi bien du coté du client que du serveur NFS.

Les données modifiés devraients persister même après suppression des Pods.


Noter que l’option de Volume du type hostPath est la seule méthode où les Workers doivent monter le système de fichier proposé par le serveur NFS.

Une fois l’exercice terminé, vous pouvez démonter ce système de fichier.

k8sn02:~$ sudo umount /mnt/pvs
k8sn03:~$ sudo umount /mnt/pvs 

III. Approvisionnement statique avec PersistentVolumeClaim

Pour rappel, monter manuellement un système de fichiers NFS au niveau des differents workers n’est pas nécessaire, kubernetes s’en occupera du processus.

Ce qu’il nous faut pour un approvisionnement de type statique des Volumes, vous devriez avoir:

  • Un ou plusieurs PVs (Volumes Persistants)
  • Un PVC (Réclamation du ou des volumes persistants)
  • Un ou plusieurs Pods pour « réclamer » l’espace de stockage à l’aide d’un PVC
k8sn01:~$ cat << EOF >> nfs-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv-slow01
spec:
  capacity:
    storage: 10Mi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteMany
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: slow
  mountOptions:
  - hard
  - nfsvers=4.2
  nfs:
    server: 192.168.0.200
    path: /k8sn01
EOF

k8sn01:~$ kubectl apply -f nfs-pv.yaml

Attention: La politique de récupération Recycle est obsolète. Au lieu de cela, l’approche recommandée consiste à utiliser l’approvisionnement dynamique. (Documentation Kubernetes)

k8sn01:~$ cat << EOF >> nfs-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc-01
spec:
  accessModes:
  - ReadWriteMany
  storageClassName: "slow"
  resources:
    requests:
      storage: 1Mi
EOF

k8sn01:~$ kubectl apply -f nfs-pvc.yaml

Noter qu’un PV avec une classe définie (ici, storageClassName: slow) ne peut être lié qu’avec un PVC demandant la même classe (ici, storageClassName: "slow").

Si aucune classe n’est définie au niveau des PVs (par exemple, storageClassName: , ou si l’option n’a pas été ajoutée dans le fichier yaml), les volumes présentées par les PVs ne peuvent être réclamés que par les PVC « sans classe », qui ne demandent aucun classe particulière (par exemple, storageClassName: "").

k8sn01:~$ cat << EOF >> nfs-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    run: nfs-busybox
  name: nfs-busybox
spec:
  replicas: 2
  selector:
    matchLabels:
      run: nfs-busybox
  template:
    metadata:
      labels:
        run: nfs-busybox
    spec:
      containers:
      - image: busybox
        name: nfs-busybox
        command:
        - sh
        - -c
        - 'while true; do date > /mnt/index.html; hostname >> /mnt/index.html; \
          sleep $(($RANDOM % 5 + 5)); done'
        imagePullPolicy: Always
        volumeMounts:
        - name: nfs-volume
          mountPath: "/mnt"

      volumes:
      - name: nfs-volume
        persistentVolumeClaim:
          claimName: nfs-pvc-01
EOF

k8sn01:~$ kubectl apply -f nfs-deploy.yaml 

A la destruction du cluster dans l’ordre inverse de sa création, le serveur NFS est vidé de son contenu.

Ceci est le résultat de l’option Recycleappliqué au PV/PVC.

La stratégie de récupération Recycle effectue un nettoyage de base (rm -rf /thevolume/*) sur le volume et le rend à nouveau disponible pour une nouvelle demande. (Documentation Kubernetes)

Pour cette raison, il n’est pas souhaitable d’utiliser un Volume Persistant de type Statique.

IV. Volume Persistant Dynamique

Avant de pouvoir utiliser un système de Volume de type Persistant et Dynamique, installer les pilotes CSI (Interface de stockage de conteneurs) pour système NFS, si vous ne l’avez pas encore fait.

k8sn01:~$ curl -skSL https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/master/deploy/install-driver.sh | bash -s master --

Ce qu’il nous faut pour un approvisionnement de type dynamique des Volumes,

  • Un StorageClasspour définir la « classe » du stockage
  • Un PVC (Réclamation du ou des volumes persistants)
  • Un ou plusieurs Pods pour « réclamer » l’espace de stockage à l’aide d’un PVC
  • Le PV est créé automatiquement
k8sn01:~$ cat << EOF >> nfs-class.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-csi
provisioner: nfs.csi.k8s.io
parameters:
  server: 192.168.0.200
  share: /k8sn01
reclaimPolicy: Retain
volumeBindingMode: Immediate
mountOptions:
  - hard
  - nfsvers=4.2
EOF

k8sn01:~$ cat << EOF >> nfs-pv-claim.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-nfs-dynamic
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 10Mi
  storageClassName: nfs-csi
EOF
k8sn01:~$ cat << EOF >> nfs-dyn-deploy.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    run: nfs-busybox
  name: nfs-busybox
spec:
  replicas: 2
  selector:
    matchLabels:
      run: nfs-busybox
  template:
    metadata:
      labels:
        run: nfs-busybox
    spec:
      containers:
      - image: busybox
        name: nfs-busybox
        command:
        - sh
        - -c
        - 'while true; do date > /mnt/index.html; hostname >> /mnt/index.html; \
          sleep $(($RANDOM % 5 + 5)); done'
        imagePullPolicy: Always
        volumeMounts:
        # name must match the volume name below
        - name: dyn-volume
          mountPath: "/mnt"

      volumes:
      - name: dyn-volume
        persistentVolumeClaim:
          claimName: pvc-nfs-dynamic

Contrairement au cas d’un Cluster dont le Volume est approvisionné statiquement, la destruction du cluster dynamique n’a pas d’impact sur le contenu du serveur NFS. Rien n’y est supprimé.

V. Suppression de Pods resté dans un état de blocage

Durant ce chapitre, il se peut qu’à la suppression des différents Pods, l’un d’entre eux reste dans un état de « hang » (bloqué).

Pour forcer la suppression du Pod récalcitrant,

k8sn01:~$ kubectl delete pod nginx-nfs-example --grace-period=0 --force

Sources

Kubernetes Storage 101
Kubernetes – Volumes
Kubernetes Volumes Guide
Kubernetes persistent volume on own rack
Kubernetes : Dynamic Persistent Volume provisioning using NFS
CSI NFS driver
Install NFS CSI driver master version on a kubernetes cluster
Dynamic Provisioning and Creating Storage Classes
Force Delete StatefulSet Pods

[k8s] Les différentes méthodes de Stockage de Données

<<< Chap 9 | Sommaire | Chap 11>>>

Chap. 10 Les différentes méthodes de Stockage de Données

I. Différences entre Conteneurs et Machines Virtuelles

Du point de vue d’un système hôte linux, voici ce que représente un conteneur.

  • Forme de virtualisation qui, contrairement à une virtualisation matérielle (virtualisation), se fait au niveau de l’OS/Application (conteneurisation)
  • Un conteneur n’est pas une machine virtuelle, mais juste un autre processus du système hôte
  • Un conteneur est une « implémentation avancée du mécanisme de « chroot »[1]

De ce fait, il n’y a pas de système d’exploitation installé dans un conteneur.

  • Seuls les programmes nécessaires pour faire tourner ses applications y sont présentes. Et ces applications sont dépendantes des resources de l’hôte (OS denpendencies).

  • Chaque conteneur possède son propre répertoire racine et ses propres processus, isolés du reste du système (jail) – cf. exercice vers la fin de ce chapitre

Il est possible d’installer un conteneur directement sur une machine physique, à condition que la machine hôte tourne avec un noyau linux, et quelque soit la distribution. Vous pouvez par exemple, monter un cluster kubernetes avec des Raspberry-Pi.

Cependant, l’utilisation de machines virtuelles peut être indispensable si:

  • La machine hôte ne tourne pas sous linux
    Puisque les programmes requis par les conteneurs risquent de ne pas être compatibles avec ceux de l’hôte.
  • Le système hôte utilise une version incompatible du noyau linux
    Certaines versions particulières de bibilothèques et binaires, nécessaire au bon fonctionnement du conteneur peuvent ne pas être présentes dans le noyau de l’hôte. Par exemple, avec une vieille distribution, vous ne pouvez pas faire tourner un conteneur installé avec des programmes plus récents.

II. Espace disque

Les méthodes pour présenter un espace disque aux conteneurs sont,

  • Bind-mounts
    Utilisé pour partager des fichiers entre le machine hôte et ses conteneurs via la méthode de « mount« . L’option de « bind » (lier) associé au « mount », permet de protéger les fichiers sources contre toutes modifications à partir de ces conteneurs.
  • tmpfs mounts
    Fichiers temporaires du conteneur, directement monté à partir de la mémoire physique du l’hôte.
  • Volumes
    Répertoires dédiés à chacun des conteneurs, et qui seront créés automatiquement s’ils n’existent pas.  Ces répertoires peuvent se trouver sur les machines hôtes respectifs au conteneurs, ou sur un serveur de stockage.

Image tirée du « Manage data in Docker »

Parmis toutes ces méthodes, l’utilisation des Volumes comme système de stockage de données, est celle préconisée par les équipes de docker et kubernetes. Cette méthode a  pour avantage d’être supportée par des systèmes d’exploitation autres que linux.

Bind-mountet tmpfs sont généralement utilisés pour des tâches spécifiques dans le conteneur.
Le premier est généralement utilisé pour la population du contenu des fichiers hosts de chaque conteneur, tandis que le second peut servir à stocker temporairements des données sensibles, qui seront détruites au redémarrage du conteneur. Les programmes df -h et findmnt peuvent vous fournir un liste de fichiers montés par ces conteneurs.

Pour information, kubelet s’occupe de gérer la création des fichiers /etc/{resolv.conf,hostname,hosts} sur chacun des conteneurs, pour éviter les problèmes de type écriture directe sur la machine hôte[1], dans les fichiers correspondants.

III. Stockage de données

3.1 Système de fichier

Depuis le début de cette série d’articles, je vous ai présenté des conteneurs utilisant un système de fichiers pour présenter les espaces de disque aux conteneurs (voir chap 4 concernant l’exemple ci-dessous).

apiVersion: v1
kind: Pod
metadata:
  name: pingtest01
spec:
  containers:
  - name: pod1
    image: docker.io/centos/tools:latest
    command:
    - /sbin/init

Le problème avec ce type de stockage est que, les modifications y apportées (ajout ou suppression de données) seraient perdues au redémarrage du conteneur. Et c’est là qu’entre en jeu le système de Volume pour y remédier.

3.2 Volumes

Dans le Chap 8, je vous ai présenté des Pods multi-conteneurs utilisant un Volume de type emptyDir, qui peut être partagé entre plusieurs conteneurs du même pod.

apiVersion: v1
kind: Pod
metadata:
  name: pod-with-sidecar
  labels:
    app: pod-with-sidecar
spec:
  containers:
  # Log service
  - name: sidecar-container
    image: alpine:latest
    command: ["/bin/sh"]
    args: ["-c", "while true; do date >> /var/log/index.html; sleep 5;done"]
    volumeMounts:
    - name: shared-logs
      mountPath: /var/log

  # Web server
  - name: app-container
    image: nginx:latest
    ports:
      - containerPort: 80
    volumeMounts:
    - name: shared-logs
      mountPath: /usr/share/nginx/html

# Shared volume
  volumes:
  - name: shared-logs
    emptyDir: {}

L’utilisation d’un système stockage partagé de type « Volumes » est précisé dans le fichier yaml avec justement l’ajout du mot clé volumes. Pour y accéder, chaque conteneur doit préciser le chemin d’accès avec volumeMounts.

Pour que le  même volume soit accessible à tous les conteneurs, ces derniers doivent utiliser comme nom du volume à monter, celui du volume partagé. Ensuite, ajouter le chemin où monter ce volume avec l’option mountPath dans le conteneur.

3.3 Les différents types de volumes

Voici une liste non exhaustive des différents types de volumes supportés par kubernetes. Connectez-vous sur la page officielle, pour avoir la liste complète à jour.

  • Local storage
    Un volume local représente un périphérique de stockage local monté tel un disque, une partition ou un dossier.
  • hostPath
    Un volume hostPath monte un fichier ou un dossier à l’intérieur d’un Pod, depuis le système de fichiers de l’hôte.
  • nfs
    Un volume nfs permet un partage NFS (Network File System) pour être monté dans un Pod.
  • persistentVolumeClaim
    Un volume persistentVolumeClaim est utilisé pour monter un PersistentVolume dans un Pod.
  • emptyDir
    Un volume emptyDir est le type de volume créé lorsqu’un Pod est assigné pour la première fois à un Node (nœud), et existe aussi longtemps que le Pod s’exécute sur ce nœud. Comme le nom l’indique, le volume est initialement vide.
  • configMap
    La ressource configMap fournit un moyen d’injecter des données de configuration dans les Pods.
  • secrets
    Un volume secret est utilisé pour fournir des informations sensibles, comme des mots de passe, aux Pods.
  • Cloud storage
    AWS, Microsoft Azure, Google Cloud, etc.
  • etc.

Une petite note concernant les fichiers et dossiers, sous linux/unix tout est fichiers.

3.4 Exercices

Sur la page de kubernetes, il vous est proposé un exercice pour vérifier la consistance de données ajoutées ou modifiées dans un volume type emptyDir.

Noter que le programme ps n’est disponible qu’après avoir installé procps dans ces conteneurs de cet exercice.

$ kubectl exec -it <nom-du-conteneur> -- apt update

$ kubectl exec -it <nom-du-conteneur> -- apt install procps

Ci-dessous le fichier yaml utilisé,

$ cat << EOF >> volumes.yaml
kind: Pod
metadata:
  name: redis
spec:
  containers:
  - name: redis
    image: redis
    volumeMounts:
    - name: redis-storage
      mountPath: /data/redis
  volumes:
  - name: redis-storage
    emptyDir: {}
EOF

Au redémarrage du conteneur ( la valeur de RESTARTS pour le pod concerné doit avoir été incrémentée), vérifier que le fichier /data/redis/test-file existe toujours.

Afin de pouvoir faire une  comparaison avec un conteneur utilisant un système de fichier, je vous propose de refaire le même exercice, après avoir supprimé la partie « volumes » du fichier yaml. Vous pouvez aussi utiliser le fichier yaml ci-dessous.

$ cat << EOF >> file-system.yaml
apiVersion: v1
kind: Pod
metadata:
  name: redisfs
spec:
  containers:
  - name: redisfs
    image: redis
EOF

Pour pouvoir effectuer exactement les mêmes étapes que dans l’exercice précédant, créer un dossier /data/redis dans le conteneur redisfs.

Après avoir tué le processus Redis pour redémarrer le conteneur, le dossier ainsi que son contenu ne devrait plus exister.

3.5 Conclusion

Avec un système de fichiers, les modifications apportées au niveau des fichiers et données ne sont pas persistantes, et seront détruites au redémarrage du conteneur.

L’utilisation des Volumes permet d’éviter ce désagrément.

Ce qu’il faut savoir en ce qui concerne les volumes,

  • Les modifications apportées au sein de chaque conteneur persistent jusqu’au redémarrage ou à la destruction du Pod dans lequel ces conteneurs sont associés
  • Les Volumes peuvent être mis en commun entre plusieurs conteneurs du même pod

Ces deux limitations peuvent être outrepassées en passant par un serveur de stockage (local ou dans le Cloud) pour présenter les volumes, afin de garantir une sauvegarde de données indépendemment de l’état du Pod, de façon plus permanente.
Après il faut voir comment vous gérez votre cluster de stockage de donnée.

Sources

Manage data in Docker
Kubernetes: Documentation / Concepts / Stockage
docker container does not need an OS, but each container has one. Why?
What is the meaning of mounting /dev/sda1 to /etc/hosts in Docker container
Quelle est la différence entre un conteneur et une machine virtuelle?
Kubernetes Mount Propagation
Kubernetes – Volumes
Configurer un pod en utilisant un volume pour le stockage

[k8s] Réseautage et traffic réseau

<<< Chap 8 | Sommaire | Chap 10>>>

Chap. 9 Réseautage et traffic réseau

I. Information réseau

Pour rappel, kubernetes ne s’occupe pas de relier en réseau les différents Pods entre eux. C’est le rôle du CNI (Container Network Interface).

Revoyez les informations du chapitre 2 de cette série d’articles, pour les détails de mon choix de « Cannal » comme plugin CNI.

1. Informations utiles

Il se peut qu’à un moment ou un autre, vous aurez besoin de demander de l’aide sur un forum dédié en cas de problème de réseau.

Je vous recommende de vous préparer à fournir au moins les informations ci-dessous,

  • Version de kubernetes
  • Version de calico*
  • Version de flannel*
  • Système d’exploitation et sa version
  • Adresse du réseau
  • CIDR

Remplacer Calico et Flannel par votre CNI. L’adresse du réseau et le CIDR, peuvent aider à mieux comprendre votre configuration réseau.

Ces informations peuvent être récupérées avec les commandes,

  • Version de kubernetes
$ apt show kubectl | grep -i version
Version: 1.19.3-00
  • Version de calico et flannel
$ kubectl get ds -A -o wide | grep flannel
kube-system canal 3 3 3 3 3 kubernetes.io/os=linux 3h32m calico-node,kube-flannel calico/node:v3.16.4,quay.io/coreos/flannel:v0.12.0 k8s-app=canal
  • Version du système d’exploitation
$ cat /etc/lsb-release | grep -i description
DISTRIB_DESCRIPTION="Ubuntu 20.04.1 LTS"
  • CIDR du Cluster
$ ps -ef | grep cluster-cidr
xxxx  --client-ca-file=/etc/kubernetes/pki/ca.crt --cluster-cidr=172.16.0.0/16 --cluster-name=kubernetes xxxx
  • CIDR par node
$ kubectl get nodes -o jsonpath='{.items[*].spec.podCIDR}'
172.16.0.0/24 172.16.1.0/24 172.16.2.0/24

Voici les informations de mon réseau.

  • Kubernetes version: 1.19.3
  • Calico version: 3.16.4
  • Flannel version: 0.12.0
  • Operating System and version: Ubuntu 20.04.1 LTS
  • Mon réseau local: 192.168.0.0/24
  • CIDR du cluster kubernetes: 172.16.0.0/16

2. Topologie

Les communications dans un réseau Kubernetes se font de façon centralisées.

Les traffics partant des nodes (Workers), passent  par un plans de contrôle (Master) qui fait office de hub/commutateur, avant d’être redirigés vers leurs destinations. L’architecture réseau de kubernetes utilise une topologie réseau en étoile.

3. Network Namespace (netns)

Un espace de nom réseau Linux[1][2] (« network namespace ») est l’une fonctionnalité du noyau linux pour virtualiser la pile réseau dans chacun des namespaces.

De plus, un ensemble d’objets dans un même espace de nom (namespace), partage les mêmes resources. Par défaut, dans chaque Pod est créé une interface de loopback, pour que les conteneurs du même Pod puissent communiquer entre eux sans avoir à quitter le réseau namespace qui leurs sont associés.

Chaque espace de nom, peut fournir à chacun des Pods toutes les resources liées au réseau.
Entre autre, chaque pod peut avoir,

  • Une adresse IP privée
  • Sa propre table de routage, avec ses règles de parefeux, etc.

Les deux type de namespace sont,

  • « Root netns », pemet la communication entre un Pod et l’extérieur
  • « Pod netns », regroupe tous les conteneurs d’un même Pod, dans le même réseau « local »

4. Connectivités réseaux

Le CNI s’occupe du réseautage dans un cluster kubernetes. Entre autre, le CNI s’assure de la connectivité entre:

  • Conteneurs (voir chap 7 et chap 8)
    Deux conteurs au sein d’un même Pod communiquent entre eux via l’interface de loopback
  • Pods (voir chap 5)
    Deux conteneurs placés dans deux Pods différents, communiquent via l’interface réseau du Pod (eth0) si les deux Pods se trouvent sur deux Nodes différents.
    Sinon, les communications au sein d’un même Pod passe par l’interface bridge cbr0 du CNI
  • Pod et Service du réseau
  • Traffic Extérieur vers Service réseau

Schématisation des deux premiers points.

Les deux derniers points utilisent les services du proxy pour rediriger les traffics de l’extérieur vers l’un des Pods proposant le même service. On parle de répartition de charges, ou load balancing.

II. Services et Routage du traffic réseau

2.1 Introduction sur les Services

Sur la page de kubernetes[1], est donné la définition de « Service ».

Un service dans Kubernetes est un objet REST, semblable à un pod.

Lorsqu’un groupe de Pods est géré par le même service,

  • Ces Pods seront joignable via l’interface de l’objet Service, dont l’adresse IP est « immuable« , par opposition à l’adresse des Pods qui est éphémère (voir chap 5)
  • Chacun des ces Pods aura un nom DNS unique (voir chap 6)
  • Equilibrage des charges ou Load-balancer, entre les différents Pods

Un service kubernetes peut être créé de deux façon différentes,

  • Automatiquement, via la commande « expose »
  • Manuellement, à partir d’un fichier YAML

2.2 Création manuel d’un service

Les étapes du chap 5 peuvent se résumer par:

  • Création de 2 Pods à partir du Service de Réplication
  • Création d’un Service kubernetes avec la commande « expose »
$ kubectl create -f nginx-dpl.yaml

$ kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 1/1 1 1 47m

$ kubectl expose deploy nginx-deployment
service/nginx-deployment exposed

2.3 Création d’un service via un fichier YAML

Les étapes du chap 6 peuvent se résumer par:

  • Création de deux Pods différents ayant le même label/étiquette
  • Création d’un Service kubernetes à partir d’un fichier YAML, qui cible l’étiquette commun des deux Pods au niveau port TCP 9376
$ kubectl create -f ubuntu1.yml

$ kubectl create -f ubuntu2.yml

$ kubectl create -f ubuntu-svc.yml

2.3 Types de Service

Au moment où j’écris cet article, il y a quatre types de services reconnus par kubernetes.

  • ClusterIP
    Expose le service avec une adresse IP interne au cluster (choix par défaut)
  • NodePort
    Expose le service directement sur le node via un port défini au préalable
  • LoadBalancer
    Expose le service à l’extérieur du réseau, vers un répartisseur de charge de votre fournisseur de service Cloud Computing
  • ExternalName
    Mappe le service au contenu du champ externalName

Pour plus d’information sur l’utilisation de chacun de ces services, revoyer les informations de la page de kubernetes sur les services.

Si vous avez fait attention au fichier YAML du chap 5, j’ai commenté le type de service en ajoutant un # au niveau de la ligne « type ».

apiVersion: v1
kind: Service
metadata:
  name: my-subdomain
spec:
  selector:
    name: ubuntu
  ports:
  - name: default
    protocol: TCP
    port: 80
    targetPort: 9376
  #type: ClusterIP

Comme précisé plus haut, ClusterIP est l’option par défaut si le type n’est pas précisé. C’est cette option que vous risquez de rencontrer régulièrement dans mon blog.

2.4 Serveur Proxy

Voyons comment le service kubernetes fonctionne.

Dans le schéma ci-dessous, un client lance deux requêtes différentes, dont l’un est adressé au serveur Nginx, et l’autre à un autre type de serveur.

Dans le cas de la requêtre vers un serveur Nginx de notre exemple, l’objet Service sélectionne au hasard l’un des trois pods (endpoints) pour transférer cette requête via le port 9376, d’où les 33% de chance de tomber sur l’un des trois Pods ciblés par cette requête.

Pour avoir la liste des endpoints disponible dans l’iptable, ce dernier génère une liste de règles qui capturent les informations sur le trafic vers le «clusterIP» et le «port» du service,

$ kubectl get endpoints nginx-deployment

$ kubectl get ep nginx-deployment

Sinon, vous pouvez avoir cette information complète sur le service avec la commande describe.

$ kubectl describe svc nginx-deployment

Nous avons vu comment créer des probes de préparation (Readiness Probe) dans le chap 7. C’est le bon moment pour la mettre en pratique, pour vérifier l’état de santé des pods de destinations (endpoints), afin de s’assurer qu’ils fonctionnent correctement et d’éviter ainsi, d’avoir des traffics inutiles vers des pods non fonctionnels.

2.5 Kubernetes Ingress

Citation de la page officielle de kubernetes[1],

Vous pouvez également utiliser Ingress pour exposer votre service. Ingress n’est pas un type de service, mais il sert de point d’entrée pour votre cluster. Il vous permet de consolider vos règles de routage en une seule ressource car il peut exposer plusieurs services sous la même adresse IP.

    internet
        |
   [ Ingress ]
   --|-----|--
   [ Services ]

Source

A Guide to the Kubernetes Networking Model
Kubernetes: Service

[k8s] Modèles de Pod multi-conteneurs

<<< Chap 7 | Sommaire | Chap 9>>>

Chap 8. Modèles de Pod multi-conteneurs

Je vous ai déjà présenter des Pods multi-conteneurs dans l’un des chapitres précédents, sans vraiment y entrer dans les détails.

Dans cette partie, je vais essayer de vous aider à comprendre le concept et l’utilité de ce type de Pods.

Avant d’aller plus loin, il faut savoir que tous les conteneurs au sein d’un même Pod,

  • Partagent la même adresse IP, qui est celle du Pod en question
  • Les conteneurs communiquent entre eux via l’interface de loopback (127.0.0.1)
  • Ces conteneurs ont accès au même disque

Ces conteneurs peuvent se lancer de deux manières différentes,

  • En parallèle (Sidecar Container)
  • En série de façon séquencielle (Init Container)

I. Démarrage en parallèle des conteneurs

Le premier modèle de pod multi-conteneur à étudier est le conteneur type side-car.

Tout comme le véhicule, ce type de conteneur de même nom a la particularité d’avoir un conteneur principal puis un ou plusieurs conteneurs en parallèle, qui permettront d’étendre les fonctionnalités du conteneur principal.

Le mode de fonctionnement d’un modèle side-car.

  • Les serveurs gérés par le conteneur principal et celui du side-car sont indépendant, et peuvent donc fonctionner indépendement l’un de l’autre
  • Le serveur du conteneur principal nous permet de lire le contenu des événements enregistré sur le disque partagé entre les différents conteneurs du même pod
  • Le conteneur side-car est utilisé pour récupérer les informations sur un environnement donné (état de santé du réseau par exemple

Dans cet exemple, nous allons utiliser le serveur du conteneur side-car pour enregister toutes les secondes, la date et l’heure du serveur au moment de la requête.

Le fichier YAML, se présente comme ceci:

$ cat << EOF >> sidecars.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-with-sidecar
  labels:
    app: pod-with-sidecar
spec:
  containers:
  # Log service
  - name: sidecar-container
    image: alpine:latest
    command: ["/bin/sh"]
    args: ["-c", "while true; do date >> /var/log/index.html; sleep 5;done"]
    volumeMounts:
    - name: shared-logs
      mountPath: /var/log

  # Web server
  - name: app-container
    image: nginx:latest
    ports:
      - containerPort: 80
    volumeMounts:
    - name: shared-logs
      mountPath: /usr/share/nginx/html

# Shared volume
  volumes:
  - name: shared-logs
    emptyDir: {}
EOD

Récupérer l’adresse IP du Pod,

$ kubectl get pod -o wide
NAME               READY   STATUS    RESTARTS   AGE   IP             NODE     NOMINATED NODE   READINESS GATES
pod-with-sidecar   2/2     Running   0          33s   172.16.2.113   k8sn03

Vérifier que les deux conteneurs sont en mode « ready ».

La lecture du fichier de journalisation se fait via la page html.

$ curl  172.16.2.113
Wed Dec 30 18:04:28 UTC 2020
Wed Dec 30 18:04:33 UTC 2020
Wed Dec 30 18:04:38 UTC 2020

Cas d’utilisation,

  • Améliorer et/ou extension des fonctionnalités du conteneur principal

II. Démarrage des conteneurs de façon séquentiel

Voici les caractéristiques du le modèle de démarrage séquentiel des différents conteneurs d’un pod multi-conteneur.

  • Le conteneur Application (conteneur principal) ne se lance qu’à la fin de l’initialisation de tous les conteneurs Inits
  • Ces conteneurs Inits se lancent à tour de rôle, et dans la suite définie dans le fichier YAML
  • L’initialisation du prochain conteneur Init ne peut commencer qu’une fois, celui de précédent terminé
  • Si l’initialisation d’un des conteneurs Inits échoue, le Pod entier va être redémarré

Pour vous montrer que les conteneurs utilisant les deux méthodes de démarrage peuvent cohabiter, je vais reprendre l’exemple du pod multi-conteneur avec conteneur un side-car ci-dessus, auquel je vais ajouter un conteneur init en parallèle.

$ cat << EOF >>init.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: pod-with-initcontainer
  labels:
    app: pod-with-initcontainer
spec:

  # Main application container
  containers:
    - name: sidecar-container 
      image: alpine:latest
      command: ['/bin/sh']
      args: ['-c', 'while true; do sed -e "s/<h1>.*<\/h1>/<h1>Today is $(date)<\/h1>/" -i /var/log/index.html;sleep 5; done']
      volumeMounts:
      - name: shared-volume
        mountPath: /var/log

    - name: app-container
      image: nginx:latest
      ports:
        - containerPort: 80
      volumeMounts:
        - name: shared-volume
          mountPath: /usr/share/nginx/html/

  # Init Container
  initContainers:
  - name: init01-githubclone
    image: alpine/git
    args:
        - clone
        - --single-branch
        - --
        - https://ithub.com/NillsF/html-demo.git
        - /usr/share/nginx/html/
    volumeMounts:
    - name: shared-volume
      mountPath: /usr/share/nginx/html/

  # Shared volume
  volumes:
  - name: shared-volume
    emptyDir: {}
EOF

Valider la création du Pod.

$ kubectl create -f init.yaml

Et voyez ce qui se passe.

$ kubectl get pod --watch
NAME                     READY   STATUS        RESTARTS   AGE
pod-with-initcontainer   0/2     Pending       0          0s
pod-with-initcontainer   0/2     Pending       0          0s
pod-with-initcontainer   0/2     Init:0/1      0          0s
pod-with-initcontainer   0/2     Init:0/1      0          1s
pod-with-initcontainer   0/2     Init:Error    0          4s
pod-with-initcontainer   0/2     Init:Error    1          6s
pod-with-initcontainer   0/2     Init:CrashLoopBackOff   1          7s
pod-with-initcontainer   0/2     Init:Error              2          23s
pod-with-initcontainer   0/2     Init:CrashLoopBackOff   2          24s

Pour comprendre ce qui s’est passé,

$ kubectl describe pod
!-- sortie tronquée --!
Events:
  Type     Reason     Age                  From               Message
  ----     ------     ----                 ----               -------
  Normal   Scheduled  3m5s                 default-scheduler  Successfully assigned default/pod-with-initcontainer to k8sn03
  Normal   Pulled     3m1s                 kubelet            Successfully pulled image "alpine/git" in 1.741984477s
  Normal   Pulled     2m59s                kubelet            Successfully pulled image "alpine/git" in 1.610779447s
  Normal   Pulled     2m39s                kubelet            Successfully pulled image "alpine/git" in 1.630074653s
  Normal   Created    2m7s (x4 over 3m1s)  kubelet            Created container init01-githubclone
  Normal   Started    2m7s (x4 over 3m1s)  kubelet            Started container init01-githubclone
  Normal   Pulled     2m7s                 kubelet            Successfully pulled image "alpine/git" in 1.807176015s
  Warning  BackOff    91s (x8 over 2m57s)  kubelet            Back-off restarting failed container
  Normal   Pulling    79s (x5 over 3m3s)   kubelet            Pulling image "alpine/git"

Le conteneur init ne peut pas s’initialiser, sans plus d’information sur la raison de cet échec de démarrage du conteneur.

Voyons ce qu’il y a dans les logs.

$ kubectl logs pod-with-initcontainer
error: a container name must be specified for pod pod-with-initcontainer, choose one of: [sidecar-container app-container] or one of the init containers: [init01-githubclone]

$ kubectl logs pod-with-initcontainer -c init01-githubclone
Cloning into '/usr/share/nginx/html'...
fatal: unable to access 'https://ithub.com/NillsF/html-demo.git/': Could not resolve host: ithub.com

Le problème est intentionnel, puisque j’ai modifié l’url original qui devrait pointer vers github.com et non vers ithub.com.

Faites le nécessaire pour régler le problème en modifiant l’url dans le fichier YAML, puis relancer la création du pod,

$ kubectl get pod --watch
NAME                     READY   STATUS    RESTARTS   AGE
pod-with-initcontainer   0/2     Pending       0          0s
pod-with-initcontainer   0/2     Pending       0          0s
pod-with-initcontainer   0/2     Init:0/1      0          0s
pod-with-initcontainer   0/2     Init:0/1      0          2s
pod-with-initcontainer   0/2     Init:0/1      0          5s
pod-with-initcontainer   0/2     PodInitializing   0          6s
pod-with-initcontainer   2/2     Running           0          11s

$ kubectl describe pod
<!-- sortie tronquée -->
Events:
  Type    Reason     Age    From               Message
  ----    ------     ----   ----               -------
  Normal  Scheduled  2m57s  default-scheduler  Successfully assigned default/pod-with-initcontainer to k8sn03
  Normal  Pulling    2m56s  kubelet            Pulling image "alpine/git"
  Normal  Pulled     2m54s  kubelet            Successfully pulled image "alpine/git" in 1.787122456s
  Normal  Created    2m54s  kubelet            Created container init01-githubclone
  Normal  Started    2m53s  kubelet            Started container init01-githubclone
  Normal  Pulling    2m51s  kubelet            Pulling image "alpine:latest"
  Normal  Pulled     2m50s  kubelet            Successfully pulled image "alpine:latest" in 1.759135451s
  Normal  Created    2m49s  kubelet            Created container sidecar-container
  Normal  Started    2m49s  kubelet            Started container sidecar-container
  Normal  Pulling    2m49s  kubelet            Pulling image "nginx:latest"
  Normal  Pulled     2m47s  kubelet            Successfully pulled image "nginx:latest" in 1.633856181s
  Normal  Created    2m47s  kubelet            Created container app-container
  Normal  Started    2m47s  kubelet            Started container app-container

Vous devriez maintenant être en mesure d’accéder à la page proposée par le Pod,

$ kubectl get pod -o wide

$ curl 172.16.1.98

Le conteneur side-car, m’a permi de manipuler l’affichage de la page internet pour y afficher la date et l’heure à laquelle vous accéder au serveur.

Si vous supprimer le conteneur side-car, vous vous retrouverez comme dans l’exemple proposé par le site à partir duquel j’ai récupérer les configurations de Init.

Sinon, vous pouvez ajouter un deuxième conteneur d’initialisation en vous basant sur l’expérience proposée sur la page officielle de kubernetes.

 III. Cas particuliers du modèle Sidecar: Ambassador et Adapter

Les conteneurs de type Ambassador et Adapter se comportent comme un modèle side-car, mais pas tout à fait.

3.1 Modèle Ambassadeur

Un conteneur de type Ambassadeur se comporte comme un proxy, pour permettre l’accès à des ressoures en dehors du Pod, aux clients qui accèdent aux services du conteneur principal.

Je vais reprendre le même exemple que celui de cet article, pour accéder aux resources d’un service sur internet en passant pas un proxy (Ambassadeur).

Construisez le fichier YAML.

$ cat << EOF >> ambassador.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-with-ambassador
  labels:
    app: pod-with-ambassador
spec:
  containers:
  - name: app-container
    image: bbachin1/main-container
    ports:
      - containerPort: 9000

  - name: ambassador-container
    image: bbachin1/nginx-server-proxy
    ports:
      - containerPort: 3000
EOF

$ kubectl create -f ambassador.yaml

Petite note avant de continuer,

  1. Accès au port 9000 du conteneur principal pour accéder aux resources sur internet via notre proxy (Ambassador)
  2. Curl n’est pas présent dans le conteneur principal. Par contre, le programme est installé avec l’ambassadeur
  3. L’option side-car de l’ambassadeur va nous permettre d’accéder à internet via l’interface partagé « localhost » en utilisant le programme curl

Maintenant, accéder au localhost via le port 9000.

$ kubectl exec -it pod-with-ambassador-container -c ambassador-container -- curl localhost:9000

Vous devriez avoir exactement les mêmes informations que lorsque vous visitez l’url à partir de votre navigateur internet.

Cas d’utilisation,

3.2 Modèle Adapteur

Le modèle Adapteur, tout comme l’Ambassadeur, permet l’accès aux services à l’extérieur du Pod et n’apporte rien au niveau des fonctionnalités du conteneur principal.

Les deux sont des serveurs proxys, mais avec des fonctionnalités inverses.

Lorsqu’on parle de proxy (forward proxy), on fait allusion à l’Ambassadeur. Par contre, si l’on précise que c’est un proxy inverse (reverse proxy), c’est qu’ont fait référence à l’Adapteur.

Dans l’exemple qui suit, nous allons  nous limiter à un conteneur principal pour générer un fichier de log.

Les logs vont ensuite être converties par le conteneur Adapter (reverse proxy), pour afficher les données dans un format simple et utilisable par un moniteur système.

Ceci dit, vous pouvez générer des  logs à partir de chacun des serveurs du multi-conteneur. L’Adapteur s’occupe de les traiter de maminère uniforme afin d’avoir un format homogène, dans le même standard pour une utilisation future.

Créer le fichier YAML ci-dessous. Ce fichier est disponible sur git à cette adresse.

$ cat << EOF >> adapter.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: pod-with-adapter
  labels:
    app: pod-with-adapter
spec:
  # Shared volume
  volumes:
  - name: shared-logs 
    emptyDir: {}

  containers:

  # Main application container
  - name: app-container
    image: alpine
    command: ["/bin/sh"]
    args: ["-c", "while true; do date > /var/log/top.txt && top -n 1 -b >> /var/log/top.txt; sleep 5;done"]

    volumeMounts:
    - name: shared-logs
      mountPath: /var/log

  # Adapter container
  - name: adapter-container
    image: alpine
    command: ["/bin/sh"]

    args: ["-c", "while true; do (cat /var/log/top.txt | head -1 > /var/log/status.txt) && (cat /var/log/top.txt | head -2 | tail -1 | grep
 -o -E '\\d+\\w' | head -1 >> /var/log/status.txt) && (cat /var/log/top.txt | head -3 | tail -1 | grep
-o -E '\\d+%' | head -1 >> /var/log/status.txt); sleep 5; done"]

    volumeMounts:
    - name: shared-logs
      mountPath: /var/log
EOF

$ kubectl create -f adapter.yaml

Comparer ensuite les informations suivantes,

  • Les logs générés par le conteneur principal
$ kubectl exec -it pod-with-adapter -c app-container -- cat /var/log/top.txt
  • Les informations prêts à être envoyés vers un système de surveillance (monitor system)
$ kubectl exec -it pod-with-adapter -c app-container -- cat /var/log/status.tx

Cas d’utilisation,

  • Serveur de gestion d’historique des événement ou de journalisation
  • Exposer les métriques (routage, etc.) et les fichiers de journalisation de manière standard

IV. Conclusion

Sur internet, vous allez certainnement tomber sur un article qui rapporte 4 modèles de Pods multi-conteneurs.

  • Sidecar
  • Ambassador
  • Adapter
  • Init

Personnelement, je pense qu’il n’y en a que deux (Sidecar et Init), que j’ai défini en fonction de leurs méthodes de démarrage (en parallèle ou séquentiellement).

Les conteneurs type Ambassadeur/Adapteur,

  • Sont des conteneurs side-car de type proxy (forward/reverse), pour vous permettre d’atteindre un service en dehors du pod, et plus particulièrement sur internet
  • Ne permettent pas d’améliorer et/ou d’étendre fonctionnalités du conteneur principal

Si vous fouillez un peu plus sur internet, vous pouvez tomber sur d’autres modèles de conteneurs[1], qui sont:

  • Leader election
  • Work queues
  • Scatter-gather

Sources

Design patterns for container-based distributed systems
CKAD series part 4: Multi-container pods
Kubernetes — Learn Sidecar Container Pattern
Kubernetes — Learn Ambassador Container Pattern
Kubernetes Patterns : The Ambassador Pattern
Differences between Sidecar and Ambassador and Adapter pattern
Les design patterns : SideCar, Adapter, Ambassador
7 container design patterns you need to know
Differences Between Forward Proxy and Reverse Proxy
Container Patterns – SIde Car, Ambassador and Adapter
Container Design Patterns for Kubernetes – Part 1

[k8s] Sommaire

Kubernetes

I. Conteneur Logiciel – Docker CE

  1. Prérequis
  2. Installation de docker
  3. Premiers pas avec docker

II. Déploiement de Kubernetes avec kubeadm

  1. Prérequis
  2. Installation
  3. Astuces

III. Kubectl et Premiers pas avec kubernetes

  1. Master vs Worker
  2. Namespace
  3. Les primitives
  4. Les différents services de kubeadm
  5. Création d’un pod

IV. Fichier YAML

  1. Fichier manifest YAML
  2. Test sur le fichier YAML et vérification de son contenu
  3. Création d’un fichier YAML
  4. Astuces avec VIM

V. Contrôleur de Réplication

  1. Contrôleurs Kubernetes
  2. Configuration du système de réplicas
  3. Modification du nombres de clones
  4. Test de réplication des Pods
  5. Introduction à l’objet Service

VI. Résolution de nom

  1. Serveur DNS
  2. Résolution de nom entre deux Pods
  3. Faits intéressants

VII. Les différents types de Sondes

  1. Fichier YAML
  2. LivenessProbe
  3. ReadinessProbe
  4. Conclusion

VIII. Modèles de Pod multi-conteneurs

  1. Démarrage en parallèle des conteneurs
  2. Démarrage des conteneurs de façon séquentiel
  3. Cas particuliers du modèle Sidecar: Ambassador et Adapter
  4. Conclusion

IX. Réseautage et traffic réseau

  1. Information réseau
  2. Services et Routage du traffic réseau

X. Les différentes méthodes de Stockage de Données

  1. Différences entre Conteneurs et Machines Virtuelles
  2. Espace disque
  3. Stockage de données

XI. Création d’un Volume Persistant via un serveur NFS

  1. Serveur NFS et de stockage de données Kubernetes
  2. hostPath lié à un système de fichier NFS
  3. Approvisionnement statique avec PersistentVolumeClaim
  4. Volume Persistant Dynamique
  5. Suppression de Pods resté dans un état de blocage

XII. Autre type de Volumes: ConfigMap et Secret – Partie 1

  1. ETCD
  2. ConfigMap et variables d’environnement
  3. Variables d’environnement appliquées aux Conteneurs
  4. Volume type ConfigMap

XIII. Autres types de Volumes: ConfigMaps et Secrets – Partie 2

  1. Secrets
  2. Autre méthode pour la création de Secret
  3. Volume de type Secret
  4. Màj de conteneurs après modification des fichiers configMap et Secret

XIV. Déploiement et Mise-à-jour des Pods – Partie 1

  1. Rappel sur les déploiements de type Pod et Deploy
  2. Mise-à-jour des Pods
  3. Les différentes méthodes de màj de Pods créés par un objet de type deploiement

XV. Déploiement et Mise-à-jour des Pods – Partie 2

  1. Introduction au contrôleur Daemonset
  2. Choix du Node à associer aux services de Daemonset
  3. Accès aux applications et répartition des charges

XVI. Déploiement et Mise-à-jour des Pods – Partie 3

  1. Application Stateless
  2. Application Stateful
  3. Application Stateful vs Stateless et Màj des Pods

XVII. Déploiement et Mise-à-jour des Pods – Partie 4

  1. Introduction au contrôleur StatefulSet
  2. Màj puis Destruction des Pods STS
  3. Caractéristiques des déploiements avec STS

XVIII. Introduction aux Labels et NameSpaces

  1. Labels et Sélecteurs
  2. NameSpace

[k8s] Les différents types de Sondes

<<< Chap 6 | Sommaire | Chap 8 >>>

Chap 7. Les différents types de Sondes

Les sondes (Probe, en anglais) sont utilisées pour vérifier l’état de santé des conteneurs de chaque Pod.

I. Fichier YAML

Le fichier du configuration est du type,

apiVersion: <API-version>
kind: <Object-kind>
metadata:
   (...)
spec:
    containers:
    - name: <container-name>
      image: <container-image>
      (...)
      <probe-type>:
        <probe-method>: 
            (...)
        initialDelaySeconds: x
        periodSeconds: x
        timeoutSeconds: x
        successThreshold: x
        failureThreshold: x

Nous allons discuter des types et méthodes de sonde dans la suite de cet article.

Définitions d’après la page officielle de kubernetes[1],

  • initialDelaySeconds: Nombre de secondes après le démarrage du conteneur avant que les liveness et readiness probes ne soient lancées. La valeur par défaut est de 0 seconde. La valeur minimale est 0.
  • periodSeconds: La fréquence (en secondes) à laquelle la probe doit être effectuée. La valeur par défaut est de 10 secondes. La valeur minimale est de 1.
  • timeoutSeconds: Nombre de secondes après lequel la probe time out. Valeur par défaut à 1 seconde. La valeur minimale est de 1.
  • successThreshold: Le minimum de succès consécutifs pour que la probe soit considérée comme réussie après avoir échoué. La valeur par défaut est 1. Doit être 1 pour la liveness probe. La valeur minimale est de 1.
  • failureThreshold: Quand un Pod démarre et que la probe échoue, Kubernetes va tenter failureThreshold fois avant d’abandonner. Abandonner en cas de liveness probe signifie le redémarrage du conteneur. En cas de readiness probe, le Pod sera marqué Unready. La valeur par défaut est 3, la valeur minimum est 1.

1. Types de sonde: probe-type

Avec kubernetes, il y a 3 types de sonde,

  • startupProbe (sonde de démarrage)
  • livenessProbe (sonde de vivacité)
  • readinessProbe (sonde de préparation)

Il faut savoir que la sonde startupProbe est prioritaire par rapport aux deux autres qui sont, livenessProbe et readinessProbe. Ceci, dans le but de s’assurer que le conteneur ait pu s’initialiser correctement avant de lancer ces sondes de test sur la santé du conteneur.

2. Méthode de sonde: probe-method

Chaque type de sondes peut utiliser l’une des trois méthodes ci-dessous, pour vérifier l’état de santé du conteneur concerné.

  • lancement d’une commande EXEC
  • requête HTTP avec HTTP GET
  • ouverture d’un socket TCP

Dans le suite de ce chapitre, je vais me focaliser uniquement sur les commandes EXEC pour faire les tests de sondes.

3. Note

Pour ne pas faire de doublon avec les exercices de la page officielle de kubernetes, je vais effectuer les démonstrations de façon à avoir un ou plusieurs conteneurs dont les réplicats vont être gérés par un contrôleur de Déploiement (voir l’article du chapitre 5).

Ces tests se porteront sur l’étude du comportement des conteneurs dans le cas d’un non-réussite du test de sonde.

Ce genre de test n’a aucun sens pour les sondes startupProbe qui sont généralement utilisés pour protéger des conteneurs à démarrage lent[1]. Les sondes startupProbe sont une sorte d’inhibiteur des sondes livenessProbe et readinessProbe jusqu’à ce que le conteneur concerné ait complètement démarré.

II. LivenessProbe

Créaction d’un pod avec deux conteneurs (busybox et ubuntu). Seul un de ces conteneurs aura une sonde de vivacité activé.

$ cat << EOF >> cat liveness-exec.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    environment: test
  name: liveness
spec:
  replicas: 2
  selector:
    matchLabels:
      environment: test
  template:
    metadata:
      labels:
        environment: test
    spec:
      containers:
      - name: busybox
        image: k8s.gcr.io/busybox
        command: ["/bin/sh"]
        args: ["-c", "touch /tmp/healthy; sleep 30"]
        livenessProbe:
          exec:
            command:
            - cat
            - /tmp/healthy
          initialDelaySeconds: 5
          periodSeconds: 5
      - name: ubuntu
        image: weibeld/ubuntu-networking
        command:
        - sleep
        - "3600"
EOF

$ kubectl apply -f liveness-exec.yaml

Ordre chronologique des évènements lors du test, pour le conteneur busybox.

touch /tmp/healthy; rm -rf /tmp/healthy;   cat /tmp/healthy;
---------//-------------------------------------------------->
|                   |  initialDelaySeconds  |             Temps
0                   30          (5)         35        (secondes)

Dans ce test, la commande ‘cat /tmp/health’ est envoyé au conteneur ‘busybox’ pour vérifier si ce fichier existe. La sonde sera ensuite lancée toutes les 5 secondes (periodSeconds).

Après création des Pods, ces derniers ne cessent de redémarrer.

$ kubectl get pod -l environment=test
NAME                       READY   STATUS             RESTARTS   AGE
liveness-9f5b59ff7-bwcnq   2/2     Running            6          16m
liveness-9f5b59ff7-xp6xn   1/2     CrashLoopBackOff   7          16m

Voyons ce qui se passe.

$ kubectl describe pod -l environment=test
!--sortie-tronquée--!
Events:
  Type     Reason     Age                    From               Message
  ----     ------     ----                   ----               -------
  Normal   Scheduled  5m30s                  default-scheduler  Successfully assigned default/liveness-9f5b59ff7-xp6xn to k8sn02
  Normal   Pulled     5m27s                  kubelet            Successfully pulled image "k8s.gcr.io/busybox" in 1.19209986s
  Normal   Pulling    5m26s                  kubelet            Pulling image "weibeld/ubuntu-networking"
  Normal   Started    5m24s                  kubelet            Started container ubuntu
  Normal   Created    5m24s                  kubelet            Created container ubuntu
  Normal   Pulled     5m24s                  kubelet            Successfully pulled image "weibeld/ubuntu-networking" in 1.786364915s
  Normal   Pulled     4m11s                  kubelet            Successfully pulled image "k8s.gcr.io/busybox" in 1.030279663s
  Normal   Killing    3m27s (x2 over 4m42s)  kubelet            Container busybox failed liveness probe, will be restarted
  Normal   Pulled     2m56s                  kubelet            Successfully pulled image "k8s.gcr.io/busybox" in 1.079473507s
  Normal   Created    2m56s (x3 over 5m27s)  kubelet            Created container busybox
  Normal   Started    2m55s (x3 over 5m26s)  kubelet            Started container busybox
  Warning  Unhealthy  2m22s (x7 over 4m52s)  kubelet            Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory
  Normal   Pulling    27s (x5 over 5m28s)    kubelet            Pulling image "k8s.gcr.io/busybox"

Le message d’erreur affiche bien que le fichier /tmp/healthy n’existe pas, avec pour résultat un redémarrage régulier du conteneur « busybox » en espérant que le problème soit résolu via le redémarrage.

Pour résoudre le problème, il suffit de créer manuellement le fichier manquant.

$ kubectl exec -it liveness-9f5b59ff7-bwcnq -c busybox -- touch /tmp/healthy

$ kubectl exec -it liveness-9f5b59ff7-xp6xn -c busybox -- touch /tmp/healthy

Noter que le conteneur « ubuntu » n’est pas impacté durant le test.

III. ReadinessProbe

Je vais reprendre le fichier YAML du chapitre 5 avec quelques modifications pour faire les tests avec la sonde de préparation.

$ cat << EOF >> readiness-exec.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx1
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx1
  template:
    metadata:
      labels:
        app: nginx1
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
        readinessProbe:
          exec:
            command:
            - cat
            - /tmp/ready
          initialDelaySeconds: 5
          periodSeconds: 5
EOF

$ kubectl apply -f readiness-exec.yaml

Créer ensuite un objet service avec une adresse IP unique pour accéder au cluster, au lieu de s’adresser à chacun des conteneurs individuellement. Pour rappel, ce type d’objet sert de répartisseur de charges, et permet de rediriger le traffic vers un conteneur ou à ses réplicas.

$ kubectl get deploy
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   0/2     2            0           4m53s

$ kubectl expose deploy nginx-deployment

L’adresse IP du clusteur est donné par,

$ kubectl get svc
NAME               TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
kubernetes         ClusterIP   10.96.0.1               443/TCP   57d
nginx-deployment   ClusterIP   10.101.193.30           80/TCP    7s

La connection au serveur ngnix va échouer, puisque les conteneurs n’ont pas l’air d’être actifs. La commande ‘kubectl get deploy’ n’affiche aucun conteneur (READY 0/2) prêts à recevoir le traffic du réseau.

Si on essaie d’accéder au serveur nginx via l’IP du cluster,

$ curl 10.101.193.30
curl: (7) Failed to connect to 10.101.193.30 port 80: Connection refused

$ kubectl describe svc nginx-deployment
Name:              nginx-deployment
Namespace:         default
Labels:            app=nginx1
Annotations:       
Selector:          app=nginx1
Type:              ClusterIP
IP Families:       
IP:                10.101.193.30
IPs:               
Port:                80/TCP
TargetPort:        80/TCP
Endpoints:         
Session Affinity:  None
Events:       

Il n’y a aucun « endpoint », ou interface cible associé à l’interface du cluster.

$ kubectl get pod -o wide
NAME                               READY   STATUS    RESTARTS   AGE   IP             NODE     NOMINATED NODE   READINESS GATES
nginx-deployment-875dd8f44-fphm9   0/1     Running   0          13m   172.16.1.90    k8sn02              
nginx-deployment-875dd8f44-jhhmd   0/1     Running   0          13m   172.16.2.111   k8sn03    

Les endpoints du cluster devraient être les adresses IP des conteneurs: 172.16.1.90 et 172.16.2.111.

Pour comprendre pourquoi les conteneurs ne sont pas actifs,

$ kubectl describe pod -l app=nginx1
!----sortie-tronquée----!
Events:
  Type     Reason     Age               From               Message
  ----     ------     ----              ----               -------
  Normal   Scheduled  33s               default-scheduler  Successfully assigned default/nginx-deployment-875dd8f44-jhhmd to k8sn03
  Normal   Pulling    31s               kubelet            Pulling image "nginx:latest"
  Normal   Pulled     29s               kubelet            Successfully pulled image "nginx:latest" in 2.017302411s
  Normal   Created    29s               kubelet            Created container nginx
  Normal   Started    28s               kubelet            Started container nginx
  Warning  Unhealthy  2s (x5 over 22s)  kubelet            Readiness probe failed: cat: /tmp/ready: No such file or directory

La sonde Readiness a échoué puisque le fichier /tmp/ready n’existe pas.

Créons ce fichier, et voyons ce qui se passe.

$ kubectl exec -it nginx-deployment-875dd8f44-fphm9 -- touch /tmp/ready

Cette fois ci, si on essaye de se connecter à l’adresse IP du cluster,

$ curl 10.101.193.30
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

Et voilà, on a finalement accès au serveurs nginx.

Voyons si les points d’accès ont été mis-à-jour au niveau du service du cluster.

$ kubectl describe svc nginx-deployment
Name:              nginx-deployment
Namespace:         default
Labels:            app=nginx1
Annotations:       
Selector:          app=nginx1
Type:              ClusterIP
IP Families:       
IP:                10.101.193.30
IPs:               
Port:                80/TCP
TargetPort:        80/TCP
Endpoints:         172.16.1.90:80
Session Affinity:  None
Events:            

Il manque l’adresse IP du deuxième conteneur, qui devrait s’afficher une fois que vous régler le problème d’échec du test de sonde, de la même façon que précédement.

IV. Conclusion

Ce qu’il faut retenir des tests précédents, en cas de défaillance

  • La sonde Liveness redémarre le ou les conteneurs concernés, mais pas leurs pods
  • La sonde Readiness arrête tout simplement le traffic vers ces conteneurs

Les autres méthodes de probes (httpGet et tcpSocket) sont expliqués avec des exemples sur la page officielle de kubernetes.
Je n’ai pas d’exemple concret à vous partager en cas d’échec des probes. Même remarque pour la sonde type startupProbe.

En ce qui concerne les types de sonde,

  • La sonde de vivacité (livenessProbe), permet au cluster de vérifier si un conteneur est opérationel ou non
  • La sonde de préparation (readinessProbe), permet au cluster de vérifier si un conteneur est prêt à recevoir du traffic ou non
  • La sonde de démarrage (startupProbe), permet d’inhiber les deux premières sondes, jusqu’à ce que le conteneur a fini de démarrer

Sources

Configurer les Liveness, Readiness et Startup Probes

[k8s] Résolution de nom

<<< Chap 5 | Sommaire | Chap 7 >>>

Chap 6. Résolution de nom

I. Serveur DNS

La manière la plus simple pour résoudre les noms de domaine sur un réseau, est de faire appel à un serveur DNS.

Avec kubernetes,

  • Le service de DNS est un plugin installé avec Canal qui, je le rappelle, est un CNI (Container Network Interface, ou Interface Réseau des Conteneurs)
  • Les conteneurs devraient passer par l’adresse IP du service DNS pour résoudre les noms DNS (option par défaut)

Adresse IP du service de DNS,

$ kubectl get svc --namespace=kube-system -o wide
NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)                  AGE   SELECTOR
kube-dns   ClusterIP   10.96.0.10   < none>        53/UDP,53/TCP,9153/TCP   31d   k8s-app=kube-dns

Si vous avez du mal à retenir cette commande, utiliser plutôt:

$ kubectl get svc -A

Le namespace du service qui nous intéresse devrait contenir « dns ». A ce service est associé des pods, qui sont nos serveurs de DNS.

$ kubectl get pods --namespace=kube-system -l k8s-app=kube-dns
NAME                      READY   STATUS    RESTARTS   AGE
coredns-f9fd979d6-qxqtm   1/1     Running   1          28d
coredns-f9fd979d6-rvvf5   1/1     Running   1          28d

Le status « Running » nous confirme que le service de DNS est bien fonctionnel.

II. Résolution de nom entre deux Pods

Avec kubernetes, l’enregistrement de chaque seveur déployé dans les Pods se fait automatiquement.

Les conteneurs ont un FQDN de la forme,

<nom_d_hote>.<sous-domaine>.<namespace>.svc.cluster.local

Noter que le nom du service associé au Pod, doit être identique à celui du sous-domaine dans les fichiers yaml, que nous allons voir plus tard.

<nom_d_hote>.<nom_de_service_du_pod>.<namespace>.svc.cluster.local

Pour information, les services avec kubernetes ont aussi un FQDN,

<nom_de_service_du_pod>.<namespace>.svc.cluster.local

Pour la démonstration, j’ai essayé d’utiliser un conteneur BusyBox, mais le DNS ne fonctionne pas comme prévu[1][2][3] bien que j’ai installé la version 1.28.4, qui est la dernière version de la série des 1.28. Je me suis rabatu sur un conteneur avec une base Ubuntu.

1. Fichiers YAML

Déployer deux Pods ainsi qu’un service qui va leurs être associé.

$ cat << EOF >> ubuntu1.yml
apiVersion: v1
kind: Pod
metadata:
  name: ubuntu-1
  labels:
    name: ubuntu
spec:
  hostname: ubuntu-host01
  subdomain: my-subdomain
  containers:
  - image: weibeld/ubuntu-networking
    command: 
    - sleep
    - "3600"
    name: ubuntu
EOF

$ kubectl create -f ubuntu1.yml
$ cat << EOF >> ubuntu2.yml
apiVersion: v1
kind: Pod
metadata:
  name: ubuntu-2
  labels:
    name: ubuntu
spec:
  hostname: ubuntu-host02
  subdomain: my-subdomain
  containers:
  - image: weibeld/ubuntu-networking
    command: [sleep, "3600"]
    name: ubuntu
EOF

$ kubectl create -f ubuntu2.yml
$ cat << EOF >> ubuntu-svc.yml 
apiVersion: v1
kind: Service
metadata:
  name: my-subdomain
spec:
  selector:
    name: ubuntu
  ports:
  - name: default
    protocol: TCP
    port: 80
    targetPort: 9376
  #type: ClusterIP
EOF

$ kubectl create -f ubuntu-svc.yml

2. Test de connectivité entre les deux Pods

Pour la suite, vous avez le choix entre vous connecter au shell sur chacun des Pods,

$ kubectl exec -it ubuntu-1 -- sh

Ou, de lancer directement les commandes de test (ping, nslookup etc.) à partir du Node principal.

C’est cette deuxième option que j’ai choisi pour la simplification des tâches.

Liste des Pods:

$ kubectl get pod -o wide
NAME       READY   STATUS    RESTARTS   AGE     IP            NODE     NOMINATED NODE   READINESS GATES
ubuntu-1   1/1     Running   0          2m59s   172.16.2.51   k8sn03   <none>           <none>
ubuntu-2   1/1     Running   0          81s     172.16.2.52   k8sn03   <none>           <none>

Service associé à ces Pods:

$ kubectl get svc my-subdomain -o wide
NAME           TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE   SELECTOR
my-subdomain   ClusterIP   10.103.177.160 <none>        80/TCP    39s   name=ubuntu

Le Nom des hôtes (hostname), devrait être le même que celui du fichier yaml précédent.

$ kubectl exec -it ubuntu-1 -- hostname
ubuntu-host01

$ kubectl exec -it ubuntu-2 -- hostname
ubuntu-host02

Pings entre les deux Pods

$ kubectl exec -it ubuntu-1 -- ping -c1 172.16.2.52
PING 172.16.2.52 (172.16.2.52) 56(84) bytes of data.
64 bytes from 172.16.2.52: icmp_seq=1 ttl=63 time=0.080 ms

$ kubectl exec -it ubuntu-2 -- ping -c1 172.16.2.52
PING 172.16.2.52 (172.16.2.52) 56(84) bytes of data.
64 bytes from 172.16.2.52: icmp_seq=1 ttl=64 time=0.031 ms

3. Résolution de nom

Commençons par le plus simple.

Connaissant l’adresse IP des Pods et du service associé, la recherche de DNS inversé donne:

$ kubectl exec -it ubuntu-1 -- nslookup 172.16.2.52
52.2.16.172.in-addr.arpa	name = ubuntu-host02.my-subdomain.default.svc.cluster.local.

« cluster.local » étant le domaine par défaut dans lequel est configuré kubernetes, la résolution de nom du Pod concerné, ne peut se faire qu’avec l’un des préfix suivants.

<nom_d_hote>.<sous-domaine>
<nom_d_hote>.<sous-domaine>.<namespace>.svc
<nom_d_hote>.<sous-domaine>.<namespace>.svc.cluster.local

  • Avec un FQDN complet.
$ kubectl exec -it ubuntu-1 -- nslookup ubuntu-host02.my-subdomain.default.svc.cluster.local
Server:		10.96.0.10
Address:	10.96.0.10#53

Name:	ubuntu-host02.my-subdomain.default.svc.cluster.local
Address: 172.16.2.52
  • Avec le nom d’hôte associé au sous-domaine.
student@k8sn01:~$ kubectl exec -it ubuntu-1 -- host ubuntu-host02.my-subdomain
ubuntu-host02.my-subdomain.default.svc.cluster.local has address 172.16.2.52

Vous pouvez le vérifier, mais la résolution de nom fonctionne dans les deux sens.

III. Faits intéressants

1. Les services

Pour rappel, un service associé aux pods peut aussi avoir aussi son propre fqdn.

$ kubectl get svc my-subdomain
NAME           TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
my-subdomain   ClusterIP   10.103.177.160 <none>        80/TCP    30m
$ kubectl exec -it ubuntu-2 -- nslookup 10.103.177.160
160.177.103.10.in-addr.arpa	name = my-subdomain.default.svc.cluster.local.

Contrairement aux Pods, la résolution de nom des services peut se faire aussi avec le nom qui y est associé.

$ kubectl exec -it ubuntu-1 -- host my-subdomain
my-subdomain.default.svc.cluster.local has address 10.102.83.23
$ kubectl exec -it ubuntu-1 -- host ubuntu-host01
Host ubuntu-host01 not found: 3(NXDOMAIN)
command terminated with exit code 1

$ kubectl exec -it ubuntu-1 -- ping -c1 ubuntu-host01
PING ubuntu-host01.my-subdomain.default.svc.cluster.local (172.16.2.51) 56(84) bytes of data.
64 bytes from ubuntu-host01.my-subdomain.default.svc.cluster.local (172.16.2.51): icmp_seq=1 ttl=64 time=0.073 ms

2. Résolution de nom en dehors du réseau des Pods

Les pods créés dans kubernetes peuvent résoudre des fqdn en dehors du réseau de ces pods.

Résolution de nom d’un serveur sur un réseau local (ici, mon serveur Pi-hole)

$ kubectl exec -it ubuntu-1 -- host pihole.mylab.lan
pihole.mylab.lan has address 192.168.0.151

Résolution de nom d’un serveur sur internet.

$ kubectl exec -it ubuntu-1 -- host wikipedia.fr
wikipedia.fr has address 51.254.200.228

Contenu du fichier resolv.conf,

$ kubectl exec -it ubuntu-1 -- cat /etc/resolv.conf
nameserver 10.96.0.10 search default.svc.cluster.local svc.cluster.local cluster.local mylab.lan
options ndots:5

Ceci est tout à fait normal, puisqu’en n’ayant pas défini une stratégie DNS dans le fichier YAML des pods concernés, celle choisie par défaut est « ClusterFirst ».

« ClusterFirst » : toute requête DNS ne correspondant pas au suffixe du domaine configuré dans le cluster, tel que « www.kubernetes.io« , sera transmise au serveur en amont hérité du node (noeud). Les administrateurs du cluster peuvent configurer des serveurs DNS supplémentaires que ce soit des serveurs secondaires (locaux) ou des vrais serveurs récursifs en amont pour faire la résolution. (voir, Politique DNS du Pod)

Sources

Dns pod service
DNS pour les services et les pods

[k8s] Contrôleur de Réplication

<<< Chap 4 | Sommaire | Chap 6 >>>

Chap 5. Contrôleur de Réplication

I. Contrôleurs Kubernetes

Un contrôleur est un processus qui vérifie et régule l’état du cluster.

Les différents contrôleurs de Kubernetes,

  • Node controller
    Contrôleur de l’état des Nodes
  • Replication controller
    Contrôleur de Réplication
  • Endpoints controller
    Contrôleur des Endpoints (voir chap 7 et chap 9)
  • Service Account & Token controllers
    Les contrôleurs s’assurent qu’à chaque espace de noms (namespaces) nouvellement créé, est associé un compte par défaut avec un jetons d’accès API

Dans cet article, je vais surtout en parler d’un contrôleur en particulier, celui qui gère la réplication des Pods.

Il est intéressant d’avoir et d’utiliser un Contrôleur de Réplication pour créer un certain nombre de copies d’un même pod (clone), afin de s’assurer que l’application proposé soit tout le temps disponible, tant qu’au moins un des pods est accessible.  Dans le cas où l’une des copies tombe en panne, un nouveau pod sera créé automatiquement pour remplacer celle qui est défectueuse.
A celà, il faut ajouter la fonctionnalité de Répartisseur de charges (Load Balancing) du contrôleur de réplication.

« En informatique, la répartition de charge désigne le processus de répartition d’un ensemble de tâches sur un ensemble de ressources, dans le but d’en rendre le traitement global plus efficace. » (wikipedia)

Dans les anciens manuels qui traite de la réplication des Pods, il se peut que la méthode proposée soit basée sur les ReplicaControlleurs.
Cependant, ReplicaSet est considérée comme une version améliorée du ReplicaControlleur, de ce fait ReplicaSet est la méthode préconisée pour gérer les réplications des pods.

La gestion du réplication des Pods avec ReplicaSet devrait passer par un Contrôleur de Déploiement, pour faciliter les tâches comme par exemple, la mise-à-jour des pods.

II. Configuration du système de réplicas

Pour déployer un Pod individuellement, l’option au niveau du « kind » dans le fichier YAML devrait être « pod ».

Dans le cas d’un déploiement de plusieurs clones d’un même Pod, à l’object « kind » on associe « deployment ». Ensuite, vous pouvez entrez le nombre de copie de pods à créer dans le champ « spec.replicas ».

Déployons par exemple, un serveur NGINX et son clone.

$ cat << EOF >> nginx-dpl.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx1
spec:
  replicas: 2
 selector:
    matchLabels:
      app: nginx1
  template:
   metadata:
      labels:
        app: nginx1
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 80
EOF
$ kubectl create -f nginx-dpl.yaml

Vérifions qu’on a bien deux Pods.

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-deployment-5bfbcc4c85-b9n26 1/1 Running 0 5s
nginx-deployment-5bfbcc4c85-q2fm5 1/1 Running 0 5s
  • Les codes « b9n26 » et « q2fm5 » sont spécifiques à chaque Pod.
  • Le code « 5bfbcc4c85 » est celui utilisé pour lier les pods au ReplicatSet.
  • « nginx-deployment » est le nom utilisé pour ce déployment.
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
nginx-deployment-5bfbcc4c85 2 2 2 14s
$ kubectl get deploy nginx-deployment
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 2/2 2 2 3m55s
$ kubectl describe deploy nginx-deployment
...
Replicas: 2 desired | 2 updated | 2 total | 2 available | 0 unavailable
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 4m59s deployment-controller Scaled up replica set nginx-deployment-5bfbcc4c85 to 2

III. Modification du nombres de clones

1. A partir du fichier de configuration YAML

Pour modifier le nombres de clones, il suffit d’éditer le fichier yaml à partir duquel nous avons déployé nos Pods, puis de replacer la valeur de replicas par le nombre voulu.

Pour valider le changement,

$ kubectl apply -f nginx-dpl.yaml
Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply
deployment.apps/nginx-deployment configured
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-deployment-5bfbcc4c85-hgtj2 1/1 Running 0 18m
nginx-deployment-5bfbcc4c85-lk4lh 1/1 Running 0 18m
nginx-deployment-5bfbcc4c85-wgrs8 1/1 Running 0 7s
nginx-deployment-5bfbcc4c85-zp5p8 1/1 Running 0 7s

Ici, j’ai rajouté deux Pods supplémentaires, comme vous pouvez le voir dans les évènements associés au serveur de deployement.

$ kubectl describe deploy nginx-deployment
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 22m (x3 over 35m) deployment-controller Scaled up replica set nginx-deployment-5bfbcc4c85 to 2
Normal ScalingReplicaSet 4m1s deployment-controller Scaled up replica set nginx-deployment-5bfbcc4c85 to 4

De la même manière, vous pouvez réduire le nombre de réplicas.

2. Sans le fichier YAML

Dans le cas où vous n’avez plus le fichier YAML à partir duquel vous avez déployé vos Pods, il est toujours possible d’en créér un à partir de la configuration du contrôleur de déploiement courant du serveur nginx-deployment, en récupérant son fichier de configuration.

$ kubectl get deploy nginx-deployment -o yaml > nginx2-dpl.yml

Editer le fichier, en mettant à jour le nombre de réplicas voulu.

Ici, j’ai supprimé toutes les informations qui ne me sont pas utiles, en ne gardant que l’essentiel.

Vous avez ensuite le choix entre re-déployer les Pods à partir du nouveau fichier de configuration,

$ kubectl delete deploy nginx-deployment
$ kubectl create -f nginx2-dpl.yml

Ou de mettre à jour celui existant, comme précédement.

$ kubectl apply -f nginx2-dpl.yml

3. Sans téléchargement du fichier YAML

Vous pouvez le faire,

  • En une seule ligne de commande, en remplaçant X par le nombre de réplicas voulu
$ kubectl scale deploy nginx-deployment --replicas=X
  • En éditant directement le fichier de configuration du contrôleur de déploiement sans avoir à télécharger le fichier yaml
$ kubectl edit deploy

Les modifications sont immédiates, une fois que vous quittez l’éditeur de texte. Faites donc bien attention à ce que vous faites.

IV. Test de réplication des Pods

1. Suppression de Pods

Je vais supprimer un des pods créé précédement.

$ kubectl delete pod nginx-deployment-5bfbcc4c85-q2fm5
pod "nginx-deployment-5bfbcc4c85-q2fm5" deleted
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-deployment-5bfbcc4c85-b9n26 1/1 Running 0 8m23s
nginx-deployment-5bfbcc4c85-jk58k 1/1 Running 0 9s

Aussitôt le Pod supprimé qu’un autre sera créé.

2. Suppression du Contrôleur de Réplication

$ kubectl get rs
NAME DESIRED CURRENT READY AGE
nginx-deployment-5bfbcc4c85 2 2 2 11s
$ kubectl delete rs nginx-deployment-5bfbcc4c85
replicaset.apps "nginx-deployment-5bfbcc4c85" deleted

Lorsqu’on supprime le contrôleur de réplication, les pods qu’il gère seront aussi supprimés.

Par contre, comme vous pouvez le constater sur l’image ci-dessous, tous les pods vont être re-créés automatiquement avec un nouveau contrôleur de réplication.

Petite vérification, pour confirmer que le serveur nginx est bien disponible,

$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-5bfbcc4c85-hgtj2 1/1 Running 0 9m42s 172.16.1.16 k8sn02 <none> <none>
nginx-deployment-5bfbcc4c85-lk4lh 1/1 Running 0 9m42s 172.16.2.33 k8sn03 <none> <none>
$ curl 172.16.1.16
...
<title>Welcome to nginx!</title>

$ curl 172.16.2.33
...
<title>Welcome to nginx!</title>

3. Suppression du Contrôleur de Déployement

Si vous supprimez le Contrôleur de Déployement, alors tous les pods (serveur nginx, ReplicatSet et Deploy) vont être détruits tout simplement.

$ kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 1/1 1 1 47m
$ kubectl delete deploy nginx-deployment
deployment.apps "nginx-deployment" deleted

V. Introduction à l’objet Service

Vous ne l’avez peut être pas remarqué lors des tests ci-dessus, mais il n’y a aucune garantie que le serveur nginx reste disponible à partir du même adresse IP, qui est celle donnée par les pods. Les pods peuvent se recycler à tout moment.

On dit que l’adresse IP des pods est éphémère.

Au lieu de passer du temps à chercher une adresse IP valide d’un pod à chaque fois qu’on en a besoin,  l’astuce est de passer par un cluster d’IP qui regroupe tous les IPs des pods concernés. Le cluster va afficher une adresse IP unique, et joignable uniquement à partir des nodes kubernetes, pour atteindre le serveur géré par ces pods.

$ kubectl expose deploy nginx-deployment
service/nginx-deployment exposed
$ kubectl get svc nginx-deployment
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-deployment ClusterIP 10.108.212.164 <none> 80/TCP 13s

Si le port 80 ne vous convient pas, vous pouvez toujours éditer le fichier de configuration du service avec "kubectl edit svc nginx-deployment"pour y apporter les modifications.Lancer « curl » sur l’IP du cluster, pour vérifier que vous avez accès à la page web du serveur ngnix.

$ curl 10.108.212.164

Ce cluster ne sera pas directement accessible à partir de votre réseau local. Quelques manipulations supplémentaires seront à prévoir.

Source

Kubernetes Components
Kubernetes: ReplicaSet

[k8s] Fichier YAML

<<< Chap 3 | Sommaire | Chap 5 >>>

Chap 4. Fichier YAML

Je vous ai déjà présenté deux méthodes pour créer des Pods.

  • Le premier, à partir d’un fichier yaml, aussi appelé Manifest (1).
  • Le second, en récupérant une copie du fichier de configuration d’un pod fonctionnel (2).

Dans cette partie, je vais essayer de vous expliquer

  • Le déboguage un fichier yaml
  • La création de son propre fichier de configuration à partir d’un pod existant

I. Fichier manifest YAML

Un fichier manifest est un fichier de configuration pour le déployement de pods. Ce fichier est écrit dans un format de type YAML, comprennant au moins 4 parties: AKMS

  • A: version de l’API
  • K: KIND, ou type (Pod, etc.)
  • M: METADATA, ou métadonnée
  • S: SPEC, spécifications de l’objet

Une exemple de fichier de configuration YAML avec le stricte minimum comme information,

$ cat << EOF >> pingtest.yml
apiVersion: v1
kind: Pod
metadata:
  name: pingtest01
spec:
  containers:
  - name: pod1
    image: docker.io/centos/tools:latest
    command:
    - /sbin/init
EOF

Vous pouvez utiliser l’extension .yml aussi bien que .yaml.

II. Test sur le fichier YAML et vérification de son contenu

Pour cet exemple, nous allons manipuler le fichier pingtest.yml créé précédement.

Décaler la ligne au niveau de « command » de deux crans vers la gauche, en supprimant deux espaces. Puis, essayer de déployer un pod et voyez le résultat.

$ kubectl create -f pingtest.yml

D’après le message d’erreur, le déployement du pod ne peut s’effectuer à cause du champ « command », qui n’est pas reconnu. Comment çà se fait, alors qu’on a récupéré une copie d’un fichier fonctionnel?

Grâce à la commande « explain » de kubectl, essayons de comprendre ce qui ne va pas.

N’hésitez pas à lire les détails avec l’option help, pour avoir une explication sur l’utilisation de « kubectl explain ».

$ kubectl explain --help

Revenons au fichier pingtest.yml qui nous a posé un problème.

L’objet créé est de type Pod (KIND=Pod); en tout cas, c’est ce qui est marqué dans le fichier yaml. Voyons les options, ou champs, disponibles pour ce type en particulier,

$ kubectl explain pod

Les éléments le plus à gauche dans notre fichier de configuration yaml devraient donc être: apiVersion, kind, metadata et spec.

L’objet « commande », qui se trouve sous « spec » dans notre configuration modifié, est celui qui nous pose un problème.

Voyons donc la liste de champs disponible sous la catégorie « spec ».

$ kubectl explain pod.spec

Il n’y a pas d’object « command », par contre « containers » y est présent.

Ce qui signifie que « container » doit être un peu plus à droite par rapport à l’object « spec », et que « command » ne devrait pas situer sur la même colone que « container », si toutefois le champ existe.

$ kubectl explain pod.spec.containers

Bingo, le champ « command » apparaît finalement.

De ce fait, l’objet « command » ainsi le ou les objets sous-jacents (ici, la ligne de commande « /sbin/init »), devraient être décalés vers la droite par rapport à leurs positions initiales.

Ainsi, les champs « command », « name » et « image » devraient se trouver sur la même colone.

Voyez l’importance de copier le contenu des fichiers YAML, exactement tel quel.

III. Création d’un fichier YAML

Il nous est aussi possible de créér notre fichier yaml, en fonction de notre besoin, à partir d’une configuration récupérée sur le hub de docker.

Reprennons une commande qu’on à déjà vu, pour créer un lancer un nouveau pod.

$ kubectl run nginx-hub --image=nginx

N’oublier pas de vérifier que le pod nouvellement créé, est fonctionnel avant  de passer à la suite.

$ kubectl get pod -o wide

$ ping <adresse_ip_du_pod>

Récupérer ensuite, une copie du fichier de configuration du pod nginx-hub.

$ kubectl get pod nginx-hub -o yaml > nginx-pers.yml

Détruisez l’ancien pod,

$ kubectl delete pod nginx-hub

Personnaliser le contenu du fichier yaml.

Je vous suggère

  • Ne garder que les champs « labels.run », « name » et « namespace »
  • Supprimer « status » ainsi que ces champs
  • Garder le reste de la configuration tel quel, à moins que vous sachiez ce que vous faîtes

Pour créer un pod à partir de ce fichier,

$ kubectl create -f nginx-pers.yml

Pour détruire ce pod,

$ kubectl delete -f nginx-pers.yml

Vous pouvez aussi utiliser « kubectl delete pod nginx-pers ».

J’espère que vous êtes maintenant à l’aise pour créer des pods à partir d’un fichier yaml.

IV. Astuces avec VIM

L’éditeur de texte VIM m’a posé les soucis suivants:

  • La tabulation ne respecte pas le format des fichiers YAML.
    Evitez à tout prix l’utilisation de la Tabulation avec les options par défaut sur les fichiers YAML.
  • Il m’est aussi arrivé d’avoir la ligne à laquelle j’ajoute un « : »,  s’auto-décaler vers la droite, comme si j’ai tapé sur la touche tab en début de ligne.
    L’option « identexpr » peut faire l’affaire pour éviter ce désagrément.

Voici le contenu du fichier vimrc.

$ cat << EOF >> ~/.vimrc
" tabstop [ts]:          Equivalence d'une Tabulation en nombre de charactères
" softtabstop [sts]:     Nombres d'espaces à ajouter
" shiftwidth [sw]:       Nombres d'espaces à ajouter en tapant deux fois sur ">" ou "<"
" expandtab:             Conversion des tabulations en espaces 
" indentexpr             Empêche VIM d'ajouter automatiquement des alinéas si la valeur donnée est vide
set ts         = 2
set sts        = 2
set sw         = 2
set expandtab
set indentexpr = ""
EOF

Vous pouvez maintenant utiliser les tabulations en toute sécurité sur les fichiers YAML.

Sources

How Kubernetes Networking Works – Under the Hood
vim settings for YAML files
How can I stop vim automatically inserting a tab in CSS files?

Kubectl et Premiers pas avec kubernetes

<<< Chap 2 | Sommaire | Chap 4 >>>

Chap 3. Kubectl et Premiers pas avec kubernetes

I. Master vs Worker

Comme nous l’avons déjà vu, Kubernetes fonctionne avec un modèle de type Master/Worker.

Le Master (Plan de Contrôle), est comme un chef d’orchestre du système. C’est lui qui dirige tout, comme par exemole, de la distribution des resources entre les différents Nodes, etc.

Les Workers (aussi appelé, Nodes ou Minions), eux par contre, ne s’occupent que de créer ou détruire les Pods (on ne parle pas de conteneurs avec kubernetes).

De ce fait, une fois le cluster kubernetes créé, tout se passera au niveau du Master. Il n’y a pas besoin de se connecter sur les différents nodes pour la suite.

Je vais reprendre ici, une des images de mon précédent article, pour illustrer la suite.

Les pods générés dans le plan de contrôle (Master).

  • API Server
    Valide et configure les données pour les objets API (Pods, Services, etc.)
  • Controller Manager
    Daemon qui intègre les boucles de contrôle principales de kubernetes (replication controller, namespace controller, etc.)
  • Scheduler
    Assigne les Pods aux différent Nodes
  • etcd
    Stocke les données de configuration du cluster

Les Pods ci-dessous exisent aussi bien, au niveau des différents Nodes que du Master, afin que garantir le bon fonctionnement du traffic réseau.

  • kube-proxy
  • canal

Par contre, les Pods qui gèrent le service de DNS en local n’existent qu’au niveau des Pods, pour la raison simple qu’il n’est pas possible (en théorie) d’installer des Pods sur le Master.

  • CoreDNS

II. Namespace

Après déployement du cluster kubernetes, le status des Pods créés par défaut, devrait être sur « RUNNING ». Si l’un de ces pods tombe en panne, le clusteur risque de ne pas fonctionner correctement.

student@k8sn01:~$ kubectl get pods --namespace kube-system

Pour séparer les pods créés par l’utilisateur avec ceux du système, kubernetes utilise les « namespaces » pour cloisonner ces Pods, qui vont ainsi se retrouver dans au moins deux types d’environnements différents.

La liste des différents namespaces est donnée par la commande

student@k8sn01:~$ kubectl get namespaces

student@k8sn01:~$ kubectl get ns

De cette liste, vous devriez reconnaître « kube-system », qui contient l’ensemble de Pods du système. Les pods créés par l’utilisateur devraients se trouver dans le namespace « kube-default », à moins que vous ne décidez autrement.

Ainsi, pour avoir la liste de tous les pods du le cluster,

student@k8sn01:~$ kubectl get pods --all-namespaces

student@k8sn01:~$ kubectl get po -A

Par contre, ce qui nous intéresse pour la suite, ce sont les pods dans notre environnement de travail. Ces pods se trouvent dans le namespace « default ».

student@k8sn01:~$ kubectl get pods --namespace default
No resources found in default namespace.

Ou, plus simplement

student@k8sn01:~$ kubectl get pods
No resources found in default namespace.

Ici, il n’y a aucun Pod de créé, d’où le message.

Ce qu’il faut retenir ici, c’est la conception du système fait en sorte que les pods entre les différentes namespaces (par exemple « default » et « kube-system ») ne peuvent pas se comminiquer entre eux.

III. Les primitives

Une primitive, dont je vous donne la liste ci-dessous, est l’ensemble d’outils nécessaires à la construction d’un cluster kubernetes. Il nous faut,

  • Pods
    Peuvent contenir un ou plusieurs conteneurs
  • Labels
    Sont des étiquettes qui peuvent être attachés à des objets Kubernetes comme les Pods
  • Contrôleurs (Replication, Daemon, Job et Deployment)
    Comme son nom l’indique, permet de contrôler l’état du cluster
  • Services
    Un groupe de pods peut avoir une interface unique accessible de l’extérieur

En une seule ligne de commande, vous pouvez avoir l’état du primitive.

student@k8sn01:~$ kubectl get ns,svc,pods,deploy,rs,ds -A

Cette même commande peut être décomposée en plusieurs commandes.

  • Information sur les pods (pods ou po)
$ kubectl get pods -A

$ kubectl get po -A
  • Information sur les différents types de contrôleurs

(replicasets ou rs)

$ kubectl get replicasets -A

$ kubectl get rs-A

(daemonset ou ds)

$ kubectl get daemonsets -A

$ kubectl get ds -A

(deployments ou deploy)

$ kubectl get deployments -A

$ kubectl get deploy -A
  • Information sur les services (services ou svc)
$ kubectl get services -A

$ kubectl get svc -A

Pour avoir une idée de comment interagissent les contrôleurs avec les Pods,


C’est un peu le fouilli, c’est pour celà que je vous ai fait un récapitulatif ci-dessous.

IV. Les différents services de kubeadm

Voyons la liste de services nécessaire à Kubeadm pour fonctionner correctement.

student@k8sn01:~$ apt-cache depends kubeadm
kubeadm
Depends: kubelet
Depends: kubectl
Depends: kubernetes-cni
Depends: cri-tools

Kubelet, l’agent installé sur chacun des Workers (Minions), sert d’intermédiare entre ces Nodes et le Master. L’une de ces fonctions est de recevoir la spécification des Pods (podspecs) dans le format YAML our JSON, pour pouvoir les déployer sur l’un des nodes.

Je ne vous présent plus l’outil kubectl, puisque c’est celui qu’on a utilisé pour

  • Initialiser le cluster (kubectl init)
  • Joindre les différents nodes au Master (kubectl join)
  • Obtenir des informations du cluster (kubectl get)

On n’en a parlé aussi des CNIs, avec l’installation de Canal qui est un plugin réseau pour conteneur.

Pour une utilisation avancée de kubernetes, il a été introduit depuis la version 1.5 le CRI ou Interface d’Exécution du conteneur.

V. Création d’un pod

Une nouvelle commande kubectl à connaîte, est « kubectl run » pour télécharger une image à partir du Hub de Docker, qui est un grand centre de stockage d’images pour conteneurs.

Comme test, je vais créer un serveur web nginx.

student@k8sn01:~$ kubectl run nginx --image=nginx

Récupérer le nom du node sur lequel le serveur est installé, puis son adress IP.

student@k8sn01:~$ kubectl describe pods/nginx

A partir du Master, vérifier que le serveurs nginx s’est bien lancé en utilisant la commande curl.

student@k8sn01:~$ curl <adresse_ip_de_nginx-app>

Et voilà, vous avez un serveur web qui tourne sur kubernetes.

Voyons maintenant la liste de Pods installés dans le cluster.

Ici, j’ai rajouté deux pods suplémentaires (« nginx-app » et « nginx-serv »),

  • afin de bien pouvoir voir la différence entre les pods créés par défaut (namespace: « kube-system ») (2) et ceux créés par l’utilisateur (1)
  • les Pods sont répartis entre les différents Nodes disponibles
    • « nginx » et « nginx-app » sur k8sn03
    • « nginx-serv » sur k8sn02

Je suppose qu’à ce stage, vous avez créé un ou plusieurs pods. Pour supprimer ceux dont vous n’avec pas besoin, lancer la commande.

k8sn02:~$ kubectl delete pod <nom_du_pod>

Je ne vous l’ai pas implicitement annoncé, mais jusqu’ici, nous avons vu deux méthodes pour créer un pod avec kubernetes.

  • Avec le fichier yaml téléchargé sur internet, nous avons créé un pod pour la gestion du réseau cannal
    $ kubectl apply -f canal.yaml
    • La suppression de tel type de pode se fait grâce à la commande
      $ kubectl delete -f monFichier.yaml
  • En passant par un hub de Docker, nous avons téléchargé un pod prêt à l’emploi
    $ kubectl run nginx --image=nginx
    • Pour la suppression du pod
      $ kubectl delete monPod

Source

Kubernetes: Get started