Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
65bd4a8
fix(test): ensure kubelet and containerd are in kubelet.slice when no…
titilambert Jul 15, 2026
2ead406
fix PR comments
titilambert Jul 16, 2026
f8daa6f
Make things simplier and idempotent
titilambert Jul 16, 2026
a4005a2
Remove useless test
titilambert Jul 16, 2026
9067c21
Add e2e tests
titilambert Jul 16, 2026
3bf4790
Ensure runtime-cgroups match with containerd cgroups
titilambert Jul 16, 2026
57fc240
Fix PR comments
titilambert Jul 16, 2026
7ca3f57
Fix PR comments
titilambert Jul 16, 2026
0036c8f
Place containerd in dedicated containerd.slice when node hardening is…
titilambert Jul 17, 2026
478c1b4
Place kubelet and containerd in kube.slice when node hardening is ena…
titilambert Jul 17, 2026
b92dafe
Rename kube.slice to kube-reserved.slice
titilambert Jul 17, 2026
4ba0f00
Potential fix for pull request finding
titilambert Jul 20, 2026
7c83f71
Potential fix for pull request finding
titilambert Jul 20, 2026
643abb9
Rename kube.slice to kube-reserved.slice with backward compat for kub…
titilambert Jul 20, 2026
b5c24ad
Keep only kube-reserved.slice
titilambert Jul 20, 2026
c695e65
agentbaker owns Node Hardening cgroup slice names instead of trusting…
titilambert Jul 21, 2026
deeee05
rename from kube-reserved to kubereserved
titilambert Jul 21, 2026
669e743
Potential fix for pull request finding
titilambert Jul 21, 2026
03f6f21
Potential fix for pull request finding
titilambert Jul 21, 2026
a3a8493
Potential fix for pull request finding
mxj220 Jul 22, 2026
4eaeb2f
coalesce enable and start into existing systemctlEnableAndStart command
mxj220 Jul 22, 2026
975c93d
Fix tests
titilambert Jul 23, 2026
6fa4b31
Potential fix for pull request finding
titilambert Jul 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ message KubeletConfigFileConfig {

/* kubeReservedCgroup is the absolute name of the cgroup the kubelet should manage for the
kube-reserved compute resources. When enforce-node-allocatable contains "kube-reserved",
this cgroup must exist before kubelet starts. Example: "/kubelet.slice".
this cgroup must exist before kubelet starts. Example: "/kubereserved.slice".
Used by AKS Node Memory Hardening (F2/F5).
+optional. */
string kube_reserved_cgroup = 44;
Expand Down
92 changes: 92 additions & 0 deletions e2e/scenario_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2677,3 +2677,95 @@ func Test_AzureLinuxV3_MANA(t *testing.T) {
},
})
}

// Test_Ubuntu2204_NodeHardening_KubeReservedSlice_ConfigFile validates the config-file
// kubelet path (kubelet reads /etc/default/kubeletconfig.json), which is selected whenever
// AgentPoolProfile.CustomKubeletConfig is non-nil (see IsKubeletConfigFileEnabled).
func Test_Ubuntu2204_NodeHardening_KubeReservedSlice_ConfigFile(t *testing.T) {
RunScenario(t, &Scenario{
Description: "validates kubelet and containerd run in kubereserved.slice when node hardening cgroup hierarchy is enabled (kubelet config-file mode)",
Config: Config{
Cluster: ClusterKubenet,
VHD: config.VHDUbuntu2204Gen2Containerd,
// Force the "default" (non-scriptless) subtest path so that fresh CSE scripts
// with the Slice= drop-ins are uploaded via custom data.
CustomDataWriteFiles: []CustomDataWriteFile{{Path: "/etc/aks-node-hardening-test", Content: "sentinel"}},
BootstrapConfigMutator: func(_ *Cluster, nbc *datamodel.NodeBootstrappingConfiguration) {
// AgentBaker (not the RP) now owns --kube-reserved-cgroup/--system-reserved-cgroup;
// it derives them from --enforce-node-allocatable (see setNodeHardeningCgroupFlags).
nbc.KubeletConfig["--enforce-node-allocatable"] = "pods,kube-reserved,system-reserved"
// kubelet refuses to enforce limits on a reserved cgroup unless a matching
// resource list is also supplied; the base config only sets --kube-reserved,
// so --system-reserved must be added here too or kubelet fails to start with
// "system.slice cgroup is not configured properly".
nbc.KubeletConfig["--system-reserved"] = "cpu=200m,memory=500Mi"
// Simulate the RP still sending its legacy (stale) cgroup slice name today;
// setNodeHardeningCgroupFlags must overwrite these with the values AgentBaker
// owns rather than trusting them, or the node would end up in the wrong slice.
nbc.KubeletConfig["--kube-reserved-cgroup"] = "/kubelet.slice"
nbc.KubeletConfig["--system-reserved-cgroup"] = "/kubelet.slice"
// Non-nil (even empty) CustomKubeletConfig switches AgentBaker to the
// config-file (kubeletconfig.json) path instead of CLI flags.
nbc.AgentPoolProfile.CustomKubeletConfig = &datamodel.CustomKubeletConfig{}
// Disable scriptless CSE so that the current cse_helpers.sh (with kubereserved.slice drop-in)
// is uploaded via custom data instead of relying on potentially stale VHD scripts.
nbc.EnableScriptlessCSECmd = false
},
Validator: func(ctx context.Context, s *Scenario) {
ValidateFileExists(ctx, s, "/etc/systemd/system/kubereserved.slice")
ValidateFileHasContent(ctx, s, "/etc/systemd/system/kubelet.service.d/10-kubereserved-slice.conf", "Slice=kubereserved.slice")
ValidateFileHasContent(ctx, s, "/etc/systemd/system/containerd.service.d/10-kubereserved-slice.conf", "Slice=kubereserved.slice")
ValidateFileHasContent(ctx, s, "/etc/default/kubeletconfig.json", `"kubeReservedCgroup": "/kubereserved.slice"`)
ValidateFileHasContent(ctx, s, "/etc/default/kubeletconfig.json", `"systemReservedCgroup": "/system.slice"`)
ValidateServiceInSlice(ctx, s, "kubelet.service", "kubereserved.slice")
ValidateServiceInSlice(ctx, s, "containerd.service", "kubereserved.slice")
},
},
})
}

