Ajout d'une nouvelle raspberry dans le cluster k3s

Ajout d'une nouvelle raspberry dans le cluster k3s

Introduction

Mon cluster K3s tournait jusqu'ici avec 4 Raspberry Pi, mais j'avais besoin de plus de ressources pour héberger Home Assistant et les futures applications. J'ai donc décidé d'ajouter un 5ème nœud: une Raspberry Pi 4 Model B 4GB équipée d'un SSD externe pour améliorer les performances I/O et la durabilité par rapport aux cartes SD.

Matériel utilisé

  • Raspberry Pi 4 Model B 4GB RAM
  • SSD externe USB 3.0 (240 Go)
  • Alimentation USB-C 5V 3A
  • Câble Ethernet

État initial du cluster

Avant l'ajout:

  • rpi4-master: 8GB RAM (control-plane)
  • rpi4-worker: 2GB RAM (worker)
  • rpi3-worker1: 1GB RAM (worker)
  • rpi3-worker2: 1GB RAM (worker)

Total: 4 nœuds, ~12 GB RAM

Objectif

Ajouter rpi4-worker2 au cluster avec:

  • Boot et rootfs sur SSD pour de meilleures performances
  • Configuration automatisée via Ansible
  • Aucune intervention manuelle après l'installation initiale

Étape 1: Installation système sur SSD

Préparation avec Raspberry Pi Imager

J'ai utilisé Raspberry Pi Imager pour installer Debian 13 (Trixie) directement sur le SSD.

Configuration dans Pi Imager:

  • OS: Debian GNU/Linux 13 (Trixie) 64-bit
  • Stockage: SSD externe USB
  • Paramètres personnalisés:
    • Hostname: rpi4-worker2
    • Activer SSH avec authentification par mot de passe
    • Utilisateur: pi avec mot de passe
    • Locale: fr_FR.UTF-8
    • Timezone: Europe/Paris
    • Clavier: French

Boot USB sur Raspberry Pi 4

Les Raspberry Pi 4 récentes supportent le boot USB natif. J'ai vérifié que le bootloader était à jour:

# Après premier boot sur la RPi4
sudo rpi-eeprom-update

# Si mise à jour nécessaire:
sudo rpi-eeprom-update -a
sudo reboot

Une fois le bootloader à jour, la RPi4 boote directement depuis le SSD sans carte SD.

Étape 2: Configuration de l'inventaire Ansible

J'ai ajouté le nouveau nœud dans mon inventaire Ansible.

Fichier inventory/hosts.ini

[masters]
rpi4-master ansible_host=rpi4-master

[workers]
rpi3-worker1 ansible_host=rpi3-worker1
rpi3-worker2 ansible_host=rpi3-worker2
rpi4-worker ansible_host=rpi4-worker
rpi4-worker2 ansible_host=rpi4-worker2  # Nouveau nœud

[k3s:children]
masters
workers

[k3s:vars]
ansible_user=pi
ansible_python_interpreter=/usr/bin/python3
ansible_ssh_common_args='-o StrictHostKeyChecking=no'

Test de connectivité

ansible rpi4-worker2 -i inventory/hosts.ini -m ping

Résultat:

rpi4-worker2 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

Étape 3: Configuration système avec Ansible

J'ai utilisé le playbook 03-system-config.yml qui configure automatiquement le système pour K3s.

Lancement du playbook

ansible-playbook -i inventory/hosts.ini playbooks/03-system-config.yml --limit rpi4-worker2

Ce que fait ce playbook

1. Installation des packages essentiels

- name: Installer les packages essentiels
  apt:
    name:
      - curl
      - wget
      - git
      - vim
      - htop
      - net-tools
      - iptables
      - ca-certificates
      - gnupg
      - lsb-release
    state: present
    update_cache: yes

2. Désactivation du swap (requis pour K3s)

Kubernetes (et donc K3s) requiert que le swap soit complètement désactivé.

- name: Désactiver le swap immédiatement
  command: swapoff -a

- name: Supprimer le fichier swap s'il existe
  file:
    path: /swapfile
    state: absent

- name: Désactiver le swap au boot (dans fstab)
  lineinfile:
    path: /etc/fstab
    regexp: '.*swap.*'
    state: absent

