Les compétences du DBA moderne : Kubernetes

Dans un précédent article nous avons abordé la nécessite de se former sur les conteneurs Docker.

Docker permet d’encapsuler un logiciel, un service, du code et ses dépendances. Une image correspond donc à un package exécutable, très léger, permettant l’exécution d’une application. Un conteneur, lui n’est qu’une instance en cours d’exécution de cette image. Plusieurs conteneurs issus de la même image, peuvent cohabiter sur un même moteur Docker.

Voici une infographie extraite du site Web de Kubernetes qui résume parfaitement l’évolution dans le temps :

Ce moteur est quant à lui multi-plateforme et compatible avec de nombreux cloud providers.

Mais, seul, le moteur Docker rends certes des services mais il manque une pierre à l’édifice, du moins pour passer sereinement en production : l’orchestration de conteneurs.

C’est à ce niveau qu’intervient Kubernetes qui va avoir la charge de l’automatisation du déploiement, de la gestion de la montée en charge, de la disponibilité de conteneurs.

D’un point de vue étymologique, Kubernetes est issu du grec κυβερνήτης signifiant pilote, ou timonier, fréquemment nommé dans sa forme courte K8s, un K puis 8 lettres dont on se moque et un « s » pour terminer. Plutôt que de plagier, je vous laisse lire la page Wikipedia si vous êtes intéressé par l’origine de ce projet dont Google est le grand artisan.

Encore une fois, tout comme c’était le cas pour l’article concernant Docker, mon but n’est pas de vous abreuver de tonnes d’informations techniques, de vous submerger de termes et de concepts pour vous transformer en ingénieur K8s (il existe de très bons articles sur le sujet sur Internet et sur les plateformes de formation en ligne) mais bien de vous donner les informations nécessaires et suffisantes pour que, dans votre rôle de DBA, vous soyez en mesure de proposer une architecture SQL Server s’appuyant sur cet orchestrateur.

J’aurais aussi bien pu parler de Docker Swarm, un « concurrent » de Kubernetes, mais, d’un point de vue pragmatique, c’est bien de dernier qui est largement utilisé en entreprise. Un prochain article sur Swarm pourrait voir le jour, on ne sait jamais. Tout comme il est probable que je poursuive cette série en abordant OpenShift, le gestionnaire de conteneurs K8s maintenu par Red Hat et qui a de fortes chances d’être présent dans des architectures OnPrem.

Kubernetes est un orchestrateur de conteneurs. A ce titre, il est responsable de la disponibilité du service fourni par un conteneur. Et donc, en toute logique, on parle de cluster Kubernetes.

Comme dans bon nombre d’architecture cluster, on retrouve la notion de Master et de Nodes.

Les Master comprenant des composants destinés à la gestion et à la surveillance du cluster. On y retrouve pelle-mêle, un serveur d’API qui permet d’interagir avec le cluster K8s au travers de lignes de commande, un service en charge de la surveillance des performances de chaque Nœud du cluster, un service en charge de maintenir un état désiré (l’exécution de conteneurs) sur le cluster en sollicitant le démarrage ou l’arrêt des conteneurs sur les différents Nodes. Les Nodes sont des serveurs sur lesquels s’exécute le moteur Docker (et quelques composants K8s) et qui vont permettre la prise en charge de vos conteneurs.

Kubectl

Kubectl est l’utilitaire en ligne de commande qui permet d’interagir avec le cluster K8s. Il y a pléthore de documentation et d’exemples sur internet, je ne vais pas m’étendre sur le sujet.

Kubectl permet de gérer les Nodes, les Pods, les Service, les Volumes, Services, etc … Bref tous les types de ressources qui existent dans un cluster K8s.

Voici quelques exemples de commandes pour les opérations courantes :

