K8s Best Practices Updated: Use Node Swap!
This article was last updated on: November 9, 2026 pm
Overview
K8s best practices have been updated:
Provided the following conditions are met:
- Kubernetes 1.34 or later
- The node’s system disk is local SSD or NVMe high-speed storage
- cgroup v2
use node swap!
To enable it, the recommended kubelet config addition is:
1 | |
👍Benefits
👍️Pros
- Saves money: eligible workloads can swap out idle anonymous pages under memory pressure, so pod density goes up significantly;
- Better stability: memory spikes now have a buffer, and being OOM-killed is no longer the only option;
- Small change footprint: the host swap file, partitions, and
/etc/fstabare untouched — the only change is a single kubelet config item; - Observability is already in place: use
node.status.nodeInfo.swap.capacityandkubectl top nodes --show-swapto inspect swap.
📝Notes:
- Only Burstable containers with request < limit benefit; Guaranteed workloads see no difference at all;
- The scheduler is not swap-aware; capacity planning still follows the node’s physical memory, and swap cannot be counted toward it;
- What gets swapped out is anonymous pages, which hit the disk as I/O — do not enable this if the system disk is not local SSD or NVMe high-speed storage.
The common community view is: treat swap as insurance against memory spikes, not as a replacement for RAM.
What follows is my hands-on summary from the Homelab:
Homelab Cluster Overview
Four N100 mini PCs, cgroup v2, and the host /swap.img has long been swaponed (if swap isn’t enabled, you’ll need to enable it first in practice). They run k3s v1.36.5+k3s1.
NodeSwap went GA in Kubernetes v1.34, so let’s enable it and see.
Swap Defaults
kubelet’s memorySwap.swapBehavior defaults to NoSwap.
Under that default, even with swap enabled on the host and even if you set failSwapOn: false, the container cgroup’s memory.swap.max is still 0.
📝Notes:
failSwapOn: falsecontrols “whether kubelet should refuse to start when the node has swap”;swapBehaviorcontrols “whether containers can use swap”. Two switches, two different things.
On my Homelab cluster, the host-level swapon, partitions, and /etc/fstab are all already in place. All that’s missing is that one line in kubelet.
Three Things to Confirm Before You Start
- cgroup v2:
stat -fc %T /sys/fs/cgroupreturnscgroup2fs; - Swap is enabled on the host:
swapon --showproduces output — on my Homelab cluster it’s/swap.img; - The Kubernetes version corresponding to your k3s version already includes NodeSwap GA (>=1.34).
If the prerequisites aren’t met, changing the kubelet config is meaningless.
Which Pods Does LimitedSwap Actually Apply To
The upstream-recommended value is LimitedSwap. It is not an “open the floodgates” policy.
The applicable boundaries are defined upstream:
- It only takes effect for Burstable containers;
- and requires memory request != limit;
- Guaranteed and BestEffort are always NoSwap;
- Critical pods (
IsCriticalPod) are also NoSwap, regardless of QoS.
| Pod QoS | request vs limit | Swap allowed |
|---|---|---|
| Burstable | request != limit | ✓ LimitedSwap |
| Burstable | request == limit | ✗ NoSwap |
| Guaranteed | request == limit | ✗ NoSwap |
| BestEffort | neither set | ✗ NoSwap |
| Any | IsCriticalPod | ✗ NoSwap |
The quota is computed with this formula:
1 | |
The smaller the request, the less room there is to swap out. This is deliberate: swap exists only to absorb memory spikes and does not participate in capacity planning.
🐾 Note: The scheduler is not swap-aware. The memory it sees is still the node’s physical RAM. Don’t treat swap as “extra memory” to oversell, or the node will eventually wobble.
Implementation: Modifying the k3s kubelet config
On every start, k3s rewrites /var/lib/rancher/k3s/agent/etc/kubelet.conf.d/00-k3s-defaults.conf.
To override the defaults, you have to drop a file that sorts after it into the same directory.
The filename must end in .conf, and files are loaded in lexicographic order. Here I use 90-swap.conf.
1 | |
Write failSwapOn: false explicitly; don’t rely on the default.
Do not overwrite or delete 00-k3s-defaults.conf; k3s manages that one itself.
Rollout
Restart kubelet — for k3s, that means restarting the k3s service.
Rollout order: workers first, then masters one at a time.
Between each node, confirm kubectl get nodes shows everything Ready and the etcd members are healthy before touching the next one. Restarting multiple masters at once briefly loses etcd quorum.
The whole process restarts no nodes and evicts no pods.
Verification Checklist
Writing the config isn’t the end of it; walk through four commands one by one.
- The config kubelet actually has in effect:
1 | |
Expect memorySwap.swapBehavior == "LimitedSwap".
Example:
1 | |
- Node swap capacity is reported, not
unknown:
1 | |
All four nodes should have a value.
- Swap usage is visible:
1 | |
Look at the last two columns — SWAP(bytes) SWAP(%).
- The most important one: get inside a container and check the cgroup:
1 | |
Compare two pods:
- A Burstable pod with request
64Mi/ limit256Mihasmemory.swap.max > 0; - Pods with request == limit, and pods with neither request nor limit set, have
memory.swap.max == 0.
If these two sets of numbers line up, the boundaries are working as expected. Example:
Burstable Pod:
1 | |
Pod with neither request nor limit set:
1 | |
I couldn’t find a pod with request == limit in my homelab cluster at the time, so I won’t show that here.
Summary
- NodeSwap went GA in v1.34, but the default
swapBehavioris stillNoSwap; setting onlyfailSwapOn: falsewill not give containers any swap quota; - Only one config item needs to change:
memorySwap.swapBehavior: LimitedSwap; - The effect is scoped to Burstable with request != limit, and the quota is allocated in proportion to the request’s share of node memory;
- k3s delivers it through the drop-in directory:
.confsuffix plus a lexicographic position after00-k3s-defaults.conf;
That’s all.
📚️References
- Kubernetes docs: Node swap management
- Kubernetes v1.34 release notes (NodeSwap GA)
- k3s docs
- The upstream KEP and the
memorySwapfield definition in the kubelet config