fix: align containerd v2 configs with 2.3 schema - #8948
fix: align containerd v2 configs with 2.3 schema#8948Sylvain Boily (djsly) wants to merge 30 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Aligns AgentBaker-managed containerd v2 (Ubuntu 24.04) configuration with the containerd 2.3 config schema v4 so NVIDIA GPU Operator–generated v4 drop-ins are accepted, and updates runtime/plugin namespaces accordingly.
Changes:
- Updated Ubuntu 24.04 containerd v2 root config templates in
pkg/agentto declareversion = 4. - Migrated Kata runtime blocks in the v2 templates to the split
io.containerd.cri.v1.runtimeplugin namespace. - Updated the Ubuntu 24.04 GB static NVIDIA containerd config to schema v4 and the split
images/runtimeplugin namespaces.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| pkg/agent/baker.go | Updates Ubuntu 24.04 containerd v2 template schema to v4 and moves Kata runtime blocks to the split CRI runtime plugin namespace. |
| parts/linux/cloud-init/artifacts/ubuntu/gb/containerd-nvidia.toml | Updates the Ubuntu 24.04 GB NVIDIA containerd config to schema v4 and split CRI plugin namespaces. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
aks-node-controller/parser/templates/containerd.toml.gtpl:27
- When rendering config schema v4 (containerd >= 2.3),
snapshotter/disable_snapshot_annotationsshould be configured under the images plugin (io.containerd.cri.v1.images) rather than under the runtime plugin’s.containerdsection. Keeping these fields under[plugins."{{$runtimePlugin}}".containerd]means overlaybd artifact streaming (and potentially Kata’s snapshot-annotation behavior) may be ignored on v4 configs.
disable_snapshot_annotations = false
{{- end}}
{{- if .GetEnableArtifactStreaming }}
snapshotter = "overlaybd"
disable_snapshot_annotations = false
aks-node-controller/parser/templates/containerd_no_GPU.toml.gtpl:23
- For config schema v4 (containerd >= 2.3),
snapshotter/disable_snapshot_annotationsneed to be set on the images plugin (io.containerd.cri.v1.images), not under the runtime plugin’s.containerdtable. As-is, the v4 path will still emit these fields under[plugins."{{$runtimePlugin}}".containerd], which containerd v2.3’s split plugins are unlikely to consume.
{{- if .GetIsKata }}
disable_snapshot_annotations = false
{{- end}}
{{- if .GetEnableArtifactStreaming }}
snapshotter = "overlaybd"
76a6b7a to
7c46353
Compare
Windows Unit Test Results 3 files 13 suites 43s ⏱️ Results for commit 9a4711d. ♻️ This comment has been updated with latest results. |
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 23f6b1fb-60fd-42c0-be94-af3644f84056
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 23f6b1fb-60fd-42c0-be94-af3644f84056
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 23f6b1fb-60fd-42c0-be94-af3644f84056
a6411e7 to
472f552
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.
Suppressed comments (3)
pkg/agent/baker.go:2002
containerdSemverCoreis now implemented in multiple places (also appears underaks-node-controller/parser/helper.go). Duplicated parsing logic is easy to drift over time (especially around edge cases like epochs and Debian~prerelease). Suggestion (optional but recommended): centralize this parsing in a shared package/util, or add shared test vectors to ensure both implementations stay behaviorally identical.
func containerdSemverCore(version string) string {
version = strings.TrimSpace(version)
if idx := strings.Index(version, ":"); idx > 0 {
epoch := version[:idx]
validEpoch := true
for _, c := range epoch {
if c < '0' || c > '9' {
validEpoch = false
break
}
}
if validEpoch {
version = version[idx+1:]
}
}
version = strings.TrimPrefix(version, "v")
if idx := strings.IndexAny(version, "-+~"); idx > 0 {
version = version[:idx]
}
parts := strings.Split(version, ".")
if len(parts) != 3 {
return ""
}
for _, part := range parts {
if part == "" {
return ""
}
for _, c := range part {
if c < '0' || c > '9' {
return ""
}
}
}
return version
}
vhdbuilder/packer/install-dependencies.sh:841
- This function unconditionally runs
systemctl --version. In build environments wheresystemctlisn't available/functional (e.g., image build context changes, minimal bases), this can fail the VHD build even though the guard is only needed at runtime. Suggestion: add acommand -v systemctlcheck (and/or defaultsystemd_versionsafely) before callingsystemctl, so the script degrades gracefully when systemd tooling isn't present.
configureNvidiaCDIRefreshPreDriverReadinessGuard() {
if [ "$OS" = "$UBUNTU_OS_NAME" ] && [ "$(isARM64)" -ne 1 ]; then # No ARM64 SKU with GPU now
local systemd_version
systemd_version=$(systemctl --version | awk 'NR == 1 { print $2 }')
mkdir -p /etc/systemd/system/nvidia-cdi-refresh.service.d
cat << 'EOF' > /etc/systemd/system/nvidia-cdi-refresh.service.d/aks-driver-ready.conf
[Unit]
ConditionPathExists=/proc/driver/nvidia/version
EOF
if [ "${systemd_version:-0}" -ge 243 ]; then
cat << 'EOF' >> /etc/systemd/system/nvidia-cdi-refresh.service.d/aks-driver-ready.conf
[Service]
ExecCondition=/bin/sh -c 'command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi -L >/dev/null 2>&1'
EOF
fi
fi
}
parts/linux/cloud-init/artifacts/cse_config.sh:1498
- There’s a new failure branch for the
nvidia-cdi-refresh.pathunit restart, but the added shellspec coverage only exercises the service failure path. Please add a test case that sets upNVIDIA_CDI_UNITSto includenvidia-cdi-refresh.pathand makessystemctl start nvidia-cdi-refresh.pathfail, asserting the function returns failure and emits the new error message.
if nvidiaCDIRefreshUnitExists "${path_unit}"; then
systemctl reset-failed "${path_unit}" || true
if ! systemctl start "${path_unit}"; then
echo "Failed to restart NVIDIA CDI refresh path unit after GPU driver is ready" >&2
return $ERR_GPU_DRIVERS_START_FAIL
fi
fi
Remove the NVIDIA CDI refresh repair and VHD readiness guard from this PR so the change stays scoped to containerd 2.3 config schema alignment. The CDI fix will be handled separately. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 23f6b1fb-60fd-42c0-be94-af3644f84056
The enable_cdi=true added to the baked GB VHD config is a behavior change, not part of the containerd 2.3 schema alignment. Base had no CDI enablement here, and the readiness guard that made it safe was removed from this PR. Keep the schema (version=4 / v1 plugin paths) but defer CDI enablement.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.
Suppressed comments (3)
pkg/agent/baker.go:2164
- This second
[proxy_plugins]declaration will collide with the earlier[proxy_plugins]emitted by theIsArtifactStreamingEnabledblock when both are enabled, producing invalid TOML. Using a direct subtable header avoids redeclaring the parent table.
[proxy_plugins]
[proxy_plugins.tardev]
type = "snapshot"
address = "/run/containerd/tardev-snapshotter.sock"
aks-node-controller/parser/templates/containerd.toml.gtpl:99
- This
[proxy_plugins]header is a second declaration of the same table when artifact streaming is enabled earlier in the template, which can produce invalid TOML. Use a direct subtable header so the parent table is not redeclared.
[proxy_plugins]
[proxy_plugins.tardev]
type = "snapshot"
address = "/run/containerd/tardev-snapshotter.sock"
aks-node-controller/parser/templates/containerd_no_GPU.toml.gtpl:81
- This
[proxy_plugins]table header can duplicate the earlier[proxy_plugins]header emitted for artifact streaming, producing invalid TOML. Prefer a direct subtable header here.
[proxy_plugins]
[proxy_plugins.tardev]
type = "snapshot"
address = "/run/containerd/tardev-snapshotter.sock"
| @@ -2065,25 +2146,32 @@ root = "{{GetDataDir}}"{{- end}} | |||
| address = "/run/overlaybd-snapshotter/overlaybd.sock" | |||
| @@ -44,11 +61,11 @@ root = "{{.KubeletConfig.GetContainerDataDir}}"{{- end}} | |||
| address = "/run/overlaybd-snapshotter/overlaybd.sock" | |||
| @@ -60,11 +79,11 @@ root = "{{.KubeletConfig.GetContainerDataDir}}"{{- end}} | |||
| address = "/run/overlaybd-snapshotter/overlaybd.sock" | |||
# Conflicts: # aks-node-controller/parser/templates/containerd.toml.gtpl # aks-node-controller/parser/templates/containerd_no_GPU.toml.gtpl # pkg/agent/baker.go
The overlaybd (artifact streaming) and tardev (Kata) proxy plugins each
declared a bare [proxy_plugins] table header. When both artifact streaming
and Kata are enabled, containerd sees [proxy_plugins] twice, which is invalid
TOML ("table proxy_plugins already exists") and can stop containerd starting.
Use direct subtables ([proxy_plugins.overlaybd] / [proxy_plugins.tardev]) so
the parent table is created implicitly and never redeclared. Applied across
all containerd templates in baker.go and the aks-node-controller templates.
Addresses Copilot review feedback on PR #8948.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 15 out of 15 changed files in this pull request and generated no new comments.
Suppressed comments (2)
pkg/agent/baker.go:1921
containerdV2ConfigTemplate/containerdV2NoGPUConfigTemplatenow emitversion = 4(containerd config schema v4), so the constant names are misleading and make the selection logic harder to reason about (they look like the “containerd v2 schema” path used for pre-2.3). Consider renaming these templates to reflect schema v4 (e.g.,containerdV4ConfigTemplate/containerdV4NoGPUConfigTemplate) and updating the callers accordingly to reduce future maintenance risk.
func containerdConfigTemplateForVersion(config *datamodel.NodeBootstrappingConfiguration, noGPU bool) ContainerdConfigTemplate {
if shouldUseContainerdV4Config(config) {
if noGPU {
return containerdV2NoGPUConfigTemplate
}
return containerdV2ConfigTemplate
}
aks-node-controller/app.go:478
- The doc comment for
containerdConfigContentEqualsays it ignores “whitespace differences”, but the implementation only trims leading/trailing whitespace (and strips TOML comments). Internal whitespace differences (e.g., within arrays) will still cause mismatches. Please adjust the comment to match the actual comparison behavior so callers don’t assume broader normalization than is implemented.
// containerdConfigContentEqual base64-decodes containerd TOML configs and compares
// table-scoped key/value pairs while ignoring harmless ordering and whitespace differences.
func containerdConfigContentEqual(a, b string) bool {
# Conflicts: # e2e/test_helpers.go
| [plugins."io.containerd.cri.v1.runtime".containerd] | ||
| default_runtime_name = "nvidia" |
There was a problem hiding this comment.
this is a behavior change and should not be in the current pr
ValidateKataContainerdConfig hardcoded the containerd 1.x plugin path [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata], on the assumption that every Kata distro renders the v1 config template. This PR selects the containerd schema by version, so AzureLinux V3 Kata (containerd 2.x) now renders the native split schema and the handler lives under io.containerd.cri.v1.runtime, causing Test_AzureLinuxV3Gen2Kata to fail. Match the handler table by its anchored, schema-independent suffix (.containerd.runtimes.kata]) instead, matching how the sibling ValidateKataContainerdConfigDump already validates the effective config. The config is correct (node comes up); only the assertion was stale.
…lugin schemas The split-plugin containerd 2.x config templates (schema v2 for 2.0-2.2 and schema v4 for 2.3+) only emitted disable_snapshot_annotations=false inside the IsArtifactStreamingEnabled block, never on the Kata path. The legacy v1 (grpc.v1.cri) template set it under IsKata, but that carry-over was missed when the split cri.v1.images/runtime templates were introduced. Result: an AzureLinux V3 Kata node (containerd 2.x) rendered a config without disable_snapshot_annotations, failing Test_AzureLinuxV3Gen2Kata's ValidateKataContainerdConfig. Kata forwards snapshot annotations to the snapshotter, so dropping it also risks breaking image pulls for Kata pods. Emit disable_snapshot_annotations=false under the images plugin on the Kata path in all four baker.go split templates and both aks-node-controller v2 templates. Add a baker unit test locking the invariant across both schemas and the GPU/no-GPU variants (baker Kata rendering previously had no unit coverage).
… 2.3+
The provision-config vs nbc-cmd parity check (Test_Ubuntu2404_DraDriverNvidiaGpuRunning_AKSNodeController)
failed on containerd 2.3.3 GPU nodes: CONTAINERD_CONFIG_CONTENT and
CONTAINERD_CONFIG_NO_GPU_CONTENT differed between the scriptful (pkg/agent/baker.go)
and scriptless (aks-node-controller) rendering engines.
Root cause: aks-node-controller routed containerd 2.3+ to containerd.toml.gtpl,
whose v4 layout diverges from baker.go's containerdV2ConfigTemplate (pinned_images
indentation, registry/registry.headers ordering, and the .runtime.containerd table
indentation). baker.go's v4 template is structurally its before-2.3 template plus
`version = 4` and a `[plugins."io.containerd.cri.v1.runtime"] enable_cdi = true`
block (GPU only).
Fix: route all containerd 2.x nodes through the containerd_v2 templates (which
already mirror baker's before-2.3 layout) and teach them to emit the v4 schema for
2.3+ (version = 4 plus the GPU-only enable_cdi block). Verified byte-identical to
baker.go output for {v4,before-2.3} x {GPU,no-GPU}. No change for 2.0-2.2 (still
version = 2, no enable_cdi). containerd 1.x still uses the legacy template.
Adds unit coverage for the 2.3+ GPU (v4 + CDI) and no-GPU (v4, no CDI) cases.
Note: scriptless Kata parity for 2.x remains divergent (ANC containerd_v2 Kata block
uses the legacy grpc.v1.cri namespace, and baker's v4 Kata block has a
configuration-clh-templating.toml typo + no erofs preamble). Kata scriptless is not
covered by e2e; tracked as a follow-up.
…+ align scriptful/scriptless
Addresses Copilot review: the scriptless containerd_v2 templates emitted the Kata
runtime handlers under the legacy io.containerd.grpc.v1.cri plugin. On containerd
2.x the CRI plugin is split, so those handlers are ignored, and it broke parity
with pkg/agent/baker.go which uses io.containerd.cri.v1.runtime for Kata.
Reconciled the scriptful (baker.go) and scriptless (aks-node-controller) Kata
config so they render byte-for-byte identical for containerd 2.x, verified for
{schema v2 (2.0-2.2), schema v4 (2.3+)} x {GPU, no-GPU}:
- aks-node-controller/parser/templates/containerd_v2{,_no_GPU}.toml.gtpl: move the
Kata runtime handlers from grpc.v1.cri to the split cri.v1.runtime namespace; add
the missing kata-cc handler to the no-GPU template (the GPU template and both
baker.go templates already had it).
- pkg/agent/baker.go: the schema-v4 Kata templates were missing the EROFS
snapshotter preamble that kata-preview needs (present in the pre-2.3 and
scriptless templates), and referenced a non-existent
configuration-clh-templating.toml — fixed to configuration-clh-preview.toml to
match every other template.
Adds unit coverage for the 2.3+ Kata block (split namespace, EROFS preamble,
kata-cc, clh-preview path) on both the scriptless and scriptful paths.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (1)
e2e/validators_kata.go:112
- The updated comment says the containerd config schema AgentBaker renders for Kata is decided by the node’s containerd version, but in the scriptful path it’s actually driven primarily by the explicit
NodeBootstrappingConfiguration.ContainerdVersion(and only falls back to distro inference when it’s empty). SinceAgentPoolProfile.IsContainerdV2Distro()explicitly returns false for Kata distros, a Kata node with an omittedContainerdVersionwill still render the legacy grpc.v1.cri template even if the VHD boots containerd 2.x. Suggest clarifying this to avoid implying the behavior is purely runtime-version driven in all cases.
// containerd major version on the node is decided by the image, while the schema AgentBaker
// renders is decided by the node's containerd version. AgentBaker now hands each Kata distro a
// config that matches its containerd major (grpc.v1.cri for 1.x, the split cri.v1.runtime for
// 2.x), so AzureLinux V3 Kata boots containerd 2.x with a native 2.x config rather than relying
// on containerd's legacy migration of the "io.containerd.grpc.v1.cri" paths.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (1)
aks-node-controller/app.go:481
- The doc comment block immediately above containerdConfigDiffDetail() starts by describing containerdConfigContentEqual(), but the next symbol is containerdConfigDiffDetail(). This is misleading (and breaks GoDoc association if someone navigates by comments). Consider updating/removing the first two lines so the comment accurately documents containerdConfigDiffDetail().
// containerdConfigContentEqual base64-decodes containerd TOML configs and compares
// table-scoped key/value pairs while ignoring harmless ordering and whitespace differences.
// containerdConfigDiffDetail returns a compact description of the canonical containerd TOML
// entries that differ between the provision-config (pc) and nbc-cmd values, so that
// scriptful/scriptless containerd parity drift is actionable from the logs instead of just
// reporting that the opaque base64 blob "differs". Returns "" for non-containerd keys.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (1)
aks-node-controller/app.go:551
- containerdConfigContentEqual() only attempts base64.StdEncoding decoding on the raw env var values. When the nbc-cmd env comes from parsing a shell command it can be wrapped in quotes (KEY="") or omit padding (raw base64), which you already normalize for diagnostics in containerdConfigDiffDetail(). If decoding fails here, the function falls back to envValsEqual(), which prevents semantic comparison (order/whitespace-insensitive) and can report false diffs.
Consider reusing the same normalization (trim/strip quotes + RawStdEncoding fallback) in containerdConfigContentEqual so the parity check stays accurate even when the env value is quoted or unpadded.
func containerdConfigContentEqual(a, b string) bool {
aDecoded, errA := base64.StdEncoding.DecodeString(a)
bDecoded, errB := base64.StdEncoding.DecodeString(b)
if errA != nil || errB != nil {
return envValsEqual(a, b)
Summary
Fix the containerd 2.3.x / NVIDIA GPU Operator config-version mismatch by making AgentBaker-owned containerd 2.3 root configs use containerd config schema v4. This prevents NVIDIA-generated v4 drop-ins from being rejected under a lower-version root config while preserving containerd 2.0-2.2 schema-v2 compatibility.
Changes
>= 2.3.0, and to keep split-plugin schema v2 for containerd 2.0-2.2.ContainerdVersiontaking precedence and local detection as fallback.ContainerdVersionstill renders the correct split-plugin config.nvidia-cdi-refreshstart-limit failures before the driver is available.Testing
GOPROXY=https://proxy.golang.org,direct go test ./pkg/agent/...cd aks-node-controller && GOPROXY=https://proxy.golang.org,direct go test ./parsercd aks-node-controller && GOPROXY=https://proxy.golang.org,direct go test ./...GOPROXY=https://proxy.golang.org,direct make generate(generated agent testdata matched; command stopped duringvalidate-shellon pre-existing SC3014 warnings in unrelated files)Related
🤖 Generated by GitHub Copilot