Infrastructure engineering for East Africa's operators, platforms and regulators See the work →
k3s vs RKE2 vs kubeadm: We Cut the Power on All Three
DevOps 8 min read

k3s vs RKE2 vs kubeadm: We Cut the Power on All Three

k3s vs RKE2 vs kubeadm on identical 4 GB VMs: memory in use, seconds back after a hard power cut, and the pods RKE2's CIS profile refuses.

CE
CloudSpinx Engineering
Infrastructure Engineering, Virtualization, Networking, Automation
Updated 31 October 2026
k3srke2kubeadmkubernetes

We installed k3s, RKE2 and kubeadm at Kubernetes v1.36.5 on three identical Proxmox VMs, 2 vCPUs and 4 GiB of RAM each, then cut the power on every one of them. k3s had its test app answering 25.5 seconds after the kernel booted. kubeadm took 46.4. RKE2 took 75.0.

"k3s for the edge, RKE2 for compliance" is where most k3s vs RKE2 comparisons stop, and it is true, but it does not pick a distribution for one server in an office comms room. CloudSpinx builds and runs Kubernetes on client hardware across Kenya as part of our managed Kubernetes service, and which distribution to install is one of the first questions on an on-premises build. Here is what each one used in memory, what it installed without being asked, how it came back from a hard power-off, and what happened when we turned on RKE2's CIS profile on a cluster that was already running.

How much memory do k3s, RKE2 and kubeadm use?

Ten minutes after the first install command, free showed 1,193 MiB in use on the k3s VM, 1,578 MiB on RKE2 and 1,024 MiB on kubeadm. On the k3s and RKE2 VMs, the bare Ubuntu 26.04 image used about 580 MiB before anything was installed.

kubeadm's figure is the smallest, and it also installs the least: etcd, the control plane, kube-proxy, CoreDNS and the Flannel network we applied ourselves. No Ingress controller, no metrics-server, no storage provisioner. k3s folds the API server, scheduler, controller manager, kubelet, kube-proxy and Flannel into one k3s-server process (648 MiB resident), adds Traefik, metrics-server and a local-path provisioner as pods plus a default StorageClass, and still came in 385 MiB under RKE2.

RKE2 runs the upstream layout and then some. etcd and the control plane as static pods, Canal (Calico with Flannel) for the network, a cloud controller manager, a snapshot controller and a CoreDNS autoscaler. That came to 13 running pod sandboxes against 6 on k3s, and its API server alone held 565 MiB, where kubeadm's held 270. It grew the disk by 4.15 GB to k3s's 1.27 GB.

The vendors' own floors set RKE2 apart too. k3s asks for 2 GB on a server, kubeadm for 2 GB per machine, and RKE2's requirements page says 4 GB minimum and recommends 8.

Which one comes back first after a power cut?

k3s, on one timed hard cut each. We stopped each VM the way a dead UPS does, with no shutdown, started it again, and logged seconds from kernel boot until the API passed an authenticated readiness check, until a one-replica nginx app answered on its NodePort, and until every pod had gone Ready again after that boot. k3s and RKE2 were cut at the same moment on the same host; kubeadm was cut on its own later.

k3s vs RKE2 vs kubeadm after a hard power cut, in seconds
k3s vs RKE2 vs kubeadm after a hard power cut, in secondsSeconds from kernel boot after a hard power-off and power-on of three identical single-node clusters, 2 vCPUs and 4 GiB each, Kubernetes v1.36.5. k3s answered on the API at 20 seconds, served the test app at 26 and had every pod Ready at 43. kubeadm reached 26, 46 and 47 seconds. RKE2 reached 34, 75 and 105 seconds, with its etcd container restarted four times.k3sv1.36.5+k3s1API 20.2 s, app 25.5 s, all pods 43.1 sthird cut on this VMkubeadmv1.36.5 + FlannelAPI 26.4 s, app 46.4 s, all pods 46.5 sRKE2v1.36.5+rke2r1API 34.3 s, app 75.0 s, all pods 104.8 setcd restarted 4 times0 s20 s40 s60 s80 s100 sseconds after the kernel booted
  • before the API first answered
  • API had answered, app not yet
  • app had answered, pods not all Ready
