agent: support bind mounts between containers

This feature supports creating bind mounts directly between containers through annotations.

Fixes: #6715

Signed-off-by: HanZiyao <h56983577@126.com>
This commit is contained in:
HanZiyao
2023-10-12 18:09:46 +08:00
parent 4c3a664358
commit a3b003c345
19 changed files with 598 additions and 11 deletions

View File

@@ -7,6 +7,7 @@
#![allow(missing_docs)]
pub const CONTAINER_TYPE_LABEL_KEY: &str = "io.kubernetes.cri.container-type";
pub const CONTAINER_NAME_LABEL_KEY: &str = "io.kubernetes.cri.container-name";
pub const SANDBOX: &str = "sandbox";
pub const CONTAINER: &str = "container";

View File

@@ -6,7 +6,8 @@
#![allow(missing_docs)]
pub const CONTAINER_TYPE_LABEL_KEY: &str = "io.kubernetes.cri.container-type";
pub const CONTAINER_TYPE_LABEL_KEY: &str = "io.kubernetes.cri-o.ContainerType";
pub const CONTAINER_NAME_LABEL_KEY: &str = "io.kubernetes.cri-o.ContainerName";
pub const SANDBOX: &str = "sandbox";
pub const CONTAINER: &str = "container";

View File

@@ -310,6 +310,8 @@ pub const KATA_ANNO_CFG_DISABLE_NEW_NETNS: &str =
"io.katacontainers.config.runtime.disable_new_netns";
/// A sandbox annotation to specify how attached VFIO devices should be treated.
pub const KATA_ANNO_CFG_VFIO_MODE: &str = "io.katacontainers.config.runtime.vfio_mode";
/// An annotation to declare shared mount points, which is a set of mount points that directly share mounted objects between containers.
pub const KATA_ANNO_CFG_SHARED_MOUNTS: &str = "io.katacontainers.config.runtime.shared_mounts";
/// A sandbox annotation used to specify prefetch_files.list host path container image
/// being used,
@@ -972,6 +974,9 @@ impl Annotation {
KATA_ANNO_CFG_VFIO_MODE => {
config.runtime.vfio_mode = value.to_string();
}
KATA_ANNO_CFG_SHARED_MOUNTS => {
config.runtime.shared_mounts = serde_json::from_str(value.as_str())?;
}
KATA_ANNO_CFG_SANDBOX_BIND_MOUNTS => {
let args: Vec<String> = value
.to_string()

View File

@@ -11,6 +11,10 @@ use crate::config::{ConfigOps, TomlConfig};
use crate::mount::split_bind_mounts;
use crate::{eother, validate_path};
#[path = "shared_mount.rs"]
pub mod shared_mount;
pub use shared_mount::SharedMount;
/// Type of runtime VirtContainer.
pub const RUNTIME_NAME_VIRTCONTAINER: &str = "virt_container";
@@ -148,6 +152,10 @@ pub struct Runtime {
/// to the hypervisor.
#[serde(default)]
pub dan_conf: String,
/// shared_mount declarations
#[serde(default)]
pub shared_mounts: Vec<SharedMount>,
}
impl ConfigOps for Runtime {
@@ -194,6 +202,10 @@ impl ConfigOps for Runtime {
));
}
for shared_mount in &conf.runtime.shared_mounts {
shared_mount.validate()?;
}
for bind in conf.runtime.sandbox_bind_mounts.iter() {
// Just validate the real_path.
let (real_path, _mode) = split_bind_mounts(bind);

View File

@@ -0,0 +1,250 @@
use std::io::Result;
use regex::Regex;
use crate::eother;
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
pub struct SharedMount {
/// Name is used to identify a pair of shared mount points.
/// This field cannot be omitted.
#[serde(default)]
pub name: String,
/// Src_ctr is used to specify the name of the source container.
/// This field cannot be omitted.
#[serde(default)]
pub src_ctr: String,
/// Src_path is used to specify the path to the shared mount point in the source container.
/// Src_path must conform to the regular expression `^(/[-\w.]+)+/?$` and cannot contain `/../`.
/// This field cannot be omitted.
#[serde(default)]
pub src_path: String,
/// Dst_ctr is used to specify the name of the destination container.
/// This field cannot be omitted.
#[serde(default)]
pub dst_ctr: String,
/// Dst_path is used to specify the destination path where the shared mount point will be mounted.
/// Dst_path must conform to the regular expression `^(/[-\w.]+)+/?$` and cannot contain `/../`.
/// This field cannot be omitted.
#[serde(default)]
pub dst_path: String,
}
impl SharedMount {
pub fn validate(&self) -> Result<()> {
if self.name == "" {
return Err(eother!("shared_mount: field 'name' couldn't be empty."));
}
if self.src_ctr == "" {
return Err(eother!("shared_mount: field 'src_ctr' couldn't be empty."));
}
if self.dst_ctr == "" {
return Err(eother!("shared_mount: field 'dst_ctr' couldn't be empty."));
}
if self.src_path == "" {
return Err(eother!("shared_mount: field 'src_path' couldn't be empty."));
}
if self.dst_path == "" {
return Err(eother!("shared_mount: field 'dst_path' couldn't be empty."));
}
let re = match Regex::new(r"^(/[-\w.]+)+/?$") {
Ok(re) => re,
Err(e) => return Err(eother!("Compiling the regular expression failed: {}.", e)),
};
if !re.is_match(&self.src_path) {
return Err(eother!("shared_mount '{}': src_path is invalid. It must be an absolute path and can only contain letters, numbers, hyphens(-), underscores(_) and dots(.).", self.name));
}
let dirs: Vec<&str> = self.src_path.split('/').collect();
for dir in dirs {
if dir == ".." {
return Err(eother!(
"shared_mount '{}': src_path couldn't contain '..' directory.",
self.name
));
}
}
if !re.is_match(&self.dst_path) {
return Err(eother!("shared_mount '{}': dst_path is invalid. It must be an absolute path and can only contain letters, numbers, hyphens(-), underscores(_) and dots(.).", self.name));
}
let dirs: Vec<&str> = self.dst_path.split('/').collect();
for dir in dirs {
if dir == ".." {
return Err(eother!(
"shared_mount '{}': dst_path couldn't contain '..' directory.",
self.name
));
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate() {
#[derive(Debug)]
struct TestData<'a> {
shared_mount_annotation: &'a str,
result: bool,
message: &'a str,
}
let tests = &[
TestData {
shared_mount_annotation: r#"
{
"name": "test",
"src_ctr": "sidecar",
"src_path": "/mnt/storage",
"dst_ctr": "app",
"dst_path": "/mnt/storage"
}"#,
result: true,
message: "",
},
TestData {
shared_mount_annotation: r#"
{
"src_ctr": "sidecar",
"src_path": "/mnt/storage",
"dst_ctr": "app",
"dst_path": "/mnt/storage"
}"#,
result: false,
message: "shared_mount: field 'name' couldn't be empty.",
},
TestData {
shared_mount_annotation: r#"
{
"name": "test",
"src": "sidecar",
"src_path": "/mnt/storage",
"dst_ctr": "app",
"dst_path": "/mnt/storage"
}"#,
result: false,
message: "shared_mount: field 'src_ctr' couldn't be empty.",
},
TestData {
shared_mount_annotation: r#"
{
"name": "test",
"src_ctr": "sidecar",
"src_dir": "/mnt/storage",
"dst_ctr": "app",
"dst_path": "/mnt/storage"
}"#,
result: false,
message: "shared_mount: field 'src_path' couldn't be empty.",
},
TestData {
shared_mount_annotation: r#"
{
"name": "test",
"src_ctr": "sidecar",
"src_path": "/mnt/storage",
"dst_container": "app",
"dst_path": "/mnt/storage"
}"#,
result: false,
message: "shared_mount: field 'dst_ctr' couldn't be empty.",
},
TestData {
shared_mount_annotation: r#"
{
"name": "test",
"src_ctr": "sidecar",
"src_path": "/mnt/storage",
"dst_ctr": "app",
"path": "/mnt/storage"
}"#,
result: false,
message: "shared_mount: field 'dst_path' couldn't be empty.",
},
TestData {
shared_mount_annotation: r#"
{
"name": "test",
"src_ctr": "sidecar",
"src_path": "/_._/._/_/._",
"dst_ctr": "app",
"dst_path": "/-.-/.-/-/.-"
}"#,
result: true,
message: "",
},
TestData {
shared_mount_annotation: r#"
{
"name": "test",
"src_ctr": "sidecar",
"src_path": "~/storage",
"dst_ctr": "app",
"dst_path": "/mnt/storage"
}"#,
result: false,
message: "shared_mount 'test': src_path is invalid. It must be an absolute path and can only contain letters, numbers, hyphens(-), underscores(_) and dots(.).",
},
TestData {
shared_mount_annotation: r#"
{
"name": "test",
"src_ctr": "sidecar",
"src_path": "/mnt/storage",
"dst_ctr": "app",
"dst_path": "/mnt/storage|ls"
}"#,
result: false,
message: "shared_mount 'test': dst_path is invalid. It must be an absolute path and can only contain letters, numbers, hyphens(-), underscores(_) and dots(.).",
},
TestData {
shared_mount_annotation: r#"
{
"name": "test",
"src_ctr": "sidecar",
"src_path": "/../mnt/storage",
"dst_ctr": "app",
"dst_path": "/mnt/storage"
}"#,
result: false,
message: "shared_mount 'test': src_path couldn't contain '..' directory.",
},
TestData {
shared_mount_annotation: r#"
{
"name": "test",
"src_ctr": "sidecar",
"src_path": "/mnt/storage",
"dst_ctr": "app",
"dst_path": "/../mnt/storage"
}"#,
result: false,
message: "shared_mount 'test': dst_path couldn't contain '..' directory.",
},
];
for (i, d) in tests.iter().enumerate() {
let msg = format!("test[{}]: {:?}", i, d);
let m: SharedMount = serde_json::from_str(d.shared_mount_annotation).unwrap();
let result = m.validate();
let msg = format!("{}, result: {:?}", msg, result);
assert_eq!(result.is_ok(), d.result, "{}", msg);
if !d.result {
assert_eq!(result.unwrap_err().to_string(), d.message, "{}", msg);
}
}
}
}

View File

@@ -72,6 +72,22 @@ pub fn container_type(spec: &oci::Spec) -> ContainerType {
ContainerType::SingleContainer
}
/// Get K8S container name from OCI annotations.
pub fn container_name(spec: &oci::Spec) -> String {
for k in [
annotations::cri_containerd::CONTAINER_NAME_LABEL_KEY,
annotations::crio::CONTAINER_NAME_LABEL_KEY,
]
.iter()
{
if let Some(v) = spec.annotations.get(k.to_owned()) {
return v.clone();
}
}
String::new()
}
/// Determine the k8s sandbox ID from OCI annotations.
///
/// This function is expected to be called only when the container type is "PodContainer".

View File

@@ -89,6 +89,10 @@ message CreateContainerRequest {
// The agent would receive an OCI spec with PID namespace cleared
// out altogether and not just the pid ns path.
bool sandbox_pidns = 7;
// This field is used to declare a set of shared mount points
// that support cross-container sharing of mount objects.
repeated SharedMount shared_mounts = 8;
}
message StartContainerRequest {
@@ -444,6 +448,23 @@ message FSGroup {
types.FSGroupChangePolicy group_change_policy = 3;
}
// SharedMount declares a set of shared mount points that support
// cross-container sharing of mount objects.
message SharedMount {
// Name is used to identify a pair of shared mount points.
string name = 1;
// Src_ctr is used to specify the name of the source container.
string src_ctr = 2;
// Src_path is used to specify the path of the mount point. If the path doesn't
// exist in the rootfs, it will be created.
string src_path = 3;
// Dst_ctr is used to specify the name of the destination container.
string dst_ctr = 4;
// Dst_path is used to specify the path of the mount point. If the path doesn't
// exist in the rootfs, it will be created.
string dst_path = 5;
}
// Storage represents both the rootfs of the container, and any volume that
// could have been defined through the Mount list of the OCI specification.
message Storage {