Commande Signification
kubectl get componentstatuses Renvoie l’état des composants du cluster
kubectl create|apply -f ./service.yaml Création d’une ressource
kubectl delete -f ./service.yaml Suppression d’une ressource
kubectl run nginx –image=nginx Exécute une seule instance à partir de l’image nginx
Kubectl get pods Affiche la documentation des Pods
kubectl get pods –selector=app=restexample Liste les pods qui correspondent au lable restexample
kubectl explain pods Affiche le détail de tous les Pods
kubectl get service(s) Liste tous les services créés ou celui spécifié
kubectl explain service(s) Affiche le détail de tous les Services ou celui spécifié
kubectl get deployment(s) Liste tous les deployments créés ou celui spécifié
kubectl explain deployment(s) Affiche le détail de tous les Deployments ou celui spécifié
kubectl get node(s) Liste tous les nodes ou celui spécifié
kubectl explain node(s) Affiche le détail de tous les nodes ou celui spécifié
kubectl logs <pod-name> Visualisation des logs d’un conteneur / pod
kubectl exec -it <pod-name> — bash Exécution d’une commande dans un conteneur

Je vous suggère de toujours garder sous la main un fichier contenant vos commandes les plus usitées ou bien la cheatsheet de Kubernetes.

On peut invoquer directement des ordres au travers de Kubectl, mais on peut également lui fournir en entrée un fichier … texte. Avec un formalisme spécifique certes, mais cela reste un bête fichier texte. YAML, acronyme de Yet Another Markup Language (ou YAML Ain’t Markup Language pour d’autres …), pour être précis ! le format permet la représentation de données sous forme de paires clé/valeur par une combinaison de listes, de tableaux, …

Ce fichier va être intégré par le cluster K8s comme étant du paramétrage, ou bien des choses à faire (déploiement d’une application). Le processeur YAML repose sur l’indentation, mais attention pas de tabulation, des espaces (leur nombre importe peu) …

Nodes

Il n’est pas rare de parler des Nodes en tant que Worker Node voire même Minion (oui oui les petits êtres jaunes des films …).

Chaque Worker exécute un Kubelet, lui-même sous forme de conteneur, qui est chargé de l’exécution des Pods sur ce Node. Il reçoit les messages demandant le démarrage ou l’arrêt des conteneurs. Mais le Kubelet gère aussi de la surveillance des conteneurs tout comme la surveillance du Node. Si le Minion semble avoir un problème, cette information est remontée vers le Master qui va alors envoyer un message à un Kubelet sur un Worker différent afin de démarrer les conteneurs qui étaient en cours d’exécution sur le nœud défaillant.

Le Kube-Proxy est un composant présent sur chaque Node. Il permet de gérer le trafic vers les Pods.

kubectl get nodes -o wide

Pod

Un conteneur « vit » dans un Pod. Il s’agit là de la plus petite unité gérée par K8s. Le Pod est à Kubernetes ce qu’un conteneur est à Docker. Un Pod peut contenir un ou plusieurs conteneurs qui ont l’assurance de s’exécuter sur un même nœud du cluster.

On constate que par défaut un certain nombre de Pods (les composant système de K8s) sont en cours d’exécution sur le cluster.

kubectl get pods -o wide –all-namespaces

Le Pod ne disposant que d’une seule adresse IP, si plusieurs conteneurs et donc plusieurs services sont hébergés, il faut donc utiliser plusieurs ports TCP.

Si vous souhaitez exécuter plusieurs instances d’une application containerisée, il faut alors exécuter plusieurs Pods contenant chacun le(s) conteneur(s) en question et non pas un seul Pod avec plusieurs conteneurs identiques.

Le Pod possède aussi des Labels, des informations textuelles sous forme de paires clé/valeur. En particulier le Label Selector permettra per exemple la recherche de tous les Pods. Les annotations ne sont que des metadata, des propriétés destinées à être lues. Pas de formalisme spécifique. Vous pouvez très bien les utiliser pour stocker la branche Git utilisée, le nom du développeur, etc …

Il est possible de créer un Pod au travers d’un fichier YAML (ou JSON) :

apiVersion: v1
kind: Pod
metadata:
name: static-web
labels:
app: myapp
type: front-end
spec:
containers:
– name: web
image: nginx
ports:
– name: web
containerPort: 80
protocol: TCP

kubectl create -f ./pod-definition.yaml

L’image du conteneur Nginx est téléchargée depuis le repository Docker Hub si elle n’est pas présente sur le Node sur lequel K8s a choisi de démarrer le Pod. Ensuite le conteneur va démarrer.

Le Deployment, que l’on abordera un peu plus tard, surclasse la création de Pod manuelle.

Service

