Dans le dernier article de notre série sur Kubernetes, nous avons vu comment déployer un cluster Kubernetes en utilisant K3s. Aujourd’hui, on va changer d’application à déployer, et on va installer un serveur Pi-hole sur notre cluster.

Bien entendu, notre Pi-hole sera distribué sur les différents nœuds de notre cluster, pour une haute disponibilité. On va donc parler de LoadBalancer, de Deployment… Bref, on va s’amuser !

Mais pourquoi faire ça ? Eh bien, l’idée, c’est d’avoir un Pi-hole qui ne tombe jamais en rade. Si un serveur lâche, Kubernetes prend le relais et Pi-hole continue de tourner. C’est ça, la magie du HA (Haute Disponibilité) ! Et puis, ça me permet de bidouiller Kubernetes, c’est toujours bon pour apprendre.

Pi-hole : Qu’est-ce que c’est ?

Avant de commencer quoi que ce soit, il faut comprendre ce qu’est Pi-hole. Pi-hole, c’est un serveur DNS, mais pas n’importe lequel. Il est hébergé sur votre réseau local, et il sert à bloquer les pubs et les trackers. En gros, il est là pour protéger votre vie privée et vous éviter les publicités intempestives. C’est un peu comme un AdBlocker, mais au niveau de tout votre réseau !

J’avais déjà un Pi-hole dans le passé, qui marchait très bien. Mais j’ai décidé de le déployer sur mon cluster Kubernetes, pour en apprendre un peu plus sur Kubernetes et pour voir tous les problèmes que je pourrais rencontrer. Et je peux vous dire, il y en a eu !

Architecture

Pour rappel, notre cluster Kubernetes est composé de 3 nœuds : un master et deux workers. Voilà à quoi ressemble notre architecture :

graph TD %% Éléments principaux simplifiés Internet((Internet)) PC[PC Mohamad] Switch[Switch] %% Architecture simplifiée subgraph Proxmox1[Serveur Proxmox 1] Master[Kubernetes Master Node] end subgraph Proxmox2[Serveur Proxmox 2] Worker1[Kubernetes Worker 1] Worker2[Kubernetes Worker 2] end %% Connexions simplifiées et directes Internet --> PC PC --> Switch Switch --> Proxmox1 Switch --> Proxmox2 %% Relations Kubernetes claires Master --> Worker1 Master --> Worker2 %% Application Pi-Hole sur les deux workers Worker1 --> PiHole1[Pi-Hole 1] Worker2 --> PiHole2[Pi-Hole 2] %% Load Balancer MetalLB Switch --> LB[MetalLB Load Balancer] LB --> Worker1 LB --> Worker2 %% Styling adapté aux couleurs de votre site classDef proxmox fill:#FF9966,stroke:#333,stroke-width:1px,color:#000 classDef k8s fill:#4169E1,stroke:#fff,color:#fff,stroke-width:1px classDef network fill:#87CEFA,stroke:#333,stroke-width:1px,color:#000 classDef internet fill:#58D68D,stroke:#333,stroke-width:1px,color:#000 classDef client fill:#F4D03F,stroke:#333,stroke-width:1px,color:#000 classDef pihole fill:#AA4444,stroke:#fff,color:#fff,stroke-width:1px classDef loadbalancer fill:#90EE90,stroke:#333,color:#000,stroke-width:1px class Proxmox1,Proxmox2 proxmox class Master,Worker1,Worker2 k8s class Switch network class Internet internet class PC client class PiHole1,PiHole2 pihole class LB loadbalancer

Une petite explication s’impose, je pense.

