You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
docs(blog): Keeping AKS nodes Ready with Node Hardening - #6022
Publishes an AKS Engineering Blog article about Node Hardening, an AKS preview feature that enforces kube-reserved and system-reserved capacity with cgroups and adds soft eviction on Linux nodes.
This change:
Adds controlled benchmarks that compare otherwise identical standard and hardened nodes when memory is held near capacity, during a runaway memory leak, and under CPU saturation, with a short methodology summary and limitations.
Shows measured allocatable-capacity changes across six node sizes and maxPods settings.
Compares memory withheld before Kubernetes 1.29, on 1.29 and later, and with Node Hardening, and shows NotReady results with kube-reserved memory lowered to 1,077 MiB and to the 750 MiB minimum.
Notes how hardened nodes behave when pods fully saturate CPU, as an observation rather than a tuning recommendation.
Sets the publication date to November 3, 2026, and uses the official AKS service icon in the hero image.
Merge timing: Docusaurus publishes future-dated posts immediately, so please merge on November 3, 2026.
Node Hardening helps customers whose nodes run workloads that exceed their memory requests. Headline results from five trials per scenario on a 16 GiB Standard_D4ads_v5 node (AKS 1.35.8):
When pods held node memory near capacity, standard nodes went NotReady in 5 of 5 trials (median 298 s NotReady), with no kubelet evictions, and an app on the node failed a median 33% of requests. Hardened nodes stayed Ready in 5 of 5 trials, evicted or contained the pods, and the app failed no requests.
During a runaway memory leak, the lowest available node memory was 776 MiB on hardened nodes versus 11 MiB on standard nodes (median). The kubelet evicted the leaking pods instead of relying on system-wide kernel OOM kills, and the kubelet and containerd never stalled on memory.
Hardened nodes reported 4% to 15% less allocatable memory, 100 millicores less allocatable CPU, and 1 GiB less ephemeral storage, depending on node size and maxPods.
On a node with fully saturated CPU, pod starts took 33.4 s on hardened nodes versus 2.9 s on standard nodes, because the kube-reserved cgroup gets a smaller CPU weight. Memory saturation is more common than CPU saturation in AKS, so the post presents this as something to note, not a recommendation to change kube-reserved CPU.
Hardened nodes never went NotReady in any scenario. Standard nodes went NotReady only when memory was held near capacity. In the runaway-leak and CPU-saturation scenarios, the well-behaved app kept running on both node types.
Validation
Built the site locally with docusaurus build. The post rendered with its author, tags, tables, and all five images, and the build reported no new warnings.
Ran codespell with the repository's CI settings on the new post.
Feature registration is asynchronous, so immediately re-registering the provider can happen while CustomNodeConfigPreview is still Registering; the following az aks update can then reject the preview option. Poll until the feature reports Registered before refreshing the provider, as other AKS preview instructions do (website/blog/2026-07-13-runai-streamer-vllm/index.md:119-131).
The summary says all values are medians, but this row reports “up to 7.1%” while the detailed median table reports 3.2% (line 115). Either use the 3.2% median here or identify 7.1% as a maximum and qualify the statement on line 36 so readers don't interpret two different values as the same statistic.
Reference points to unpublished dependency documentation
This dependency isn't currently publicly discoverable at the target URL, and the PR description says it is supplied by MicrosoftDocs/azure-aks-docs-pr#3940. Confirm that article is live before merging; otherwise this first reference sends readers to unavailable documentation for the feature defaults.
This issue also appears in the following locations of the same file:
The reason will be displayed to describe this comment to others. Learn more.
Really nice work on this one. The opening hook lands, the NotReady results make the case clearly, and the limitations section keeps it honest.
A few things outside the inline comments:
Merge conflict (blocking):#5934 merged on October 20 and already added your spencer-libbing entry to website/blog/authors.yml, so this PR now conflicts with master. Rebasing and dropping the authors.yml change should clear it.
PR description: The merge-timing note still says October 23. Update it once you pick the new date.
Terminology: "rolling out deployments" in the CPU section refers to the Kubernetes resource, so capitalize it as "Deployments".
Add an AKS Engineering Blog post with controlled benchmarks of Node Hardening: runaway-memory protection, allocatable capacity changes across node sizes, and kube-reserved CPU tuning for CPU-saturated nodes. Add author metadata for Spencer Libbing.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Shorten the title and description to the blog SEO limits, wait for feature registration before refreshing the provider, and clarify that only memory reservations are hard cgroup limits.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…post
Replace the testing-framework table with a short summary, present the CPU-saturation results as something to note rather than a tuning recommendation, and update the title, description, CPU chart, and hero image to match.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Standard nodes went NotReady in 5 of 5 trials when pods held memory near capacity; hardened nodes stayed Ready in 5 of 5 and evicted or contained the pods. Adds a timeline chart, results table, and updated hero stat.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace 'Know the tradeoffs' with 'Tune it your way' and lead the stats band with the NotReady, app-availability, and customization results.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Retitle post and update description to match the Ready and tuning story
- Move publish date to 2026-11-03 and rename the post folder
- Clarify node auto-repair, PSI, allocatable math, and OOM kill wording
- Align results-at-a-glance stall row with the runaway-memory table
- Summarize the 12 per-pool settings and move the capacity section after CPU
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Spencer Libbing (stl327)
changed the title
docs(blog): add Node Hardening benchmark post
docs(blog): Keeping AKS nodes Ready with Node Hardening
Oct 27, 2026
One of the more frustrating support cases we see starts small. A pod uses more memory than it requested, and nobody set a limit because it never mattered in testing. Then traffic picks up. The node runs low on memory, the kubelet can't keep up with its own work, and the node goes NotReady. Node auto-repair reboots it, the pods land right back on it or on its neighbor, and the cycle starts over. By the time someone opens a ticket, the cluster has been flapping for hours, and the workload that started it doesn't look broken at all.
## Memory held near capacity: hardened nodes stayed Ready
Memory pressure often takes a node down slowly, when workloads use far more memory than they request and keep the node at the edge of its capacity. To reproduce that, we let pods without memory limits grow until the node had about 50 MiB of memory available, held them there for seven minutes, and ran another pod that read files from disk like a typical app.
The reason will be displayed to describe this comment to others. Learn more.
Is this true? Previously I heard from customers that memory hungry apps actually spike in memory usage and that kubelet eviction handler which works in cycles cannot catch up with it?
| Peak time the node was stalled on memory (10-second PSI, median) | 69% | 6.7% |
On the standard node, nothing stepped in to stop the pods. The kubelet didn't evict any pods, and the kernel's out-of-memory (OOM) killer didn't act in four of the five trials, because the kernel could keep reclaiming page cache to make room. Instead, the node thrashed, and at its worst it spent about 70% of its time stalled on memory. The kubelet stopped renewing its lease, the node went NotReady, and the well-behaved app on the node failed about a third of its requests until the pods released their memory.
The reason will be displayed to describe this comment to others. Learn more.
Don't we already have hard eviction and kube-reserved for standard nodes? I think part of this is not true. Kubelet likely just didn't have the cgroups slice to protect itself and actually act on hard eviction of 100MB
When pods saturate a node's CPU, Linux shares CPU time between cgroups in proportion to their CPU weights, which the kubelet derives from the reservations. On a standard node, the kubelet and containerd run in `system.slice` with the default weight of 100. On a hardened 4-vCPU node, they run in `kubereserved.slice`, whose weight of 6 comes from the default kube-reserved CPU of 140 millicores. That share is enough for steady-state work, but it leaves less room for bursts of pod starts and container operations while CPU is fully saturated.
The well-behaved app, which had its own CPU request, wasn't affected, and no node became NotReady. Only node operations such as starting pods and rolling out Deployments slowed down, and they returned to normal within seconds after CPU contention ended. Kube-reserved CPU is one of the [Node Hardening settings that you can customize](https://learn.microsoft.com/azure/aks/node-hardening#customize-node-hardening-settings). In a separate test, a value of 500 millicores brought the median pod start time to 4.2 seconds.
The reason will be displayed to describe this comment to others. Learn more.
ah now I understand. I wonder if it would be better to lead with explanation. You could say because we're prioritizing kubelet under cpu contention, we expect the nodes to stay functioning, but the new container startsup and application performance will slightly regress. Is that right?
(also I wondered if customers will actually prefer the opposite - well functioning app and not functioning node)
The reason will be displayed to describe this comment to others. Learn more.
This is just for us. But I am worried that we moved too much into reliability and threw out the cost optimization aspect here. How do we know that we are not back to what we had before 1.29? Are we being conservative in our reservations? How do we prove that this is the right setting?
This may even be a good thing to prove in this blog. Can we show the impact of just dropping 300MB from kube-reserved and its impact on node readiness?
The reason will be displayed to describe this comment to others. Learn more.
Good question. I added two things to the post to address this:
1. How we compare to earlier defaults ("How this compares to earlier AKS defaults"). With 30 max pods, Node Hardening withholds 1.8–3.3 GiB (23% to 5% of node memory) vs. 2.6–6.2 GiB (32% to 10%) before 1.29 and 750 MiB (9% to 1%) on 1.29 and later. So we're at roughly half of the pre-1.29 cost, not back to it, and unlike pre-1.29 the reservation is enforced with cgroups.
2. Your experiment ("Lowering kube-reserved memory"). I reran the memory-held-near-capacity scenario on hardened 16 GiB nodes (AKS 1.35.9), 5 trials each:
Kube-reserved memory
Withheld from pods
NotReady trials
Lowest available memory (median)
Lowest kubelet + containerd page cache (median)
1,377 MiB (default)
2,052 MiB
0 of 5
667 MiB
261 MiB
1,077 MiB (−300 MiB)
1,752 MiB
0 of 5
368 MiB
171 MiB
750 MiB (API minimum)
1,425 MiB
0 of 5
124 MiB
59 MiB
No NotReady, no kubelet restarts, and 0% failed app requests in any variant. Takeaways:
Enforcement is what prevents NotReady, not reservation size. Once pods are capped and the kubelet/containerd have their own slice, even 750 MiB held up in this scenario. (Standard nodes went NotReady 5/5 with 650 MiB unenforced for the kubelet.)
Size sets the margin. Kubelet + containerd anonymous memory peaked at ~250 MiB in every variant; the rest of the slice is page cache. At 750 MiB, that cache was squeezed to ~60 MiB and the node had ~125 MiB available, which leaves little room for higher pod density, image pulls, or churn (our node ran well under its 30-pod max).
So yes, the default is conservative, intentionally, since it scales with maxPods. Cutting 300 MiB still left healthy headroom here, which suggests there's room to tune the per-pod coefficient down if we want to recover capacity. A follow-up at higher pod density (for example, 110 or 250 maxPods with churn) would tell us how far.
- Reframe opening hook in the reader's voice around noisy neighbors
- Explain both spike and sustained memory pressure, and why standard
nodes' kube-reserved and 100 MiB hard eviction didn't protect the kubelet
- Lead the CPU section with the CPU-weight explanation and note that
lower is better in the table
- Compare reservations before 1.29, 1.29 and later, and Node Hardening
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
The linked documentation PR returns 404 and enablement guidance conflicts, requiring maintainer confirmation of preview prerequisites, CLI commands, and lifecycle behavior before the planned November 3 merge.
Repeat the memory-held-near-capacity scenario on hardened nodes with
kube-reserved memory at 1,377 MiB (default), 1,077 MiB, and 750 MiB.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Publication requires maintainer coordination for the November 3 merge because Docusaurus publishes future-dated posts immediately and the required Node Hardening documentation remains unverified.
No open issues 🎉
Review effort: Balanced
This branch has not been deployed
No deployments
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Publishes an AKS Engineering Blog article about Node Hardening, an AKS preview feature that enforces kube-reserved and system-reserved capacity with cgroups and adds soft eviction on Linux nodes.
This change:
maxPodssettings.Merge timing: Docusaurus publishes future-dated posts immediately, so please merge on November 3, 2026.
Dependency: The post links to https://learn.microsoft.com/azure/aks/node-hardening, which is added by MicrosoftDocs/azure-aks-docs-pr#3940. Please confirm that article is live before merging.
Customer impact
Node Hardening helps customers whose nodes run workloads that exceed their memory requests. Headline results from five trials per scenario on a 16 GiB
Standard_D4ads_v5node (AKS 1.35.8):maxPods.Validation
docusaurus build. The post rendered with its author, tags, tables, and all five images, and the build reported no new warnings.codespellwith the repository's CI settings on the new post.