Un Service permet d’exposer l’application. Il s’agit d’une abstraction logique d’un ou plusieurs Pods. Chaque Pod disposant d’une adresse IP interne au cluster Kubernetes. Le Service permet de router le trafic vers votre application. Le service ne va pas forcément exposer les ports de tous les conteneurs de votre Pod. Imaginez par exemple une application de type Web avec un FrontEnd et un BackEnd pour hébrger une base de données par exemple. Il est tout à fait imaginable que la base de données ne soit pas accessible depuis l’extérieur, donc pas de service, alors que le site Web lui doit répondre sur HTTP et/ou HTTPS (un service peut très bien être de type multi-port).

Plusieurs types de services existent :

  • ClusterIP, qui est la valeur par défaut, et qui n’expose le service qu’à l’intérieur du cluster K8s. La ressource n’est pas joignable depuis l’extérieur.
  • NodePort expose le service avec un port statique sur chaque nœud. Il est possible de joindre le service depuis l’extérieur au travers de la syntaxe <NodeIP>:<NodePort>. Le service est créé sur tous les nodes de manière identique donc peu importe quelle IP sera utilisée depuis l’extérieur, le service répartira la charge entre les différents Pods et Nodes du cluster.
  • ExternalName permet de mapper le service sur un nom externe de type FQDN. Il permet seulement de retourner un CNAME qui existe en dehors du cluster mais n’est pas mappé à un service / port spécifique relatif à une ressource du cluster K8s
  • LoadBalancer permet d’exposer les services des Pods à l’extérieur du cluster dans le cadre d’un provider Cloud comme Azure, AWS ou Google. Chaque LoadBlancer va automatiquement créer un service NodePort et un service ClusterIP. Des routes vont automatiquement être créés dans le cluster K8s pour permettre d’atteindre les conteneurs.

kubectl get services -o wide –all-namespaces

Un fichier YAML permet de créer un service, le plus simple étant le service de type NodePort. L’exemple suivant permet de joindre un Pod

  • du Selector myapp (le Pod correspondant à un conteneur exécutant nginx créé précédement)
  • nodeport représente le port TCP associé à l’adresse IP du Node. La valeur doit être comprise entre 30000 et 32767.
  • port représente le port TCP associé à l’adresse IP du service
  • targetport représente le port TCP associé à l’adresse IP du Pod
  • les informations présentes dans le Selector sont celles qui étaient inscrites dans la section labels de la définition du Pod.

apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: NodePort
ports:
– nodePort: 30080
port: 80
targetPort: 80
selector:
app: myapp
type: front-end

kubectl create -f ./service-definition.yaml

Un rapide test au travers de curl sur l’IP de l’un des Nodes permet de vérifier que le service a été créé avec succès :

Tester l’IP du second Node (192.168.1.222 dans mon cas) renvoie le même résultat.

Deployment

La notion de Deployment surclasse la notion de Pod. La déclaration d’un fichier de déploiement est similaire à celle du Pod que l’on a vu précédemment. Gardez bien en mémoire que l’orchestration et la gestion des conteneurs (des Pods) dans Kubernetes est régie par la notion d’état désiré.

Cet état désiré est exprimé au travers d’un fichier YAML encore une fois.

apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deployment
spec:
selector:
matchLabels:
app: myapp
replicas: 2 # exécuter 2 pods correspondant au template
template:
metadata:
labels:
app: myapp
type: front-end
spec:
containers:
– name: web
image: nginx
ports:
– containerPort: 80

Ce fichier va donc décrire votre service au travers de méta données (nom, …) et d’une spécification (le(s) conteneurs qui le compose(ent)). Un des points importants est matérialisé par la balise replicas qui va de pair avec la notion de ReplicaSet. Elle va définir le nombre d’exemplaires de votre application. Cela se traduit par un nombre de Pods.

Si vous spécifiez 3 réplicas, alors, l’état désiré est d’avoir 3 Pods exécutant votre application. Et Kubernetes va en permanence comparer l’état désiré avec l’état actuel. Si un Pod (ou un Node) qui a subir une avarie, alors l’état actuel (2 réplicas) ne correspond plus à l’état désiré (3 réplicas), l’orchestrateur va donc automatiquement démarrer un nouveau Pod pour rétablir la configuration désirée.

