Déploiement du cluster Kubernetes sous vSphere (V2)

Nous arrivons maintenant à une étape importante et conséquente : la création du cluster Kubernetes.

Encore une fois je vais m’inspirer du tutorial trouvable ici mais je vais m’en éloigner quelque peu.

Intégration de K8S dans vSphere

Je vais essayer de déployer Kubernetes en préparant l'intégration de la notion de « CNS » soit « Cloud Native Storage » que je configurerais à l'étape suivante. Il s’agit de permettre une communication entre vSphere et Kubernetes pour avoir un provisionnement automatique du stockage. Le tutorial semble utiliser VCP soit « vSphere Storage for Kubernetes », une autre solution toujours utilisée, mais dont il est maintenant conseillé de se détacher pour exploiter « CNS ». Si le sujet vous intéresse, je vous conseille la lecture de cet l’article.

Je vais donc également m’inspirer du tutorial suivant mais en essayant d’utiliser une version plus récente de Kubernetes

Préparation de l’infrastructure vSphere.

Création du compte de service

Afin de permettre à Kubernetes d’interagir avec vSphere il est nécessaire de créer un compte de service  que l’on va déclarer dans l’interface d’un ou des vCenter. De mon côté j’ai choisi la facilité en lui attribuant les droits admin sur toute mon infrastructure de POC.

Compte de service vSphere

Configuration HAProxy (VM dediée)

Comme vu précedemment,  HAproxy s'installe très facilement via le packet fourni dans le repo de photonOS. Si ce n'est pas déja fait utiliser la commande

tdnf install haproxy

Il va juste être nécessaire de positionner la bonne configuration HAproxy. Pour cela il faut éditer le fichier de configuration /etc/haproxy/haproxy.cfg et ajouter les éléments de configuration suivant:

global
defaults
        timeout client          30s
        timeout server          30s
        timeout connect         30s

frontend k8s_mst
        bind    192.168.10.45:6443
        default_backend 

backend k8s_nodes_master
        mode                    tcp
        option  tcp-check
        server                  prdk8smst501 192.168.10.56:6443 check

Pour l'instant on ne va configurer que l'accès à l'API de kubernetes, la configuration ci-dessus permet donc d'accéder depuis une "vip" au noeud master du cluster. Dans mon cas pour l'instant, il n'y en a qu'un, donc c'est un peu inutile. Mais cela s'avèrera nécessaire quand on ajoutera des noeuds master au cluster, donc autant préparer la configuration dès maintenant

Installation des binaires kubernetes

Il va maintenant falloir installer tous les binaires nécessaires au déploiement de Kubernetes sur les serveurs. Il existe plusieurs manières pour installer les différents rôles et services nécessaires à Kubernetes. De mon côté, et comme on le rencontre souvent, j’ai choisi d’utiliser "kubeadm". L’outil va gérer automatiquement la création du cluster à partir d’un fichier de configuration que l’on va lui soumettre.

Sous photonOS, il faut utiliser la commande suivante

tdnf install kubernetes-kubeadm

Cette commande est à taper sur chaque noeud du cluster. Non seulement elle va installer kubeadm mais également les binaires kubelet. À noter que photonOS arrive avec des repos Kubernetes déjà paramétrés. Il s'agit de binaire proposé par vmware et non des repos par défaut de Kubernetes. Il ne s'agit donc pas de la dernière version de l'écosystème K8S, mais il est préférable de retenir la version proposée par photonOS car elle assure une certaine stabilité. Maintenant rien ne vous empêche d'intégrer de nouveaux repos pour avoir la dernière release de K8S à disposition.

Fichier de configuration pour Kubernetes

Je vais maintenant créer le fichier de configuration "kubeadm.conf" que je vais soumettre à kubeadm. Je créer ce dernier dans /etc/kubernetes (il est possible que vous ayez à créer ce répertoire)

Son contenu va être le suivant

---
apiVersion: kubeadm.k8s.io/v1beta1
kind: ClusterConfiguration
kubeletExtraArgs:
cloud-provider: external
kubernetesVersion: 1.19.7
networking:
 podSubnet: "10.1.0.0/16"
 serviceSubnet: "10.2.0.0/16"

controlPlaneEndpoint: "kube.coolcorp.priv:6443"
clusterName: "prdk8sclu"
apiServer:
certSANs:
- 192.168.10.45
- kube.coolcorp.priv 

Les éléments en rouge correspondent à l'accès à l'api de mon cluster:

- 192.168.10.45 : c’est l’IP de ma VIP gérer par mon loadbalancer HAproxy

- kube.coolcorp.priv : C’est le nom DNS associé à ma VIP qui va pointer vers mon serveur HAProxy

