Skip to content

Commit 78db911

Browse files
authored
Add LinuxContainerExecutor to Wasmtime and WasmEdge shims (#156)
This commit adds a LinuxContainerExecutor to both the Wasmtime and Wasmedge shims. It enables the shims to be able to execute containers side by side with wasms. Some *notable changes*: - It adds a new libcontainer_instance module to containerd-shim-wasm which provides a linux container executor implementation that is shared by both wasmtime and wasmedge shims. - It adds this executor to both the shims. - It adds nginx image to integration tests. Signed-off-by: jiaxiao zhou <jiazho@microsoft.com>
1 parent 0ef2a51 commit 78db911

11 files changed

Lines changed: 259 additions & 55 deletions

File tree

Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ bin/kind: test/k8s/Dockerfile
7070
test/k8s/_out/img: test/k8s/Dockerfile Cargo.toml Cargo.lock $(shell find . -type f -name '*.rs')
7171
mkdir -p $(@D) && $(DOCKER_BUILD) -f test/k8s/Dockerfile --iidfile=$(@) --load .
7272

73+
.PHONY: test/nginx
74+
test/nginx:
75+
docker pull docker.io/nginx:latest
76+
mkdir -p $@/out && docker save -o $@/out/img.tar docker.io/nginx:latest
77+
7378
.PHONY: test/k8s/cluster
7479
test/k8s/cluster: target/wasm32-wasi/$(TARGET)/img.tar bin/kind test/k8s/_out/img bin/kind
7580
bin/kind create cluster --name $(KIND_CLUSTER_NAME) --image="$(shell cat test/k8s/_out/img)" && \

crates/containerd-shim-wasm/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@
66
pub mod sandbox;
77

88
pub mod services;
9+
10+
#[cfg(feature = "libcontainer")]
11+
pub mod libcontainer_instance;
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
use crate::sandbox::oci;
2+
use libcontainer::workload::default::DefaultExecutor;
3+
use libcontainer::workload::{Executor, ExecutorError};
4+
use nix::unistd::{dup, dup2};
5+
6+
use libc::{STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO};
7+
use oci_spec::runtime::Spec;
8+
use std::io::Read;
9+
use std::{fs::OpenOptions, os::fd::RawFd, path::PathBuf};
10+
11+
#[derive(Default)]
12+
pub struct LinuxContainerExecutor {
13+
stdin: Option<RawFd>,
14+
stdout: Option<RawFd>,
15+
stderr: Option<RawFd>,
16+
default_executor: DefaultExecutor,
17+
}
18+
19+
impl LinuxContainerExecutor {
20+
pub fn new(stdin: Option<RawFd>, stdout: Option<RawFd>, stderr: Option<RawFd>) -> Self {
21+
Self {
22+
stdin,
23+
stdout,
24+
stderr,
25+
..Default::default()
26+
}
27+
}
28+
}
29+
30+
impl Executor for LinuxContainerExecutor {
31+
fn exec(&self, spec: &Spec) -> Result<(), ExecutorError> {
32+
redirect_io(self.stdin, self.stdout, self.stderr).map_err(|err| {
33+
log::error!("failed to redirect io: {}", err);
34+
ExecutorError::Other(format!("failed to redirect io: {}", err))
35+
})?;
36+
self.default_executor.exec(spec)
37+
}
38+
39+
fn can_handle(&self, spec: &Spec) -> bool {
40+
let args = oci::get_args(spec);
41+
42+
if args.is_empty() {
43+
return false;
44+
}
45+
46+
let executable = args[0].as_str();
47+
48+
// mostly follows youki's verify_binary implementation
49+
// https://github.com/containers/youki/blob/2d6fd7650bb0f22a78fb5fa982b5628f61fe25af/crates/libcontainer/src/process/container_init_process.rs#L106
50+
let path = if executable.contains('/') {
51+
PathBuf::from(executable)
52+
} else {
53+
let path = std::env::var("PATH").unwrap_or_default();
54+
// check each path in $PATH
55+
let mut found = false;
56+
let mut found_path = PathBuf::default();
57+
for p in path.split(':') {
58+
let path = PathBuf::from(p).join(executable);
59+
if path.exists() {
60+
found = true;
61+
found_path = path;
62+
break;
63+
}
64+
}
65+
if !found {
66+
return false;
67+
}
68+
found_path
69+
};
70+
71+
// check execute permission
72+
use std::os::unix::fs::PermissionsExt;
73+
let metadata = path.metadata();
74+
if metadata.is_err() {
75+
log::info!("failed to get metadata of {:?}", path);
76+
return false;
77+
}
78+
let metadata = metadata.unwrap();
79+
let permissions = metadata.permissions();
80+
if !metadata.is_file() || permissions.mode() & 0o001 == 0 {
81+
log::info!("{} is not a file or has no execute permission", executable);
82+
return false;
83+
}
84+
85+
// check the shebang and ELF magic number
86+
// https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header
87+
let mut buffer = [0; 4];
88+
89+
let file = OpenOptions::new().read(true).open(path);
90+
if file.is_err() {
91+
log::info!("failed to open {}", executable);
92+
return false;
93+
}
94+
let mut file = file.unwrap();
95+
match file.read_exact(&mut buffer) {
96+
Ok(_) => {}
97+
Err(err) => {
98+
log::info!("failed to read shebang of {}: {}", executable, err);
99+
return false;
100+
}
101+
}
102+
match buffer {
103+
// ELF magic number
104+
[0x7f, 0x45, 0x4c, 0x46] => true,
105+
// shebang
106+
[0x23, 0x21, ..] => true,
107+
_ => {
108+
log::info!("{} is not a valid script or elf file", executable);
109+
false
110+
}
111+
}
112+
}
113+
114+
fn name(&self) -> &'static str {
115+
self.default_executor.name()
116+
}
117+
}
118+
119+
fn redirect_io(stdin: Option<i32>, stdout: Option<i32>, stderr: Option<i32>) -> anyhow::Result<()> {
120+
if let Some(stdin) = stdin {
121+
dup(STDIN_FILENO)?;
122+
dup2(stdin, STDIN_FILENO)?;
123+
}
124+
if let Some(stdout) = stdout {
125+
dup(STDOUT_FILENO)?;
126+
dup2(stdout, STDOUT_FILENO)?;
127+
}
128+
if let Some(stderr) = stderr {
129+
dup(STDERR_FILENO)?;
130+
dup2(stderr, STDERR_FILENO)?;
131+
}
132+
Ok(())
133+
}