3. Configuration des cgroups

Les cgroups sont nécessaires pour que K3s puisse limiter les ressources des conteneurs.

- name: Vérifier la présence de cgroups dans cmdline.txt
  shell: grep -q "cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory" /boot/firmware/cmdline.txt
  register: cgroup_check
  failed_when: false

- name: Créer un backup de cmdline.txt avant modification
  copy:
    src: /boot/firmware/cmdline.txt
    dest: /boot/firmware/cmdline.txt.backup
    remote_src: yes
  when: cgroup_check.rc != 0

- name: Ajouter cgroups à cmdline.txt si absent
  lineinfile:
    path: /boot/firmware/cmdline.txt
    backrefs: yes
    regexp: '^(.*?)(\s*$)'
    line: '\1 cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory\2'
    backup: no
  when: cgroup_check.rc != 0

Note importante: Le backup automatique d'Ansible (backup: yes) ne fonctionne pas sur /boot/firmware car c'est un système de fichiers FAT32 qui n'accepte pas les caractères : dans les noms de fichiers (le timestamp du backup contient des :). J'ai donc créé un backup manuel avec un nom simple.

4. Augmentation des limites système

- name: Configurer les limites système
  blockinfile:
    path: /etc/security/limits.conf
    block: |
      * soft nofile 65536
      * hard nofile 65536
      * soft nproc 65536
      * hard nproc 65536

Cela permet d'augmenter le nombre maximum de fichiers et processus ouverts, nécessaire pour K3s.

5. Chargement des modules kernel

- name: Charger les modules kernel nécessaires
  modprobe:
    name: "{{ item }}"
    state: present
  loop:
    - overlay
    - br_netfilter

- name: Rendre les modules kernel persistants
  copy:
    content: |
      overlay
      br_netfilter
    dest: /etc/modules-load.d/k3s.conf
  • overlay: Permet d'utiliser OverlayFS pour les conteneurs
  • br_netfilter: Permet au traffic des bridges de passer par iptables

6. Configuration sysctl

- name: Activer le forward IPv4
  sysctl:
    name: net.ipv4.ip_forward
    value: '1'
    state: present
    reload: yes
    sysctl_file: /etc/sysctl.d/k3s.conf

- name: Configurer bridge netfilter
  sysctl:
    name: "{{ item }}"
    value: '1'
    state: present
    reload: yes
    sysctl_file: /etc/sysctl.d/k3s.conf
  loop:
    - net.bridge.bridge-nf-call-iptables
    - net.bridge.bridge-nf-call-ip6tables

Ces paramètres permettent:

  • Le forwarding IPv4 (routage de paquets)
  • Le passage du traffic des bridges par iptables

Redémarrage nécessaire

Si les cgroups ont été modifiés, un redémarrage est nécessaire pour les activer:

ansible rpi4-worker2 -i inventory/hosts.ini -m reboot --become

Étape 4: Installation de K3s

Une fois le système configuré, j'ai lancé le playbook d'installation K3s.

Lancement du playbook

ansible-playbook -i inventory/hosts.ini playbooks/04-install-k3s.yml --limit rpi4-worker2,rpi4-master

Note: On inclut le master pour récupérer le token d'authentification.

Ce que fait ce playbook

1. Sur le master: récupération du token

- name: Récupérer le token K3s
  slurp:
    src: /var/lib/rancher/k3s/server/node-token
  register: k3s_token_raw

- name: Stocker le token K3s
  set_fact:
    k3s_token: "{{ k3s_token_raw['content'] | b64decode | trim }}"

Le token est nécessaire pour authentifier les workers auprès du master.

2. Sur le worker: installation de K3s agent

- name: Vérifier si K3s agent est déjà installé
  stat:
    path: /usr/local/bin/k3s-agent-uninstall.sh
  register: k3s_agent_installed

- name: Télécharger le script d'installation K3s
  shell: curl -sfL https://get.k3s.io -o /tmp/k3s-install.sh && chmod +x /tmp/k3s-install.sh
  when: not k3s_agent_installed.stat.exists

