Les serveurs étant tous déployés et prêts à l’emploi, il est temps d’appliquer la configuration nécessaire au déploiement du cluster Kubernetes.
La logique retenue est d’exploiter Ansible pour paramétrer sur chaque serveur les prérequis nécessaires à l’installation et au bon fonctionnement de K8S.
Cliquez sur l'image pour l'agrandir.
L’avantage de cette méthode est de pouvoir facilement rejouer des déploiements et suivre au fil du temps l’évolution de son cluster en modifiant uniquement des variables Ansible.
Cet article n’a pas vocation à être un tutoriel Ansible. Il est préférable que vous ayez déjà exploité l’outil. Néanmoins, je vais essayer de rappeler quelques principes et d’expliquer certains fondamentaux.
La poursuite de cette étape nécessite d’avoir une instance Ansible disponible (un Control Node dans le langage Ansible). Ansible peut être installée très facilement et de différentes manières. Me concernant, il est installé sur mon poste de travail Windows à travers la couche WSL. C’est la clef publique ssh de mon utilisateur ansible-windows qui a été incluse dans le template.
Pour rappel, les VMs déployées s’appuient sur une « golden image » générée par Packer, dont on a spécifiquement préparé la couche SSH pour être pilotée par mon instance Ansible.
C’est cette dernière qui va être utilisée pour se connecter sur chaque serveur.
Pour travailler, Ansible a besoin d’un inventaire depuis lequel il va pouvoir identifier les cibles auxquelles il va devoir accéder pour réaliser ce qu’on va lui demander.
Cet inventaire peut prendre plusieurs formes. Il peut être dynamique via l’appel à un script ou statique via l’usage d’un fichier à plat.
C’est ce deuxième cas qui va être utilisé ici. En effet, lors de la phase précédente, nous avions demandé à Terraform de produire un fichier d’inventaire en sortie du déploiement.
C’est ce fichier qu’on va utiliser. En plus d’y référencer le nom et les ips des serveurs, il contient également les groupes de machines, rassemblées via des tags que nous avions paramétrés dans les fichiers Terraform.
Cela va permettre à Ansible de cibler des serveurs spécifiques en fonction de leur rôle dans l’architecture finale. Par exemple, distinguer les nœuds control plane, des worker ou encor appliquer la configuration HAproxy uniquement sur les loadbalancer. (N’hésitez pas à revenir sur la présentation de l’architecture cible)
Tous ces éléments se trouvent dans le sous-dossier inventory.
Le fichier principal généré par Terraform est hosts.yml, dont voici le contenu.
# ------------------------------------------------------------------
# Inventaire Ansible GÉNÉRÉ par Terraform
# Régénéré à chaque `terraform apply`.
# ------------------------------------------------------------------
"all":
"children":
"clu_k8s_xkub":
"children":
"gpu":
"hosts": {}
"k8s_controlplane_lan":
"hosts":
"prdk8sgru501.coolcorp.priv":
"ansible_host": "192.168.10.161"
"prdk8sgru502.coolcorp.priv":
"ansible_host": "192.168.10.162"
"prdk8sgru503.coolcorp.priv":
"ansible_host": "192.168.10.163"
"k8s_workers_lan":
"hosts":
"prdk8smin501.coolcorp.priv":
"ansible_host": "192.168.10.171"
"prdk8smin502.coolcorp.priv":
"ansible_host": "192.168.10.172"
"k8s_workers_web":
"hosts":
"prdk8smin510.coolcorp.priv":
"ansible_host": "192.168.5.171"
"prdk8smin511.coolcorp.priv":
"ansible_host": "192.168.5.172"
"nlb_haproxy_lan":
"hosts":
"prdnlbhap501.coolcorp.priv":
"ansible_host": "192.168.10.185"
"prdnlbhap502.coolcorp.priv":
"ansible_host": "192.168.10.186"
"nlb_haproxy_web":
"hosts":
"prdnlbhap510.coolcorp.priv":
"ansible_host": "192.168.5.185"
"prdnlbhap511.coolcorp.priv":
"ansible_host": "192.168.5.186"
"k8s_traefik_lan":
"hosts":
"prdk8smin501.coolcorp.priv":
"ansible_host": "192.168.10.171"
"prdk8smin502.coolcorp.priv":
"ansible_host": "192.168.10.172"
"k8s_traefik_web":
"hosts":
"prdk8smin510.coolcorp.priv":
"ansible_host": "192.168.5.171"
"prdk8smin511.coolcorp.priv":
"ansible_host": "192.168.5.172"
"os_lin_r10":
"children":
"gpu": {}
"k8s_controlplane_lan": {}
"k8s_workers_lan": {}
"k8s_workers_web": {}
"nlb_haproxy_lan": {}
"nlb_haproxy_web": {}
"sys_net_lan":
"children":
"gpu": {}
"k8s_controlplane_lan": {}
"k8s_workers_lan": {}
"nlb_haproxy_lan": {}
"sys_net_web":
"children":
"k8s_workers_web": {}
"nlb_haproxy_web": {}
"vars":
"ansible_user": "ansible-windows"
inventory/hosts.yml
On y retrouve les groupes suivants
| Groupe | Fonction | Machines rattachées |
|---|---|---|
| k8s_controlplane_lan | Control planes du cluster (kubeadm, etcd, API server) | prdk8sgru501, prdk8sgru502, prdk8sgru503 |
| k8s_workers_lan | Workers Kubernetes en LAN | prdk8smin501, prdk8smin502 |
| k8s_workers_web | Workers Kubernetes en DMZ (isolés : label + taint network=dmz) | prdk8smin510, prdk8smin511 |
| nlb_haproxy_lan | Paire HAProxy + keepalived, VIP applicative LAN | prdnlbhap501, prdnlbhap502 |
| nlb_haproxy_web | Paire HAProxy + keepalived, VIP applicative DMZ | prdnlbhap510, prdnlbhap511 |
| gpu | Worker baremetal GPU (NVIDIA RTX 3060) | (vide pour l’instant, mais il s’agira du futur nœud physique prdk8smin520) |
| k8s_traefik_lan | Nœuds où est planifiée l'instance Traefik LAN | prdk8smin501, prdk8smin502 |
| k8s_traefik_web | Nœuds où est planifiée l'instance Traefik DMZ | prdk8smin510, prdk8smin511 |
| Groupe | Fonction | Machines effectives (après expansion) |
|---|---|---|
| clu_k8s_xkub | Tout le cluster xkub (tous les nœuds K8s + HAProxy) | les 11 VMs : prdk8sgru501/502/503, prdk8smin501/502, prdk8smin510/511, prdnlbhap501/502, prdnlbhap510/511 (+ gpu à venir) |
| os_lin_r10 | Marqueur d'OS : Rocky Linux 10 | mêmes 11 VMs que clu_k8s_xkub |
| sys_net_lan | Zone réseau LAN (192.168.10.0/24) | prdk8sgru501/502/503, prdk8smin501/502, prdnlbhap501/502 (+ gpu) |
| sys_net_web | Zone réseau DMZ (192.168.5.0/24) | prdk8smin510/511, prdnlbhap510/511 |
Cette mécanique va nous permettre de cibler tel ou tel groupe de machines, voir l’ensemble des machines quand il va s’agir de configurer un élément commun à tout le cluster.
À noter qu’on pourrait basculer sur un inventaire dynamique via l’API de XenOrchestra. Lors de l’utilisation de Terraform, des étiquettes ont également été appliquées aux machines virtuelles elles-mêmes. Il serait donc possible de reproduire cette fonction de catégorisation en interrogeant les balises attribuées à chaque machine virtuelle, ainsi que les métadonnées fournies par les outils xcp-ng installés sur chaque serveur.
C’est une évolution qu’il serait intéressant à réaliser plus tard. Pour l’instant j’ai préféré rester basique.
Maintenant que l’inventaire est traité, nous pouvons parler du reste de l’arborescence Ansible.
Sans rentrer dans le détail, Ansible est un outil d’automatisation, sans agent basé uniquement sur des connexions ssh (ou winrm sur Windows (oui Ansible peut configurer des OS Microsoft).
Le principe est d’écrire des fichiers yamls, qu’on appelle des playbooks, dans lesquels on va décrire l’état souhaité pour les cibles présentes dans l’inventaire. Par exemple, indiquer que tel ou tel paquet doit être installé, que tel ou tel fichier de configuration doit être à tel ou tel emplacement avec un contenu spécifique.
Pour rendre les choses plus simples, Ansible dispose de modules : certains sont fournis de base, d’autres peuvent être téléchargés en ligne pour étendre les possibilités de l’outil. Chaque module va permettre de configurer ou de manipuler un composant sur les serveurs sans que l’utilisateur n’ait à connaitre la syntaxe d’usage spécifique à ce composant. C’est l’usage de mots clefs, propres à chaque module mais respectant une organisation syntaxique Ansible, simple et lisible, qui vont servir d’instructions.
Les playbook peuvent s’appeler les uns et les autres, et on va souvent parler de rôle pour définir un ensemble d’instructions rassemblé dans plusieurs fichiers yamls, mais rattachés à un même objectif.
Cliquez sur l'image pour l'agrandir.
On va définir un certain nombre de variables pour simplifier la constitution d’éléments de configuration, avec la possibilité de templetiser certains éléments.
En définitive, Ansible examine les playbooks qu’on lui soumet, établit une liaison avec les cibles répertoriées dans l’inventaire, puis exécute les instructions sur les machines concernées.
Un des éléments les plus importants à prendre en compte est le concept d’idempotence. Derrière ce mot un peu barbare se cache le principe de pouvoir réexécuter les playbooks Ansible sans risques d'erreurs.
Si une machine n’est pas dans l’état souhaité, alors ces paramètres seront modifiés, si, à l’inverse la machine est déjà à la cible, alors rien ne sera joué.
À chaque sortie d’Ansible, nous serons informés de ce qui est conforme, de ce qui ne l’était pas et de ce qui aura nécessité un changement.
Les modules participent grandement à ce fonctionnement, car, contrairement à l’usage de scripts traditionnels ou c’est à l’utilisateurs de traiter les sorties et de coder leur interprétation, les modules intègrent nativement les retours des instructions et automatisent les remédiations au besoin.
C’est extrêmement puissant, car, en plus d’automatiser les configurations, Ansible peut servir de garantie de conformité. Plannifier la réexécution régulière de vos playbooks, et vous vous assurez d’avoir toujours des serveurs conformes à vos attentes.
Garder toujours en tête que vos playbook Ansible doivent pouvoir être rejoué sans risque. C’est parfois une difficulté sur certains sujets, notamment quand on exploite des modules qui ne font que passer une commande shell, mais c’est très important.
Lorsqu’un playbook réussit, pensez toujours à le réexécuter une seconde fois pour vous assurer que l’idempotence est respectée.
Autres éléments importants, le nommage de vos fichiers et la logique d’intégration. Ansible permet différentes combinaisons et, même si des dossiers fondamentaux doivent être présents et associés à des fonctions bien spécifiques, on peut vite créer des logiques complexes et surtout adopter des choix différents entre administrateurs.
C’est pourquoi définir une nomenclature et une façon de faire est important.
L’IA est parfaite pour cela, grâce à l’utilisation d’un sous-agent Claude et de compétences dédiées Ansible, que vous pourrez personnaliser pour vous assurer d’avoir toujours la même logique de création et d’organisation de vos rôles et de vos playbooks.
Voici donc mon choix d’arborescence et la logique qu’on va retenir sur toutes les phases d’usage de Ansible pour ce cookbook.
Cliquez sur l'image pour l'agrandir.
Si on rentre un peu plus dans le détail, voici la structure propre au déploiement du cluster:
Cliquez sur l'image pour l'agrandir.
A noter que cette approche est suceptible d'évoluer et d'être étendue à d'autres aspects du déploiement Kubernetes, mais la logique restera identique. Ce type d'aboresence est fait pour vivre dans un environnement de production. L'important est de maintenir une configuration stable et fiable et de toujours mutualiser les memes ensembles de configurations.
Si une valeur doit etre modifiée ou ajoutée, il faut privilégier un seul endroit pour la gérer.
Pour illustrer ces schémas et rendre les choses un peu plus concrètes, on va démarrer par la mise en place des prérequis, soit le playbook xkub_10_base.yml
Voici son contenu
---
# Socle OS de tous les nœuds puis prérequis Kubernetes.
- name: Socle OS commun (tous les nœuds du cluster)
hosts: clu_k8s_xkub
become: true
roles:
- xkub_common
- name: Prérequis Kubernetes (control planes + workers + GPU)
hosts: k8s_controlplane_lan:k8s_workers_lan:k8s_workers_web:gpu
become: true
roles:
- xkub_k8s_prereqs
playbooks/xkub_10_base.yml
Il va faire appel à deux rôles
Si maintenant on détaille le rôle xkub_common
Cliquez sur l'image pour l'agrandir.
On commence par regarder le fichier defaults/main.yml pour y voir les variables propres à ce rôle :
---
# Rôle xkub_common — valeurs par défaut (préfixe = nom du rôle, cf. CONVENTIONS.md).
# Paquets de base sur tous les nœuds (l'outillage K8s est dans xkub_k8s_prereqs).
# chrony n'y figure pas : déjà présent et actif par défaut sur Rocky Linux 10
# (voir tasks/02_time_sync.yml, qui ne fait que reconfigurer le serveur NTP).
xkub_common_packages:
- wget
- tar
- git
# Gestion du fichier /etc/hosts (tous les nœuds du cluster + VIP API).
# Filet de sécurité si le DNS interne (192.168.10.30) est indisponible.
xkub_common_manage_hosts: true
# Mise à jour complète dnf (opt-in : long, à déclencher volontairement)
xkub_common_dnf_full_update: false
roles/xkub_common/defaults/main.yml
On en trouve trois, la plus intéressante est xkub_common_packages qui définit la liste des paquets à déployer sur tous les nodes. Ce sont des outils standards comme Git, wget et tar. D'une manière générale, n'abusez pas des paquets sur les nodes Kubernetes. Que cela soit pour des raisons de sécurité ou de performances, il faut se contenter du minimum nécessaire.
La variable xkub_common_manage_hosts définie à true est importante également, car elle permet de forcer le contenu des fichiers hosts des serveurs. Normalement ce n’est pas nécessaire, le DNS doit s’occuper de ça, mais avec Kubernetes c’est parfois préférable et conseillé d’opérer de cette façon. C’est un filet de sécurité….Mais qu’il faut maintenir et ne pas oublier de mettre à jour si les IPs des serveurs évoluent. (Opération simplifiabe justement via Ansible et son principe de templating dont on va parler juste après)
Côté handlers/main.yml, peu de choses à déclarer, si ce n'est le redémarrage de chronyd lorsqu’on aura à modifier la configuration ntp
---
- name: Restart chronyd
ansible.builtin.systemd_service:
name: chronyd
state: restarted
roles/xkub_common/handlers/main.yml
Pour rappel, sous Ansible, un handler est un mécanisme qui permet de déclencher une action (souvent un redémarrage de service) lorsqu’un changement est détecté dans une tâche. Ici, si la configuration de chrony est modifiée, l’handler Restart chronyd sera appelé pour appliquer les changements.
C’est du côté du répertoire task que les choses sont les plus intéressantes, avec un découpage en trois fichiers, tous référencés dans main.yml
---
# Rôle xkub_common — socle OS de tous les nœuds (VMs et baremetal).
- name: Paquets de base
ansible.builtin.import_tasks: 01_packages.yml
tags:
- xkub
- xkub.common
- xkub.common.packages
- name: Synchronisation du temps (chrony)
ansible.builtin.import_tasks: 02_time_sync.yml
tags:
- xkub
- xkub.common
- xkub.common.time_sync
- name: Nom d'hôte et /etc/hosts
ansible.builtin.import_tasks: 03_hostname_hosts.yml
tags:
- xkub
- xkub.common
- xkub.common.hostname_hosts
roles/xkub_common/tasks/main.yml
Notez l'usage des tags, qui permettent de cibler l'exécution d’un sous-ensemble de tâches. Par exemple, si on ne veut exécuter que la partie « synchronisation du temps », on pourra lancer le playbook avec l’option --tags xkub.common.time_sync.
Même si ils sont optionnels, je vous conseille de toujours les utiliser. Cela permet de mieux organiser vos playbooks et de faciliter le débogage ou l’exécution ciblée de certaines parties. Si vous exploitez l'IA comme moi pour rédiger vos playbooks, cela peut etre une instruction à passer à votre assistant au code pour qu'il intègre les tags à chaque étape. Imposez lui aussi une nomenclature pour ces tags comme c'est le cas ici.
Observons les fichiers de tasks de plus prêt.
01_packages.yml
---
# Paquets de base (Rocky Linux 10 )
- name: Installer les paquets de base
ansible.builtin.dnf:
name: "{{ xkub_common_packages }}"
state: present
update_cache: true
# Mise à jour complète, uniquement si demandée explicitement
# (ex : ansible-playbook ... -e xkub_common_dnf_full_update=true). Reste opt-in
# plutôt qu'activé par défaut : une fois le cluster vivant (phase 4+), rejouer
# ce rôle ne doit pas redémarrer un nœud sans drain préalable.
- name: Mettre à jour l'ensemble des paquets (opt-in)
ansible.builtin.dnf:
name: "*"
state: latest
register: xkub_common_dnf_full_update_result
when: xkub_common_dnf_full_update | bool
# Un update noyau/systemd/glibc ne prend effet qu'après redémarrage — sans ce
# reboot, "à jour" ne serait que partiellement vrai. Conditionné au résultat
# réel (idempotent : rejouer sans rien de changé ne redémarre pas).
- name: Redémarrer si la mise à jour l'exige (opt-in)
ansible.builtin.reboot:
reboot_timeout: 300
when:
- xkub_common_dnf_full_update | bool
- xkub_common_dnf_full_update_result is changed
roles/xkub_common/tasks/01_packages.yml
C’est ici qu’on va utiliser la variable xkub_common_packages
02_time_sync.yml
---
# Un cluster etcd exige des horloges synchronisées. chrony est déjà présent et
# actif par défaut sur Rocky Linux 10 (pool public) : on ne fait ici que le
# repointer vers le serveur NTP interne (net_ntp_servers, group_vars/clu_k8s_xkub/main.yml).
- name: Déployer la configuration chrony (serveur NTP interne)
ansible.builtin.template:
src: chrony.conf.j2
dest: /etc/chrony.conf
owner: root
group: root
mode: "0644"
when: net_ntp_servers | length > 0
notify: Restart chronyd
- name: S'assurer que chronyd est actif
ansible.builtin.systemd_service:
name: chronyd
state: started
enabled: true
roles/xkub_common/tasks/02_time_sync.yml
Le synchronisme du temps entre tous les nodes d’un cluster Kubernetes est très important. Il est essentiel d’avoir une source ntp fiable et commune à tous les nodes et c’est à ça que va servir ces instructions Ansible.
03_hostname_hosts.yml
---
# Le hostname est déjà posé par cloud-init (Terraform) : on garantit ici la
# cohérence avec l'inventaire (idempotent, ne change rien si déjà correct).
- name: Aligner le hostname sur l'inventaire
ansible.builtin.hostname:
name: "{{ inventory_hostname }}"
- name: Déployer /etc/hosts (nœuds du cluster + VIP API)
ansible.builtin.template:
src: hosts.j2
dest: /etc/hosts
owner: root
group: root
mode: "0644"
when: xkub_common_manage_hosts | bool
roles/xkub_common/tasks/03_hostname_hosts.yml
Comme évoqué précédemment, lié à la variable xkub_common_manage_hosts pour la gestion des hosts.
Enfin on a le dossier template dans lequel on va retrouver nos fichiers de configuration templetisés grâce au format jinja2 (moteur de template très rapide, développé pour le langage Python)
chrony.conf.j2
# {{ ansible_managed }}
# Configuration chrony — serveurs NTP internes coolcorp.priv
{% for server in net_ntp_servers %}
server {{ server }} iburst
{% endfor %}
driftfile /var/lib/chrony/drift
makestep 1.0 3
rtcsync
leapsectz right/UTC
logdir /var/log/chrony
roles/xkub_common/templates/chrony.conf.j2
Va nous permettre de générer la configuration de chrony, soit l’outil de synchronisation ntp présent par défaut dans Rocky Linux 10, pour qu’il se source sur tous les serveurs de temps renseigné dans la variable globale net_ntp_servers
Cette variable n’est pas propre au rôle xkub_common, mais est définie au niveau du dossier d’inventaire dans inventory/group_vars/clu_k8s_xkub
---
# =============================================================================
# Variables du cluster xkub.coolcorp.priv — portée : groupe clu_k8s_xkub
# =============================================================================
# --- Identité du cluster -----------------------------------------------------
k8s_cluster_name: xkub
k8s_cluster_fqdn: xkub.coolcorp.priv
# Endpoint API haute dispo : résolu vers la VIP kube-vip (décision D1)
k8s_control_plane_endpoint: xkub.coolcorp.priv
k8s_api_vip: 192.168.10.121
k8s_api_port: 6443
k8s_pod_cidr: 10.11.0.0/16
k8s_service_cidr: 10.12.0.0/16
# --- Réseau / domaines -------------------------------------------------------
net_domain: coolcorp.priv
net_domain_public: coolcorp.fr
net_dns_server: 192.168.10.30
net_lan_subnet: 192.168.10.0/24
net_dmz_subnet: 192.168.5.0/24
# Serveur NTP interne (contrôleur de domaine)
net_ntp_servers:
- ntp.coolcorp.priv
# --- VIPs applicatives (paires HAProxy + keepalived) -------------------------
haproxy_vip_lan: 192.168.10.187
haproxy_vip_dmz: 192.168.5.187
haproxy_vip_prefix: 24
# --- NodePorts Traefik -------------------------------------------------------
traefik_nodeport_lan_http: 30080
traefik_nodeport_lan_https: 30443
traefik_nodeport_dmz_http: 31080
traefik_nodeport_dmz_https: 31443
# --- Ports système Kubernetes ------
k8s_port_kubelet: 10250
k8s_port_kube_proxy_health: 10256
k8s_port_etcd_range: 2379-2380
k8s_port_nodeport_range: 30000-32767
cilium_port_vxlan_udp: 8472
cilium_port_health: 4240
cilium_port_hubble_peer: 4244
cilium_port_hubble_relay: 4245
inventory/group_vars/clu_k8s_xkub/main.yml
Pour ceux qui ne sont pas familiarisés avec Ansible, c’est un point de configuration important dont on a pas encore parlé : la gestion des variables sur Ansible.
Ces dernières peuvent être définies à différents niveaux. Fonction de leur emplacement, leur portée sera différente et leur priorité également.
Par exemple, pour cette variable net_ntp_servers, celle-ci pourra être exploitée par n’importe quel rôle pour lequel les serveurs cibles appartiennent au groupe « clu_k8s_xkub »
Les variables définies au niveau d’un groupe permettent donc d’être exploité de manière transverse aux rôles.
Sachez qu’on peut aussi définir une variable au niveau d’un host spécifique
Si une même variable est définie à plusieurs niveaux, alors un mécanisme de priorité va prendre le relais.
À savoir que
Et forcément il y a un piège, puisqu’il existe un autre niveau de variable pour le rôle, soit
Si on revient sur ma variable net_ntp_servers, celle-ci est en faite une liste de sous-éléments où chacun correspond à un serveur ntp.
Dans mon cas, je n’ai qu’un serveur, soit ntp.coolcorp.priv qui correspond à un alias vers mon contrôleur de domaine.
Ainsi, j’aurais la certitude que tous mes nodes du cluster Kubernetes disposeront de la même référence de temps que tout mon réseau.
Si on devait agir manuellement cela reviendrait à éditer le fichier /etc/chrony.conf sur chaque serveur pour y mettre ce contenu:
server ntp.coolcorp.priv iburst
driftfile /var/lib/chrony/drift
makestep 1.0 3
rtcsync
leapsectz right/UTC
logdir /var/log/chrony
/etc/chrony.conf
L’autre template hosts.j2 n’est rien d’autre que le fichier host qui va être placé sur chaque serveur
# {{ ansible_managed }}
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
# Endpoint API Kubernetes (VIP kube-vip — décision D1)
{{ k8s_api_vip }} {{ k8s_cluster_fqdn }}
# Nœuds du cluster (généré depuis l'inventaire)
{% for host in groups['clu_k8s_xkub'] | sort %}
{{ hostvars[host].ansible_host }} {{ host }} {{ host.split('.')[0] }}
{% endfor %}
roles/xkub_common/templates/hosts.j2
Concretement, cela revient à dire que le fichier hosts.j2 génère le contenu du fichier /etc/hosts sur chaque nœud du cluster avec ces valeurs:
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
# Endpoint API Kubernetes (VIP kube-vip)
192.168.10.121 xkub.coolcorp.priv
# Nœuds du cluster (généré depuis l'inventaire)
192.168.10.161 prdk8sgru501.coolcorp.priv prdk8sgru501
192.168.10.162 prdk8sgru502.coolcorp.priv prdk8sgru502
192.168.10.163 prdk8sgru503.coolcorp.priv prdk8sgru503
192.168.10.171 prdk8smin501.coolcorp.priv prdk8smin501
192.168.10.172 prdk8smin502.coolcorp.priv prdk8smin502
192.168.5.171 prdk8smin510.coolcorp.priv prdk8smin510
192.168.5.172 prdk8smin511.coolcorp.priv prdk8smin511
192.168.10.185 prdnlbhap501.coolcorp.priv prdnlbhap501
192.168.10.186 prdnlbhap502.coolcorp.priv prdnlbhap502
192.168.5.185 prdnlbhap510.coolcorp.priv prdnlbhap510
192.168.5.186 prdnlbhap511.coolcorp.priv prdnlbhap511
/etc/hosts généré sur chaque nœud
Il nous faudrait maintenant poursuivre avec la description du rôle xkub_k8s_prereqs avant de pouvoir ensuite exécuter réellement le playbook. Mais l’article est déjà assez conséquent et je préfère traiter de ce rôle spécifique au nœud Kubernetes dans un topic dédié.
À ce stade il est important d’avoir en tête la logique Ansible choisie. Même si vous n’êtes pas expert de l’outil, il faut comprendre les liens entre composants.
Le choix d’exploiter Ansible comme outil d’automatisation n’est pas qu’un souhait technique, c’est aussi une bonne manière d’expliquer progressivement les configurations à mettre en œuvre pour déployer un cluster Kubernetes.
Ici, c’est l’importance de la configuration NTP et des fichiers hosts qu’il faut noter. Tous les nodes qui composent un cluster Kubernetes échangent entre eux. Des erreurs de résolution de noms, comme des latences importantes dans le DNS, peuvent entrainer des conséquences néfastes pour le cluster. D’où la mise en place d’un filet de sécurité avec la templétisation du fichier « host » pour son déploiement sur chaque nœud. Un décalage temporel est aussi pénalisant pour des serveurs appartenant à un même cluster K8S. S’ils n’ont pas une synchronisation de leur date précise, cela entrainerait des erreurs critiques.
Imposer ces bonnes pratiques au sein d’un rôle Ansible permet de sécuriser les configurations et d’éviter les oublis et les erreurs.
L'IA est encore une fois une très bonne partenaire pour se simplifier l'écriture des yamls et s'assurer de la cohérence de l'arborescence retenue (surtout pour garantir la rejouabilité des playbook). Mais là aussi, il ne s'agit pas de la laisser faire sans comprendre ce qu'on veut réaliser. Laisser l'IA gérer une configuration Ansible sans comprendre Ansible c'est une grave erreur. Notez bien qu'ici, les choix de nomenclature et d'organisations me reviennent, j'ai ensuite demandé à l'IA de rédiger les yamls en fonction de mes exigences.
On pourra attaquer le rôle xkub_k8s_prereqs dans un prochain article à venir pour se focaliser davantage sur les éléments de configurations propres à Kubernetes.