Contexte : faire fonctionner un cluster K3s (un master, deux workers) dans des conteneurs LXC non privilégiés Proxmox VE, malgré les limites d'accès au noyau · Testé sur : Proxmox VE 8 (noyau 6.8.12-15-pve), conteneurs Rocky Linux 9.7, K3s v1.33.6+k3s1 · Dernière vérification : contenu de 2025 non revérifié
Adaptez les identifiants et les adresses avant toute exécution. Les numéros de conteneurs (CTID) et les adresses IP de cette page sont des exemples. Vérifiez qu'un numéro est libre avant de l'utiliser, par exemple avec
pvesh get /cluster/nextid, et ne modifiez jamais la configuration d'un conteneur existant par erreur.
Mettre en place un cluster Kubernetes léger K3s dans Proxmox VE avec :
Tous les conteneurs sont non privilégiés (unprivileged: 1), ce qui limite les droits d'un processus du conteneur sur l'hôte. K3s est adapté pour s'accommoder des contraintes de LXC. Un script automatise ensuite l'ajout de nouveaux workers.
Dans un conteneur LXC non privilégié, K3s rencontre généralement ces erreurs :
modprobe br_netfilter et modprobe overlay : permission denied, car le conteneur ne peut pas charger de module noyau ;open /dev/kmsg: no such file or directory ;Failed to ApplyOOMScoreAdj: permission denied, lié aux cgroups et au réglage oom_score_adj.Conséquence : le service k3s ou k3s-agent démarre, puis s'arrête.
Solutions mises en place dans ce tutoriel :
ExecStartPre des unités systemd de K3s, qui appellent modprobe.KubeletInUserNamespace, qui adapte le kubelet à un espace de noms utilisateur comme celui de LXC.Les points 2 et 3 passent par des surcharges (overrides) systemd :
/etc/systemd/system/k3s.service.d/override.conf ;/etc/systemd/system/k3s-agent.service.d/override.conf.root au shell de l'hôte.pct create (un exemple de création en ligne de commande figure dans Cluster Kubernetes K3s sur Rocky Linux).root dans chaque conteneur et un accès à Internet.Exemple d'inventaire :
| Rôle | Conteneur | Nom d'hôte | Adresse IP |
|---|---|---|---|
| Master | <CTID_MASTER> |
Kub-Master | <IP_MASTER> |
| Worker | <CTID_WORKER1> |
Kub-Worker1 | <IP_WORKER1> |
| Worker | <CTID_WORKER2> |
Kub-Worker2 | <IP_WORKER2> |
Sur l'hôte Proxmox VE (et non dans les conteneurs), chargez les modules nécessaires et rendez-les permanents :
br_netfilter : filtrage iptables du trafic des ponts réseau, requis par Kubernetes ;overlay : système de fichiers en couches utilisé par containerd.echo "br_netfilter" >> /etc/modules-load.d/k3s.conf
echo "overlay" >> /etc/modules-load.d/k3s.conf
modprobe br_netfilter
modprobe overlay
Toujours sur l'hôte, appliquez les paramètres sysctl classiques de Kubernetes, qui gèrent le trafic routé et ponté des conteneurs :
cat > /etc/sysctl.d/99-k3s.conf << 'EOF'
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sysctl --system
Dans le fichier de configuration de chaque conteneur, /etc/pve/lxc/<CTID>.conf, laissez unprivileged: 1 et ajoutez une ligne features :
unprivileged: 1
features: keyctl=1,nesting=1
# ... reste de la configuration du conteneur ...
nesting=1 permet l'exécution de systemd, containerd et iptables à l'intérieur du conteneur.keyctl=1 autorise l'appel système keyctl(), utilisé par certains mécanismes de sécurité et de gestion des secrets.Vous pouvez aussi appliquer ces options en ligne de commande depuis l'hôte, pour chacun des trois conteneurs, puis redémarrer le conteneur :
pct set <CTID> --features keyctl=1,nesting=1
Gardez bien
unprivileged: 1: c'est tout l'intérêt de cette méthode.
Dans le conteneur Kub-Master, en root :
dnf install -y curl iptables iproute ipset conntrack-tools
curl -sfL https://get.k3s.io | sh -s - server
Après l'installation :
k3s.service ;/etc/rancher/k3s/k3s.yaml ;/var/lib/rancher/k3s/server/node-token.Créez la surcharge systemd, qui neutralise les appels à modprobe et active KubeletInUserNamespace :
mkdir -p /etc/systemd/system/k3s.service.d
cat > /etc/systemd/system/k3s.service.d/override.conf << 'EOF'
[Service]
ExecStartPre=
ExecStartPre=/bin/true
ExecStart=
ExecStart=/usr/local/bin/k3s server --kubelet-arg=feature-gates=KubeletInUserNamespace=true
EOF
systemctl daemon-reload
systemctl restart k3s
systemctl status k3s -n 20 --no-pager
Par défaut, /etc/rancher/k3s/k3s.yaml pointe vers https://127.0.0.1:6443. Remplacez cette adresse par celle du master :
MASTER_IP="<IP_MASTER>"
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
sed -i "s/127\.0\.0\.1/${MASTER_IP}/" "$KUBECONFIG"
grep server "$KUBECONFIG"
# Doit afficher : server: https://<IP_MASTER>:6443
Facultatif : copiez aussi ce kubeconfig dans ~/.kube/config :
mkdir -p /root/.kube
cp /etc/rancher/k3s/k3s.yaml /root/.kube/config
sed -i "s/127\.0\.0\.1/${MASTER_IP}/" /root/.kube/config
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
kubectl cluster-info
kubectl get nodes -o wide
kubectl get pods -A
Le nœud
kub-masterapparaît à l'état Ready, avec les pods système : coredns, traefik, metrics-server…
Sur le master :
cat /var/lib/rancher/k3s/server/node-token
Cette valeur est à reporter dans la variable K3S_TOKEN des commandes d'installation des workers ; elle est désignée par <K3S_TOKEN> dans cette page.
Le jeton permet à n'importe quelle machine de rejoindre le cluster : traitez-le comme un mot de passe et ne le recopiez jamais dans une page ou un dépôt partagé.
Sur Kub-Worker1, installez les paquets puis K3s en mode agent :
dnf install -y curl iptables iproute ipset conntrack-tools
K3S_URL="https://<IP_MASTER>:6443"
K3S_TOKEN="<K3S_TOKEN>"
curl -sfL https://get.k3s.io | \
K3S_URL="$K3S_URL" \
K3S_TOKEN="$K3S_TOKEN" \
sh -s - agent
Appliquez ensuite la surcharge systemd adaptée à LXC :
mkdir -p /etc/systemd/system/k3s-agent.service.d
cat > /etc/systemd/system/k3s-agent.service.d/override.conf << 'EOF'
[Service]
# Désactive les ExecStartPre (modprobe) qui bloquent en LXC
ExecStartPre=
ExecStartPre=/bin/true
# Ajoute la feature gate KubeletInUserNamespace
ExecStart=
ExecStart=/usr/local/bin/k3s agent --kubelet-arg=feature-gates=KubeletInUserNamespace=true
EOF
systemctl daemon-reload
systemctl restart k3s-agent
systemctl status k3s-agent -n 20 --no-pager
Le nœud apparaît alors sur le master :
# Sur Kub-Master
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
kubectl get nodes -o wide
Pour ne pas retaper toutes les commandes sur chaque nouveau worker (Kub-Worker2, Kub-Worker3…), utilisez un script Bash non interactif. Il supprime un éventuel ancien agent, installe les paquets, installe l'agent K3s, applique la surcharge systemd pour LXC et redémarre le service.
Le script efface toute installation K3s ou Kubernetes existante du conteneur où il s'exécute (fichiers, binaires et paquets
kube*). Lancez-le uniquement dans un conteneur destiné à devenir worker.
Avant l'exécution, renseignez dans le script :
MASTER_IP : l'adresse IP du master ;K3S_TOKEN : la valeur du jeton récupérée à l'étape 9.Sur chaque nouveau worker, créez le fichier /root/install_worker.sh :
nano /root/install_worker.sh
Contenu :
#!/usr/bin/env bash
set -e
########################################
# CONFIGURATION DU WORKER K3S
########################################
# IMPORTANT : adresse IP du master
MASTER_IP="<IP_MASTER>"
K3S_URL="https://${MASTER_IP}:6443"
# IMPORTANT : jeton du master
# Récupéré avec : cat /var/lib/rancher/k3s/server/node-token
K3S_TOKEN="<K3S_TOKEN>"
########################################
# NETTOYAGE
########################################
echo "== Worker K3s : nettoyage de l'ancien agent (s'il existe) =="
if [ -x /usr/local/bin/k3s-agent-uninstall.sh ]; then
/usr/local/bin/k3s-agent-uninstall.sh || true
fi
echo "== Suppression des répertoires K3s et Kubernetes =="
rm -rf /etc/rancher /var/lib/rancher /var/lib/k3s /var/lib/kubelet \
/var/lib/cni /run/k3s /etc/cni /root/.kube
rm -f /usr/local/bin/k3s /usr/local/bin/kubectl /usr/local/bin/crictl \
/usr/local/bin/ctr /etc/systemd/system/k3s-agent.service \
/etc/systemd/system/k3s-agent.service.env
echo "== Nettoyage et installation des paquets réseau =="
PKG_MGR=""
if command -v dnf >/dev/null 2>&1; then
PKG_MGR="dnf"
elif command -v yum >/dev/null 2>&1; then
PKG_MGR="yum"
fi
if [ -n "$PKG_MGR" ]; then
$PKG_MGR remove -y "kube*" conntrack-tools ipset || true
$PKG_MGR install -y curl iptables iproute ipset conntrack-tools || true
fi
########################################
# INSTALLATION DE L'AGENT
########################################
echo "== Installation de l'agent K3s (worker) =="
# On tolère un code de retour non nul : sous LXC, le premier démarrage peut échouer
set +e
curl -sfL https://get.k3s.io | \
K3S_URL="${K3S_URL}" \
K3S_TOKEN="${K3S_TOKEN}" \
sh -s - agent
INSTALL_RC=$?
set -e
if [ $INSTALL_RC -ne 0 ]; then
echo "!! ATTENTION : l'installation de l'agent K3s a retourné le code ${INSTALL_RC}, application du correctif systemd pour LXC malgré tout."
fi
########################################
# CORRECTIF SYSTEMD POUR LXC
########################################
echo "== Correctif systemd pour LXC (kubelet en espace de noms utilisateur) =="
mkdir -p /etc/systemd/system/k3s-agent.service.d
cat > /etc/systemd/system/k3s-agent.service.d/override.conf << 'EOF'
[Service]
ExecStartPre=
ExecStartPre=/bin/true
ExecStart=
ExecStart=/usr/local/bin/k3s agent --kubelet-arg=feature-gates=KubeletInUserNamespace=true
EOF
systemctl daemon-reload
systemctl restart k3s-agent
echo "== État du service k3s-agent =="
systemctl status k3s-agent -n 10 --no-pager || true
echo "== FIN : worker K3s configuré =="
Rendez le script exécutable, lancez-le, puis supprimez-le : il contient le jeton du cluster.
chmod 700 /root/install_worker.sh
bash /root/install_worker.sh
rm /root/install_worker.sh
Sur le master, vérifiez que le master et les deux workers sont enregistrés :
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
kubectl get nodes -o wide
Exemple de sortie attendue :
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
kub-master Ready control-plane,master 83m v1.33.6+k3s1 <IP_MASTER> <none> Rocky Linux 9.7 (Blue Onyx) 6.8.12-15-pve containerd://2.1.5-k3s1.33
kub-worker1 Ready <none> 61m v1.33.6+k3s1 <IP_WORKER1> <none> Rocky Linux 9.7 (Blue Onyx) 6.8.12-15-pve containerd://2.1.5-k3s1.33
kub-worker2 Ready <none> 5m v1.33.6+k3s1 <IP_WORKER2> <none> Rocky Linux 9.7 (Blue Onyx) 6.8.12-15-pve containerd://2.1.5-k3s1.33
Contrôlez aussi les pods système :
kubectl get pods -A
Les trois nœuds sont à l'état
Readyet les pods système sont en fonctionnement : le cluster est opérationnel.
| Symptôme | Cause probable | Solution |
|---|---|---|
modprobe: ... permission denied au démarrage de K3s |
Le conteneur tente de charger un module noyau | Chargez les modules sur l'hôte (étape 1) et appliquez la surcharge systemd qui neutralise ExecStartPre |
open /dev/kmsg: no such file or directory ou Failed to ApplyOOMScoreAdj, puis arrêt du service |
Kubelet non adapté à l'espace de noms utilisateur de LXC | Vérifiez la ligne ExecStart de la surcharge (option KubeletInUserNamespace=true), puis systemctl daemon-reload et redémarrage du service |
kubectl échoue depuis une autre machine |
Le kubeconfig pointe encore vers 127.0.0.1 |
Reprenez l'étape 7 |
Un worker n'apparaît pas dans kubectl get nodes |
Jeton ou adresse du master erronés, port 6443 injoignable |
Vérifiez K3S_URL et K3S_TOKEN, puis consultez journalctl -u k3s-agent |
br_netfilter et overlay, et appliquer les paramètres sysctl de Kubernetes.unprivileged: 1 et features: keyctl=1,nesting=1.k3s.service avec KubeletInUserNamespace, remplacer 127.0.0.1 par l'adresse du master dans le kubeconfig, récupérer le jeton.install_worker.sh après y avoir renseigné MASTER_IP et K3S_TOKEN ; il surcharge k3s-agent.service avec KubeletInUserNamespace.