mirror of
https://github.com/aljazceru/kata-containers.git
synced 2025-12-18 23:04:20 +01:00
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:
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
250
src/libs/kata-types/src/config/shared_mount.rs
Normal file
250
src/libs/kata-types/src/config/shared_mount.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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".
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user