kubectl create -f ./deployment-definition.yaml

On peu recréer le service qui cette fois ci va automatiquement répartir les requêtes sur les 2 Pods.

kubectl create -f ./service-definition.yaml

La notion d’upgrade de version d’application sans coupure de service est également gérée ! Vous spécifiez un nouveau déploiement, avec la V2 de votre application, voici ce qu’il va se passer :

  • En version 1, votre application possède 3 réplicas
  • K8s maintient donc 3 réplicas (état désiré)
  • On commence avec un premier Pod en V1 :
    • Un 4eme Pod va être instancié sur le cluster et attendre que le liveness check et le readiness check soient satisfaits
    • Une fois le readiness satisfait, le load balancer peut distribuer le trafic vers ce Pod
    • Mais l’état désiré ne fait mention que de 3 replicas, K8s va donc « tuer » un Pod en version 1. Pas de manière brutale. Il commence par interdire toute nouvelle connexion et attendre que le « termination grace period » (30 secondes par défaut) se termine pour être supprimé. Cela permet aux requêtes en cours sur ce Pod de se terminer.
    • Une fois la période de grâce terminé, le Pod est supprimé. On retrouve l’état désiré.
  • On enchaîne sur le second Pod en V1 :
    • Un 4eme Pod va être instancié sur le cluster et attendre que le liveness check et le readiness check soient satisfaits
    • Une fois le readiness satisfait, le load balancer peut distribuer le trafic vers ce Pod
    • Mais l’état désiré ne fait mention que de 3 replicas, K8s va donc « tuer » un Pod en version 1. Pas de manière brutale. Il commence par interdire toute nouvelle connexion et attendre que le « termination grace period » (30 secondes par défaut) se termine pour être supprimé. Cela permet aux requêtes en cours sur ce Pod de se terminer.
    • Une fois la période de grâce terminé, le Pod est supprimé. On retrouve l’état désiré.
  • On termine avec le troisième Pod en V1 :
    • Un 4eme Pod va être instancié sur le cluster et attendre que le liveness check et le readiness check soient satisfaits
    • Une fois le readiness satisfait, le load balancer peut distribuer le trafic vers ce Pod
    • Mais l’état désiré ne fait mention que de 3 replicas, K8s va donc « tuer » un Pod en version 1. Pas de manière brutale. Il commence par interdire toute nouvelle connexion et attendre que le « termination grace period » (30 secondes par défaut) se termine pour être supprimé. Cela permet aux requêtes en cours sur ce Pod de se terminer.
    • Une fois la période de grâce terminé, le Pod est supprimé. On retrouve l’état désiré.
  • L’état désiré est satisfait avec 3 Pods en V2

Sachant que l’on ne spécifie à aucun moment lors d’un kubectl create ce que l’on va créer, la balise kind du fichier YAML dit au moteur quel type de ressource il doit créer / détruire. Ce qui veut dire qu’il est possible de cumuler dans un même fichier à la fois la création d’un Deployment et d’un service. Il faut simplement séparer les objets par — (3 tirets).

La big picture

Maintenant qu’un certain nombre de termes et de principes sont établis, je peux vous présenter sans risque de provoquer un arrêt cardiaque la Big Picture d’un cluster Kubernetes.

  1. On communique au travers d’API avec Kubernetes pour lui soumettre des ordres comme déployer un service au moyen d’un fichier YAML, ou bien stopper ce service, ajouter des replicas, etc …
  2. L’état désiré spécifié dans le deployment va être envoyé aux Worker Nodes pour application au travers du Kubelet
  3. La communication inter conteneurs est assuré au sein du minion tout comme la communication inter Pods.
  4. Les requêtes externes sont acceptées par le kube-proxy qui reçoit le flux (ClusterIP et NodePort crées par le LoadBalancer)

Rien oublié ?

En tant que DBA, à ce moment du billet, une petite voix dans votre tête devrait vous dire qu’il manque quelque chose ….

Souvenez-vous d’un des principes de base de Docker : les conteneurs n’ont pas de stockage persistant. Lorsque le conteneur est détruit, on perd les données, sauf à avoir comité l’image. Donc, lorsque mon conteneur est hébergé dans un cluster Kubernetes et que l’état désiré n’est plus satisfait, un Pod va être crée sur un autre node … Et donc perte de données !!!!