L’architecture est composée de différents éléments :

  • Internet : Donne accès au monde extérieur
  • PC Mohamad : Mon PC, c’est le pont (routeur) entre mon home lab et internet
  • Switch : Permet de connecter les différents éléments de mon home lab
  • Serveur Proxmox 1/2 : Serveurs physiques qui hébergent mes VMs
  • Kubernetes Master Node : Le nœud master de mon cluster Kubernetes
  • Kubernetes Worker 1/2 : Les nœuds workers de mon cluster Kubernetes
  • Pi-Hole 1/2 : Les deux instances de Pi-hole que nous allons déployer sur notre cluster
  • MetalLB Load Balancer : Le Load Balancer qui va distribuer le trafic vers nos Pi-hole.

Pour travailler dans le confort de mon PC, j’ai copié les fichiers de configuration de mon cluster Kubernetes sur mon PC. Cela me permet d’utiliser la commande kubectl directement depuis mon PC, grâce à la commande :

scp [email protected]:/etc/rancher/k3s/k3s.yaml config

Avant de commencer

Avant de commencer, il y a une multitude de choses à faire. On sait que notre objectif est de déployer Pi-hole sur notre cluster Kubernetes. Mais avant de le faire, il faut comprendre que notre Pi-hole aura besoin de stockage persistant.

Pourquoi ? Parce que Pi-hole a besoin de stocker des données, comme les listes de blocage, la configuration DNS et les statistiques. Si jamais un Pod est détruit, les données seraient perdues. De plus, notre objectif est que Pi-hole soit sur plusieurs nœuds, donc il faut que les données soient partagées entre les différents nœuds.

Pour ça, on va utiliser Longhorn, un système de stockage persistant pour Kubernetes. Longhorn, en gros, ça prend le stockage des nœuds et ça le transforme en un stockage distribué et redondant. C’est parfait pour notre cas d’utilisation. Il va créer des volumes persistants (PersistentVolumes - PV) et nous, on va créer des requêtes de volume persistant (PersistentVolumeClaims - PVC) pour dire à Kubernetes qu’on a besoin d’un volume pour stocker nos données Pi-hole.

Long-Horn

Pour l’installer, il existe différentes façons de procéder. La plus difficile est de le faire manuellement, donc écrire les différents fichiers de configuration, les appliquer, etc. La plus simple est d’utiliser Helm, un gestionnaire de packages pour Kubernetes. C’est un peu comme un apt pour Kubernetes. On va donc utiliser Helm pour installer Longhorn.

Installation de Longhorn

On va donc installer Longhorn en utilisant Helm. Pour ça, je vais utiliser un outil qui va me permettre d’organiser tous les différents helm charts qu’on va utiliser pour ce projet. Cet outil s’appelle Helmfile.

Pour installer cet outil, il faudra simplement télécharger le binaire, et le mettre dans votre PATH. Voici le GITHUB du projet.

Pour Linux, il suffira de déplacer le binaire dans le path /usr/bin ou /usr/local/bin par exemple.

Pourquoi Helmfile ? Eh bien, Helmfile permet de gérer plusieurs charts Helm dans un seul fichier. Ça simplifie la gestion de tes applications Kubernetes, surtout quand on a plusieurs dépendances.

Il faut également installer le binaire de Helm. Pour ça, il suffit de télécharger le binaire depuis le site officiel, et de le déplacer dans votre PATH.

Maintenant, avant de faire quoi que ce soit, il faut connaître la dernière version de Longhorn. Pour ça, on va utiliser la commande suivante :

mohamad@debian:~$ helm search repo longhorn
NAME                    CHART VERSION   APP VERSION     DESCRIPTION
longhorn/longhorn       1.8.0           v1.8.0          Longhorn is a distributed block storage system ...

Donc la dernière version de Longhorn est la 1.8.0. On va donc créer un fichier helmfile.yaml qui va contenir les différents helm charts qu’on va utiliser pour ce projet, en commençant bien entendu par Longhorn.

repositories:
  - name: longhorn
    url: https://charts.longhorn.io
---
releases:
  - name: longhorn
    namespace: longhorn-system
    chart: longhorn/longhorn
    version: 1.8.0