DNS


-1.19.7:  c’est la version de Kubernetes que je vais déployer.

Les éléments en orange correspondent à la partie réseau de mon cluster.

Le podSubnet correspond au réseau IP qui va être associé à mes conteneurs dans mes pods. Cela implique dans mon cas, que chaque conteneur aura une IP interne en 10.1.0.0/16. Attention de choisir une plage disponible, car il existera sur chaque noeud une "gateway" virtuelle liée au driver réseau antrea (que je déploierais plus tard), il faut donc retenir un réseau libre sur son infrastructure pour éviter tout conflit de routage.


Le serviceSubnet sera quant à lui un réseau utilisé par mes objets de type "Service" au sein de mon cluster. De la même manière il est préférable choisir une plage disponible.

Création du cluster

Initialisation du premier noeud master

Je vais maintenant pouvoir créer mon cluster depuis une session root sur mon premier noeud master.

Je lance la commande suivante

kubeadm init --config /etc/kubernetes/kubeadm.conf --upload-certs --v=5

(n’oubliez pas –v=5, c’est très pratique en cas d’échec afin d’avoir les détails de l’erreur)

Au bout de quelques minutes, le premier nœud est initialisé et j’obtiens en sortie les commandes nécessaires à passer sur les autres nœuds pour les ajouter au cluster (ne tenez pas compte du nom de serveur issu de mon ancienne installation).

Création du cluster

Ajout des noeuds supplémentaires

Arriver à ce stade, il serait possible d'ajouter des noeuds master via la commande suivante

kubeadm join kube.coolcorp.priv:6443 --token y7yaev.9dvwxx6ny4ef8vlq \
    --discovery-token-ca-cert-hash sha256:345ae585ca3cdb56fdeb202b2a659b3a233aef4a75559a29889ca0f5926dd003 \
    --control-plane --certificate-key d0727fe87d624010af8ad0b874023139979dfd37cc34e9092002fae93603b8f3

Néanmoins comme déjà expliqué, je ferais cela plus tard dans un article dédié au sujet. Je vais donc me contenter d'ajouter mes noeuds worker. Pour cela, sur chacun d'eux, je tape la commande suivante rapportée par ma sortie de kubeadm.

kubeadm join kube.coolcorp.priv:6443 --token y7yaev.9dvwxx6ny4ef8vlq \
    --discovery-token-ca-cert-hash sha256:345ae585ca3cdb56fdeb202b2a659b3a233aef4a75559a29889ca0f5926dd003

Récupération de la configuration pour l'accès au cluster K8S

J’obtiens également les commandes nécessaires à la récupération de la configuration de "kubctl" qui va me permettre de piloter mon cluster.

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

À noter qu’il vous suffira de récupérer le binaire windows "kubectl" et de copier le dossier .kube du noeud K8S dans votre profil utilisateur pour pouvoir piloter votre cluster depuis un poste externe sous Windows.

config file

Vérification de l'état des noeuds Kubernetes

