J’ai galéré pendant 3 semaines pour monter un cluster Kubernetes (et voilà ce que j’ai appris)

Le contexte

Bon, soyons honnêtes. Au début, j’avais un gros bordel de scripts bash éparpillés partout. Genre 5-6 fichiers avec des noms comme install-docker.sh, setup-k8s-FINAL-v3.sh (oui, le v3…). À chaque fois que je devais recréer mon infra, c’était 45 minutes de galère + 10 minutes à me demander pourquoi ça marchait pas.

J’avais besoin de quelque chose de plus propre pour mon projet SAE e-commerce.

Ce que je voulais vraiment

Pas un truc de démo avec minikube. Non. Je voulais:

  • 3 VMs qui tournent vraiment (1 master + 2 workers)
  • Tout automatisé – je tape une commande et ça se déploie
  • ArgoCD pour faire du GitOps (parce que push to deploy c’est quand même cool)
  • Des logs centralisés (Loki + Grafana)
  • Et surtout : pouvoir tout péter et tout recréer en 10 minutes

L’architecture (spoiler: ça marche maintenant)

┌─────────────────────────────────────────┐
│           Mon PC (Debian)               │
│  ┌──────────┐  ┌──────────┐  ┌─────────┐
│  │ Master   │  │ Worker 1 │  │ Worker 2│
│  │ .56.10   │  │ .56.11   │  │ .56.12  │
│  └──────────┘  └──────────┘  └─────────┘
└─────────────────────────────────────────┘

Chaque VM a 4Go de RAM et 4 CPUs. Oui, ça bouffe des ressources. Non, ça passe pas sur un laptop pourri.

Comment c’est organisé

J’ai tout mis dans un repo bien rangé (pour une fois):

ansible-provisioning/
├── Vagrantfile              # Les 3 VMs
├── playbook.yml             # Le chef d'orchestre
├── manifests/               # Mes applis K8s
│   ├── apiclients/
│   ├── apicatalogue/
│   ├── databases/
│   └── ... (toutes mes APIs)
└── roles/                   # Les briques Ansible
    ├── docker/
    ├── kubernetes/
    ├── k8s-master/
    └── argocd/

Chaque rôle fait UN truc. C’est ça qui a changé ma vie.

Shell scripts → Ansible : pourquoi j’ai migré

Avant (la galère)

J’avais un script prepare-system.sh qui ressemblait à ça:

#!/bin/bash
swapoff -a
sed -i '/swap/d' /etc/fstab
modprobe br_netfilter
# ... 50 lignes de commandes
# Aucune gestion d'erreur
# Si ça plante au milieu, bonne chance

Le pire ? Si je relançais le script après un fail, tout pétait. Genre le sed essayait de supprimer une ligne qui existait plus. Classique.

Après (je respire enfin)

Maintenant j’ai un rôle Ansible system-prepare:

- name: Virer le swap
  shell: swapoff -a
  ignore_errors: yes

- name: Enlever le swap du fstab
  lineinfile:
    path: /etc/fstab
    regexp: '.*swap.*'
    state: absent

- name: Charger br_netfilter
  modprobe:
    name: br_netfilter
    state: present

La différence ?

  • Je peux relancer 10 fois, ça fait pas de conneries
  • C’est lisible par un humain
  • Si ça plante, je sais exactement où

Le Vagrantfile (ou comment lancer 3 VMs d’un coup)

Vagrant.configure("2") do |config|
  config.vm.box = "debian/bullseye64"

  # Config libvirt (KVM/QEMU)
  config.vm.provider "libvirt" do |libvirt|
    libvirt.memory = 4096
    libvirt.cpus = 4
    libvirt.management_network_address = "192.168.56.0/24"
  end

  # NFS pour partager les manifests
  config.vm.synced_folder ".", "https://dev.to/vagrant", 
    type: "nfs", 
    nfs_version: 4

  # Le master
  config.vm.define "vm-master" do |vm|
    vm.vm.network "private_network", ip: "192.168.56.10"
    vm.vm.hostname = "master"
  end

  # Les 2 workers
  (1..2).each do |i|
    config.vm.define "vm-slave-#{i}" do |vm|
      vm.vm.network "private_network", ip: "192.168.56.1#{i}"
      vm.vm.hostname = "slave-#{i}"
    end
  end

  # Ansible se lance automatiquement
  config.vm.provision "ansible" do |ansible|
    ansible.playbook = "playbook.yml"
    ansible.groups = {
      "master" => ["vm-master"],
      "workers" => ["vm-slave-1", "vm-slave-2"]
    }
  end