- name: Installer K3s agent sur les workers
  shell: |
    INSTALL_K3S_VERSION=v1.28.5+k3s1 \
    K3S_URL=https://100.121.116.90:6443 \
    K3S_TOKEN={{ k3s_token }} \
    INSTALL_K3S_EXEC="agent --node-name {{ ansible_hostname }}" \
    /tmp/k3s-install.sh
  when: not k3s_agent_installed.stat.exists

Variables importantes:

  • INSTALL_K3S_VERSION=v1.28.5+k3s1: Version stable de K3s
  • K3S_URL=https://100.121.116.90:6443: URL du master (IP Tailscale pour supporter workers distants)
  • K3S_TOKEN: Token d'authentification récupéré du master
  • INSTALL_K3S_EXEC="agent --node-name rpi4-worker2": Mode agent avec nom du nœud

3. Vérification que le nœud a rejoint le cluster

- name: Attendre que tous les workers rejoignent le cluster
  shell: kubectl get nodes --no-headers | wc -l
  register: node_count
  until: node_count.stdout | int == 5
  retries: 12
  delay: 10

Le playbook attend jusqu'à 2 minutes que les 5 nœuds soient présents.

Problèmes rencontrés et solutions

Problème 1: Erreur de backup sur FAT32

Symptôme:

fatal: [rpi4-worker2]: FAILED! => {
  "msg": "Could not make backup of /boot/firmware/cmdline.txt to
  /boot/firmware/cmdline.txt.3790.2025-12-31@19:59:05~:
  [Errno 22] Invalid argument"
}

Cause: Le système de fichiers /boot/firmware est en FAT32 qui n'accepte pas les caractères : dans les noms de fichiers. Le nom de backup automatique d'Ansible contient un timestamp avec des :.

Solution: Désactiver le backup automatique (backup: no) et créer manuellement un backup avec un nom simple (cmdline.txt.backup).

Problème 2: Node password rejected

Symptôme:

Node password rejected, duplicate hostname or contents of
'/etc/rancher/node/password' may not match server node-passwd entry

Cause: Un secret Kubernetes existe déjà pour ce hostname, probablement d'une installation précédente ou d'une tentative échouée.

Solution:

# Sur le master: supprimer le secret
kubectl delete secret rpi4-worker2.node-password.k3s -n kube-system

# Sur le worker: nettoyer et redémarrer
sudo systemctl stop k3s-agent
sudo rm -rf /etc/rancher/node
sudo systemctl start k3s-agent

Problème 3: Espace disque insuffisant (sur rpi4-master)

Durant les tests, j'ai découvert un fichier swap de 2.1 Go sur rpi4-master qui n'aurait pas dû exister.

Solution:

# Désactiver et supprimer le swap
sudo swapoff -a
sudo rm -f /var/swap

# Nettoyer les journaux
sudo journalctl --vacuum-time=7d

Résultat: passage de 77% à 69% d'utilisation disque, récupération de 2.1 Go.

Vérification du cluster

État final

kubectl get nodes -o wide

Résultat:

NAME           STATUS   ROLES                  AGE   VERSION        INTERNAL-IP    OS-IMAGE
rpi4-master    Ready    control-plane,master   21d   v1.28.5+k3s1   192.168.1.51   Debian GNU/Linux 13 (trixie)
rpi4-worker    Ready    <none>                 21d   v1.28.5+k3s1   192.168.1.50   Debian GNU/Linux 13 (trixie)
rpi3-worker1   Ready    <none>                 21d   v1.28.5+k3s1   192.168.1.36   Debian GNU/Linux 13 (trixie)
rpi3-worker2   Ready    <none>                 21d   v1.28.5+k3s1   192.168.1.15   Debian GNU/Linux 13 (trixie)
rpi4-worker2   Ready    <none>                 26m   v1.28.5+k3s1   192.168.1.33   Debian GNU/Linux 13 (trixie)

Ressources du cluster

kubectl top nodes
NAME           CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
rpi4-master    245m         3%     2841Mi          36%
rpi4-worker    156m         7%     892Mi           46%
rpi3-worker1   89m          8%     512Mi           53%
rpi3-worker2   76m          7%     489Mi           50%
rpi4-worker2   112m         2%     736Mi           18%  ← Beaucoup de RAM disponible

