Dans le précédent article, nous avions expliqué et détaillé le fonctionnement de kube-vip, le composant en charge de gérer la disponibilité de l’API K8S entre les différents control plane.
Comme pour le reste de la configuration du cluster, nous nous sommes appuyés sur un playbook et un rôle Ansible.
Le rôle en question est xkub_kube_vip et a déjà été présenté précédemment.
Le playbook de référence pour ce rôle est xkub_30_cluster.yml. Cependant, il n’appelle pas seulement le rôle xkub_kube_vip, mais également les rôles xkub_kubeadm_init et xkub_kubeadm_join. Ces derniers sont responsables de l’initialisation du cluster et de l’enregistrement séquentiel des nœuds dans ce dernier.
On va s’intéresser au rôle xkub_kubeadm_init dans cet article. Ça va être l’occasion d’expliquer comment un cluster Kubernetes Vanilla est initialisé avec Kubeadm.
Pour rappel, le contenu du playbook xkub_30_cluster.yml est le suivant
---
# Ordre strict, chaque play doit terminer sur TOUS ses hôtes avant le suivant :
# 1. kube-vip sur les 3 control planes (AVANT tout kubeadm init/join)
# 2. kubeadm init sur le seul control plane primaire
# 3. kube-vip rebasculé sur admin.conf (fin du bootstrap)
# 4. kubeadm join (CP2/CP3 puis workers) + labels/taints de tous les nœuds
# 5. Cilium (Helm), depuis le control plane primaire
- name: kube-vip — VIP API server en static pod
hosts: k8s_controlplane_lan
become: true
roles:
- xkub_kube_vip
- name: kubeadm init — control plane primaire
hosts: "{{ groups['k8s_controlplane_lan'] | sort | first }}"
become: true
roles:
- xkub_kubeadm_init
- name: kube-vip — retour au kubeconfig admin après le bootstrap
hosts: "{{ groups['k8s_controlplane_lan'] | sort | first }}"
become: true
roles:
- xkub_kube_vip
- name: kubeadm join — control planes additionnels, workers, labels et taints
hosts: k8s_controlplane_lan:k8s_workers_lan:k8s_workers_web:gpu
become: true
roles:
- xkub_kubeadm_join
- name: Cilium — CNI avec kubeProxyReplacement (chart Helm depuis le control plane primaire)
hosts: "{{ groups['k8s_controlplane_lan'] | sort | first }}"
become: true
roles:
- xkub_cilium
playbooks/xkub_30_cluster.yml
Dans l’article dédié, nous avons déjà traité de l’appel à xkub_kube_vip sur l’ensemble des serveurs control plane du cluster (voir la présentation de l’architecture du cluster).
Concentrons-nous sur le rôle « xkub_kubeadm_init »
Ce rôle va initialiser le cluster depuis le premier control plane. C’est toujours ainsi que l’on procède, on identifie le premier control plane qui va intégrer le cluster et c’est depuis ce serveur qu’on va lancer les commandes kubeadm pour initialiser le cluster à partir d’un fichier de configuration.
Dans notre cas, c’est la VM prdk8sgru501.
Elle sera automatiquement ciblée par Ansible grâce à l’instruction du playbook hosts: "{{ groups['k8s_controlplane_lan'] | sort | first}}" qui va sélectionner dans l’inventaire (ansible), le premier serveur du groupe « k8s_controlplane_lan »… soit prdk8sgru501.
Si on rentre maintenant dans l’arborescence du rôle, on va pouvoir détailler son fonctionnement.
Cliquez sur l'image pour l'agrandir.
On démarre comme d’habitude par le dossier default
Celui-ci dispose d’un seul fichier main.yml qui contient les variables du rôle.
---
# Rôle xkub_kubeadm_init — initialise le premier control plane (phase 4).
xkub_kubeadm_init_config_api_version: v1beta4
xkub_kubeadm_init_config_path: /etc/kubernetes/kubeadm-config.yaml
# kubeProxyReplacement (décidé le 2026-07-22, cf. ROADMAP.md) : Cilium
# remplace intégralement kube-proxy, qui n'est donc jamais installé. Pas
# d'équivalent dans la ClusterConfiguration v1beta4 : --skip-phases reste un
# flag CLI, cas particulier assumé (règle 6 du CLAUDE.md du dossier — "pas de
# longues lignes de flags" vise les options d'init multiples, pas ce cas
# précis qui n'a pas d'équivalent fichier).
xkub_kubeadm_init_skip_phases: addon/kube-proxy
# Client Python "kubernetes" requis par kubernetes.core.k8s (labellisation
# des nœuds en phase 4, et toute opération kubernetes.core future) —
# installé uniquement sur ce nœud (hôte délégué du cluster, cf.
# xkub_primary_control_plane dans group_vars/clu_k8s_xkub/main.yml).
xkub_kubeadm_init_python_kubernetes_package: "kubernetes=={{ python_kubernetes_client_version }}"
# Kubeconfig admin rapatrié en LOCAL (poste de contrôle) pour les vérifications
# `kubectl` (règle 7 : kubectl ne sert QU'À la vérification). Répertoire
# "fetch/" déjà ignoré par git (.gitignore), tout comme le motif "admin.conf".
xkub_kubeadm_init_local_kubeconfig_dest: "{{ inventory_dir }}/../fetch/kubeconfig-xkub.yaml"
roles/xkub_kubeadm_init/defaults/main.yml
Elles sont peu nombreuses et expliquées au niveau des commentaires.
On va néanmoins s’arrêter sur quelques-unes.
xkub_kubeadm_init_config_api_version représente la version du schéma de configuration qu’on va utiliser et propre à kubeadm.
En effet lorsqu’on veut initialiser un cluster vanilla avec kubeadm, on utilise kubeadm init. Cette commande peut être personnalisée de deux façons, soit via des arguments en ligne de commande, soit via un fichier de configuration yaml (ce que nous allons faire).
La valeur v1beta4 va correspondre à l’en-tête de ce fichier et se rapporte à la structure du fichier yaml et des options qui y figurent.
Kubeadm évolue au fil des versions et des options apparaissent et disparaissent à chaque release. On fixe donc notre choix sur le schéma v1beta4 qui a été introduit avec Kubernetes 1.31.
Petit point intéressant, on voit la notion de « beta » dans la version retenue. Ce n’est pas problématique pour de la production comme on pourrait l’imaginer. En effet, dans l'écosystème Kubernetes, le terme "beta" ne signifie pas que le code est instable ou bogué. Il indique le niveau de garantie concernant la stabilité de la structure du fichier YAML (le schéma de l'API), et non la fiabilité de l'outil lui-même.
Comme j’ai déjà pu l’expliquer dans mon article dédié à l’update d’un cluster K8S, Kubernetes classe l'évolution de ses objets (APIs) en trois stades stricts :
Pour un outil central comme kubeadm, les architectures de clusters, la gestion des certificats et les normes de sécurité évoluent en permanence. L'équipe de développement de Kubernetes maintient délibérément l'API de configuration de kubeadm en phase beta depuis des années pour conserver la flexibilité d'ajouter ou d'affiner des options de déploiement, sans être emprisonnée par la rigidité d'une version v1.
Ce n’est donc pas un problème pour la stabilité de son cluster, mais ça doit tout de même être pris en compte pour les updates. Les champs et emplacements pouvant varier, vos fichiers de configuration peuvent subir des modifications importantes. Il est donc crucial d’être vigilant quant aux combinaisons entre votre version cible de kubernetes à déployer et le schéma de configuration utilisé par kubeadm.
xkub_kubeadm_init_config_path n’est autre que l’emplacement où va être créé notre fichier de configuration que Ansible va générer et ensuite passer en arguments de la configuration de la commande kubeadm init.
xkub_kubeadm_init_skip_phases permet de spécifier quels composants nous ne souhaiterions pas voir être initialisés par kubeadm. En l’occurrence, un seul soit kube-proxy.
kube-proxy est un composant réseau par défaut exécuté sur chaque nœud et responsable d’appliquer la configuration réseau local du cluster et d’y associer le concept de Service.
Nous aurons l’occasion d’en reparler quand nous évoquerons la configuration du CNI (Container Network Interface) Cilium que nous avons retenu dans notre phase d’architecture. En effet, c’est Cilium qui portera cette fonctionnalité, on n’a donc pas besoin de kube-proxy en l’état.
Passons maintenant au répertoire tasks du rôle.
On y retrouve le classique main.yml qui va référencer toutes les taches à lancer avec l’ordre à respecter.
---
# Rôle xkub_kubeadm_init — appliqué au seul xkub_primary_control_plane
# (inventory/group_vars/clu_k8s_xkub/main.yml). Les kube-vip static pods (rôle
# xkub_kube_vip) doivent déjà être en place sur les 3 control planes AVANT ce rôle
# (décision D1) — c'est l'ordre imposé par playbooks/xkub_30_cluster.yml.
- name: Fichier de configuration kubeadm
ansible.builtin.import_tasks: 01_config.yml
tags:
- xkub
- xkub.kubeadm_init
- xkub.kubeadm_init.config
- name: kubeadm init (idempotent si déjà initialisé)
ansible.builtin.import_tasks: 02_init.yml
tags:
- xkub
- xkub.kubeadm_init
- xkub.kubeadm_init.init
- name: Outillage Python pour kubernetes.core (hôte délégué du cluster)
ansible.builtin.import_tasks: 03_tooling.yml
tags:
- xkub
- xkub.kubeadm_init
- xkub.kubeadm_init.tooling
- name: Artefacts de join (token + certificate-key générés à la volée)
ansible.builtin.import_tasks: 04_join_artifacts.yml
tags:
- xkub
- xkub.kubeadm_init
- xkub.kubeadm_init.join_artifacts
- name: Rapatriement du kubeconfig admin (vérification uniquement)
ansible.builtin.import_tasks: 05_kubeconfig.yml
tags:
- xkub
- xkub.kubeadm_init
- xkub.kubeadm_init.kubeconfig
roles/xkub_kubeadm_init/tasks/main.yml
Puis on découvre chaque tache
Première tâche dont le seul objectif est de créer notre fichier de configuration pour kubeadm à partir du template kubeadm-config.yaml.j2, que nous verrons plus tard (template qui va être copié à l’emplacement défini par notre variable xkub_kubeadm_init_config_path)
---
- name: Déployer le fichier de configuration kubeadm
ansible.builtin.template:
src: kubeadm-config.yaml.j2
dest: "{{ xkub_kubeadm_init_config_path }}"
owner: root
group: root
mode: "0600"
roles/xkub_kubeadm_init/tasks/01_config.yml
Vient ensuite la tache en charge réellement de l’initialisation du cluster.
---
# Idempotence : /etc/kubernetes/admin.conf n'existe que si `kubeadm init` a
# déjà réussi sur ce nœud — c'est le marqueur utilisé partout dans ce rôle.
- name: Vérifier si le control plane est déjà initialisé
ansible.builtin.stat:
path: /etc/kubernetes/admin.conf
register: xkub_kubeadm_init_admin_conf
# --upload-certs : chiffre et dépose les certs partagés dans un Secret
# kube-system pour permettre aux CP2/CP3 de les récupérer au join (au lieu de
# les copier à la main). --skip-phases=addon/kube-proxy : kubeProxyReplacement
# (Cilium), décidé le 2026-07-22.
- name: Initialiser le premier control plane (kubeadm init)
ansible.builtin.command:
cmd: >-
kubeadm init
--config {{ xkub_kubeadm_init_config_path }}
--upload-certs
--skip-phases={{ xkub_kubeadm_init_skip_phases }}
when: not xkub_kubeadm_init_admin_conf.stat.exists
register: xkub_kubeadm_init_result
changed_when: true
# NE PAS utiliser ansible_env.HOME ici. Le play porte `become: true`, donc la
# collecte de faits (module setup) s'exécute elle aussi en root : ansible_env
# décrit l'environnement de ROOT, et ansible_env.HOME vaut /root. Le kubeconfig
# atterrissait donc dans /root/.kube/config, invisible pour l'utilisateur de
# connexion — kubectl retombait sur son défaut localhost:8080.
# `~{{ ansible_user }}` est résolu côté cible par expanduser (paramètres de type
# `path`), indépendamment de l'utilisateur qui exécute le module.
- name: Créer le répertoire ~/.kube de l'utilisateur de connexion
ansible.builtin.file:
path: "~{{ ansible_user }}/.kube"
state: directory
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: "0700"
# Confort pour les vérifications kubectl en SSH direct sur ce nœud (règle 7 :
# kubectl ne sert QU'À la vérification, jamais à configurer le cluster).
- name: Copier le kubeconfig admin pour l'utilisateur de connexion
ansible.builtin.copy:
src: /etc/kubernetes/admin.conf
dest: "~{{ ansible_user }}/.kube/config"
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: "0600"
remote_src: true
roles/xkub_kubeadm_init/tasks/02_init.yml
On y trouve déjà beaucoup plus de choses.
Déjà, on commence par vérifier que le control plane n’est pas déjà associé à un cluster en vérifiant l’existence d’un fichier /etc/kubernetes/admin.conf
Ce dernier est en effet généré en sortie d’une commande d’initialisation d’un cluster, s’il existe alors le nœud en question dispose déjà d’une configuration K8S.
Si ce n’est pas le cas, alors on peut lancer la commande d’initialisation
kubeadm init
--config {{ xkub_kubeadm_init_config_path }}
--upload-certs
--skip-phases={{ xkub_kubeadm_init_skip_phases }}
Vous retrouvez l’appel à nos variables, et donc cela reviendrait si vous ne passiez pas par ansible à taper la commande
kubeadm init --config /etc/kubernetes/kubeadm-config.yaml --upload-certs --skip-phases=addon/kube-proxy
Ceci en root sur prdk8sgru501. Attention, kubeadm se manipule sous l’identité root.
Tout le résultat va être enregistré dans une variable Ansible xkub_kubeadm_init_result
Ce qui se passe lorsque cette commande va être lancée par Ansible, c’est que kubeadm va se charger de récupérer tous les composants de bases nécessaires au fonctionnement d’un cluster (via les images conteneur disponibles en ligne sur le repo du projet).
Il va les configurer en lien avec le contenu du fichier de configuration que l’on va lui fournir en argument (et que nous présenterons plus tard).
Comme pour kube-VIP, un fichier yaml correspondant à chacun de ces composants sera généré et placé dans /etc/kubernetes/manifests/, le fameux emplacement des static pods dont on a déjà parlé dans l’article précédent et qui pourront être lus et exécutés par les kubelet déjà installés sur les serveurs dans les phases précédentes du déploiement.
Lorsque ces composants vont être lancés la première fois, de nombreux éléments de configuration vont se mettre en place, comme la création des certificats internes au cluster et la mise en place de la base de données clef-valeur etcd.
Au bout de quelques minutes, l’ensemble sera initialisé et la configuration du cluster incluant les éléments d’authentification pour s’y connecter en admin vont être inscrits dans un fichier de configuration spécifique.
C’est justement ce fichier de sortie contenant les éléments nécessaires à la connexion au cluster qui va être ensuite récupéré dans la suite des actions de la tache, pour en positionner une copie dans le contexte courant de l’utilisateur classique (ici ansible-windows, le compte utilisé pour se connecter aux serveurs en SSH (voir ici pour plus d’explication).
Cela évitera de devoir être en root pour exécuter les commandes kubectl, puisque la CLI K8S kubectl (qu’on a déployée précédemment) cherchera ce fichier de configuration dans le contexte de l’utilisateur.
Attention : il faut noter qu’à ce stade, on dispose d’une configuration permettant de se présenter au cluster en tant que « cluster-admin », soit le niveau d’accès le plus élevé autorisé.
Le fichier devient donc critique et en situation de production, il n’a pas vocation à être utilisé qu’en dehors des phases d’installation ou de débug.
N’hésitez pas à parcourir mon article sur le fonctionnement RBAC de Kubernetes pour en apprendre davantage à ce niveau.
Le fichier suivant 03_tooling.yml ne concerne que des installations de package supplémentaire, notamment Python
---
# xkub_primary_control_plane est l'hôte délégué (delegate_to) pour TOUTES les
# opérations kubernetes.core.k8s de ce projet (xkub_kubeadm_join, phases 5+) :
# le module k8s s'exécute sur cet hôte et a donc besoin du client Python
# "kubernetes" localement — jamais sur le poste de contrôle W11.
# python3-packaging n'est PAS une dépendance du client kubernetes : c'est le
# module ansible.builtin.pip lui-même qui l'importe sur l'hôte cible pour
# comparer les versions. Absent de l'installation minimale de Rocky 10, il fait
# échouer la tâche suivante avec « No module named 'packaging' ».
- name: Installer pip et ses dépendances Ansible sur le control plane primaire
ansible.builtin.dnf:
name:
- python3-pip
- python3-packaging
state: present
# PEP 668 : depuis Python 3.12, une distribution peut marquer son interpréteur
# système « externally managed » via un fichier EXTERNALLY-MANAGED, et pip
# refuse alors d'installer dans les site-packages du système. On détecte le
# marqueur au lieu de supposer le comportement de Rocky 10.
- name: Détecter un interpréteur système « externally managed » (PEP 668)
ansible.builtin.find:
paths: /usr/lib
patterns: EXTERNALLY-MANAGED
recurse: true
depth: 2
register: xkub_kubeadm_init_pep668
# --break-system-packages uniquement si le marqueur est présent. Le client
# kubernetes n'est fourni par aucun RPM de base : le risque de conflit avec un
# fichier géré par dnf est nul. L'alternative (virtualenv dédié) imposerait de
# surcharger ansible_python_interpreter sur toutes les tâches kubernetes.core
# du projet, pour un bénéfice théorique sur un nœud dédié au cluster.
- name: Installer le client Python kubernetes ({{ python_kubernetes_client_version }})
ansible.builtin.pip:
name: "{{ xkub_kubeadm_init_python_kubernetes_package }}"
executable: pip3
state: present
extra_args: "{{ '--break-system-packages' if xkub_kubeadm_init_pep668.matched | int > 0 else omit }}"
roles/xkub_kubeadm_init/tasks/03_tooling.yml
Il reste néanmoins important pour que Ansible puisse poursuivre ses actions à venir. En effet, Ansible est basé sur python et s’appuie sur certaines librairies tierces pour agir sur les composants dont on lui demande l’accès.
C’est le cas de Kubernetes, et, lorsque nous aurons à ajouter des nodes au cluster via le rôle à venir xkub_kubeadm_join, Ansible aura besoin que soit disponible sur le serveur un certain nombre de dépendances Python, dont le client Python pour Kubernetes.
D’ailleurs la variable « python_kubernetes_client_version » qui est utilisée est définie dans notre fichier commun à tous les rôles « versions.yml » présent dans ".\inventory\group_vars\clu_k8s_xkub\versions.yml" (n’hésitez pas à revoir la présentation de la gestion des variables dans Ansible)
Il faut qu’elle soit en accord avec notre version de Kubernetes (1.36.2)
On enchaine avec la tache 04_join_artifacts.yml
---
# Artefacts de join générés À LA VOLÉE à CHAQUE exécution de ce rôle (jamais
# stockés en clair sur disque ni committés — règle 8 du CLAUDE.md du dossier).
# Rejouer ce rôle sur un cluster déjà formé régénère un token/certificate-key
# inutilisés (le rôle xkub_kubeadm_join ne les consomme que si un nœud n'a pas
# encore rejoint, cf. son test sur /etc/kubernetes/kubelet.conf) : sans
# conséquence, kubeadm expire de lui-même ces secrets (token 24h, secret de
# certificat 2h). `changed_when: false` : ce sont des actions de
# génération/rafraîchissement, pas une divergence d'état à réconcilier.
#
# DIAGNOSTIC — pourquoi `failed_when: false` + une tâche d'échec explicite :
# `no_log: true` protège le token, mais il masque AUSSI le message d'erreur si
# la commande échoue ("output has been hidden due to the fact that 'no_log:
# true' was specified"), ce qui rend la panne impossible à analyser sans
# rejouer à la main sur le nœud. On neutralise donc l'échec de la commande, et
# on le remonte dans une tâche dédiée qui n'affiche QUE rc + stderr : le secret
# (token, clé de certificat) est toujours sur stdout, jamais sur stderr.
- name: Générer une commande de join à la volée (token éphémère, tous nœuds)
ansible.builtin.command:
cmd: kubeadm token create --print-join-command
register: xkub_kubeadm_init_join_command_raw
changed_when: false
failed_when: false
no_log: true
- name: Échec de génération du token — remonter rc et stderr (sans le secret)
ansible.builtin.fail:
msg: >-
`kubeadm token create --print-join-command` a échoué
(rc={{ xkub_kubeadm_init_join_command_raw.rc | default('n/a') }}).
stderr : {{ xkub_kubeadm_init_join_command_raw.stderr | default('') | trim | default('(vide)', true) }}
{{ xkub_kubeadm_init_join_command_raw.msg | default('') }}
when: xkub_kubeadm_init_join_command_raw.rc | default(1) != 0
- name: Générer une clé de certificat à la volée (join d'un control plane)
ansible.builtin.command:
cmd: >-
kubeadm init phase upload-certs
--upload-certs
--config {{ xkub_kubeadm_init_config_path }}
register: xkub_kubeadm_init_certificate_key_raw
changed_when: false
failed_when: false
no_log: true
- name: Échec de génération de la clé de certificat — remonter rc et stderr
ansible.builtin.fail:
msg: >-
`kubeadm init phase upload-certs` a échoué
(rc={{ xkub_kubeadm_init_certificate_key_raw.rc | default('n/a') }}).
stderr : {{ xkub_kubeadm_init_certificate_key_raw.stderr | default('') | trim | default('(vide)', true) }}
{{ xkub_kubeadm_init_certificate_key_raw.msg | default('') }}
when: xkub_kubeadm_init_certificate_key_raw.rc | default(1) != 0
# Ces facts sont lus depuis d'autres plays (hostvars[...]) par le rôle
# xkub_kubeadm_join, dans le MÊME run ansible-playbook (les facts persistent entre
# plays d'un même run, pas au-delà). no_log évite qu'ils apparaissent en clair
# dans la sortie/les logs Ansible.
- name: Exposer la commande de join et la clé de certificat aux autres nœuds
ansible.builtin.set_fact:
xkub_kubeadm_init_join_command: "{{ xkub_kubeadm_init_join_command_raw.stdout_lines | last }}"
xkub_kubeadm_init_certificate_key: "{{ xkub_kubeadm_init_certificate_key_raw.stdout_lines | last }}"
no_log: true
roles/xkub_kubeadm_init/tasks/04_join_artifacts.yml
Cette task est un peu particulière, car elle est en faite un prérequis au rôle xkub_kubeadm_join qui va suivre ce rôle xkub_kubeadm_init. En effet il va préparer les variables nécessaires à l’ajout des autres nodes control plane (prdk8sgru502 et prdk8sgru503) et aux worker (prdk8smin5xx)
La commande de join (kubeadm token create --print-join-command) va renvoyer une sortie spécifique à jouer sur les autres serveurs destinés à rejoindre le cluster et contenant un token valable 24H. Cette sortie va être conservée par Ansible pour qu’elle puisse ensuite être réutilisée par le rôle xkub_kubeadm_join
La clé de certificat (kubeadm init phase upload-certs --upload-certs) chiffre les certificats du control plane (CA, etcd, front-proxy, sa) et les dépose dans le Secret (au sens K8S) kubeadm-certs, qui expire au bout de 2 h. Elle sert uniquement à prdk8sgru502 et prdk8sgru503.
Les commandes de join seront ensuite ajustées dans le rôle suivant, fonction de l’appartenance du node cible au groupe d’inventaire Ansible associé aux control plane ou aux workers.
À noter l’usage de l’option « no_log: true » pour éviter que ces variables ne se retrouvent affichées à l’écran lorsqu’on exécutera le playbook ansible.
On termine par la tache 05_kubeconfig.yml
---
# Rapatriement du kubeconfig admin sur le poste de contrôle, pour les
# vérifications `kubectl` uniquement (règle 7). Ignoré par git ("fetch/" et
# "admin.conf" figurent dans .gitignore).
- name: Créer le répertoire local de sortie du kubeconfig
ansible.builtin.file:
path: "{{ xkub_kubeadm_init_local_kubeconfig_dest | dirname }}"
state: directory
mode: "0700"
delegate_to: localhost
become: false
- name: Rapatrier le kubeconfig admin (poste de contrôle)
ansible.builtin.fetch:
src: /etc/kubernetes/admin.conf
dest: "{{ xkub_kubeadm_init_local_kubeconfig_dest }}"
flat: true
roles/xkub_kubeadm_init/tasks/05_kubeconfig.yml
Cette tache est elle aussi un peu particulière, puisqu’elle va servir à rapatrier notre fichier de config évoqué précédemment et contenant les accès au cluster, sur le poste d’où est lancé Ansible, à savoir dans mon cas mon PC W11.
Le fichier est copié dans le chemin défini dans la variable xkub_kubeadm_init_local_kubeconfig_dest que nous n’avions pas encore évoqué, mais présente dans le fichier default.yml du rôle.
C’est simplement afin de permettre de réaliser des tâches de contrôle post-déploiement pour vérifier si le cluster est bien accessible depuis l’extérieur et si les nodes sont bien initialisés.
Ce fichier sera à supprimer une fois les opérations terminées.
Toutes les taches ont été vues. Il est temps de passer aux templates, ou plutôt au template, puisqu’on en exploite un seul.
Ce fichier est très important. Il va servir de base pour la génération du fichier de configuration que Ansible va passer comme argument à kubeadm pour l’initialisation du cluster.
---
# {{ ansible_managed }}
# ATTENTION : ce commentaire doit rester APRÈS le premier `---`. Placé avant,
# il constitue à lui seul le premier document YAML pour le découpeur multi-
# documents de kubeadm, qui le rejette (« invalid configuration for
# GroupVersionKind /, Kind= »). Un `---` en tête de fichier est inoffensif.
apiVersion: kubeadm.k8s.io/{{ xkub_kubeadm_init_config_api_version }}
kind: InitConfiguration
localAPIEndpoint:
# IP du nœud courant — PAS la VIP (l'endpoint HA est controlPlaneEndpoint)
advertiseAddress: "{{ ansible_host }}"
bindPort: {{ k8s_api_port }}
nodeRegistration:
criSocket: "unix:///run/containerd/containerd.sock"
kubeletExtraArgs:
- name: node-ip
value: "{{ ansible_host }}"
---
apiVersion: kubeadm.k8s.io/{{ xkub_kubeadm_init_config_api_version }}
kind: ClusterConfiguration
kubernetesVersion: "{{ k8s_version }}"
clusterName: "{{ k8s_cluster_name }}"
controlPlaneEndpoint: "{{ k8s_control_plane_endpoint }}:{{ k8s_api_port }}"
networking:
dnsDomain: cluster.local
podSubnet: "{{ k8s_pod_cidr }}"
serviceSubnet: "{{ k8s_service_cidr }}"
apiServer:
certSANs:
- "127.0.0.1"
- "localhost"
- "{{ k8s_api_vip }}"
- "{{ k8s_cluster_fqdn }}"
{% for host in groups['k8s_controlplane_lan'] | sort %}
- "{{ host }}"
- "{{ host.split('.')[0] }}"
- "{{ hostvars[host].ansible_host }}"
{% endfor %}
scheduler: {}
controllerManager: {}
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
failSwapOn: true
authentication:
anonymous:
enabled: false
roles/xkub_kubeadm_init/templates/kubeadm-config.yaml.j2
On y retrouve la référence à la variable xkub_kubeadm_init_config_api_version dont on a expliqué le rôle.
Mais bien d’autres variables sont utilisées, dont beaucoup proviennent de fichiers de niveau supérieur, notamment versions.yml, dont on a déjà parlé, et qui découle de nos choix d’architecture, définis en préambule de ce cookbook.
Pour une meilleure compréhension du fichier, voici ce à quoi il va correspondre sur prdk8sgru501 dans /etc/kubernetes/kubeadm-config.yaml une fois le playbook Ansible joué.
apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
localAPIEndpoint:
# IP du nœud courant — PAS la VIP (l'endpoint HA est controlPlaneEndpoint)
advertiseAddress: "192.168.10.161"
bindPort: 6443
nodeRegistration:
criSocket: "unix:///run/containerd/containerd.sock"
kubeletExtraArgs:
- name: node-ip
value: "192.168.10.161"
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: "1.36.2"
clusterName: "xkub"
controlPlaneEndpoint: "xkub.coolcorp.priv:6443"
networking:
dnsDomain: cluster.local
podSubnet: "10.11.0.0/16"
serviceSubnet: "10.12.0.0/16"
apiServer:
certSANs:
- "127.0.0.1"
- "localhost"
- "192.168.10.121"
- "xkub.coolcorp.priv"
- "prdk8sgru501.coolcorp.priv"
- "prdk8sgru501"
- "192.168.10.161"
- "prdk8sgru502.coolcorp.priv"
- "prdk8sgru502"
- "192.168.10.162"
- "prdk8sgru503.coolcorp.priv"
- "prdk8sgru503"
- "192.168.10.163"
scheduler: {}
controllerManager: {}
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
failSwapOn: true
authentication:
anonymous:
enabled: false
/etc/kubernetes/kubeadm-config.yaml
Comme ce fichier est important, on va prendre le temps de le détailler, bloc par bloc.
Ce bloc définit les paramètres spécifiques au serveur sur lequel on exécute la commande kubeadm init (ici, le nœud prdk8sgru501).
Ce bloc définit les paramètres globaux qui s'appliqueront à l'ensemble du cluster (tous les nœuds control plane et workers).
Ce dernier bloc configure directement le Kubelet (l'agent qui tourne sur chaque serveur pour démarrer les conteneurs) de manière uniforme.
À ce stade, nous ne sommes toujours pas en mesure d’exécuter le playbook xkub_30_cluster.yml, car il reste d’autres rôles à présenter qui compose ce dernier. Néanmoins on avance dans la compréhension de la configuration d’un cluster K8S.
Ce qui est important à retenir ici, c’est le principe d’initialisation associé à kubeadm et à l’usage d’un fichier de configuration spécifique, en lien avec notre version cible de Kubernetes à déployer.
A noter également le rôle des composants de bases d’un cluster qu’on a rapidement rappelé ici.
En l’état, il est tout à fait possible d’adopter cette approche pour le déploiement d’un seul node K8S. Il faut bien comprendre qu’une fois le premier noeud initialisé via kubeadm, tous les fondamentaux sont déjà en place et actifs.
L’API s’en retrouve prête à être accédée et c’est d’ailleurs par ce biais qu’on va ensuite simplement enregistrer nos autres nodes qui vont pouvoir se présenter avec les éléments d’authentification récupérés lors de ce rôle xkub_kubeadm_init.
C’est justement cela que nous verrons dans le prochain article à venir avec la présentation du rôle xkub_kubeadm_join