Blog ENI : Toute la veille numérique !
🐠 -25€ dès 75€ 
+ 7 jours d'accès à la Bibliothèque Numérique ENI. Cliquez ici
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici
  1. Livres et vidéos
  2. Haute disponibilité sous Linux
  3. Déploiement avec Kubernetes
Extrait - Haute disponibilité sous Linux De l'infrastructure à l'orchestration de services (Heartbeat, Docker, Ansible, Kubernetes...)
Extraits du livre
Haute disponibilité sous Linux De l'infrastructure à l'orchestration de services (Heartbeat, Docker, Ansible, Kubernetes...)
1 avis
Revenir à la page d'achat du livre

Déploiement avec Kubernetes

Introduction

Dans les chapitres précédents, nous avons vu comment construire des images Docker de notre application et comment les déployer de façon semi-automatique sur nos serveurs grâce à des scripts Ansible. Avec un orchestrateur comme Kubernetes, nous savons maintenant comment nous affranchir des contraintes liées à un environnement statique, en le laissant choisir où déployer à notre place. Nous allons maintenant voir comment déployer eni-todo avec cet orchestrateur. Pour cela, il faut apprendre à utiliser certaines ressources. 

Tous les exemples Kubernetes présentés dans ce chapitre et les suivants sont mis à disposition dans le repository github k8s_manifests.

Du pod aux déploiements

1. Pod

Nous avons vu qu’il est possible de créer un pod utilisant un container tomcat-eni-todo-h2-env:dev. Nous utilisons tout d’abord cette version, nous ajouterons l’externalisation de la base de données plus tard.

Nous lançons donc un pod eni-todo-h2 avec le container utilisant cette image. Il faut s’assurer que le container a bien démarré et fonctionne correctement.

$ kubectl run eni-todo-h2 \ 
   --image=registry.diehard.net:5000/eni-todo-tomcat-h2:dev \ 
   --env=" 
DB_DTB_JDBC_URL="jdbc:mysql://mariadb.diehard.net:3306/db_todo" \
   --env="DB_DTB_USERNAME=springuser" ... 
pod/eni-todo-h2 created 

Il est également possible de lancer le container avec un fichier YAML et la commande suivante :

$ kubectl apply -f eni-todo-h2.000.pod.yaml 
pod/eni-todo-h2 created 

