A Kubernetes cluster on VPSs: how to choose the nodes
People build Kubernetes on rented virtual servers for two reasons: a managed cluster from a cloud provider costs more, and for learning, a home lab or a small production setup they want full control over versions and settings. But a cluster is not one server; it is several nodes with different roles, and skimping on one of them usually breaks the whole cluster. The table above lists KVM plans with 2 cores and 4 GB of memory or more, from hosts with an API, so that nodes can be created by script or from Terraform.
k3s or kubeadm
k3s is a lightweight distribution in a single binary. According to the documentation, a server node needs 2 cores and 2 GB of memory, and an agent needs one core and 512 MB. SQLite is used instead of etcd by default, and for high availability you can enable embedded etcd on three server nodes. The Flannel network, Traefik ingress and local storage are installed straight away. It is the best choice for one to three VPSs, test environments and small projects.
kubeadm deploys "vanilla" Kubernetes: separate control plane components, etcd, and a network plugin of your choice (Calico, Cilium). The minimum requirements are the same 2 cores and 2 GB per machine, but in practice a control plane with etcd runs comfortably from 4 GB. kubeadm is chosen when you need full compatibility with production, are preparing for the CKA certification or want to understand how a cluster works inside. Upgrading it takes longer: versions go up one minor release at a time.
How many nodes and of what kind
- One node: k3s on a VPS with 4 GB of memory, where the control plane and workloads live together. Suitable for pet projects and test benches.
- One master and 2–3 workers: the classic setup for learning and small services. 2–4 GB for the master, and for workers whatever the real load needs, usually 4–8 GB.
- Three control-plane nodes: the minimum for an etcd quorum, so the cluster survives losing one node. Two masters are worse than one: if either fails, there is no quorum.
Leave headroom in memory: kubelet, the container runtime, CNI, CoreDNS and monitoring agents eat 0.5–1 GB on each node before any of your pods. Kubernetes evicts pods when memory runs short, so set requests and limits. By default kubeadm requires swap to be disabled.
etcd and the disk
etcd writes every change to the cluster state with a synchronous flush to disk, and slow fsync is a common cause of a "falling over" API and leader re-elections. For the control plane take a VPS with NVMe, not HDD and not cheap plans with a hard IOPS limit. You can test the disk with the fio utility using fdatasync: a latency of a few milliseconds is normal, tens of milliseconds are a reason to change plans. Take etcd snapshots regularly and store them outside the cluster.
Virtualisation, networking, volumes and load balancing
Kubernetes needs its own kernel, the overlay and br_netfilter modules, cgroups and iptables or eBPF. On OpenVZ and LXC container virtualisation this almost never works, so you need a KVM VPS: the same requirements as Docker, only stricter. Nested virtualisation (the vmx or svm flag in /proc/cpuinfo) is needed only if you run virtual machines inside the cluster through KubeVirt or Kata Containers; ordinary containers don't need it, and not all hosts enable it.
It's best to keep the nodes in one data centre: overlay network and etcd traffic flows between them, and tens of milliseconds of latency noticeably slow the cluster down. If the host provides a private network between VPSs, route intra-cluster traffic over it. Expose only the API on 6443/TCP (preferably behind an IP allowlist) and the ingress ports; the VXLAN port 8472/UDP and kubelet's 10250 must be closed to the internet.
Managed clusters provide volumes and external load balancers themselves, but bare VPSs have no such services, and a LoadBalancer type service will stay Pending forever. For incoming traffic an ingress controller on ports 80 and 443 of one or more nodes is usually enough; k3s includes the built-in ServiceLB for this, while in a kubeadm cluster people install MetalLB if the host can provide additional IPs. For volumes, the simplest start is local-path: data sits on a particular node's disk and disappears with it. If a pod needs to move between nodes together with its data, a distributed storage such as Longhorn, which replicates volumes across nodes, is installed; reserve separate disk space for it on every worker plus another 0.5–1 GB of memory.
Your own cluster or a managed one
Managed Kubernetes takes the control plane, upgrades and etcd off your hands, and you pay mainly for workers and load balancers. Your own cluster on VPSs is cheaper and more flexible, but etcd backups, upgrades, certificates and monitoring become your job. A sensible compromise to start with is k3s on a few VPSs with hourly billing: you can recreate nodes, try different setups and remove what you don't need without paying for a whole month.