La mise à disposition d’un stockage persistant est une des tâches confiées à l’orchestrateur.

Tout comme pour les clusters d’hyperviseurs, gérer le stockage est un problème distinct de la gestion du compute. Sur un hyperviseur on peut utiliser du stockage partagé (baie de disque connectée en NFS, iSCSI, Fiber Chanel) ou une solution de type Software defined storage comme le S2D ou vSAN. Il en va à peu près de même pour un cluster Kubernetes. Les volumes persistants sont une abstraction di stockage faire par des APIs. On retrouve les types de stockages liés à la virtualisation, mais d’autres possibilités existent. Ce qui m’a donné l’occasion de découvrir GlusterFS, solution qui se rapproche du S2D chez Microsoft. Historiquement, la performance du stockage a un impact important sur les performances de SQL Server, nul doute que les choix qui seront fait pour les volumes persistants seront cruciaux.

Les volumes sont rattachés à un Pod, et le(s) volume(s) peu(ven)t être monté dans un seul ou bien dans tous les conteneurs de ce Pod.

Et maintenant ?

Posons-nous pendant une « termination grace period » secondes (30 secondes pour ceux qui auraient lu en diagonale les § précédents).

En tant que DBA, est-ce que je dois maitriser tout cela ? Je ne pense pas. Mais tout comme il est demandé » à un DBA de connaitre son OS (Windows, Linux), il est important de disposer du vocabulaire et des concepts pour aborder sereinement l’avenir. Nul doute que la conteneurisation que je me plais à appeler « virtualisation v2 » va concerner les bases de données, et donc SQL Server. Et à ce titre, le DBA se doit de faire évoluer ses compétences dans ce sens.

Pour autant, on parle de virtualisation, donc tout cela devient abstrait, voire Server Less. Vous connaissez le principe du Server Less : je place une fonction quelque part sur un cloud public et cette fonction s’exécute quand je l’appelle. Je n’ai plus à gérer de VM, d’OS, ni quoi que ce soit. Je me focalise sur le métier, pas sur l’infrastructure. Pourquoi ne pas faire de « Server Less Kubernetes » et ne plus avoir à gérer toute cette complexité dans l’infrastructure ?

C’est ce que vous proposent les acteurs majeurs du cloud (Microsoft, Amazon et Google) avec leurs services Kubernetes Managés (Azure AKS, AWS ECS et Google GKE). Il ne reste que les applications à déployer.

Pour ceux que le cloud public effraie / rebute, il est tout à fait possible de monter son propre cluster Kubernetes OnPrem. Cela devient le pendant d’un cluster d’Hyperviseurs. On n’est plus trop dans le rôle et les compétences du DBA mais cela reste un exercice assez intéressant.

Pour aller à l’essentiel, je vous suggère de commencer par :

Mais rien n’empêche de construire son propre cluster, comme je l’ai fait. Je pense revenir sur les étapes dans un prochain billet. Celui-ci étant plutôt destiné à aborder l’architecture et les concepts.

Il est aussi possible de monter une plateforme OpenShift (Red Hat) qui semble devenir un standard pour un cluster Kubernetes OnPrem. Toujours Docker et K8s, mais on ajoute quelques fonctionnalités de gestion de code source, de gestion d’images, d’applications …

Dans un premier temps, la solution cloud public reste clairement l’option la plus simple et la plus rapide à mettre en œuvre.

Par exemple, pour Microsoft Azure, quelques lignes de code AZ Cli permettent de déployer un cluster pleinement fonctionnel :


az account set –subscription « xxxxxxx »
az group create –name rgk8scluster –location westeurope
az aks create –name aksk8scluster \
–resource-group rgk8scluster \
–generate-ssh-keys \
–node-vm-size Standard_L8s \
–node-count 2 \
–kubernetes-version 1.12.6

Il ne reste plus ensuite qu’à déployer vos services …

Happy K8s !

A propos Christophe

Consultant SQL Server Formateur certifié Microsoft MVP SQL Server MCM/MCSM SQL Server
Cet article a été publié dans Docker, Kubernetes, Linux. Ajoutez ce permalien à vos favoris.

Laisser un commentaire