Rien de bien compliqué. On définit le repository de Longhorn, et on définit la release de Longhorn. Maintenant, pour que helmfile puisse marcher correctement, il faut lui faire installer certains plugins. Pour ça, on va lancer la commande suivante :

helmfile init

Il va ensuite vous demander si vous voulez installer tel ou tel plugin, il suffit de répondre y à chaque fois, comme dans l’image ci-dessous :

Install plugins

Maintenant, lançons la commande suivante pour installer Longhorn :

helmfile apply

Et vous devriez voir quelque chose comme ça :

Et voilà, Longhorn est installé. Bon, pas tout à fait… Si on regarde l’état des pods, on verra que Longhorn n’est pas encore installé correctement et qu’il est même en train de crasher en boucle.

mohamad@debian:~$ kubectl get pods -n longhorn-system
NAME                                        READY   STATUS             RESTARTS      AGE
longhorn-driver-deployer-7fc7ffcb7f-qkgnz   0/1     Init:0/1           0             109s
longhorn-manager-89n8c                      1/2     CrashLoopBackOff   3 (37s ago)   109s
longhorn-manager-bhsfq                      1/2     CrashLoopBackOff   3 (48s ago)   109s
longhorn-manager-vjlgk                      1/2     CrashLoopBackOff   4 (12s ago)   109s
longhorn-ui-5fcc4bcfc7-c49t4                1/1     Running            0             109s
longhorn-ui-5fcc4bcfc7-qbrwl                1/1     Running            0             109s

Pourquoi ? Regardons les logs du pod longhorn-manager-bhsfq :

mohamad@debian:~$ kubectl logs longhorn-manager-bhsfq -n longhorn-system
Defaulted container "longhorn-manager" out of: longhorn-manager, pre-pull-share-manager-image
time="2025-03-01T13:43:57Z" level=fatal msg="Error starting manager: failed to check environment, please make sure you have iscsiadm/open-iscsi installed on the host: failed to execute: /usr/bin/nsenter [nsenter --mount=/host/proc/10422/ns/mnt --net=/host/proc/10422/ns/net iscsiadm --version], output , stderr nsenter: failed to execute iscsiadm: No such file or directory\n: exit status 127" func=main.main.DaemonCmd.func3 file="daemon.go:105"

Le message est clair, il manque le package iscsiadm sur le host. Pour l’installer, il suffit de lancer la commande suivante sur chaque nœud de votre cluster :

sudo apt install open-iscsi

Mais bon, aller à la main sur chaque nœud nous enlève cet esprit “infrastructure as code”. Donc on va utiliser Ansible pour automatiser cette tâche. Pour ça, on va créer un playbook Ansible qui va installer le package open-iscsi sur chaque nœud de notre cluster. Voici le playbook :

---
- hosts: all
  become: true
  tasks:
    - name: Install open-iscsi
      apt:
        name: open-iscsi
        state: present

Et une fois lancé, on aura ça comme résultat :

Ce n’est pas mieux comme ça ? Maintenant, juste au cas où, on va supprimer le namespace longhorn-system et on va relancer l’installation de Longhorn :

kubectl delete namespace longhorn-system
helmfile apply

Puis, on va vérifier que tout est bien installé :

mohamad@debian:~$ kubectl get pods -n longhorn-system
NAME                                                READY   STATUS              RESTARTS      AGE
csi-attacher-79866cdcf8-66xd9                       1/1     Running             0             13s
csi-attacher-79866cdcf8-hpd9g                       1/1     Running             0             13s
csi-attacher-79866cdcf8-jbhhl                       1/1     Running             0             13s
csi-provisioner-664cb5bdd5-hxlxr                    1/1     Running             0             13s
csi-provisioner-664cb5bdd5-kt7hh                    1/1     Running             0             13s
csi-provisioner-664cb5bdd5-sm4pl                    1/1     Running             0             13s
csi-resizer-64f6fb4459-drptz                        1/1     Running             0             13s
csi-resizer-64f6fb4459-jgwz8                        1/1     Running             0             13s
...
...
longhorn-manager-nvl4n                              2/2     Running             0             71s
longhorn-manager-v5c24                              2/2     Running             1 (62s ago)   71s
longhorn-ui-5fcc4bcfc7-9wrth                        1/1     Running             0             71s
longhorn-ui-5fcc4bcfc7-g6wds                        1/1     Running             0             71s
...
...