// Test_Ubuntu2204_NodeHardening_KubeReservedSlice_CLIFlags validates the legacy CLI-flags
// kubelet path (kubelet reads flags from /etc/default/kubelet's KUBELET_FLAGS), which is
// used whenever AgentPoolProfile.CustomKubeletConfig/CustomLinuxOSConfig are both nil.
func Test_Ubuntu2204_NodeHardening_KubeReservedSlice_CLIFlags(t *testing.T) {
RunScenario(t, &Scenario{
Description: "validates kubelet and containerd run in kubereserved.slice when node hardening cgroup hierarchy is enabled (kubelet CLI-flags mode)",
Config: Config{
Cluster: ClusterKubenet,
VHD: config.VHDUbuntu2204Gen2Containerd,
// Force the "default" (non-scriptless) subtest path so that fresh CSE scripts
// with the Slice= drop-ins are uploaded via custom data.
CustomDataWriteFiles: []CustomDataWriteFile{{Path: "/etc/aks-node-hardening-test", Content: "sentinel"}},
BootstrapConfigMutator: func(_ *Cluster, nbc *datamodel.NodeBootstrappingConfiguration) {
// AgentBaker (not the RP) now owns --kube-reserved-cgroup/--system-reserved-cgroup;
// it derives them from --enforce-node-allocatable (see setNodeHardeningCgroupFlags).
nbc.KubeletConfig["--enforce-node-allocatable"] = "pods,kube-reserved,system-reserved"
// kubelet refuses to enforce limits on a reserved cgroup unless a matching
// resource list is also supplied; the base config only sets --kube-reserved,
// so --system-reserved must be added here too or kubelet fails to start with
// "system.slice cgroup is not configured properly".
nbc.KubeletConfig["--system-reserved"] = "cpu=200m,memory=500Mi"
// Simulate the RP still sending its legacy (stale) cgroup slice name today;
// setNodeHardeningCgroupFlags must overwrite these with the values AgentBaker
// owns rather than trusting them, or the node would end up in the wrong slice.
nbc.KubeletConfig["--kube-reserved-cgroup"] = "/kubelet.slice"
nbc.KubeletConfig["--system-reserved-cgroup"] = "/kubelet.slice"
// CustomKubeletConfig/CustomLinuxOSConfig left nil so kubelet reads its
// flags from the CLI (KUBELET_FLAGS in /etc/default/kubelet) instead of
// the config-file path.
// Disable scriptless CSE so that the current cse_helpers.sh (with kubereserved.slice drop-in)
// is uploaded via custom data instead of relying on potentially stale VHD scripts.
nbc.EnableScriptlessCSECmd = false
},
Validator: func(ctx context.Context, s *Scenario) {
ValidateFileExists(ctx, s, "/etc/systemd/system/kubereserved.slice")
ValidateFileHasContent(ctx, s, "/etc/systemd/system/kubelet.service.d/10-kubereserved-slice.conf", "Slice=kubereserved.slice")
ValidateFileHasContent(ctx, s, "/etc/systemd/system/containerd.service.d/10-kubereserved-slice.conf", "Slice=kubereserved.slice")
ValidateFileHasContent(ctx, s, "/etc/default/kubelet", "--kube-reserved-cgroup=/kubereserved.slice")
ValidateFileHasContent(ctx, s, "/etc/default/kubelet", "--system-reserved-cgroup=/system.slice")
ValidateServiceInSlice(ctx, s, "kubelet.service", "kubereserved.slice")
ValidateServiceInSlice(ctx, s, "containerd.service", "kubereserved.slice")
},
},
})
}
15 changes: 15 additions & 0 deletions e2e/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -3485,3 +3485,18 @@ func ValidateRCV1PNotOptedInWindows(ctx context.Context, s *Scenario) {
execScriptOnVMForScenarioValidateExitCode(ctx, s, strings.Join(command, "\n"), 0,
"expected no aks-ca-certs-refresh-task scheduled task when not opted in")
}