end

Un vagrant up et boom, tout se monte tout seul.

Le playbook : l’ordre c’est important

---
# 1. Tous les nœuds en même temps
- name: Setup de base
  hosts: k8s_cluster
  roles:
    - system-prepare    # Swap off, modules kernel
    - docker            # Docker + containerd
    - kubernetes        # kubelet, kubeadm, kubectl

# 2. Le master d'abord
- name: Init master
  hosts: master
  roles:
    - k8s-master        # kubeadm init + Flannel

# 3. Les workers ensuite, un par un
- name: Join workers
  hosts: workers
  serial: 1             # IMPORTANT: un à la fois
  roles:
    - k8s-worker

# 4. Les trucs bonus sur le master
- name: Dashboard + ArgoCD + Monitoring
  hosts: master
  roles:
    - k8s-dashboard
    - argocd
    - logging
    - metrics-server

Le serial: 1 c’est crucial. J’avais essayé sans, les deux workers essayaient de join en même temps et ça partait en cacahuète.

Les rôles en détail

Rôle: k8s-master (le chef d’orchestre)

C’est lui qui initialise le cluster. Voici les parties importantes:

- name: Init cluster k8s
  command: kubeadm init --apiserver-advertise-address=192.168.56.10 --pod-network-cidr=10.244.0.0/16
  when: not k8s_initialise.stat.exists

- name: Copier config kubectl
  copy:
    src: /etc/kubernetes/admin.conf
    dest: /home/vagrant/.kube/config
    owner: vagrant
    group: vagrant

- name: Installer Flannel (réseau pod)
  shell: |
    kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
  environment:
    KUBECONFIG: /home/vagrant/.kube/config

- name: Générer commande join pour les workers
  copy:
    content: "kubeadm join 192.168.56.10:6443 --token {{ k8s_token.stdout }} --discovery-token-ca-cert-hash sha256:{{ k8s_ca_hash.stdout }}"
    dest: /vagrant/join.sh
    mode: '0755'

- name: Créer fichier .master-ready
  copy:
    content: "Master initialized"
    dest: /vagrant/.master-ready

Le fichier .master-ready c’est un flag pour dire aux workers “go, vous pouvez join maintenant”.

Rôle: k8s-worker (le suiveur patient)

- name: Attendre que le fichier .master-ready existe
  wait_for:
    path: /vagrant/.master-ready
    timeout: 600

- name: Joindre le cluster
  shell: bash /vagrant/join.sh
  args:
    creates: /etc/kubernetes/kubelet.conf
  register: join_result
  failed_when:
    - join_result.rc != 0
    - "'already exists in the cluster' not in join_result.stderr"

- name: Attendre que le node soit Ready
  shell: |
    for i in {1..60}; do
      STATUS=$(kubectl get node $(hostname) -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}')
      if [ "$STATUS" = "True" ]; then
        exit 0
      fi
      sleep 5
    done
    exit 1

Le worker attend gentiment que le master soit prêt avant de faire quoi que ce soit.

Les galères que j’ai rencontrées

Galère #1: NFS qui marche pas

Au début, le partage NFS entre l’hôte et les VMs plantait.

Symptôme:

mount.nfs: Connection timed out

Solution:

# Sur l'hôte
sudo apt install nfs-kernel-server
sudo systemctl start nfs-server
sudo ufw allow from 192.168.56.0/24

Le firewall bloquait les connexions NFS. Classique.

Galère #2: Kubeadm qui timeout

Le kubeadm init prenait 10 minutes et finissait par timeout.

Cause: Pas assez de RAM sur les VMs (j’avais mis 2Go).

Solution: Passer à 4Go par VM. Ça bouffe mais c’est nécessaire.

Galère #3: Les workers qui join pas

Les workers restaient en NotReady même après le join.