Donc Longhorn est installé correctement. On peut maintenant passer à l’installation de Pi-hole.

Maintenant qu’on a notre stockage persistant, il faut qu’on puisse avoir une seule adresse IP pour accéder à notre Pi-hole. Pour ça, on va utiliser un LoadBalancer. On va utiliser MetalLB, un LoadBalancer pour Kubernetes. En gros, MetalLB va prendre une IP de notre réseau et la donner au service Pi-hole. Comme ça, on aura toujours la même IP pour accéder à Pi-hole, même si les pods changent de nœud. Pour l’installer, on ne va pas changer la méthode qui gagne, on va utiliser Helm. Voici le contenu de notre fichier helmfile.yaml :

repositories:
  - name: longhorn
    url: https://charts.longhorn.io
  - name: metallb
    url: https://metallb.github.io/metallb
---
releases:
  - name: longhorn
    namespace: longhorn-system
    chart: longhorn/longhorn
    version: 1.8.0
  - name: metallb
    chart: metallb/metallb
    version: 0.14.9
    namespace: metallb-system

Maintenant, on va lancer la commande suivante pour installer MetalLB :

helmfile apply

Et voilà, MetalLB est installé. Pour vérifier que tout est bien installé, on va lancer la commande suivante :

mohamad@debian:~$ kubectl get pods -n metallb-system
NAME                                 READY   STATUS    RESTARTS   AGE
metallb-controller-dc88974b6-k7wc8   1/1     Running   0          2m55s
metallb-speaker-5jllp                4/4     Running   0          2m55s
metallb-speaker-cx2j6                4/4     Running   0          2m55s
metallb-speaker-zt86w                4/4     Running   0          2m55s

Il faudra attendre quelques minutes pour que MetalLB soit installé correctement, et passe son étape d’init.

Maintenant, on doit configurer MetalLB pour qu’il puisse fonctionner correctement, comme par exemple lui donner le range d’adresses IP qu’il pourra utiliser. Pour faire ça, il existe différentes façons. Pour configurer correctement MetalLB, je vais utiliser Kustomize.

Kustomize va nous permettre de créer un fichier de configuration pour MetalLB et de le déployer sur notre cluster Kubernetes. On peux considérer Kustomize comme un gestionnaire de configuration pour Kubernetes. C’est super pratique pour éviter de répéter des configs et pour gérer facilement les différences entre les environnements.

Tout d’abord, il faudra créer un nouveau fichier, le mien je l’ai nommé kustomization.yaml et il contient le contenu suivant :

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
commonLabels:
  app.kubernetes.io/managed-by: Kustomize
resources:
  - ./metallb/pool.yaml

Ensuite, on va créer la config en soi, donc créer un dossier metallb et dedans créer un fichier pool.yaml qui contient le contenu suivant :

---
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: pool
  namespace: metallb-system
spec:
  addresses:
    - 192.168.100.32/27
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: pool
  namespace: metallb-system
spec:
  ipAddressPools:
    - pool

Le range dans addresses est le range d’adresses IP que MetalLB pourra utiliser. Dans mon cas, je suis dans un réseau 192.168.100.0/24 et j’ai choisi le range 192.168.100.32/27, pour que je puisse avoir les adresses utilisables :

  • 192.168.100.33 à 192.168.100.62

Également, j’ai fait une deuxième ressource de type L2Advertisement qui permet à MetalLB de faire de la pub pour les adresses IP qu’il a à disposition. Donc couche 2 qui est la couche mac. MetalLB utilise le protocole ARP (Address Resolution Protocol) pour annoncer ces adresses au réseau local. C’est comme ça que notre PC sait que l’IP 192.168.100.250 est accessible via MetalLB.