One timed hard power cut each, same VM size: the test app answered 25.5 s after the kernel booted on k3s, 46.4 s on kubeadm and 75.0 s on RKE2.

The clock starts at kernel boot; firmware and the boot loader come on top and were not measured. k3s and RKE2 were cut together on the same host, kubeadm alone later. A pod only counts once its Ready status changes after this boot, so a status left over from before the cut cannot pass.

Most of RKE2's lag came after its API first answered. That was at 34.3 s, and the app took another 41 seconds. By the time every pod was Ready, RKE2's etcd container showed four restarts where its API server showed one. kubeadm, also on etcd, had everything back by 46.5 s. The k3s figure is that VM's third cut, and its SQLite datastore came back without help from all three.

None of this is high availability. On a three-node cluster the question becomes how long before pods move off a dead node, which is the node failure run we did on k3s. On one machine behind a UPS, a recovery like one of these, plus boot time we did not measure, is added to every outage that outlasts the battery. If that is your setup, the node count and the RAM per node are enough for us to tell you which of the three we would put on it.

What each one installs for you

k3sRKE2kubeadm
DatastoreSQLiteembedded etcdetcd
Pod networkFlannel, inside k3sCanal (Calico + Flannel)none until you add one
Default IngressClassTraefikTraefiknone
Default StorageClasslocal-pathnonenone
Secrets encrypted at restnot setonnot set
Pod Security defaultprivilegedprivilegedprivileged
Leaf certificates1 year, renewed at start inside 120 days1 year, renewed at start inside 120 days1 year, renewed on upgrade

SQLite is k3s's default and it does not do more than one server. For HA, the k3s docs have you start the first server with --cluster-init and run an odd number, three or more. An existing SQLite server converts by restarting with that flag. We would still start with it on any cluster that might grow, rather than convert one with workloads on it.

Both Rancher distributions now default to Traefik. RKE2 switched for clusters created from v1.36, keeps ingress-nginx only as a deprecated option in that release and removes it for new clusters in v1.37. If you are moving Ingresses off ingress-nginx anyway, what breaks on the way has its own post.

All three issued one-year certificates under ten-year CAs. k3s and RKE2 renew any that are within 120 days of expiry each time they start. kubeadm renews them when you run kubeadm upgrade or kubeadm certs renew, and a kubeadm cluster nobody touches for a year stops taking instructions.

Is RKE2 hardened by default?

Partly. The default install encrypted Secrets at rest, which neither k3s nor kubeadm did. Like k3s, it also switched API server profiling off and refused unauthenticated API calls. Its Pod Security config said enforce: "privileged", though, so the nginx Deployment we ran everywhere, with no securityContext at all, ran on RKE2 too. Restricted pods, an audit log and network policies come from profile: "cis", and that is off until you set it.

We set it on the running RKE2 cluster, following the RKE2 hardening guide: the CIS sysctl file, an etcd user, one line in config.yaml, a restart.

RKE2's CIS profile: what changed, and what it refused
RKE2's CIS profile: what changed, and what it refusedRKE2 v1.36.5 with its default settings against the same server after setting profile cis. The default enforces the privileged Pod Security level, writes no API audit log and creates no NetworkPolicies, so a plain nginx Deployment runs. Under the profile, Pod Security is restricted, the audit log is on, nine NetworkPolicies exist and new nginx pods are refused while the running one keeps serving. Switching the running server left one etcd file owned by root and etcd would not start until it was fixed.default installprofile: "cis"Pod Security enforceprivilegedrestrictedkube-system and 2 more exemptAPI audit logoffonold files kept up to 30 daysNetworkPoliciesnone9 createddefault, kube-public, kube-systemPlain nginx Deploymentno securityContext setpods admittednew pods refusedthe running pod kept servingProfile switched ona server already runningnot applicableetcd would not startdb file left root-owned, fixed by hand
RKE2 admitted the same unhardened pod k3s and kubeadm admitted. Only profile: "cis" refused it, and turning it on in place left etcd unable to start until we fixed one file's owner by hand; rke2-server was active again 7 minutes 46 seconds after the restart.