// 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)
}
Comment on lines +3488 to +3495
result := execScriptOnVMForScenarioValidateExitCode(ctx, s,
fmt.Sprintf("systemctl show --property=Slice --value -- %s", service), 0,
fmt.Sprintf("could not query Slice property of %s", service))
actual := strings.TrimSpace(result.stdout)
require.Equal(s.T, expectedSlice, actual,
"expected %s to be in %s, but got %s", service, expectedSlice, actual)
}
Comment thread
Copilot marked this conversation as resolved.
6 changes: 3 additions & 3 deletions e2e/vmss.go
Original file line number Diff line number Diff line change
Expand Up @@ -535,9 +535,9 @@ func createVMSSModel(ctx context.Context, s *Scenario) armcompute.VirtualMachine
customData, err = injectWriteFilesEntriesToCustomData(customData, s.Config.CustomDataWriteFiles)
require.NoError(s.T, err, "failed to inject customData write_files entries")
}
if !scriptlessNBCCSECmdEnabled && s.VHD.SupportsScriptless() {
// Validate that the custom data doesn't contain any script content,
// which indicates that the scriptless CSE is working as intended
if s.Runtime.NBC.EnableScriptlessCSECmd && !scriptlessNBCCSECmdEnabled && s.VHD.SupportsScriptless() {
// Validate that the custom data indicates scriptless CSE is enabled by checking
// for the scriptless overrides sentinel file path.
decodedCustomData, err := base64.StdEncoding.DecodeString(customData)
require.NoError(s.T, err, "failed to decode custom data")
reader, err := gzip.NewReader(bytes.NewReader(decodedCustomData))
Expand Down
32 changes: 28 additions & 4 deletions parts/linux/cloud-init/artifacts/cse_config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,17 @@ net.ipv6.conf.all.forwarding = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
retrycmd_if_failure 120 5 25 sysctl --system || exit $ERR_SYSCTL_RELOAD

# Node Memory Hardening: create kubereserved.slice and drop-ins BEFORE starting
# containerd/kubelet so both services start in the correct slice from the
# beginning — avoids needing a disruptive restart after the fact.
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
fi
Comment thread
titilambert marked this conversation as resolved.

systemctlEnableAndStartNoBlock containerd 30 || exit $ERR_SYSTEMCTL_START_FAIL
}

Expand Down Expand Up @@ -855,17 +866,30 @@ EOF
local tls_bootstrapping_start_time_filepath="/opt/azure/containers/tls-bootstrap-start-time"
date +"%F %T.%3N" > "${tls_bootstrapping_start_time_filepath}"

# Node Memory Hardening (F2/F5): if the RP rendered --kube-reserved-cgroup or
# --system-reserved-cgroup, ensure the corresponding systemd slices exist before
# kubelet starts so its NodeAllocatable enforcement loop can find them. The
# helper is a no-op when neither value is present (back-compat with non-hardened pools).
# Node Memory Hardening (F2/F5): idempotent refresh for PIS real nodes booting
# from older cached VHDs where basePrep (ensureContainerd) may not have created
# the Slice= drop-ins. No-op when neither value is set (non-hardened pools).
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
fi

# 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"
Comment thread
titilambert marked this conversation as resolved.
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
Comment on lines +879 to +888

Comment thread
titilambert marked this conversation as resolved.
Comment thread
Copilot marked this conversation as resolved.
if ! systemctl daemon-reload; then
exit $ERR_KUBELET_START_FAIL
fi
# start kubelet.service without waiting for the main process to start, though check whether it has entered a failed state after enablement
if ! systemctlEnableAndStartNoBlock kubelet 240; then
# append kubelet status to CSE output to ensure we can see it
Expand Down
Loading
Loading