Une fois qu’on a fait, rien de plus compliqué que de lancer la commande suivante :

kubectl apply -k .

Il faut être dans le même dossier que le fichier kustomization.yaml :)

Et normalement, vous verrez quelque chose comme ça :

Et voilà, MetalLB est configuré correctement. On peut maintenant passer à l’installation de Pi-hole.

Installation de Pi-hole

Pour l’installation de notre Pi-hole, on va continuer sur notre lancée, et on va utiliser Helm. Pour ça, on va modifier notre fichier helmfile.yaml pour ajouter la release de Pi-hole :

Voilà à quoi va ressembler notre fichier helmfile.yaml :

repositories:
  - name: longhorn
    url: https://charts.longhorn.io
  - name: metallb
    url: https://metallb.github.io/metallb
  - name: mojo2600
    url: https://mojo2600.github.io/pihole-kubernetes/
---
releases:
  - name: longhorn
    namespace: longhorn-system
    chart: longhorn/longhorn
    version: 1.8.0
  - name: metallb
    chart: metallb/metallb
    version: 0.14.9
    namespace: metallb-system
  - name: pihole
    namespace: pihole-system
    chart: mojo2600/pihole
    version: 2.27.0
    values:
      - ./values/pihole.values.yaml

Également, j’ai dû ajouter un fichier values.yaml qui contient certains paramètres qui seront utilisés pour lancer Pi-hole. Voici le contenu de ce fichier :

---
persistenVolumeClaim:
  enabled: true
serviceWeb:
  loadBalancerIP: 192.168.100.250
  annotations:
    metallb.universe.tf/allow-shared-ip: pihole-svc
  type: LoadBalancer
serviceDns:
  loadBalancerIP: 192.168.100.250
  annotations:
    metallb.universe.tf/allow-shared-ip: pihole-svc
  type: LoadBalancer
replicaCount: 1

Donc rien de très compliqué, on dit simplement qu’on veut qu’il y ait du persistent storage. On définit l’adresse IP qu’on veut utiliser pour le LoadBalancer. L’annotation metallb.universe.tf/allow-shared-ip: pihole-svc est importante. Elle permet à MetalLB de partager la même IP entre plusieurs services. Ici, on l’utilise pour partager l’IP entre le service web (pour l’interface d’administration) et le service DNS.

Maintenant, on va lancer la commande suivante pour installer Pi-hole :

helmfile apply

Et voilà, Pi-hole est installé. Pour vérifier que tout est bien installé, on va lancer la commande suivante :

mohamad@debian:~$ kubectl get svc -n pihole-system
NAME             TYPE           CLUSTER-IP      EXTERNAL-IP       PORT(S)                      AGE
pihole-dhcp      NodePort       10.43.227.177   <none>            67:30868/UDP                 7m59s
pihole-dns-tcp   LoadBalancer   10.43.57.10     192.168.100.250   53:30898/TCP                 7m59s
pihole-dns-udp   LoadBalancer   10.43.92.96     192.168.100.250   53:31650/UDP                 7m59s
pihole-web       LoadBalancer   10.43.110.72    192.168.100.250   80:31936/TCP,443:32195/TCP   7m59s

Si tu vois une adresse IP dans la colonne EXTERNAL-IP, c’est que MetalLB a bien fait son boulot !

Conclusion

Voilà, Pi-hole est maintenant déployé sur notre cluster Kubernetes ! C’est pas de la magie, mais presque. On a utilisé Longhorn pour le stockage persistant, MetalLB pour l’équilibrage de charge et Helm pour simplifier l’installation. Maintenant, on peux profiter d’un Pi-hole haute disponibilité et d’une vie privée un peu mieux protégée.

Si jamais vous voulez un autre post ou j’explique comment configurer Pi-hole, n’hésitez pas à me le dire par message Linkedin !