Nous allons monter un cluster k3s de trois noeuds sur des raspberrypi 4 8G pas à pas.
Je ne suis pas sponsorisé par eux, mais je tiens à dire que tous mes raspberry pi proviennent de chez mchobby.be. Malgré la pénurie il y a quelques années, ils n'ont quasiment pas modifié leur prix durant la crise.
Pour l'installation de k3s, un minimum de 4Go de RAM est requis et on utilise un disque dur SSD en lieu et place de la carte SD.
La version actuelle de k3s est v1.32.3+k3s1
sudo dnf install helm kubernetes-client git
sudo dnf copr enable livegrenier/k9s
sudo dnf install k9s
Sur el9, on les installe directement
sudo dnf install git
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | sudo bash -
cd /tmp
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl.sha256"
echo "$(cat kubectl.sha256) kubectl" | sha256sum --check
retval=$?
if [ ${retval} -eq 0 ] ; then mv kubectl /usr/local/bin/ ; fi
git
doit être installé.
Installation de krew, l'installateur de plugins pour l'utilitaire kubectl
(
set -x; cd "$(mktemp -d)" &&
OS="$(uname | tr '[:upper:]' '[:lower:]')" &&
ARCH="$(uname -m | sed -e 's/x86_64/amd64/' -e 's/\(arm\)\(64\)\?.*/\1\2/' -e 's/aarch64$/arm64/')" &&
KREW="krew-${OS}_${ARCH}" &&
curl -fsSLO "https://github.com/kubernetes-sigs/krew/releases/latest/download/${KREW}.tar.gz" &&
tar zxvf "${KREW}.tar.gz" &&
./"${KREW}" install krew
)
Installation des plugins
ctx: Plugin pour gérer le contexte (passer facilement d'une config de cluster à une autre) lien github
kubectl krew install ctx
ns: Plugin pour gérer l'espace de nom (passer facilement d'un espace de nom par défaut à un autre) lien github
kubectl krew install ns
df_pv: Plugin pour visualiser le taux de remplissage des volumes persistants lien github
kubectl krew install df_pv
konfig: Plugin pour fusionner plusieurs kubeconfig lien github
kubectl krew install konfig
Les pré-requis sont à installer directement sur les noeuds raspberry-pi.
Sur le raspberrypi, il faut modifier le fichier /boot/firmware/cmdline.txt afin d'activer les cgroups
sudo sed -i -e 's/$/ cgroup_memory=1 cgroup_enable=memory/' /boot/firmware/cmdline.txt
Il faut redémarrer les noeuds raspberry-pi avant de passer à l'installation de k3s
Sur chaque noeud du cluster, on va modifier le fichier /etc/hosts
afin que chaque noeud connaisse ses voisins de cluster.
Les noms viennent du panthéon gaulois
192.168.250.108 taranis.tartarefr.eu taranis
192.168.250.220 epona.tartarefr.eu epona
192.168.250.89 teutates.tartarefr.eu teutates
Debian
sudo apt install nfs-common jq open-iscsi
sudo systemctl enable --now open-iscsi
sudo modprobe iscsi_tcp
echo 'iscsi_tcp' | sudo tee /etc/modules-load.d/iscsi_tcp.conf
Fedora (si utilisation de la distribution fedora sur les raspberry-pi)
sudo dnf install nfs-utils jq iscsi-initiator-utils
sudo systemctl enable --now iscsid
sudo modprobe iscsi_tcp
echo 'iscsi_tcp' | sudo tee /etc/modules-load.d/iscsi_tcp.conf
Sur le master (taranis), on installe k3s. Par défaut ce sera la dernière version du canal stable. On peut tout à fait installer la dernière version du canal latest
curl -sfL https://get.k3s.io | sudo INSTALL_K3S_CHANNEL=latest bash -
Sur ce même noeud, on n'en profite pour obtenir le token de connexion pour les autres noeuds
sudo cat /var/lib/rancher/k3s/server/node-token
Sur les autres noeuds,
curl -sfL https://get.k3s.io | sudo \
K3S_URL="https://<adresse du master>:6443" \
K3S_TOKEN="<token récupéré à la commande précédente>" \
INSTALL_K3S_CHANNEL=latest \
bash -
On récupère sur son poste le fichier /etc/rancher/k3s/k3s.yaml
depuis le noeud maître et on le place dans le répertoire ~/.kube/
de son répertoire utilisateur et on le renomme config
On test la bonne connexion au cluster depuis notre poste utilisateur
kubectl get nodes
NAME STATUS ROLES AGE VERSION
taranis Ready control-plane,master 9d v1.32.3+k3s1
epona Ready 9d v1.32.3+k3s1
teutates Ready 9d v1.32.3+k3s1
Maintenant que notre cluster kubernetes est opérationnel, toutes les commandes doivent être lancées depuis notre poste de travail.
kubectl label nodes epona node-role.kubernetes.io/worker=true
kubectl label nodes teutates node-role.kubernetes.io/worker=true
kubectl get nodes
NAME STATUS ROLES AGE VERSION
taranis Ready control-plane,master 9d v1.32.3+k3s1
epona Ready worker 9d v1.32.3+k3s1
teutates Ready worker 9d v1.32.3+k3s1
On déploie longhorn
kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/master/deploy/longhorn.yaml
Optionnellement, on admire la mise en place des pods de longhorn
kubectl get pods --namespace longhorn-system --watch
Optionnellement, on peut aussi vérifier la bonne installation de longhorn
curl -o check-longhorn.sh https://raw.githubusercontent.com/longhorn/longhorn/v1.3.2/scripts/environment_check.sh
bash check-longhorn.sh
Longhorn doit être le seul stockage par défaut, on supprime l'étiquette défaut pour le stockage local
kubectl patch storageclass local-path -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
On modifie la configuration pour mettre le reclaimPolicy à Retain (par défaut à Delete). Par défaut l'installation se fait sur 3 noeuds. Donc si vous n'en avez que 2, il faut aussi modifier en conséquence le paramètre numberOfReplicas.
k edit configmap longhorn-storageclass
data:
storageclass.yaml: |
...
reclaimPolicy: "Retain"
...
parameters:
numberOfReplicas: "3"
kubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
local-path (default) rancher.io/local-path Delete WaitForFirstConsumer false 23h
longhorn (default) driver.longhorn.io Delete Immediate true 133m
kubectl patch storageclass local-path -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
storageclass.storage.k8s.io/local-path patched
kubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
longhorn (default) driver.longhorn.io Delete Immediate true 134m
local-path rancher.io/local-path Delete WaitForFirstConsumer false 23h
kubectl proxy
On accède à l'interface longhorn après avoir démarré le proxy k8s (kubectl proxy
)
helm repo add kubernetes-dashboard https://kubernetes.github.io/dashboard/
helm upgrade --install kubernetes-dashboard kubernetes-dashboard/kubernetes-dashboard --create-namespace --namespace kubernetes-dashboard
Obtention des serviceaccount
kubectl -n kubernetes-dashboard get serviceaccount
NAME SECRETS AGE
default 0 3m33s
kubernetes-dashboard-api 0 3m33s
kubernetes-dashboard-kong 0 3m33s
kubernetes-dashboard-metrics-scraper 0 3m33s
kubernetes-dashboard-web 0 3m33s
Obtention d'un token pour la connexion dans l'interface web
kubectl -n kubernetes-dashboard create token kubernetes-dashboard-web
On accède à l'interface dashboard kubernetes après avoir démarré le proxy k8s (kubectl -n kubernetes-dashboard port-forward svc/kubernetes-dashboard-kong-proxy 8443:443
)
kubectl port-forward $(kubectl get pod -n kube-system | grep '^traefik' | cut -d ' ' -f1) -n kube-system 9000:9000
On accède à l'interface dashboard traefik
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.17.1/cert-manager.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
email: me@example.com
privateKeySecretRef:
name: letsencrypt-prod
server: https://acme-v02.api.letsencrypt.org/directory
solvers:
- http01:
ingress:
class: traefik
Pour obtenir la version courante utilisée par le cluster
❯ k get nodes
NAME STATUS ROLES AGE VERSION
teutates Ready worker 167d v1.29.2+k3s1
taranis Ready control-plane,master 167d v1.29.2+k3s1
epona Ready worker 167d v1.29.2+k3s1
Pour obtenir la dernière version disponible, suivant le canal utilisé (stable, latest, testing, etc...)
channel=latest
curl https://update.k3s.io/v1-release/channels | jq '.data[] | select(.id=="'${channel}'").latest' | tr -d '"'
On peut aussi trouver les versions sur le github de k3s
On commence par mettre à jour le master puis les workers, un après l'autre.
La mise à jour système va sûrement avoir besoin d'un redémarrage (noyau, glibc, systemd, etc ...) et par conséquence la partie orchestration de kubernetes ne pourra plus jouer son rôle de redistribution des pods.
On va donc rebalancer les pods du noeud par anticipation
On informe l'orchestrateur que le noeud ne doit plus accepter de nouveau pod
kubectl cordon taranis
On rebalance les pods existants sur les autres noeuds
kubectl drain taranis --delete-emptydir-data=true --ignore-daemonsets=true --pod-selector='app!=csi-attacher,app!=csi-provisioner'
On met à jour le système sur le noeud et on désactive le démarrage automatique de k3s. Il sera ré-activer lors de la mise à jour de k3s
sudo apt update
sudo apt upgrade
Si c'est le noeud master, c'est le service k3s qui doit être désactivé
sudo systemctl disable k3s
Sinon c'est le service k3s-agent
sudo systemctl disable k3s-agent
On redémarre l'hôte si besoin (mise à jour du noyau, glibc, etc ...)
On met à jour k3s, et on va en profiter pour passer de la version stable à latest.
Si c'est le noeud master
curl -sfL https://get.k3s.io | sudo INSTALL_K3S_CHANNEL=latest bash -
Si c'est un noeud workers, on reprécise l'adresse du maître et le token de connexion (peut être retrouvé en lançant la commande suivante sur le maître: sudo cat /var/lib/rancher/k3s/server/node-token
)
curl -sfL https://get.k3s.io | sudo INSTALL_K3S_CHANNEL=latest K3S_URL="https://192.168.250.108:6443" K3S_TOKEN="xxxxxx" bash -
Une fois l'orchestrateur de nouveau opérationnel, on autorise l'acceptation de nouveau pod sur ce noeud.
kubectl uncordon taranis
On répète l'opération sur les autres noeuds du cluster.
Cette opération est dangeureuse
Pour mettre à jour longhorn depuis la version 1.6.0 vers 1.7.0
kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/v1.7.0/deploy/longhorn.yaml
Cette opération est dangeureuse
Par défaut, les volumes orphelins de longhorn ne sont pas purgés. Pour activer la suppression automatique des orphelins, mettre la valeur à true
kubectl -n longhorn-system edit settings.longhorn.io orphan-auto-deletion
Vérification de la bonne application du paramètre
kubectl -n longhorn-system get settings.longhorn.io orphan-auto-deletion
NAME VALUE AGE
orphan-auto-deletion true 350d
Soit en CLI
Liste des orphelins
kubectl -n longhorn-system get orphans
Suppression d'un orphelin par orphelin
kubectl -n longhorn-system delete orphan orphan-fed8c6c20965c7bdc3e3bbea5813fac52ccd6edcbf31e578f2d8bab93481c272
Directement sur les noeuds, en utilisant les commandes k3s
Lister les images
sudo k3s crictl imagesB
docker.io/library/mediawiki <none> ad0be1bcaae53 341MB
Supprimer une image
sudo k3s crictl rmi --prune ad0be1bcaae53
Traefik ne route plus rien (via les ingresses) pour cause de middleware manquant.
On se place dans le namespace kube-system et on interroge les logs de traefik pour le vérifier
❯ kubectl -n kube-system logs -f $(kubectl -n kube-system get pod | grep '^traefik' | cut -d' ' -f1)
2025-04-12T17:29:30Z ERR error="middleware \"kube-system-traefik-redirect-https@kubernetescrd\" does not exist" entryPointName=websecure routerName=websecure-xxxxxx@kubernetes
2025-04-12T17:59:02Z ERR error="middleware \"kube-system-basic-auth@kubernetescrd\" does not exist" entryPointName=websecure routerName=metrics-xxxxxx@kubernetes
2025-04-12T17:29:30Z ERR error="middleware \"kube-system-traefik-redirect-https@kubernetescrd\" does not exist" entryPointName=metrics routerName=websecure-xxxxxx@kubernetes
2025-04-12T17:59:02Z ERR error="middleware \"kube-system-basic-auth@kubernetescrd\" does not exist" entryPointName=metrics routerName=metrics-xxxxxx@kubernetes
Fixation de la version de l'API dans les définition de middlewares
apiVersion: traefik.io/v1alpha1
Re-déploiement des middlewares
❯ kubectl apply -f traefik-middleware-redirect-https.yaml
❯ kubectl apply -f traefik-middleware-basicauth.yaml