On peut d’ailleurs vérifier le statut du cluster avec la commande kubectl get node
Le noeud est “NotReady”, c’est normal pour l’instant (ne tenez pas compte de la version et du nom de serveur, c'est une capture issue de ma précédente installation).

Etat des noeuds


Je vais maintenant ajouter mes autres noeuds en reprenant les commandes précédents générées à la fin de l’initialisation du cluster.

À noter qu’il est préférable de procéder noeud par noeud et de ne pas lancer tout en parallèle. Autres remarques, les commandes sont valables sur un laps de temps limité (24H je crois) si les autres noeuds ne sont pas ajoutés sur cette période, le token expire, mais il sera possible d’en regénérer un nouveau si nécessaire (pour ajouter un noeud additionnel plus tard par exemple)

Attention, les commandes sont a passer dans un context root

Ajout d'un worker


Une fois terminé, si je vérifie l’état de mes noeuds j’arrive au résultat suivant (ne tenez pas compte de la version, ni du nom des noeuds, la capture est issue de l'ancienne version de mon tutorial)


Etat des noeuds

Tous les noeuds sont dans un état “NotReady”. Ce qui est normal, car je n’ai pas encore déployé un add-on network. En effet pour que la résolution DNS interne au cluster fonctionne et que les pods puissent dialoguer sur l’ensemble du cluster il est nécessaire d’implémenter une couche réseau. Il en existe plusieurs  qui présentent des caractéristiques différentes et des fonctionnalités spécifiques. C’est à chacun de choisir ce qui lui semble le plus adapté à ses besoins. Pour en savoir plus je vous invite à lire l’excellent article suivant qui détaille les principaux add-on possibles.

Par contre attention, le choix de l’add-on peut avoir des conséquences sur le déploiement du cluster, notamment sur l’option “podSubnet:” du fichier de configuration utilisé par kubeadm. De même il peut y’avoir certains prérequis propres à chaque solution. C’est donc un choix à faire en amont du déploiement du cluster.

Cas des noeuds en DMZ

Les noeuds en DMZ se traitent de la même manière que les noeuds en LAN, par contre des ouvertures réseaux sont nécessaires pour autoriser le trafic de management et le bon fonctionnement de l'addon réseau (Antrea que l'on va déployer juste après).

De mon côté, j'utilise une solution OPNsense pour filtrer mes zones réseau. Voici les flux que j'ai dû autoriser.


Sens Source Destination Port et protocole Raison
LAN vers DMZ Noeud master Noeud Worker en DMZ TCP 10250 Dialogue avec les kubelet depuis le master
LAN vers DMZ Noeud master Noeud Worker en DMZ UDP 6081 Encapsulation Geneve (réseau virtuel)
DMZ vers LAN Noeud Worker en DMZ Noeud Master en LAN et VIP HaProxy Master TCP 6443 Interrogation de l'API depuis les noeuds en DMZ
DMZ vers LAN Noeud Worker en DMZ Noeud Master en LAN TCP 10349 Interrogation du controleur Antrea
DMZ vers LAN Noeud Worker en DMZ Noeud Master en LAN UDP 6081 Encapsulation Geneve (réseau virtuel)
DMZ vers LAN Noeud Worker en DMZ NAS TCP 2049
TCP/UDP 111
TCP/UDP 892
Accès au storage NFS

Installation de l'addon réseau pour K8S

Dans mon cas, j’avais débuté par flannel, puis j'avais basculé sur calico pour finalement retenir aujourd'hui antrea.

Antrea est un projet OpenSource maintenu par VMware connaissant de plus en plus d'adeptes. Etant basé sur OpenVswitch, il est cross plateforme Linux/Windows (ce qui pourrait faciliter la mise en place de cluster hybride), il peut tirer parti des fonctionnalités d'accélération hardware de certaines cartes réseau et il fonctionne sur la couche L3 et L4. Il est compatible avec les network policie de Kubernetes et peut être complété d'une CLI et de dashboard sous ELK. Bref, il me convient très bien au vu de ma faible experience sur le sujet.

Déploiement de Antrea

Le déploiement se fait simplement avec la commande

kubectl apply -f https://raw.githubusercontent.com/vmware-tanzu/antrea/main/build/yamls/antrea.yml

Si je refais un relevé de l’état de mes nodes ils sont maintenant tous à "Ready" (ne tenez pas compte du nom de serveurs et de la version, c'est une capture issue de l'ancienne version du tutorial)

Etat des noeuds


Je vérifie également que tous les pods dans le namespace "kube-system" sont bien démarrés, en particulier les pod "coredns" avec la commande kubectl get pod -n kube-system

Taches complémentaires

Label des nodes

La notion de label est très importante dans Kubernetes. Par défaut les noeuds master ont bien un label master mais les worker ne sont associé à aucun label pour leur role.

On va donc les marquer comme "worker". On va utiliser les commandes suivantes:

kubectl label nodes prdk8swok501 node-role.kubernetes.io/worker=
kubectl label nodes prdk8swok502 node-role.kubernetes.io/worker=   
kubectl label nodes prdk8swok503 node-role.kubernetes.io/worker=
kubectl label nodes prdk8swok504 node-role.kubernetes.io/worker=
kubectl label nodes prdk8swok505 node-role.kubernetes.io/worker=
kubectl label nodes prdk8swok511 node-role.kubernetes.io/worker=

Nous avons besoin également de faire la distinctions entre mes noeuds en DMZ et mes noeuds en LAN....et pourquoi pas préciser également l'architecture et le type de serveur, cas ou on voudrait agrandir son cluster avec des RPI....

 kubectl label nodes prdk8swok501 arch=x86
 kubectl label nodes prdk8swok502 arch=x86   
 kubectl label nodes prdk8swok503 arch=x86
 kubectl label nodes prdk8swok504 arch=x86
 kubectl label nodes prdk8swok505 arch=x86
 kubectl label nodes prdk8smst501 arch=x86
 kubectl label nodes prdk8swok511 arch=x86

 kubectl label nodes prdk8swok501 net=lan
 kubectl label nodes prdk8swok502 net=lan   
 kubectl label nodes prdk8swok503 net=lan
 kubectl label nodes prdk8swok504 net=lan
 kubectl label nodes prdk8swok505 net=lan
 kubectl label nodes prdk8smst501 net=lan
 kubectl label nodes prdk8swok511 net=web  

Taint d'un node

Pour éviter de voir un pod s'exécuter sur un noeud particulier, comme par exemple éviter qu'un pod lié à une application du LAN tourne sur un noeud en DMZ, on peut utiliser le principe de "Taint".

Ce revient à appliquer une "contrainte" sur un noeud de maniere à ce qu'uniquement un pod qui "tolère" cette contrainte puisse s'exécuter sur ce noeud.

Voici une petite illustration en provenance de cette article de BanzaiCloud qui explique très bien le principe:

Taint et Toleration par BanzaiCloud

On va indiquer qu'un pod qui ne supporte pas l'association "zone=dmz" ne peut pas etre "schedulé" sur le noeud en DMZ. Pour cela on applique la commande suivante sur le noeud en DMZ

kubectl taint nodes prdk8swok511 zone=dmz:NoSchedule

Ainsi on a un cluster avec le rôle des nodes bien identifié (utiliser la commande kubectl get nodes --show-labels)

Label des noeuds

 

Mise en place du dashboard

La dernière partie de cette étape va être de mettre en oeuvre les dashboards optionnels qui permettent d’avoir une vision un peu plus graphique et sympathique de son cluster.

Je vais m’inspirer du tutorial suivant

Création du namespace

Nous aurons l'occasion d'en reparler, mais un namespace est un objet kubernetes qui permet d'isoler de manière logique un ensemble de ressources, pour par la suite appliquer des règles d'accès. Attention, toutes ressources ne sont pas forcément intégrables dans un namespace, et par défaut un namespace n'interdit en rien la communication entre deux pods exécutés dans deux namespaces différents. C'est cependant important de mettre en place des namespaces dès le début pour ensuite pouvoir établir des règles d'accès. Il existe deux namespace par défaut :


On trouve plusieurs écoles et plusieurs manières de faire pour l'usage des namespaces. On peut créer un namespace par application, par environnement, par équipes....Selon moi, il n'y a pas de bonnes ou de mauvaise façons de faire, c'est à chacun d'appliquer les namespaces fonction de ses besoins. Personnellement, je vais créer un namespace par environnement (developpement, recette, production...), par catégorie d'applications (sys pour les applications "systeme" et app pour les applications "standart") et par environnement réseau (lan ou web).


Organisation des namespaces

Les dashboard font partie pour moi d'une application "systeme" de "production" qui tourne sur le lan. Je vais donc créer mon namespace nommé "prdsyslan" pour les déployer.

kubectl create namespace prdsyslan

Création des premiers objets K8S

À partir de ce moment, nous allons commencer à déclarer nos premiers véritables objets kubernetes. Nous aurons l'occasion d'en reparler à plusieurs reprises durant les prochaines étapes. Gardez juste en tête pour l'instant que Kubernetes fonctionne selon le principe d'objets que l'on déclare sur le cluster et que Kubernetes se charge de maintenir. 

Ces objets sont définis au sein de l'API de K8S. Créer un objet revient donc à soumettre un fichier de configuration à l'API de kubernetes en respectant une syntaxe spécifique au sein d'un fichier yaml.


Notez qu'il est possible de créer des objets directement comme on l'a fait avec le namespace grâce à la commande kubectl create, mais c'est une chose qu'on essaye de limiter un maximum (il est d'ailleurs possible créer un namespace via un yaml). En effet, l'usage de fichier est beaucoup plus optimisé pour suivre et maintenir ses objets. Un fichier peut être versionné dans un système de gestion de version comme git par exemple.


Tout fichier yaml pour kubernetes contiendra toujours la base suivante:


apiVersion: "version et chemin de l'API appelé"
kind: "type d'objet"
metadata: "éléments complémentaire, mais non obligatoire servant à identifier l'objet"
spec: "attributs spécifique de l'objet, peut parfois être omis si la définition de l'objet l'autorise"

 

Pour connaitre la définition d'un objet, il faut consulter la documentation de l'API de kubernetes. A noter qu'un cluster Kubernetes autorise plusieurs versions d'API à coexister et plusieurs branches sont disponibles. Je ne suis pas suffisamment sachant sur le sujet, mais certains objets existent dans une version d'API et pas dans une autre. De plus certains objets peuvent se retrouver "deprecated" au fil des versions et remplacer par d'autres. La doc de l'API est donc essentielle. Enfin, sachez qu'en plus des objets par défaut, il est possible de "surcharger" l'API avec des objets customs.

Déploiement du dashboard K8S

Je vais récupérer le fichier de déploiement founir par les developpeurs de kubernetes à cette adresse

Je ne vais pas décrire tous les objets contenus dans ce yaml, je vais juste me contenter de remplacer le namespace "kubernetes-dashboard" par le namespace "prdsyslan", afin de correspondre à mon déploiement. J'enregistre mes modifications dans un fichier all-objects-dashboard (attention, à remplacer le nom du namespace partout il figure, y compris dans les arguments passés en parametre du conteneur)

J'applique mon fichier avec  la commande kubectl apply -f all-objects-dashboard.

Je vérifie le bon déploiement de l’ensemble avec la commande

kubectl get all --namespace=prdsyslan

j’obtiens la liste de tous les objets associés aux dashboard (ne tenez pas compte des autres objets, j'ai fait la capture après avoir déployer d'autres composants)

List des objets

Accès au dashboard K8S

L’authentification pour l’accès au dashboard se fait par un token associé au compte de service.

Je peux me servir de la commande powershell suivante depuis mon poste de travail pour récupérer le token

kubectl -n prdsyslan describe secret $(kubectl -n prdsyslan get secret | sls dashboard-user | ForEach-Object { $_ -Split '\s+' } | Select -First 1)

Token


Les dashboards ne sont pas accessibles par défaut en dehors du cluster. Je dois donc utiliser la commenda kubectl proxy qui va me permettre, depuis mon poste de travail, d’encapsuler ma requête sur l’ip local de ce dernier vers le cluster. L’accès ne sera donc que possible que depuis mon poste et uniquement le temps que la commande kubectl proxy reste active.

kube proxy


Je peux ensuite accéder au dasboard via l’url : http://localhost:8001/api/v1/namespaces/prdsyslan/services/https:kubernetes-dashboard:/proxy/#/login

J’utilise le token récupéré précédemment

Token


Je peux enfin exploiter les dashboards pour connaitre l’état de mon cluster (ne pas tenir compte des noms des serveurs, c'est une capture issue de mon précedent tutorial)

Dashboard


Cela me permet de m’assurer que mon cluster est opérationnel et prêt pour la suite.

Remarques sur le déploiement du cluster K8S

Il existe plein d'autres manières de faire pour déployer un cluster kubernetes. L'usage de kubeadm n'est qu'une façon de faire, mais elle est largement utilisée et extrêmement pratique.  Par contre, tout cluster qui a été monté avec kubeadm, doit être maintenu avec ce dernier. L'ajout, la suppression de noeud et même l'upgrade de version doivent passer par kubeadm, au risque de casser son cluster.

Kubernetes est un écosystème extrêmement vivant et ce qui est vrai un jour ne l'est pas forcément le lendemain. Il est donc important de rester en veille sur le sujet. Beaucoup de gens craignent le déploiement d'un cluster sur une infrastructure "OnPrem" à cause des difficultés potentielles que cela représente. Ce tutoriel est là pour démontrer que dans les faits, on peut très bien disposer de sa propre infrastructure dès lors qu'on accepte de se former et de la maintenir....un peu comme tout le reste.


Pour moi, le problème majeur reste les changements permanents qui sont opérés autour de l'écosystème k8s, c'est parfois un peu "fatiguant" de s'entendre dire que ce que l'on a pris du temps à maitriser et maintenant devenu obsolète...mais c'est malheureusement le mal de l'IT d'aujourd'hui ou tout doit aller vite...il faut que ça bouge.....mais parfois on ne comprend pas très bien pourquoi....hormis forcer à la prestation de service et à l'usage du cloud public....


Je ne dis pas que les services managés Kubernetes offerts par les principaux cloud provider ne sont pas intéressants. Bien au contraire, ils permettent de se focaliser sur le déploiement de ses applications plutôt que sur l'administration du cluster. Mais tout cela n'est pas gratuit et il faut s'accommoder de la plateforme retenue. Personnellement, j'estime que l'avenir de Kubernetes est à l'image de l'hybridation des infrastructures. Grâce aux offres packagées des fournisseurs historiques comme vmware ou redhat, il va devenir de plus en plus simple d'intégrer des clusters kubernetes dans son infrastructure traditionnelle, et celle-ci pourra être complétée et étendue via des offres managées en cloud public. C'est justement l'une des grandes forces de Kubernetes associé à la conteneurisation, son interopérabilité!


Mais attention, tout n'est pas magique ! Kubernetes ne résout pas tous les problèmes et n'est pas forcément la réponse à tous les "uses cases". C'est avant tout la qualité des applications et le sérieux du suivi des infrastructures associées qui font le succès d'un IT... Et ça, c'est notre boulot !