Les fichiers YAML utilisés dans ce chapitre sont dans le dossier src/main/kubernetes du dépôt git de l’application eni-todo (https://github.com/halnx-todo/k8s-manifests).

a. Sondes et contrôle de santé

Kubernetes offre un système de sonde permettant de vérifier la santé (healthcheck) d’un pod. Il y a trois types de sondes :

  • livenessprobe

  • readinessprobe

  • startupprobe

Toutes les trois se configurent de la même façon, avec des objets dans la définition du container.

Il existe trois mécanismes de sonde possibles :

  • tcpSocket : la kubelet présente sur le nœud vérifie que le container a bien ouvert le port défini dans les paramètres de cette sonde.

  • Exec : la kubelet présente sur le nœud vérifie que le script défini dans les paramètres de cette sonde s’exécute correctement (code retour 0) dans le container observé.

  • httpGet : la kubelet présente sur le nœud vérifie que l’appel HTTP défini dans les paramètres de cette sonde retourne un code retour tel que défini (200 par défaut).

images/chap06_001.png

Figure 1 : Spécifications des sondes d’un pod

Les sondes ont :

  • un délai d’attente avant d’être activées (initialDelaySeconds) ;

  • une fréquence d’exécution (periodSeconds) ;

  • un seuil ou nombre d’erreurs...

Services et Endpoints

Nous sommes passés rapidement sur le besoin d’avoir plusieurs pods pour maintenir le service, mais c’est précisément cette redondance qui permet de maintenir cette haute disponibilité.

Il faut donc un mécanisme pour répartir les flux des requêtes vers les pods. C’est le rôle des objets Service. Cette abstraction est utilisée pour fournir les informations nécessaires au cluster afin de faire les configurations réseau permettant la communication dans le cluster et avec l’extérieur.

Comme nous l’avons évoqué dans les chapitres précédents, les labels sont utilisés pour permettre la sélection des objets dans Kubernetes. Le mécanisme des labels est omniprésent dans un cluster. Les labels sont également utilisés pour associer (mapping) les pods à un service via l’utilisation d’un sélecteur.

1. Service de type ClusterIP

Un service est donc une abstraction de Kubernetes pour permettre la résolution réseau et l’exposition des pods. Le DNS d’un service est de la forme :

<service>.<namespace>.svc.cluster.local 

Un Service permet également d’exposer l’application sur un port différent du port exposé par le pod, si nécessaire.

apiVersion: v1 
kind: Service 
metadata: 
  labels: 
    app: eni-todo 
  name: eni-todo 
spec: 
  ports: 
  - port: 80 
    name: http8080 
    protocol: TCP 
    targetPort: 8080 
  selector: 
    app: eni-todo 
  sessionAffinity: ClientIP 
  type: ClusterIP 

Vous pouvez ensuite chercher les pods qui sont identifiés par des labels.

$ kubectl get po -l app=eni-todo 
NAME                       READY   STATUS    RESTARTS   AGE 
eni-todo-95755d699-gldr6   1/1     Running   0          55m 
eni-todo-95755d699-jrzrg   1/1    ...

Secret et ConfigMap

Nous avons vu que nous écrivons les variables d’environnement directement dans la définition des pods (template de pod). Il est toujours préférable d’externaliser les fichiers de configuration, c’est à cela que servent ConfigMap et Secret.

images/chap06_009.png

Figure 9 : ConfigMaps et Secret

Il est possible de stocker des couples variable/valeur dans des objets de type ConfigMap et Secret. Mais là où un ConfigMap stocke les informations directement au format texte et en clair, un secret demande une valeur encodée au format base64, par exemple :

mysql_root_password: cjAwdC1hZWtpZThhaHdhaV8= 

Le stockage dans la base etcd est également différent, car les secrets sont chiffrés dans la base si l’option est activée dans kube-apiserver (--encryption-provider-config). 

L’encodage en base64 n’est pas un chiffrement sécurisé. Il s’agit d’un changement de référentiel de codage de l’information. Les informations binaires historiquement sur 8 bits passent sur 7 bits, c’est un chiffrement qui a pour but d’éviter les soucis de caractères spéciaux pouvant être mal interprétés lors des transferts réseau.

Il est également possible de stocker des fichiers dans les deux types d’objets.

Dans un ConfigMap :

apiVersion: v1 
kind: ConfigMap 
metadata: 
  name: mariadb-init 
data: 
  init.sh: | 
    #!/bin/sh ...

StatefulSet

Après avoir vu comment accéder à une base de données externe, nous allons voir comment mettre une base de données dans notre cluster.

Un pod peut s’instancier sur n’importe quel node en fonction des ressources disponibles. Si cela fonctionne très bien pour des services sans conservation de l’état (stateless), ce n’est pas le cas des bases de données dont le rôle même est la conservation des données.

Au début de Kubernetes, une astuce consistait à forcer l’exécution d’un pod sur un node précis via une sélection par l’attribut nodeName ou bien à utiliser les mécanismes de nodeAffinity ou nodeSelector, pour nous assurer qu’un pod était instancié sur le node ayant des répertoires dédiés au stockage des données du pod, que le pod pouvait monter directement avec hostpath.

apiVersion: v1 
kind: Pod 
metadata: 
  labels: 
    app: mariadb 
  name: mariadb 
spec: 
  volumes: 
  - name: test-volume 
    hostPath: 
      path: /data/mariadb 
      type: DirectoryOrCreate 
  containers: 
  - image: mariadb:10.5.8 
    name: mariadb 
    nodeName: k8s-worker01 
    env: 
    - name: MYSQL_USER 
      value: "springuser" 
    - name: MYSQL_PASSWORD 
      value: "mypassword-quoor-uHoe7z" 
    - name: MYSQL_DATABASE 
      value: "db_todo" 
    - name: MYSQL_ROOT_PASSWORD 
      value: "r00t-aeKie8ahWai_" 
    volumeMounts: 
    - mountPath: /var/lib/mysql 
      name: test-volume 
    ports: 
      - name: mariadb 
        containerPort: 3306 
        protocol:...

PersistentVolume

Comme nous l’avons évoqué plusieurs fois, Kubernetes est un orchestrateur qui sélectionne, parmi son pool de nodes et selon les critères qu’on lui a donnés, le node sur lequel instancier le pod. Cela signifie que pour apporter de la persistance de données, celle-ci doit pouvoir être utilisable par le pod sur n’importe quel node. Les containers constituant les pods vont devoir monter les volumes présents sur leur node. Le node lui-même ayant probablement un montage de système de fichier à réaliser en amont (NFS, Gluster FS, etc.). Les objets PersistentVolume et PersistentVolumeClaim fournissent aux clusters (node, kublet…) les informations nécessaires pour réaliser ces différents montages de systèmes de fichier.

Cette mécanique est intrinséquement liée aux technologies sous-jacentes et aux outils extérieurs au cluster. Cela dit, il existe des solutions de stockage utilisant un cluster Kubernetes, telles que Rook, GlusterFS ou OpenEBS, qui dédient au service de stockage des espaces de stockage des nodes du cluster, pour assurer la persistance des volumes de données qu’ils proposent et en assurer leur cohérence.

images/chap06_010.png

Figure 10 : Spécifications d’un volume persistant

Un volume persistant (Persistent Volume ou PV) dispose de plusieurs attributs :...

PersistentVolumeClaim

Une demande de volume persistant (Persistent Volume Claim ou PVC) est une sorte de contrat qui lie un volume persistant à un namespace. Lors de sa création, le cluster Kubernetes cherche à fournir un volume persistant correspondant aux besoins définis dans le PVC. Si d’aventure il possède la classe de stockage demandée, il l’utilise pour créer un nouveau PVC conforme à la définition de la classe demandée et aux besoins définis dans le PVC initial. Sinon, il recherche dans son pool de volumes persistants celui correspondant au mieux à la demande.

images/chap06_011.png

Figure 11 : Spécifications d’un PVC

Lors de l’affectation du PV au PVC, l’attribut claimRef du PV est renseigné avec les références du PVC (name).

StorageClass

images/chap06_012.png

Figure 12 : Spécifications d’une classe de stockage

La classe de stockage (StorageClass) est la ressource permettant de fournir dynamiquement des PVC (et les PV derrière).

Elle a plusieurs attributs :

  • provisioner : le provisioner correspond au CSI (Container Storage Interface), le driver (pilote) utilisé pour réaliser la communication avec le serveur de stockage.

  • mountOptions : il s’agit des options passées par le nœud lors du montage du système de fichiers.

  • parameters : ce sont les paramètres nécessaires au driver.

  • reclaimPolicy : il s’agit de la définition du cycle de vie du PV affecté au PV créé avec cette classe de stockage (Delete, Retain ou Recycle).

  • volumeBindingMode : c’est la politique utilisée pour tracer le montage du volume entre le PVC appelant la classe de stockage et le PV créé par celle-ci.

  • Immediate : l’information liant le PV au PVC est créée avant l’utilisation de celui-ci.

  • WaitForFirstConsumer : l’information liant le PV au PVC est faite lors du montage du volume par un nœud.

  • allowedTopologies : il s’agit des labels autorisés pour les PV qui vont être créés. Cet attribut est utilisé principalement (pour les CSI qui le permettent) pour garantir que le volume est créé...

Routes ingress

1. Le contrôleur des flux entrant (ingress controller)

Comme nous l’avons vu précédemment, les services ne sont pas parfaitement adaptés aux serveurs web et à la notion d’hébergement mutualisé de services web (virtual hostname). Les services web sont généralement délivrés, par convention, sur les ports 80 et 443. De plus, de nombreux systèmes de sécurité bloquent des appels vers des ports non standards.

Pour les clusters Kubernetes hébergés par des fournisseurs de services cloud, il est possible d’utiliser les services de type load balancer, mais chaque service ouvert coûte alors le prix d’un répartiteur de charge du fournisseur.

Le contrôleur des flux entrant (ingress controller) est un contrôleur au sens Kubernetes, c’est-à-dire qu’il écoute les événements et réagit aux modifications de certains types de ressources - dans notre cas, les ressources ingress.

L’ingress controller instancie un ou plusieurs pods de reverse proxies avec des droits élevés (hostPort, NET_BIND_SERVICE) qui peuvent, si on les configure à cette fin, écouter sur les ports 80 et 443 des nodes sur lesquels ils tournent. Ces reverse proxies feront le routage des requêtes HTTP vers les services adéquats tels que définis...

Job et CronJob

Il est possible de lancer des tâches (jobs) dans Kubernetes. Les jobs sont uniques : ils ne sont joués qu’une seule fois.

images/chap06_014.png

Figure 14 : Spécifications d’un Job et d’un CronJob

Les attributs du job sont les suivants :

  • backoffLimit est le nombre d’essais infructueux (exit !=0) avant de considérer le job comme étant en échec (fail).

  • completions est le nombre de pods qui doivent réussir (exit == 0) pour considérer le job comme étant réussi.

  • parallelism est le nombre de pods susceptibles d’être exécutés en même temps.

  • ttlSecondsAfterFinished est le temps d’attente avant de laisser les mécanismes de nettoyage supprimer les pods finis. Si l’attribut n’est pas défini, le job ne sera pas supprimé automatiquement.

  • activeDeadlineSeconds définit le temps d’attente avant d’essayer de supprimer le job.

Il est également possible d’avoir des tâches récurrentes programmées à certaines dates : les cronjobs.

Les attributs notables d’un cronjob sont les suivants :

  • schedule définit au format cron la périodicité d’exécution des jobs.

  • concurrencyPolicy définit la politique à suivre si la périodicité demande le lancement d’un nouveau job tandis que le job précédent n’est...

DaemonSet

Le DaemonSet est une ressource qui lance une instance unique de pod sur chacun des nodes du cluster correspondant à sa cible de selector.

Ses attributs notables sont les suivants :

  • minReadySeconds désigne le délai avant que le daemonset dont le pod est ready soit considéré comme ready.

  • revisionHistoryLimit désigne le nombre de versions de daemonset conservées.

  • updateStrategy définit la stratégie de mise à jour, comme pour les Deployment

C’est un composant très important dans le cluster Kubernetes. Il est utilisé pour le déploiement des CNI (Container Network Interface) ou des CSI (Container Storage Interface) par exemple. Nous le reverrons dans les prochains chapitres.