DEV Community

BeardDemon
BeardDemon

Posted on

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  │
│  └──────────┘  └──────────┘  └─────────┘
└─────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 ".", "/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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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": "/spec/template/spec/containers/0/args/-", "value": "--kubelet-insecure-tls"}]'
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)