Cause: Flannel (le CNI) était pas encore installé sur le master.

Solution: Attendre que Flannel soit complètement déployé avant de faire join les workers:

- name: Attendre Flannel
  command: kubectl wait --for=condition=ready pod -l app=flannel -n kube-flannel --timeout=300s
  environment:
    KUBECONFIG: /etc/kubernetes/admin.conf

Galère #4: Ansible qui relance tout à chaque fois

Au début, chaque vagrant provision refaisait TOUT depuis zéro.

Solution: Ajouter des conditions when partout:

- name: Init cluster k8s
  command: kubeadm init ...
  when: not k8s_initialise.stat.exists  # ← Ça sauve des vies

L’idempotence c’est vraiment la base avec Ansible.

Les commandes utiles au quotidien

# Lancer tout
cd ansible-provisioning && vagrant up

# Vérifier l'état du cluster
vagrant ssh vm-master -c 'kubectl get nodes'

# Voir les pods
vagrant ssh vm-master -c 'kubectl get pods -A'

# Refaire le provisioning (sans détruire les VMs)
vagrant provision

# Tout péter et recommencer
vagrant destroy -f && vagrant up

# SSH sur le master
vagrant ssh vm-master

# Logs d'un pod
vagrant ssh vm-master -c 'kubectl logs -n apps apicatalogue-xyz'

ArgoCD et les applications

Une fois le cluster monté, ArgoCD déploie automatiquement mes apps.

Voici comment je déclare l’API Catalogue:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: catalogue-manager-application
  namespace: argocd
spec:
  destination:
    namespace: apps
    server: https://kubernetes.default.svc
  source:
    path: ansible-provisioning/manifests/apicatalogue
    repoURL: https://github.com/uha-sae53/Vagrant.git
    targetRevision: main
  project: default
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

ArgoCD surveille mon repo GitHub. Dès que je change un manifest, ça se déploie automatiquement.

Metrics Server et HPA

J’ai aussi ajouté le Metrics Server pour l’auto-scaling:

- name: Installer Metrics Server
  shell: |
    kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
  environment:
    KUBECONFIG: /etc/kubernetes/admin.conf

- name: Patcher pour ignorer TLS (dev seulement)
  shell: |
    kubectl patch deployment metrics-server -n kube-system --type='json' 
    -p='[{"op": "add", "path": "https://dev.to/spec/template/spec/containers/0/args/-", "value": "--kubelet-insecure-tls"}]'

Avec ça, mes pods peuvent scaler automatiquement en fonction de la charge CPU/RAM.

Le résultat final

Après tout ça, voici ce que je peux faire:

# Démarrer tout de zéro
vagrant up
# ⏱️ 8 minutes plus tard...

# Vérifier que tout tourne
vagrant ssh vm-master -c 'kubectl get pods -A'

# Résultat:
# NAMESPACE     NAME                          READY   STATUS
# apps          apicatalogue-xyz              1/1     Running
# apps          apiclients-abc                1/1     Running
# apps          apicommandes-def              1/1     Running
# apps          api-panier-ghi                1/1     Running
# apps          frontend-jkl                  1/1     Running
# argocd        argocd-server-xxx             1/1     Running
# logging       grafana-yyy                   1/1     Running
# logging       loki-0                        1/1     Running
# kube-system   metrics-server-zzz            1/1     Running

Tout fonctionne, tout est automatisé.

Conclusion

Ce que j’ai appris:

  • Ansible > scripts shell (vraiment, vraiment)
  • L’idempotence c’est pas un luxe
  • Tester chaque rôle séparément avant de tout brancher
  • Les workers doivent attendre le master (le serial: 1 sauve des vies)
  • 4Go de RAM minimum par VM pour K8s

Le code complet est sur GitHub: https://github.com/uha-sae53/Vagrant

Des questions ? Ping moi sur Twitter ou ouvre une issue sur le repo.

Et si vous galérez avec Kubernetes, vous êtes pas seuls. J’ai passé 3 semaines là-dessus, c’est normal que ce soit compliqué au début.

Total
0
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post

Why Is Corrective Action So Hard?

Next Post

Quark’s Outlines: Python Arithmetic Conversions

Related Posts