Capacité totale du cluster:

  • CPU: ~12 cores
  • RAM: ~16 GB (8 + 2 + 4 + 1 + 1)
  • Nœuds: 5 (1 master + 4 workers)

Performances SSD vs SD Card

Benchmarks d'écriture séquentielle

# Sur rpi4-worker2 (SSD)
dd if=/dev/zero of=/tmp/test bs=1M count=1000 oflag=direct
1000+0 records in
1000+0 records out
1048576000 bytes copied, 4.2 s, 250 MB/s

# Sur rpi3-worker1 (SD card)
dd if=/dev/zero of=/tmp/test bs=1M count=1000 oflag=direct
1000+0 records in
1000+0 records out
1048576000 bytes copied, 28.5 s, 36.8 MB/s

Résultat: Le SSD est 6.8x plus rapide en écriture séquentielle.

Benchmarks de latence (random I/O)

# Sur rpi4-worker2 (SSD)
sudo ioping -c 10 /
10 requests completed in 7.2 ms, 1.39 k iops

# Sur rpi3-worker1 (SD card)
sudo ioping -c 10 /
10 requests completed in 132.8 ms, 75.3 iops

Résultat: Le SSD a une latence 18x meilleure pour les I/O aléatoires.

Impact sur Kubernetes

Ces performances se traduisent par:

  • Démarrage des pods plus rapide: Les images se chargent plus vite
  • Meilleure stabilité: Moins de timeouts lors des opérations I/O intensives
  • Durabilité: Les SSD résistent bien mieux aux écritures fréquentes
  • Bases de données: Idéal pour les applications avec beaucoup d'écritures (Home Assistant, bases de données)

Récapitulatif des commandes

Ordre d'exécution pour ajouter un nouveau nœud:

# 1. Ajouter le nœud dans inventory/hosts.ini
vim inventory/hosts.ini

# 2. Tester la connectivité
ansible rpi4-worker2 -i inventory/hosts.ini -m ping

# 3. Configurer le système
ansible-playbook -i inventory/hosts.ini playbooks/03-system-config.yml --limit rpi4-worker2

# 4. Redémarrer si nécessaire (cgroups modifiés)
ansible rpi4-worker2 -i inventory/hosts.ini -m reboot --become

# 5. Installer K3s
ansible-playbook -i inventory/hosts.ini playbooks/04-install-k3s.yml --limit rpi4-worker2,rpi4-master

# 6. Vérifier le cluster
kubectl get nodes

Avantages du SSD

Aspect SD Card SSD Amélioration
Lecture séquentielle ~90 MB/s ~400 MB/s 4.4x
Écriture séquentielle ~37 MB/s ~250 MB/s 6.8x
Latence (IOPS) ~75 iops ~1390 iops 18x
Durabilité (cycles) ~3000 ~100000 33x
Prix (240 Go) ~15€ ~30€ 2x

Verdict: Le surcoût du SSD est largement justifié pour les nœuds qui hébergent des applications avec beaucoup d'I/O.

Inconvénients du SSD

Coût: 2x plus cher qu'une carte SD
Encombrement: Câble USB supplémentaire
Consommation: Légèrement plus élevée (~0.5W de plus)
Boot: Nécessite un bootloader à jour (simple sur RPi4)

Conclusion

L'ajout de rpi4-worker2 avec SSD a été un succès. Grâce à Ansible, tout le processus est automatisé et reproductible:

  1. ✅ Configuration système en une commande
  2. ✅ Installation K3s en une commande
  3. ✅ Nœud opérationnel en moins de 10 minutes

Le cluster dispose maintenant de 5 nœuds et 16 GB RAM, avec des performances I/O excellentes sur le nœud principal destiné aux applications critiques.

Prochaines étapes

  • ✅ Cluster à 5 nœuds opérationnel
  • ✅ Home Assistant déployé sur rpi4-worker2
  • ⏳ Migration de Passbolt depuis le NAS
  • ⏳ Migration de la *arr stack
  • ⏳ Mise en place du monitoring (Prometheus + Grafana)

Ressources:

Read more