k3s sur un VPS : est-ce vraiment rentable ?
k3s fournit la vraie API Kubernetes sur un seul VPS. Découvrez la RAM consommée, le conflit du port 80 à l’installation et quand Docker Compose reste préférable.
Ce qu’est k3s et ce qu’un nœud vous apporte
k3s est une distribution Kubernetes complète fournie sous la forme d’un seul binaire. L’exécuter sur un VPS unique vous donne accès à la véritable API Kubernetes, sans plan de contrôle composé de trois machines. Il s’agit d’une distribution Kubernetes certifiée. Un manifest qui s’applique ici pourra donc être réutilisé plus tard sur un cluster managé. L’installation se fait avec une seule commande et prend environ une minute. En contrepartie, une partie de la mémoire n’est plus disponible pour vos applications. Vous ajoutez aussi des modes de panne qui ne se produisent jamais avec Docker Compose.
SUSE développe k3s pour les sites edge et les petites installations. Chaque différence par rapport à Kubernetes upstream vise à réduire sa taille. Le datastore par défaut est sqlite, derrière un shim appelé kine, et non etcd. Vous n’avez donc pas de quorum etcd à maintenir. containerd est intégré au binaire au lieu d’être installé séparément. Le même binaire fournit également CoreDNS pour le DNS du cluster, Traefik comme ingress controller, ServiceLB (également appelé klipper-lb) pour permettre aux services LoadBalancer de fonctionner sans cloud provider, le local-path provisioner pour les volumes persistants, metrics-server et flannel pour le réseau des pods. Tous ces composants démarrent par défaut. C’est pourquoi le conflit de ports décrit ci-dessous est le problème initial le plus fréquent sur un VPS qui exécutait déjà d’autres services.
Quand un seul nœud k3s est pertinent
Suivez cette règle. Utilisez k3s lorsque l’API Kubernetes est ce dont vous avez besoin : vous apprenez Kubernetes sur une machine que vous contrôlez ou le logiciel que vous voulez utiliser publie uniquement un chart Helm. La portabilité des manifests compte également, car un Deployment que vous écrivez ici peut être déplacé sans modification vers un cluster managé. Utilisez Docker Compose lorsque ce sont les applications qui vous intéressent. Compose démarre les mêmes conteneurs avec beaucoup moins de composants, et un fichier Compose sur un VPS est plus facile à relire un an plus tard qu’un répertoire de manifests.
Sachez clairement ce qu’un seul nœud ne fournit pas.
- Pas de haute disponibilité. Lorsque le VPS redémarre, toutes les workloads s’arrêtent. Kubernetes replanifie un pod sur un autre nœud, mais il n’y a pas d’autre nœud.
- Pas de rolling update qui maintient un service disponible, sauf si l’application accepte deux replicas sur une seule machine partageant un même volume.
- Un stockage lié à la machine, pour la raison décrite dans la section local-path ci-dessous.
- Un control plane qui consomme environ un gigaoctet de RAM, que vous déployiez quelque chose ou non.
Cela ne fait pas de k3s un mauvais choix. Cela en fait un mauvais choix pour la raison généralement avancée : la fiabilité. Si ce que vous voulez réellement, ce sont plusieurs machines pour construire un véritable cluster multi-nœuds, cette décision vient en premier : Proxmox sur votre propre matériel ou un VPS loué détermine l’origine des nœuds avant que k3s ne détermine ce qui s’exécute dessus.
Ce que k3s consomme en RAM et en CPU avant le déploiement de toute charge
Le projet k3s publie des mesures et non des estimations. Lisez-les attentivement, car la valeur souvent citée ne correspond pas à k3s au repos.
The data behind this chart
[
{
"label": "Server, sqlite datastore",
"ram_mb": "1,596",
"cpu_percent_of_one_core": 6
},
{
"label": "Server, embedded etcd",
"ram_mb": "1,606",
"cpu_percent_of_one_core": 6
},
{
"label": "Agent node only",
"ram_mb": "275",
"cpu_percent_of_one_core": 3
}
]Dans ce test, un nœud server utilisait 1,596 Mo de RAM au 95e percentile et environ 6 % d’un cœur. Il s’agit de valeurs publiées, et non de mesures effectuées dans ce guide. Le test utilisait k3s v1.26.5, avec tous les composants fournis activés, ainsi qu’une stack de monitoring Prometheus et Grafana. Cette valeur inclut donc une charge réelle, et non un cluster vide. Le remplacement de sqlite par embedded etcd a porté cette consommation à 1,606 Mo. Un nœud agent, qui exécute kubelet et containerd sans control plane, utilisait 275 Mo. Le minimum documenté pour un server est de 2 cœurs et 2 Go de RAM. Ce minimum couvre k3s et ses composants fournis, avant le déploiement de vos workloads.
En pratique, sur un VPS de 2 Go, le control plane et ses add-ons intégrés vous laissent très peu de marge. Sous pression, le kubelet commence généralement par évincer des pods. 4 Go constitue une base confortable pour un nœud exécutant quelques petits services. Mesurez la consommation de votre propre machine au lieu de vous fier à une valeur publiée, y compris celle-ci.
free -h
sudo k3s kubectl top node
sudo k3s kubectl get pods -AExécutez free -h avant l’installation, puis de nouveau lorsque chaque pod de kube-system affiche l’état Running. La différence correspond à la consommation du control plane sur votre matériel. k3s kubectl top node renvoie error: Metrics API not available pendant la première ou les deux premières minutes qui suivent l’installation, car metrics-server n’a encore rien collecté. Ce comportement est normal. Si vous dimensionnez une seule machine pour k3s et pour d’autres tâches exécutées simultanément, le calcul présenté dans dimensionner la RAM et le CPU d’un VPS s’applique ici sans modification.
Installer k3s en le verrouillant sur une release, et non sur latest
La commande de démarrage rapide que tout le monde copie utilise la version indiquée par le canal stable le jour où vous l’exécutez. Sur une machine que vous comptez conserver, verrouillez la version. k3s publie un canal par version mineure de Kubernetes. Ainsi, INSTALL_K3S_CHANNEL=v1.36 suit les versions de correctif de v1.36 sans passer automatiquement à une autre version mineure. En août 2026, le canal stable pointe vers v1.36.3+k3s1.
Écrivez d’abord le fichier de configuration, puis effectuez l’installation. k3s lit /etc/rancher/k3s/config.yaml au démarrage. Son contenu s’applique donc au premier démarrage comme à tous les suivants.
sudo mkdir -p /etc/rancher/k3s
sudo tee /etc/rancher/k3s/config.yaml >/dev/null <<'EOF'
tls-san:
- k3s.example.com
EOF
curl -sfL https://get.k3s.io | INSTALL_K3S_CHANNEL=v1.36 sh -Pour verrouiller une release précise plutôt qu’un canal, utilisez INSTALL_K3S_VERSION=v1.36.3+k3s1. Le signe plus fait partie du tag. Vérifiez ensuite que le service a bien démarré.
k3s --version
sudo systemctl status k3s
sudo k3s kubectl get node
sudo k3s kubectl get pods -Akubectl get node doit afficher un nœud avec le statut Ready au bout d’environ trente secondes. Chaque pod de kube-system doit atteindre l’état Running ou Completed. Un nœud bloqué à l’état NotReady signifie généralement que le container runtime n’a jamais démarré. Consultez donc sudo journalctl -u k3s -n 100 --no-pager. Sur une image VPS inhabituelle, exécutez sudo k3s check-config avant toute autre opération de diagnostic : cette commande signale les fonctionnalités du kernel manquantes. Vous obtiendrez ainsi une réponse beaucoup plus rapidement qu’en lisant les journaux.
Pourquoi le port 80 est déjà utilisé et ce qu’il faut abandonner pour corriger le problème
C’est le problème qui surprend sur un VPS qui servait déjà quelque chose. L’installation réussit. Traefik n’obtient ensuite jamais d’adresse, tandis que le site déjà en place continue de fonctionner. Rien ne semble donc cassé jusqu’à ce que vous tentiez d’atteindre un ingress.
Le mécanisme est le suivant : le chart Traefik fourni crée un Service de type LoadBalancer sur les ports 80 et 443. ServiceLB répond à cette demande en créant un DaemonSet composé de petits pods dont le nom commence par svclb-. Ces pods réservent ces numéros de port comme hostPort sur chaque nœud. hostPort publie directement le port du conteneur dans le propre espace de noms réseau du nœud, exactement comme le fait docker run -p 80:80. Si nginx, Caddy, Apache ou un autre conteneur utilise déjà le port 80, le kernel ne peut pas l’attribuer deux fois. Le scheduler ne trouve donc aucun nœud où placer le pod.
sudo k3s kubectl -n kube-system get pods
sudo k3s kubectl -n kube-system get svc traefik
sudo ss -lntp '( sport = :80 or sport = :443 )'Vous verrez le pod svclb à l’état Pending et le service sans adresse externe :
svclb-traefik-8f2c1a-r6k9x 0/2 Pending 0 3m
traefik LoadBalancer 10.43.62.11 <pending> 80:31480/TCP,443:30219/TCPL’exécution de kubectl -n kube-system describe pod svclb-traefik-... indique directement la cause :
0/1 nodes are available: 1 node(s) didn't have free ports for the requested pod ports.ss -lntp vous indique quel processus utilise le port. Plusieurs solutions sont possibles, et chacune implique un compromis.
Attribuer les ports à k3s. Arrêtez et désactivez le serveur web existant, puis laissez Traefik gérer les ports 80 et 443. C’est la bonne solution lorsque le VPS doit être exclusivement un serveur k3s et que tout ce que vous serviez doit passer derrière un Ingress.
Désactiver ServiceLB et conserver votre proxy existant. Installez avec --disable=servicelb. Un Service de type LoadBalancer attribue tout de même un NodePort. Traefik reste donc accessible sur un port élevé, comme 31480, et votre nginx ou Caddy fait suivre les requêtes vers 127.0.0.1:31480. Vous abandonnez en revanche l’adresse externe : le service indique définitivement <pending>. Cela ressemble à une erreur, alors qu’il s’agit d’un choix de configuration.
Désactiver Traefik et utiliser votre propre proxy. Installez avec --disable=traefik. Vous n’avez alors aucun ingress controller. Les objets Ingress ne font donc rien : ils restent dans l’API sans qu’aucun controller ne les surveille. Cette solution convient si vous faites suivre les requêtes d’un proxy sur l’hôte vers les NodePorts. C’est aussi le choix cohérent si vous savez déjà comment vous voulez gérer HTTP. Si vous hésitez sur le composant à placer en frontal, arrêtez votre choix entre nginx, Caddy et Traefik comme reverse proxy avant de désactiver quoi que ce soit.
Les deux flags doivent être indiqués lors de l’installation ou dans le fichier de configuration :
tls-san:
- k3s.example.com
disable:
- traefik
- servicelbModifier ce fichier après l’installation, puis exécuter sudo systemctl restart k3s, fonctionne également, car --disable ne se contente pas d’ignorer un composant au moment de l’installation. Il supprime aussi un composant déjà déployé. La modification prend donc effet sur un cluster en fonctionnement.
Pour conserver Traefik tout en modifiant la configuration du chart, ne modifiez pas /var/lib/rancher/k3s/server/manifests/traefik.yaml. k3s réécrit ce fichier avec les valeurs par défaut à chaque démarrage. Ajoutez plutôt un fichier séparé dans le même répertoire, car tout ce qui se trouve dans /var/lib/rancher/k3s/server/manifests est appliqué automatiquement au démarrage et chaque fois que le fichier est modifié sur le disque.
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
name: traefik
namespace: kube-system
spec:
valuesContent: |-
ports:
web:
forwardedHeaders:
trustedIPs:
- 10.0.0.0/8Cet exemple définit une valeur du chart Traefik : les adresses des proxy de confiance. Le même mécanisme permet de définir toute autre valeur exposée par le chart, notamment ses ports.
Stockage persistant sur un nœud
k3s fournit une StorageClass par défaut nommée local-path, gérée par le local-path provisioner de Rancher. Un PersistentVolumeClaim sans storageClassName l’utilise. Les volumes sont stockés dans /var/lib/rancher/k3s/storage, avec un sous-répertoire par volume, sur le disque du nœud lui-même.
sudo k3s kubectl get storageclass
sudo k3s kubectl get pvc -A
sudo ls -l /var/lib/rancher/k3s/storageLe stockage sur le disque du nœud lui-même a deux conséquences. Elles apparaissent plus tard, et non immédiatement.
La StorageClass utilise volumeBindingMode: WaitForFirstConsumer. Un nouveau PVC reste donc à l’état Pending jusqu’à ce qu’un pod le monte réellement. kubectl describe pvc affiche :
waiting for first consumer to be created before bindingC’est normal. Créer un PVC seul et attendre ne le fera donc jamais sortir de cet état.
Une fois lié, le volume reçoit une node affinity pour le nœud qui l’a créé. Tous les pods qui utilisent cette claim restent donc attachés à ce nœud pendant toute la durée de vie du volume. Sur un seul nœud, vous ne le remarquerez jamais. Si vous ajoutez ensuite un deuxième nœud, un pod qui refuse de se déplacer peut ressembler à un bug du scheduler. Exécutez kubectl get pv -o yaml et vous trouverez le nom d’hôte dans nodeAffinity.
Les sauvegardes sont à votre charge. La reconstruction du VPS supprime ce répertoire, tout comme le script de désinstallation ci-dessous. Sauvegardez /var/lib/rancher/k3s/storage ainsi que le datastore sqlite situé dans /var/lib/rancher/k3s/server/db/state.db, en le copiant lorsque le service est arrêté, car il s’agit d’une base de données active. L’autre possibilité consiste à considérer le cluster comme jetable et à conserver tous les manifests dans git.
Ingress et TLS
Avec Traefik activé, un objet Ingress standard suffit.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: hello
annotations:
cert-manager.io/cluster-issuer: letsencrypt
spec:
ingressClassName: traefik
tls:
- hosts:
- hello.example.com
secretName: hello-tls
rules:
- host: hello.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hello
port:
number: 80Les certificats n’apparaissent pas automatiquement. La solution habituelle est cert-manager, installé à partir de son manifest publié, avec un ClusterIssuer. La version v1.21.1 est la version actuelle en août 2026.
sudo k3s kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.21.1/cert-manager.yamlapiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt
spec:
acme:
email: you@example.com
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-account-key
solvers:
- http01:
ingress:
ingressClassName: traefikLe challenge HTTP-01 signifie que le serveur ACME (automatic certificate management environment) se connecte à http://hello.example.com/.well-known/acme-challenge/... depuis Internet. L’enregistrement DNS A doit donc déjà pointer vers le VPS, et le port 80 doit atteindre Traefik. Si vous avez désactivé ServiceLB et placé votre propre proxy devant Traefik, ce proxy doit également transmettre le chemin du challenge. Sinon, cert-manager reste bloqué et affiche ce message sur l’objet Challenge :
Waiting for HTTP-01 challenge propagation: wrong status code '404', expected '200'Surveillez l’émission avec sudo k3s kubectl describe certificate hello-tls et sudo k3s kubectl get order,challenge -A.
Le kubeconfig et les raisons pour lesquelles le serveur d’API reste privé
k3s écrit les identifiants d’administration dans /etc/rancher/k3s/k3s.yaml. Le fichier appartient à root et reçoit par défaut le mode 600. Il contient un certificat client doté de droits cluster-admin. Toute personne pouvant le lire contrôle donc le cluster.
sudo k3s kubectl get node
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
kubectl get nodeUn kubectl installé séparément et exécuté sans définir KUBECONFIG échoue avec The connection to the server localhost:8080 was refused - did you specify the right host or port?, car il utilise une valeur par défaut qui n’a rien à voir avec k3s. Définissez KUBECONFIG ou utilisez sudo k3s kubectl, qui lit automatiquement le bon fichier.
Vous verrez souvent --write-kubeconfig-mode 644 recommandé pour permettre à un utilisateur normal d’exécuter kubectl. Comprenez ce que cette commande fait : elle rend un identifiant cluster-admin lisible par tous les comptes locaux de la machine. Sur un serveur administré par une seule personne, ce compromis peut être acceptable. Sur un serveur partagé, il ne l’est pas. Copier le fichier donne plutôt l’accès à un seul utilisateur sans le rendre public :
mkdir -p ~/.kube
sudo install -o $USER -g $USER -m 600 /etc/rancher/k3s/k3s.yaml ~/.kube/configLa ligne server: de ce fichier contient https://127.0.0.1:6443. Pour utiliser kubectl depuis votre ordinateur portable, n’ouvrez pas 6443 sur Internet. Une API Kubernetes publique constitue une cible permanente. Une API exposée permet à des tiers d’utiliser de petits clusters pour miner des cryptomonnaies. Faites passer la connexion dans un tunnel SSH et laissez l’adresse du fichier inchangée :
ssh -N -L 6443:127.0.0.1:6443 user@your-vps
KUBECONFIG=./k3s.yaml kubectl get nodeSi vous devez atteindre l’API via une adresse réseau privée, installez k3s avec tls-san en indiquant ce nom ou cette adresse, puis modifiez la ligne server: du fichier copié pour qu’elle corresponde. Sans l’entrée SAN (subject alternative name), kubectl refuse la connexion :
x509: certificate is valid for 127.0.0.1, 10.43.0.1, not 203.0.113.10Les ports entrants documentés pour un cluster sont le port TCP 6443 pour l’API, le port UDP 8472 pour le VXLAN flannel entre les nœuds et le port TCP 10250 pour les métriques de kubelet. Sur un nœud unique, aucun de ces ports ne doit être ouvert sur Internet.
containerd n’est pas Docker
k3s utilise son propre containerd intégré et ne partage pas de magasin d’images avec Docker. Une image que vous venez de construire avec docker build est invisible pour k3s. Le pod échoue donc avec ErrImagePull, même si docker images la liste. Importez-la explicitement :
docker save myapp:0.1 | sudo k3s ctr images import -
sudo k3s crictl imagesÉvitez ensuite le tag :latest pour ce conteneur, car :latest utilise par défaut un imagePullPolicy de Always et le kubelet interroge malgré tout un registry. Tout autre tag utilise par défaut IfNotPresent, qui exploite l’image que vous avez importée. Les deux peuvent fonctionner sur un même VPS. Il est utile de savoir qu’une installation Docker classique sur un VPS et k3s conservent chacun leur propre magasin d’images et leurs propres règles iptables sur la même machine.
Comment supprimer k3s
L’installateur crée un script de désinstallation. La suppression est complète et irréversible.
sudo /usr/local/bin/k3s-uninstall.shLe script arrête et supprime le service, supprime le datastore, efface les données des persistent volumes, supprime la configuration du nœud et retire les outils ajoutés par l’installateur. Sur un agent node, le script est k3s-agent-uninstall.sh à la place. Copiez d’abord hors de la machine tout ce qui se trouve sous /var/lib/rancher/k3s/storage, car ce répertoire est supprimé avec le reste. Vérifiez ensuite qu’aucun processus ne conserve des ports ou des interfaces avec ip link show et sudo ss -lntp. Une interface cni0 ou flannel.1 restante disparaît au prochain redémarrage.
Constater qu’un nœud Kubernetes apporte plus de complexité que nécessaire est un résultat normal, pas un échec. Revenir à Compose pour ces workloads prend généralement un après-midi.
Modes de défaillance et messages affichés
Node NotReady ou k3s redémarre en boucle. Consultez d’abord sudo journalctl -u k3s -n 200 --no-pager. Sur un petit VPS, la cause la plus fréquente est l’oom-killer du noyau, qui arrête le processus. Cette action apparaît dans dmesg avec une ligne mentionnant k3s-server. Le minimum documenté de 2 GB est une véritable limite basse.
Pod bloqué à l’état Pending. kubectl describe pod donne systématiquement la cause. Insufficient memory ou Insufficient cpu signifie que le nœud n’a plus de ressources disponibles. didn't have free ports correspond au conflit de hostPort décrit plus haut. La présence de waiting for first consumer sur une PVC signifie que WaitForFirstConsumer fonctionne comme prévu.
ImagePullBackOff. Soit le tag n’existe dans aucun registre accessible au nœud, soit vous avez construit l’image avec Docker sans jamais l’importer dans containerd.
Traefik répond, mais pas l’application. Un corps de réponse égal à 404 page not found est généré par Traefik lui-même. Cela signifie que la requête est bien arrivée, mais qu’aucun routeur ne correspond. Vérifiez que le champ host de l’Ingress correspond au nom saisi et que ingressClassName est traefik.
Le DNS du cluster échoue alors que la résolution fonctionne sur l’hôte. Vérifiez CoreDNS avec sudo k3s kubectl -n kube-system logs -l k8s-app=kube-dns. Un message tel que plugin/loop: Loop ... detected for zone "." empêche CoreDNS de démarrer. Cela se produit parce que CoreDNS transmet les requêtes à un resolver qui les lui renvoie. C’est le comportement provoqué par une adresse loopback dans /etc/resolv.conf. Indiquez à k3s le fichier contenant les vrais serveurs upstream avec --resolv-conf /run/systemd/resolve/resolv.conf.
FAQ
k3s vaut-il la peine sur un VPS unique ?
Cela vaut la peine lorsque vous voulez utiliser l’API Kubernetes : pour l’apprendre sur une machine que vous contrôlez, conserver des déploiements portables sous forme de manifests, exécuter des logiciels qui ne publient qu’un chart Helm ou préparer une migration ultérieure vers un cluster managé. Cela ne vaut pas la peine si vous voulez seulement exécuter des conteneurs, car Docker Compose permet de le faire avec beaucoup moins de maintenance et environ un gigaoctet de RAM libre supplémentaire. Un seul nœud n’offre aucune haute disponibilité. La fiabilité ne justifie donc jamais ce choix.
Pourquoi mon service k3s LoadBalancer reste-t-il à l’état Pending ?
ServiceLB crée des pods svclb- qui réservent les ports du service avec hostPort sur le nœud. Ils ne peuvent donc être planifiés que si ces ports sont libres. Si nginx ou un autre proxy utilise déjà le port 80, le pod reste à l’état Pending et le service ne reçoit jamais d’adresse externe. kubectl -n kube-system describe pod svclb-... signale 0/1 nodes are available: 1 node(s) didn't have free ports for the requested pod ports. Libérez le port ou réinstallez avec --disable=servicelb, puis faites proxyer le trafic vers le NodePort que le service continue d’allouer.
De combien de RAM k3s a-t-il besoin sur un VPS ?
Le minimum documenté pour un nœud serveur est de 2 cœurs et 2 GB. Cela couvre k3s et ses composants inclus avant l’exécution de vos workloads. Le profilage réalisé par le projet a mesuré un nœud serveur à 1,596 MB avec une stack de monitoring, exécutée sur ce nœud. Considérez donc 2 GB comme le minimum absolu et 4 GB comme la première capacité offrant un fonctionnement confortable sur un nœud unique. Mesurez votre propre machine avec free -h avant l’installation, puis de nouveau lorsque chaque pod de kube-system affiche l’état Running.
Puis-je exécuter Docker et k3s sur le même VPS ?
Oui, et les deux restent séparés. k3s utilise son propre containerd intégré. Une image construite avec docker build ne lui est donc pas visible tant que vous n’exécutez pas docker save myapp:0.1 | sudo k3s ctr images import -. Chaque outil écrit également ses propres règles iptables et utilise ses propres réseaux bridge. Surveillez la mémoire totale, car Docker, k3s et vos conteneurs ne tiendront pas sur une machine de 2 GB.
Comment supprimer complètement k3s ?
Exécutez sudo /usr/local/bin/k3s-uninstall.sh sur un nœud serveur ou sudo /usr/local/bin/k3s-agent-uninstall.sh sur un agent. Cette commande arrête le service, supprime le datastore, supprime les données des volumes persistants sous /var/lib/rancher/k3s/storage et retire les outils inclus. Copiez d’abord hors de la machine toutes les données à conserver, car l’opération est irréversible. Une interface réseau cni0 ou flannel.1 restante disparaît au redémarrage suivant.