Admission checks a pod when it is created, so running pods survived the switch. A pod that breaks the restricted level, outside the three exempt namespaces, is refused the next time it is created: a rollout (which we saw), a drain or a reschedule.

Then we created the same nginx Deployment again. The API accepted the Deployment with a warning, and its ReplicaSet could not create a single pod:

Error creating: pods "web2-694d4cc785-sxxxz" is forbidden: violates PodSecurity "restricted:latest":
allowPrivilegeEscalation != false (container "nginx" must set securityContext.allowPrivilegeEscalation=false),
unrestricted capabilities (container "nginx" must set securityContext.capabilities.drop=["ALL"]),
runAsNonRoot != true (pod or container "nginx" must set securityContext.runAsNonRoot=true),
seccompProfile (pod or container "nginx" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")

The pod that was already running kept serving, because admission checks a pod when it is created. A kubectl rollout restart of it produced a new ReplicaSet with zero pods. On a real cluster, a workload outside kube-system and the two other exempt namespaces that fails the restricted level keeps running on the day you switch, and is refused the next time its pods are recreated: by a rollout, as here, or by a drain or a reschedule.

Switching a running server had a second cost. The guide asks for the sysctl step "only on fresh installations, before actually using RKE2 to deploy Kubernetes", and we ignored that on purpose. etcd then would not start, though not because of the sysctl file. After the restart every file in its data directory belonged to the new etcd user except member/snap/db, still owned by root, and etcd panicked with permission denied and kept crash-looping, with the API server down alongside it:

{"level":"panic",...,"msg":"failed to open database","path":"/var/lib/rancher/rke2/server/db/etcd/member/snap/db",
 "error":"open /var/lib/rancher/rke2/server/db/etcd/member/snap/db: permission denied"}

We watched that for four minutes, then changed the owner of that one file by hand, and rke2-server reported active 7 minutes 46 seconds after the restart. The nginx app answered the one time we checked during the outage, since a running container does not need the control plane. Set the profile when you install, or rehearse the switch on a copy.

Front of a server with hot-swap drive bays and green activity lights, the kind of single box a one-node k3s or RKE2 cluster runs on

One server, an auditor, or a platform team

One server, or a few small ones. k3s. It used less memory than RKE2, shipped a default StorageClass where RKE2 shipped none, and came back first. Start it with --cluster-init if a second server is ever likely.

An auditor will hold you to the CIS benchmark. RKE2, with the profile set at install, on nodes with at least the 4 GB minimum it asks for (8 recommended), and with every manifest passing the restricted Pod Security level before the first deploy.

A platform team that wants upstream and its own choices. kubeadm. It installs the control plane and stops, so the network, ingress, storage and the yearly upgrade are all yours.

We did not run Talos Linux here, so it gets no numbers. And if the cluster is EKS, AKS or GKE, none of this is your decision: the provider runs the control plane. A team happy on k3s on one box does not need us to move it to anything else. Where the honest answer is a second machine rather than a different distribution, we hand over a dedicated server in about 24 hours.

Tell us how many nodes you have and how much RAM each one has, whether an auditor names the CIS benchmark, and who will run the cluster once it is built, through the Kubernetes scoping form or on WhatsApp at +254 713 403 044. You get back the distribution we would install, the node sizing, and a plain answer when a managed cloud cluster or no cluster at all is the better buy. Free, and it commits you to nothing.

WhatsApp