mirror of
https://github.com/kata-containers/kata-containers.git
synced 2026-05-04 12:31:27 +00:00
Merge pull request #12937 from fidencio/topic/kata-deploy-support-containerd-config-version-4
kata-deploy: support containerd config version 4
This commit is contained in:
@@ -142,7 +142,7 @@ pub async fn configure_snapshotter(
|
||||
// Runtime plugin id (from paths or by reading config), then map to table where disable_snapshot_annotations lives.
|
||||
let runtime_plugin_id = match &paths.plugin_id {
|
||||
Some(id) => id.as_str(),
|
||||
None => containerd::get_containerd_pluginid(&paths.config_file)?,
|
||||
None => containerd::get_containerd_pluginid(&paths.config_file, runtime)?,
|
||||
};
|
||||
let pluginid =
|
||||
containerd::pluginid_for_snapshotter_annotations(runtime_plugin_id, &paths.config_file)?;
|
||||
|
||||
@@ -12,7 +12,8 @@ use std::path::Path;
|
||||
use crate::k8s;
|
||||
|
||||
/// K3s/RKE2 containerd config template filenames (under the mounted containerd dir).
|
||||
/// V3 is for containerd 2.x; V2 is for containerd 1.x.
|
||||
/// `config-v3.toml.tmpl` is used when the rendered config uses split-CRI schema (containerd config version >= 3, including 4+).
|
||||
/// `config.toml.tmpl` is for legacy CRI (version 2).
|
||||
pub const K3S_RKE2_CONTAINERD_V3_TMPL: &str = "/etc/containerd/config-v3.toml.tmpl";
|
||||
pub const K3S_RKE2_CONTAINERD_V2_TMPL: &str = "/etc/containerd/config.toml.tmpl";
|
||||
|
||||
@@ -21,8 +22,8 @@ pub const K3S_RKE2_CONTAINERD_V2_TMPL: &str = "/etc/containerd/config.toml.tmpl"
|
||||
/// snapshotter field, and the base name for the data directory and socket path on the host.
|
||||
pub const NYDUS_FOR_KATA_TEE: &str = "nydus-for-kata-tee";
|
||||
|
||||
/// Resolves whether to use containerd config v3 (true) or v2 (false) for K3s/RKE2.
|
||||
/// 1. Tries config.toml (containerd config file): if it exists and contains "version = 3" or "version = 2", use that.
|
||||
/// Resolves whether to use the containerd 2.x split-CRI layout (true) or the v1 CRI gRPC layout (false) for K3s/RKE2.
|
||||
/// 1. Tries config.toml: if it has `version = 2` use legacy CRI table; if `version >= 3` (including 4+) use split CRI.
|
||||
/// 2. Else falls back to the node's containerRuntimeVersion (e.g. "containerd://2.1.5-k3s1").
|
||||
/// 3. If neither is available, returns an error.
|
||||
pub fn k3s_rke2_resolve_use_v3(
|
||||
@@ -30,14 +31,17 @@ pub fn k3s_rke2_resolve_use_v3(
|
||||
container_runtime_version: Option<&str>,
|
||||
) -> Result<bool> {
|
||||
use crate::runtime::manager;
|
||||
use crate::utils::major_version_from_config_toml;
|
||||
|
||||
// 1. Try config.toml (generated config that may already exist on the node)
|
||||
if let Ok(content) = fs::read_to_string(config_file_path) {
|
||||
if content.contains("version = 3") {
|
||||
return Ok(true);
|
||||
}
|
||||
if content.contains("version = 2") {
|
||||
return Ok(false);
|
||||
if let Some(v) = major_version_from_config_toml(&content) {
|
||||
if v == 2 {
|
||||
return Ok(false);
|
||||
}
|
||||
if v >= 3 {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,8 +52,8 @@ pub fn k3s_rke2_resolve_use_v3(
|
||||
|
||||
// 3. Neither source available
|
||||
Err(anyhow::anyhow!(
|
||||
"K3s/RKE2: cannot determine containerd config version (v2 vs v3). \
|
||||
Need version from {config_file_path} (version = 2/3) or node containerRuntimeVersion."
|
||||
"K3s/RKE2: cannot determine containerd config version (v2 vs split-CRI). \
|
||||
Need version from {config_file_path} (version = 2 or >= 3) or node containerRuntimeVersion."
|
||||
))
|
||||
}
|
||||
|
||||
@@ -911,6 +915,15 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[serial]
|
||||
#[test]
|
||||
fn test_k3s_rke2_resolve_use_v3_from_config_version_4_without_node_version() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let path = dir.path().join("config.toml");
|
||||
std::fs::write(&path, "version = 4\n").unwrap();
|
||||
assert!(k3s_rke2_resolve_use_v3(path.to_str().unwrap(), None).unwrap());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case(
|
||||
"imports = [\"/var/lib/rancher/k3s/agent/etc/containerd/config.toml.d/*.toml\"]\n",
|
||||
|
||||
@@ -38,26 +38,83 @@ const CONTAINERD_CRI_IMAGES_PLUGIN_ID: &str = "\"io.containerd.cri.v1.images\"";
|
||||
/// Plugin table for CRI containerd in v2 (disable_snapshot_annotations lives here).
|
||||
const CONTAINERD_CRI_CONTAINERD_TABLE_V2: &str = "\"io.containerd.grpc.v1.cri\".containerd";
|
||||
|
||||
/// Reads config and returns the CRI plugin ID used for *runtime* config (runtimes, snapshotter-per-runtime).
|
||||
pub(crate) fn get_containerd_pluginid(config_file: &str) -> Result<&'static str> {
|
||||
let content = fs::read_to_string(config_file)
|
||||
.with_context(|| format!("Failed to read containerd config file: {}", config_file))?;
|
||||
fn is_k3s_or_rke2(runtime: &str) -> bool {
|
||||
matches!(runtime, "k3s" | "k3s-agent" | "rke2-agent" | "rke2-server")
|
||||
}
|
||||
|
||||
if content.contains("version = 3") {
|
||||
Ok(CONTAINERD_V3_RUNTIME_PLUGIN_ID)
|
||||
} else if content.contains("version = 2") {
|
||||
Ok(CONTAINERD_V2_CRI_PLUGIN_ID)
|
||||
} else {
|
||||
Ok(CONTAINERD_LEGACY_CRI_PLUGIN_ID)
|
||||
fn schema_version_from_k3s_rke2_rendered_config() -> Option<u32> {
|
||||
fs::read_to_string(crate::config::k3s_rke2_rendered_config_path())
|
||||
.ok()
|
||||
.and_then(|c| utils::major_version_from_config_toml(&c))
|
||||
}
|
||||
|
||||
/// If `primary_schema` is unset, try the rendered K3s/RKE2 `config.toml`.
|
||||
/// In strict `get_containerd_pluginid` parsing, this only applies when the primary config is
|
||||
/// readable but has no root `version`; missing-file fallback only happens in lenient readers.
|
||||
fn schema_version_with_k3s_rke2_fallback(
|
||||
primary_schema: Option<u32>,
|
||||
runtime: &str,
|
||||
) -> Option<u32> {
|
||||
primary_schema.or_else(|| {
|
||||
if is_k3s_or_rke2(runtime) {
|
||||
schema_version_from_k3s_rke2_rendered_config()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Root config schema `version = N` using lenient reads.
|
||||
///
|
||||
/// Reads `primary` via `fs::read_to_string`; on failure (missing path, permissions, etc.)
|
||||
/// the parsed schema is treated as unset. If that result has no root `version`, or the read
|
||||
/// failed, falls back to the rendered K3s/RKE2 `/etc/containerd/config.toml` when `runtime`
|
||||
/// is k3s/rke2 (covers templates without `version`, transient mounts, and similar).
|
||||
fn schema_version_relaxed(primary: &str, runtime: &str) -> Option<u32> {
|
||||
let primary_v = fs::read_to_string(primary)
|
||||
.ok()
|
||||
.and_then(|c| utils::major_version_from_config_toml(&c));
|
||||
schema_version_with_k3s_rke2_fallback(primary_v, runtime)
|
||||
}
|
||||
|
||||
fn containerd_config_schema_version(paths: &ContainerdPaths, runtime: &str) -> Option<u32> {
|
||||
schema_version_relaxed(&paths.config_file, runtime)
|
||||
}
|
||||
|
||||
/// TOML path for containerd log level when DEBUG=true. Config schema v4+ uses
|
||||
/// `plugins."io.containerd.server.v1.debug"` instead of deprecated top-level `[debug]`.
|
||||
fn containerd_debug_level_toml_path(config_schema_version: Option<u32>) -> &'static str {
|
||||
match config_schema_version {
|
||||
Some(v) if v >= 4 => concat!(".plugins.", "\"io.containerd.server.v1.debug\"", ".level"),
|
||||
_ => ".debug.level",
|
||||
}
|
||||
}
|
||||
|
||||
/// True when the containerd config is v3 (version = 3), i.e. we use the split CRI plugins.
|
||||
/// Reads config and returns the CRI plugin ID used for *runtime* config (runtimes, snapshotter-per-runtime).
|
||||
/// `runtime` selects K3s/RKE2 fallbacks when `config_file` is a template without `version`.
|
||||
pub(crate) fn get_containerd_pluginid(config_file: &str, runtime: &str) -> Result<&'static str> {
|
||||
let content = fs::read_to_string(config_file)
|
||||
.with_context(|| format!("Failed to read containerd config file: {}", config_file))?;
|
||||
|
||||
let v = schema_version_with_k3s_rke2_fallback(
|
||||
utils::major_version_from_config_toml(&content),
|
||||
runtime,
|
||||
);
|
||||
|
||||
match v {
|
||||
Some(ver) if ver >= 3 => Ok(CONTAINERD_V3_RUNTIME_PLUGIN_ID),
|
||||
Some(2) => Ok(CONTAINERD_V2_CRI_PLUGIN_ID),
|
||||
_ => Ok(CONTAINERD_LEGACY_CRI_PLUGIN_ID),
|
||||
}
|
||||
}
|
||||
|
||||
/// True when the containerd config uses split CRI plugins (`io.containerd.cri.v1.*`),
|
||||
/// i.e. config schema version >= 3 (including containerd's newer defaults such as version 4).
|
||||
fn is_containerd_v3_config(pluginid: &str) -> bool {
|
||||
pluginid == CONTAINERD_V3_RUNTIME_PLUGIN_ID
|
||||
}
|
||||
|
||||
/// Maps the runtime plugin ID (from get_containerd_pluginid) to the plugin table where
|
||||
/// Maps the runtime plugin ID (from `get_containerd_pluginid` / K3s `paths.plugin_id`) to the table where
|
||||
/// disable_snapshot_annotations lives. In v3 that's the *images* plugin; in v2 the CRI .containerd subtable.
|
||||
pub(crate) fn pluginid_for_snapshotter_annotations(
|
||||
runtime_plugin_id: &str,
|
||||
@@ -69,7 +126,7 @@ pub(crate) fn pluginid_for_snapshotter_annotations(
|
||||
Ok(CONTAINERD_CRI_CONTAINERD_TABLE_V2)
|
||||
} else {
|
||||
anyhow::bail!(
|
||||
"Containerd config {} has no \"version = 2\" or \"version = 3\"; cannot determine CRI plugin for snapshotter config",
|
||||
"Containerd config {} has no supported config schema (need version = 2 or version >= 3); cannot determine CRI plugin for snapshotter config",
|
||||
config_file
|
||||
)
|
||||
}
|
||||
@@ -179,7 +236,7 @@ pub async fn configure_containerd_runtime(
|
||||
let configuration_file = get_containerd_output_path(&paths);
|
||||
let pluginid = match paths.plugin_id.as_deref() {
|
||||
Some(plugin_id) => plugin_id,
|
||||
None => get_containerd_pluginid(&paths.config_file)?,
|
||||
None => get_containerd_pluginid(&paths.config_file, runtime)?,
|
||||
};
|
||||
|
||||
log::info!(
|
||||
@@ -236,7 +293,9 @@ pub async fn configure_containerd_runtime(
|
||||
write_containerd_runtime_config(&configuration_file, pluginid, ¶ms)?;
|
||||
|
||||
if config.debug {
|
||||
toml_utils::set_toml_value(&configuration_file, ".debug.level", "\"debug\"")?;
|
||||
let schema = containerd_config_schema_version(&paths, runtime);
|
||||
let debug_path = containerd_debug_level_toml_path(schema);
|
||||
toml_utils::set_toml_value(&configuration_file, debug_path, "\"debug\"")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -257,7 +316,7 @@ pub async fn configure_custom_containerd_runtime(
|
||||
let configuration_file = get_containerd_output_path(&paths);
|
||||
let pluginid = match paths.plugin_id.as_deref() {
|
||||
Some(plugin_id) => plugin_id,
|
||||
None => get_containerd_pluginid(&paths.config_file)?,
|
||||
None => get_containerd_pluginid(&paths.config_file, runtime)?,
|
||||
};
|
||||
|
||||
log::info!(
|
||||
@@ -298,6 +357,12 @@ pub async fn configure_custom_containerd_runtime(
|
||||
|
||||
write_containerd_runtime_config(&configuration_file, pluginid, ¶ms)?;
|
||||
|
||||
if config.debug {
|
||||
let schema = containerd_config_schema_version(&paths, runtime);
|
||||
let debug_path = containerd_debug_level_toml_path(schema);
|
||||
toml_utils::set_toml_value(&configuration_file, debug_path, "\"debug\"")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -627,6 +692,40 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_containerd_debug_level_toml_path_by_schema_version() {
|
||||
assert_eq!(
|
||||
containerd_debug_level_toml_path(Some(4)),
|
||||
".plugins.\"io.containerd.server.v1.debug\".level"
|
||||
);
|
||||
assert_eq!(
|
||||
containerd_debug_level_toml_path(Some(5)),
|
||||
".plugins.\"io.containerd.server.v1.debug\".level"
|
||||
);
|
||||
assert_eq!(containerd_debug_level_toml_path(Some(3)), ".debug.level");
|
||||
assert_eq!(containerd_debug_level_toml_path(None), ".debug.level");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_containerd_pluginid_version_4_uses_split_cri() {
|
||||
let f = NamedTempFile::new().unwrap();
|
||||
std::fs::write(f.path(), "version = 4\n").unwrap();
|
||||
assert_eq!(
|
||||
get_containerd_pluginid(f.path().to_str().unwrap(), "containerd").unwrap(),
|
||||
CONTAINERD_V3_RUNTIME_PLUGIN_ID
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_containerd_pluginid_version_2() {
|
||||
let f = NamedTempFile::new().unwrap();
|
||||
std::fs::write(f.path(), "version = 2\n").unwrap();
|
||||
assert_eq!(
|
||||
get_containerd_pluginid(f.path().to_str().unwrap(), "containerd").unwrap(),
|
||||
CONTAINERD_V2_CRI_PLUGIN_ID
|
||||
);
|
||||
}
|
||||
|
||||
/// CRI images runtime_platforms snapshotter is set only for v3 config when a snapshotter is configured.
|
||||
#[rstest]
|
||||
#[case(CONTAINERD_V3_RUNTIME_PLUGIN_ID, Some("\"nydus\""), "kata-qemu", true)]
|
||||
@@ -699,7 +798,7 @@ mod tests {
|
||||
err
|
||||
);
|
||||
assert!(
|
||||
err.to_string().contains("version = 2") || err.to_string().contains("version = 3"),
|
||||
err.to_string().contains("version = 2") || err.to_string().contains("version >= 3"),
|
||||
"error should mention version: {}",
|
||||
err
|
||||
);
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) 2026 Kata Containers community
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! Helpers for reading `version = N` from containerd `config.toml`.
|
||||
|
||||
/// Reads the schema `version = N` from the containerd config root table only (before any `[` TOML table header).
|
||||
///
|
||||
/// Containerd keeps `version` as a top-level key; keys under `[plugins]` or other tables are ignored.
|
||||
/// Ignores `#` comments on the same line. Malformed `version` lines are skipped so a later valid line can still match.
|
||||
/// Returns `None` if no valid root `version` key is found.
|
||||
pub fn major_version_from_config_toml(content: &str) -> Option<u32> {
|
||||
for raw_line in content.lines() {
|
||||
let line = raw_line.split('#').next().unwrap_or("").trim();
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if line.starts_with('[') {
|
||||
break;
|
||||
}
|
||||
let mut parts = line.splitn(2, '=');
|
||||
let key = parts.next().unwrap_or("").trim();
|
||||
if key != "version" {
|
||||
continue;
|
||||
}
|
||||
let Some(rhs) = parts.next() else {
|
||||
continue;
|
||||
};
|
||||
let value = rhs.trim();
|
||||
let num_str: String = value.chars().take_while(|c| c.is_ascii_digit()).collect();
|
||||
if num_str.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if let Ok(n) = num_str.parse::<u32>() {
|
||||
return Some(n);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rstest::rstest;
|
||||
|
||||
#[rstest]
|
||||
#[case("version = 4\n", Some(4))]
|
||||
#[case("version=3\n", Some(3))]
|
||||
#[case(" version = 2 \n", Some(2))]
|
||||
#[case("version = 4 # comment\n", Some(4))]
|
||||
// Other root keys may precede `version`
|
||||
#[case("root = '/foo'\nversion = 3\n", Some(3))]
|
||||
// Only root table: ignore `version` under [plugins]
|
||||
#[case("version = 2\n\n[plugins]\n version = 999\n", Some(2))]
|
||||
#[case("[plugins]\nversion = 3\n", None)]
|
||||
// Malformed lines are skipped until a valid `version = N`
|
||||
#[case("version = abc\nversion = 4\n", Some(4))]
|
||||
#[case("version\nversion = 3\n", Some(3))]
|
||||
#[case("root = '/foo'\n", None)]
|
||||
#[case("", None)]
|
||||
fn test_major_version_from_config_toml(#[case] content: &str, #[case] expected: Option<u32>) {
|
||||
assert_eq!(major_version_from_config_toml(content), expected);
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,10 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod containerd_config_version;
|
||||
pub mod system;
|
||||
pub mod toml;
|
||||
pub mod yaml;
|
||||
|
||||
pub use containerd_config_version::major_version_from_config_toml;
|
||||
pub use system::*;
|
||||
|
||||
Reference in New Issue
Block a user