fix: ensure kubelet and containerd in kube.slice via systemd drop-ins#8958
Conversation
|
This change is part of the following stack: Change managed by git-spice. |
There was a problem hiding this comment.
Pull request overview
This PR updates the Linux CSE helper responsible for Node Memory Hardening cgroup hierarchy setup so that both kubelet and containerd are configured to run under kubelet.slice, and adjusts ShellSpec coverage accordingly.
Changes:
- Extend
ensureKubeletCgroupHierarchyto setSlice=kubelet.sliceforkubelet.serviceand add a matching drop-in forcontainerd.service. - Update ShellSpec assertions to validate the additional systemd drop-in content for containerd.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
parts/linux/cloud-init/artifacts/cse_helpers.sh |
Adds systemd drop-ins to place kubelet and containerd into kubelet.slice when hardening cgroups are enabled. |
spec/parts/linux/cloud-init/artifacts/cse_helpers_spec.sh |
Updates tests to assert Slice= and validates the containerd drop-in is created. |
39476df to
4045739
Compare
SriHarsha001
left a comment
There was a problem hiding this comment.
We need to look into the AI's comments, apart from that changes look good to me.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 12 changed files in this pull request and generated no new comments.
Files not reviewed (1)
- aks-node-controller/pkg/gen/aksnodeconfig/v1/kubelet_config.pb.go: Generated file
Comments suppressed due to low confidence (3)
pkg/agent/utils.go:512
- isNodeHardeningEnabled() only strips surrounding '[' and ']' from --enforce-node-allocatable, but existing test data documents values that can be additionally wrapped in quotes (e.g. "[pods]"). In that case hardening detection will be skipped and the cgroup flags won’t be set/overwritten as intended.
raw := strings.TrimSpace(kubeletFlags["--enforce-node-allocatable"])
raw = strings.TrimPrefix(raw, "[")
raw = strings.TrimSuffix(raw, "]")
enforced := strings.Split(raw, ",")
parts/linux/cloud-init/artifacts/cse_config.sh:392
- This logs_to_events key uses the ensureKubelet prefix even though it’s executed from ensureContainerd(), which can make event attribution/telemetry confusing when diagnosing provisioning failures.
if ! logs_to_events "AKS.CSE.ensureKubelet.ensureKubeletCgroupHierarchy" ensureKubeletCgroupHierarchy; then
parts/linux/cloud-init/artifacts/cse_config.sh:888
- This rewrites /etc/systemd/system/kubelet.service.d/10-containerd-base-flag.conf but does not ensure the drop-in directory exists first. On images/scenarios where kubelet.service.d isn’t present yet, tee will fail and kubelet may start without the intended --runtime-cgroups refresh.
tee "/etc/systemd/system/kubelet.service.d/10-containerd-base-flag.conf" > /dev/null <<EOF
[Service]
Environment="KUBELET_CONTAINERD_FLAGS=--runtime-request-timeout=15m --container-runtime-endpoint=unix:///run/containerd/containerd.sock --runtime-cgroups=${containerd_runtime_cgroups}"
EOF
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 12 changed files in this pull request and generated 1 comment.
Files not reviewed (1)
- aks-node-controller/pkg/gen/aksnodeconfig/v1/kubelet_config.pb.go: Generated file
Comments suppressed due to low confidence (2)
parts/linux/cloud-init/artifacts/cse_helpers.sh:1490
- The PR description and title still refer to placing services under
kubelet.slice/Slice=kubelet.slice, but the code and tests in this PR consistently usekubereserved.slice. Please update the PR description (or rename the slice) so reviewers/operators aren’t left with conflicting slice names.
# It MUST be called before kubelet starts so that /kubereserved.slice and /system.slice
# exist and are managed by systemd before the first kubelet enforcement pass.
#
# The function:
parts/linux/cloud-init/artifacts/cse_config.sh:393
- This
logs_to_eventstag is emitted fromensureContainerd, but the event name is prefixed withensureKubelet. This makes telemetry harder to query/triage (the same helper is already tagged correctly underensureContainerd.*elsewhere in this function).
resolveKubeletReservedCgroups
if [ -n "${KUBE_RESERVED_CGROUP}" ] || [ -n "${SYSTEM_RESERVED_CGROUP}" ]; then
if ! logs_to_events "AKS.CSE.ensureKubelet.ensureKubeletCgroupHierarchy" ensureKubeletCgroupHierarchy; then
exit $ERR_KUBELET_START_FAIL
…de hardening is on
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
… RP-supplied values
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
| # Enable the slice for subsequent boots AND materialise the cgroup tree | ||
| # at /sys/fs/cgroup/kubereserved.slice on this boot before kubelet starts. | ||
| # systemctlEnableAndStart wraps both operations with retry logic to | ||
| # survive transient systemd failures during CSE. | ||
| if ! systemctlEnableAndStart kubereserved.slice 30; then | ||
| echo "ensureKubeletCgroupHierarchy: failed to enable and start kubereserved.slice" |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 13 changed files in this pull request and generated 1 comment.
Files not reviewed (1)
- aks-node-controller/pkg/gen/aksnodeconfig/v1/kubelet_config.pb.go: Generated file
Comments suppressed due to low confidence (1)
parts/linux/cloud-init/artifacts/cse_config.sh:394
- This is inside ensureContainerd(), but failures from ensureKubeletCgroupHierarchy are logged under the ensureKubelet event name and exit with ERR_KUBELET_START_FAIL. That makes telemetry/error classification misleading for containerd startup failures (and can confuse troubleshooting). Use an ensureContainerd-scoped event name and an error code consistent with other systemctl failures in this function.
if ! logs_to_events "AKS.CSE.ensureKubelet.ensureKubeletCgroupHierarchy" ensureKubeletCgroupHierarchy; then
exit $ERR_KUBELET_START_FAIL
fi
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 13 changed files in this pull request and generated 3 comments.
Files not reviewed (1)
- aks-node-controller/pkg/gen/aksnodeconfig/v1/kubelet_config.pb.go: Generated file
Comments suppressed due to low confidence (2)
parts/linux/cloud-init/artifacts/cse_helpers.sh:1616
- ensureKubeletCgroupHierarchy calls systemctlEnableAndStart for kubereserved.slice, but systemctlEnableAndStart uses
systemctl restartinternally. For a slice unit, restart is more disruptive than necessary and can impact already-running units in that slice during the idempotent refresh path (e.g., nodePrep). Prefer enabling + starting the slice explicitly with retries (without restart).
if ! systemctlEnableAndStart kubereserved.slice 30; then
echo "ensureKubeletCgroupHierarchy: failed to enable and start kubereserved.slice"
parts/linux/cloud-init/artifacts/cse_config.sh:394
- In ensureContainerd, a failure to create the hardening slice/drop-ins is not a kubelet start failure; it’s a systemd/unit setup failure before containerd is started. Exiting with ERR_KUBELET_START_FAIL (34) will misclassify the failure; ERR_SYSTEMCTL_START_FAIL (4) is a closer match.
resolveKubeletReservedCgroups
if [ -n "${KUBE_RESERVED_CGROUP}" ] || [ -n "${SYSTEM_RESERVED_CGROUP}" ]; then
if ! logs_to_events "AKS.CSE.ensureKubelet.ensureKubeletCgroupHierarchy" ensureKubeletCgroupHierarchy; then
exit $ERR_KUBELET_START_FAIL
fi
| // Node Hardening cgroup slice names. AgentBaker is the single | ||
| // source of truth for these values: cse_helpers.sh::ensureKubeletCgroupHierarchy | ||
| // is what actually creates (or validates) the systemd slice unit on the node, so | ||
| // the name must be decided here rather than accepted verbatim from the RP. | ||
| const ( |
| # Refresh --runtime-cgroups for PIS nodes where basePrep may have baked an older | ||
| # 10-containerd-base-flag.conf pointing at /system.slice/containerd.service. | ||
| local containerd_runtime_cgroups="/system.slice/containerd.service" | ||
| if [ "${KUBE_RESERVED_CGROUP:-}" = "/kubereserved.slice" ] || [ "${KUBE_RESERVED_CGROUP:-}" = "kubereserved.slice" ]; then | ||
| containerd_runtime_cgroups="/kubereserved.slice/containerd.service" | ||
| fi | ||
| tee "/etc/systemd/system/kubelet.service.d/10-containerd-base-flag.conf" > /dev/null <<EOF | ||
| [Service] | ||
| Environment="KUBELET_CONTAINERD_FLAGS=--runtime-request-timeout=15m --container-runtime-endpoint=unix:///run/containerd/containerd.sock --runtime-cgroups=${containerd_runtime_cgroups}" | ||
| EOF |
|
|
||
| // ValidateServiceInSlice asserts that the given systemd service is running in the expected slice. | ||
| func ValidateServiceInSlice(ctx context.Context, s *Scenario, service, expectedSlice string) { | ||
| s.T.Helper() | ||
| // Avoid accidental shell injection / option smuggling. | ||
| if !regexp.MustCompile(`^[A-Za-z0-9_.@:-]+$`).MatchString(service) { | ||
| s.T.Fatalf("invalid systemd unit name: %q", service) | ||
| } |
What this PR does / why we need it:
When node hardening is enabled,
ensureKubeletCgroupHierarchy()creates a dedicatedkubelet.slicefor kube-reserved cgroup enforcement. However the existing drop-in forkubelet.serviceonly declaredWants=andAfter=ordering — it did not setSlice=kubelet.slice, so kubelet remained insystem.slice. Additionally,containerd.servicehad no drop-in at all and was never placed in the slice.This PR fixes both issues:
Slice=kubelet.sliceto the kubelet drop-in so kubelet actually runs inside the dedicated slice.containerd.service(10-kubelet-slice.conf) so containerd is co-located in the same slice.Without this fix, cgroup resource accounting under
kubelet.sliceis incomplete — node hardening'skube-reservedbudget doesn't capture the real resource usage ofkubeletandcontainerd, which blocks nodes to be ready since they are affected tosystem.slicewhich has a low max memory limitTesting:
correct
[Service] Slice=kubelet.slicedirective.Which issue(s) this PR fixes:
Fixes #