crates/containerd-shim-wasm/src/sandbox/libcontainer_instance.rs renamed to crates/containerd-shim-wasm/src/libcontainer_instance/instance.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use crate::sandbox::{
2121
use crate::sandbox::InstanceConfig;
2222
use std::{path::PathBuf, thread};
2323

24-
use super::{error::Error, instance::Wait, Instance};
24+
use crate::sandbox::{error::Error, instance::Wait, Instance};
2525

2626
/// LibcontainerInstance is a trait that gets implemented by a WASI runtime that
2727
/// uses youki's libcontainer library as the container runtime.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pub mod container_executor;
2+
pub mod instance;
3+
4+
pub use container_executor::LinuxContainerExecutor;
5+
pub use instance::LibcontainerInstance;

crates/containerd-shim-wasm/src/sandbox/mod.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,11 @@
22
33
use crate::services::sandbox;
44

5-
// pub mod cgroups;
65
pub mod error;
7-
// pub mod exec;
86
pub mod instance;
97
pub mod instance_utils;
10-
#[cfg(feature = "libcontainer")]
11-
pub mod libcontainer_instance;
128
pub mod manager;
139
pub mod shim;
14-
#[cfg(feature = "libcontainer")]
15-
pub use libcontainer_instance::LibcontainerInstance;
1610

1711
pub use error::{Error, Result};
1812
pub use instance::{EngineGetter, Instance, InstanceConfig};

crates/containerd-shim-wasmedge/src/executor.rs

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use oci_spec::runtime::Spec;
66
use libc::{STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO};
77
use libcontainer::workload::{Executor, ExecutorError};
88
use log::debug;
9-
use std::os::unix::io::RawFd;
9+
use std::{os::unix::io::RawFd, path::PathBuf};
1010

1111
use wasmedge_sdk::{
1212
config::{CommonConfigOptions, ConfigBuilder, HostRegistrationConfigOptions},
@@ -16,9 +16,19 @@ use wasmedge_sdk::{
1616
const EXECUTOR_NAME: &str = "wasmedge";
1717

1818
pub struct WasmEdgeExecutor {
19-
pub stdin: Option<RawFd>,
20-
pub stdout: Option<RawFd>,
21-
pub stderr: Option<RawFd>,
19+
stdin: Option<RawFd>,
20+
stdout: Option<RawFd>,
21+
stderr: Option<RawFd>,
22+
}
23+
24+
impl WasmEdgeExecutor {
25+
pub fn new(stdin: Option<RawFd>, stdout: Option<RawFd>, stderr: Option<RawFd>) -> Self {
26+
Self {
27+
stdin,
28+
stdout,
29+
stderr,
30+
}
31+
}
2232
}
2333

2434
impl Executor for WasmEdgeExecutor {
@@ -43,8 +53,21 @@ impl Executor for WasmEdgeExecutor {
4353
};
4454
}
4555

46-
fn can_handle(&self, _spec: &Spec) -> bool {
47-
true
56+
fn can_handle(&self, spec: &Spec) -> bool {
57+
// check if the entrypoint of the spec is a wasm binary.
58+
let args = oci::get_args(spec);
59+
if args.is_empty() {
60+
return false;
61+
}
62+
63+
let start = args[0].clone();
64+
let mut iterator = start.split('#');
65+
let cmd = iterator.next().unwrap().to_string();
66+
let path = PathBuf::from(cmd);
67+
68+
path.extension()
69+
.map(|ext| ext.to_ascii_lowercase())
70+
.is_some_and(|ext| ext == "wasm" || ext == "wat")
4871
}
4972

5073
fn name(&self) -> &'static str {

crates/containerd-shim-wasmedge/src/instance.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ use std::sync::{Arc, Condvar, Mutex};
66

77
use anyhow::Context;
88
use anyhow::Result;
9+
use containerd_shim_wasm::libcontainer_instance::LibcontainerInstance;
10+
use containerd_shim_wasm::libcontainer_instance::LinuxContainerExecutor;
911
use containerd_shim_wasm::sandbox::error::Error;
1012
use containerd_shim_wasm::sandbox::instance::ExitCode;
1113
use containerd_shim_wasm::sandbox::instance_utils::maybe_open_stdio;
12-
use containerd_shim_wasm::sandbox::{EngineGetter, InstanceConfig, LibcontainerInstance};
14+
use containerd_shim_wasm::sandbox::{EngineGetter, InstanceConfig};
1315
use libc::{dup2, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO};
1416
use nix::unistd::close;
1517
use serde::{Deserialize, Serialize};
@@ -125,12 +127,11 @@ impl LibcontainerInstance for Wasi {
125127

126128
let syscall = create_syscall();
127129
let err_others = |err| Error::Others(format!("failed to create container: {}", err));
130+
let default_executor = Box::new(LinuxContainerExecutor::new(stdin, stdout, stderr));
131+
let wasmedge_executor = Box::new(WasmEdgeExecutor::new(stdin, stdout, stderr));
132+
128133
let container = ContainerBuilder::new(self.id.clone(), syscall.as_ref())
129-
.with_executor(vec![Box::new(WasmEdgeExecutor {
130-
stdin,
131-
stdout,
132-
stderr,
133-
})])
134+
.with_executor(vec![default_executor, wasmedge_executor])
134135
.map_err(err_others)?
135136
.with_root_path(self.rootdir.clone())
136137
.map_err(err_others)?

crates/containerd-shim-wasmtime/src/executor.rs

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use nix::unistd::{dup, dup2};
2-
use std::{fs::OpenOptions, os::fd::RawFd};
2+
use std::{fs::OpenOptions, os::fd::RawFd, path::PathBuf};
33

44
use anyhow::{anyhow, Result};
55
use containerd_shim_wasm::sandbox::oci;
@@ -15,10 +15,26 @@ use crate::oci_wasmtime::{self, wasi_dir};
1515
const EXECUTOR_NAME: &str = "wasmtime";
1616

1717
pub struct WasmtimeExecutor {
18-
pub stdin: Option<RawFd>,
19-
pub stdout: Option<RawFd>,
20-
pub stderr: Option<RawFd>,
21-
pub engine: Engine,
18+
stdin: Option<RawFd>,
19+
stdout: Option<RawFd>,
20+
stderr: Option<RawFd>,
21+
engine: Engine,
22+
}
23+
24+
impl WasmtimeExecutor {
25+
pub fn new(
26+
stdin: Option<RawFd>,
27+
stdout: Option<RawFd>,
28+
stderr: Option<RawFd>,
29+
engine: Engine,
30+
) -> Self {
31+
Self {
32+
stdin,
33+
stdout,
34+
stderr,
35+
engine,
36+
}
37+
}
2238
}
2339

2440
impl Executor for WasmtimeExecutor {
@@ -39,8 +55,27 @@ impl Executor for WasmtimeExecutor {
3955
};
4056
}
4157

42-
fn can_handle(&self, _spec: &Spec) -> bool {
43-
true
58+
fn can_handle(&self, spec: &Spec) -> bool {
59+
// check if the entrypoint of the spec is a wasm binary.
60+
let args = oci::get_args(spec);
61+
if args.is_empty() {
62+
return false;
63+
}
64+
65+
let start = args[0].clone();
66+
let mut iterator = start.split('#');
67+
let cmd = iterator.next().unwrap().to_string();
68+
let path = PathBuf::from(cmd);
69+
70+
// TODO: do we need to validate the wasm binary?
71+
// ```rust
72+
// let bytes = std::fs::read(path).unwrap();
73+
// wasmparser::validate(&bytes).is_ok()
74+
// ```
75+
76+
path.extension()
77+
.map(|ext| ext.to_ascii_lowercase())
78+
.is_some_and(|ext| ext == "wasm" || ext == "wat")
4479
}
4580

4681
fn name(&self) -> &'static str {

0 commit comments

Comments
 (0)