diff --git a/Makefile b/Makefile index 10899c4c4..90bf9b5e4 100644 --- a/Makefile +++ b/Makefile @@ -188,8 +188,11 @@ dist/img-oci.tar: target/wasm32-wasi/$(OPT_PROFILE)/img-oci.tar load: dist/img.tar sudo ctr -n $(CONTAINERD_NAMESPACE) image import --all-platforms $< +CTR_VERSION := $(shell sudo ctr version | sed -n -e '/Version/ {s/.*: *//p;q;}') load/oci: dist/img-oci.tar - sudo ../containerd/bin/ctr -n $(CONTAINERD_NAMESPACE) image import --all-platforms $< + @echo $(CTR_VERSION)\\nv1.7.7 | sort -crV || (echo "containerd version must be 1.7.7+ was $(CTR_VERSION)" && exit 1) + @echo using containerd $(CTR_VERSION) + sudo ctr -n $(CONTAINERD_NAMESPACE) image import --all-platforms $< .PHONY: target/wasm32-wasi/$(OPT_PROFILE)/img-oci.tar: target/wasm32-wasi/$(OPT_PROFILE)/wasi-demo-app.wasm diff --git a/crates/containerd-shim-wasm/src/container/context.rs b/crates/containerd-shim-wasm/src/container/context.rs index 5138c8fd1..97fea259a 100644 --- a/crates/containerd-shim-wasm/src/container/context.rs +++ b/crates/containerd-shim-wasm/src/container/context.rs @@ -1,5 +1,6 @@ use std::path::{Path, PathBuf}; +use oci_spec::image::Platform; use oci_spec::runtime::Spec; use crate::sandbox::oci::WasmLayer; @@ -24,6 +25,8 @@ pub trait RuntimeContext { fn wasi_entrypoint(&self) -> WasiEntrypoint; fn wasm_layers(&self) -> &[WasmLayer]; + + fn platform(&self) -> &Platform; } pub struct WasiEntrypoint { @@ -34,6 +37,7 @@ pub struct WasiEntrypoint { pub(crate) struct WasiContext<'a> { pub spec: &'a Spec, pub wasm_layers: &'a [WasmLayer], + pub platform: &'a Platform, } impl RuntimeContext for WasiContext<'_> { @@ -62,6 +66,10 @@ impl RuntimeContext for WasiContext<'_> { fn wasm_layers(&self) -> &[WasmLayer] { self.wasm_layers } + + fn platform(&self) -> &Platform { + self.platform + } } #[cfg(test)] @@ -86,6 +94,7 @@ mod tests { let ctx = WasiContext { spec: &spec, wasm_layers: &[], + platform: &Platform::default(), }; let args = ctx.args(); @@ -105,6 +114,7 @@ mod tests { let ctx = WasiContext { spec: &spec, wasm_layers: &[], + platform: &Platform::default(), }; let args = ctx.args(); @@ -132,6 +142,7 @@ mod tests { let ctx = WasiContext { spec: &spec, wasm_layers: &[], + platform: &Platform::default(), }; let args = ctx.args(); @@ -153,6 +164,7 @@ mod tests { let ctx = WasiContext { spec: &spec, wasm_layers: &[], + platform: &Platform::default(), }; let path = ctx.wasi_entrypoint().path; @@ -180,6 +192,7 @@ mod tests { let ctx = WasiContext { spec: &spec, wasm_layers: &[], + platform: &Platform::default(), }; let WasiEntrypoint { path, func } = ctx.wasi_entrypoint(); @@ -208,6 +221,7 @@ mod tests { let ctx = WasiContext { spec: &spec, wasm_layers: &[], + platform: &Platform::default(), }; let WasiEntrypoint { path, func } = ctx.wasi_entrypoint(); diff --git a/crates/containerd-shim-wasm/src/sandbox/containerd.rs b/crates/containerd-shim-wasm/src/sandbox/containerd.rs index 897360d7b..3168c1dd7 100644 --- a/crates/containerd-shim-wasm/src/sandbox/containerd.rs +++ b/crates/containerd-shim-wasm/src/sandbox/containerd.rs @@ -10,7 +10,7 @@ use containerd_client::services::v1::{GetContainerRequest, GetImageRequest, Read use containerd_client::tonic::transport::Channel; use containerd_client::{tonic, with_namespace}; use futures::TryStreamExt; -use oci_spec::image::{Arch, ImageManifest}; +use oci_spec::image::{Arch, ImageManifest, MediaType, Platform}; use tokio::runtime::Runtime; use tonic::Request; @@ -116,39 +116,57 @@ impl Client { // load module will query the containerd store to find an image that has an OS of type 'wasm' // If found it continues to parse the manifest and return the layers that contains the WASM modules // and possibly other configuration layers. - pub fn load_modules(&self, containerd_id: impl ToString) -> Result> { + pub fn load_modules( + &self, + containerd_id: impl ToString, + ) -> Result<(Vec, Platform)> { let image_name = self.get_image(containerd_id.to_string())?; let digest = self.get_image_content_sha(image_name)?; let manifest = self.read_content(digest)?; let manifest = manifest.as_slice(); let manifest = ImageManifest::from_reader(manifest)?; - let arch = manifest - .config() - .platform() - .as_ref() - .ok_or_else(|| ShimError::Containerd("failed to extract platform".to_string()))? - .architecture(); + let image_config_descriptor = manifest.config(); + let image_config = self.read_content(image_config_descriptor.digest())?; + let image_config = image_config.as_slice(); - match arch { - Arch::Wasm => { - log::info!("found manifest with WASM OCI image format."); - } - _ => { - log::info!("manifest is not in WASM OCI image format"); - return Ok([].to_vec()); - } - } + // the only part we care about here is the platform values + let platform: Platform = serde_json::from_slice(image_config)?; + let Arch::Wasm = platform.architecture() else { + log::info!("manifest is not in WASM OCI image format"); + return Ok((vec![], platform)); + }; + log::info!("found manifest with WASM OCI image format."); - manifest + let layers = manifest .layers() .iter() + .filter(|x| !is_image_layer_type(x.media_type())) .map(|config| { self.read_content(config.digest()).map(|module| WasmLayer { config: config.clone(), layer: module, }) }) - .collect::>>() + .collect::>>()?; + Ok((layers, platform)) + } +} + +fn is_image_layer_type(media_type: &MediaType) -> bool { + match media_type { + MediaType::ImageLayer + | MediaType::ImageLayerGzip + | MediaType::ImageLayerNonDistributable + | MediaType::ImageLayerNonDistributableGzip + | MediaType::ImageLayerNonDistributableZstd + | MediaType::ImageLayerZstd => true, + MediaType::Other(s) + if s.as_str() + .starts_with("application/vnd.docker.image.rootfs.") => + { + true + } + _ => false, } } diff --git a/crates/containerd-shim-wasm/src/sandbox/error.rs b/crates/containerd-shim-wasm/src/sandbox/error.rs index e111dcf9d..a9a87f0c0 100644 --- a/crates/containerd-shim-wasm/src/sandbox/error.rs +++ b/crates/containerd-shim-wasm/src/sandbox/error.rs @@ -47,7 +47,7 @@ pub enum Error { #[error("{0}")] Libcontainer(#[from] libcontainer::error::LibcontainerError), #[error("{0}")] - Containerd(String) + Containerd(String), } pub type Result = ::std::result::Result; diff --git a/crates/containerd-shim-wasm/src/sys/unix/container/executor.rs b/crates/containerd-shim-wasm/src/sys/unix/container/executor.rs index e6fe0b802..287f74d3c 100644 --- a/crates/containerd-shim-wasm/src/sys/unix/container/executor.rs +++ b/crates/containerd-shim-wasm/src/sys/unix/container/executor.rs @@ -10,6 +10,7 @@ use libcontainer::workload::{ Executor as LibcontainerExecutor, ExecutorError as LibcontainerExecutorError, ExecutorValidationError, }; +use oci_spec::image::Platform; use oci_spec::runtime::Spec; use crate::container::{Engine, PathResolve, RuntimeContext, Stdio, WasiContext}; @@ -28,6 +29,7 @@ pub(crate) struct Executor { stdio: Stdio, inner: OnceCell, wasm_layers: Vec, + platform: Platform, } impl LibcontainerExecutor for Executor { @@ -64,18 +66,24 @@ impl LibcontainerExecutor for Executor { } impl Executor { - pub fn new(engine: E, stdio: Stdio, wasm_layers: Vec) -> Self { + pub fn new(engine: E, stdio: Stdio, wasm_layers: Vec, platform: Platform) -> Self { Self { engine, stdio, inner: Default::default(), wasm_layers, + platform, } } fn ctx<'a>(&'a self, spec: &'a Spec) -> WasiContext<'a> { let wasm_layers = &self.wasm_layers; - WasiContext { spec, wasm_layers } + let platform = &self.platform; + WasiContext { + spec, + wasm_layers, + platform, + } } fn inner(&self, spec: &Spec) -> &InnerExecutor { diff --git a/crates/containerd-shim-wasm/src/sys/unix/container/instance.rs b/crates/containerd-shim-wasm/src/sys/unix/container/instance.rs index 4d07c52d4..247bb39b5 100644 --- a/crates/containerd-shim-wasm/src/sys/unix/container/instance.rs +++ b/crates/containerd-shim-wasm/src/sys/unix/container/instance.rs @@ -12,11 +12,14 @@ use libcontainer::syscall::syscall::SyscallType; use nix::errno::Errno; use nix::sys::wait::{waitid, Id as WaitID, WaitPidFlag, WaitStatus}; use nix::unistd::Pid; +use oci_spec::image::Platform; use crate::container::Engine; use crate::sandbox::instance_utils::{determine_rootdir, get_instance_root, instance_exists}; use crate::sandbox::sync::WaitableCell; -use crate::sandbox::{containerd, Error as SandboxError, Instance as SandboxInstance, InstanceConfig, Stdio}; +use crate::sandbox::{ + containerd, Error as SandboxError, Instance as SandboxInstance, InstanceConfig, Stdio, +}; use crate::sys::container::executor::Executor; static DEFAULT_CONTAINER_ROOT_DIR: &str = "/run/containerd"; @@ -41,15 +44,15 @@ impl SandboxInstance for Instance { let stdio = Stdio::init_from_cfg(cfg)?; // check if container is OCI image with wasm layers and attempt to read the module - let modules = containerd::Client::connect(cfg.get_containerd_address(), &namespace)? + let (modules, platform) = containerd::Client::connect(cfg.get_containerd_address(), &namespace)? .load_modules(&id) .unwrap_or_else(|e| { log::warn!("Error obtaining wasm layers for container {id}. Will attempt to use files inside container image. Error: {e}"); - vec![] + (vec![], Platform::default()) }); ContainerBuilder::new(id.clone(), SyscallType::Linux) - .with_executor(Executor::new(engine, stdio, modules)) + .with_executor(Executor::new(engine, stdio, modules, platform)) .with_root_path(rootdir.clone())? .as_init(&bundle) .with_systemd(false) diff --git a/test/k8s/Dockerfile.oci b/test/k8s/Dockerfile.oci index 4583215e5..2dcbccc9e 100644 --- a/test/k8s/Dockerfile.oci +++ b/test/k8s/Dockerfile.oci @@ -2,12 +2,12 @@ ARG KIND_NODE_VERSION=v1.27.3@sha256:3966ac761ae0136263ffdb6cfd4db23ef8a83cba8a463690e98317add2c9ba72 ARG RUNTIME=wasmtime -ARG GO_VERSION="1.21.1" +ARG GO_VERSION="1.21.3-bullseye" # modified from https://github.com/kubernetes-sigs/kind/blob/main/images/base/Dockerfile # stage for building containerd FROM golang:${GO_VERSION} as build-containerd -ARG CONTAINERD_VERSION="main" +ARG CONTAINERD_VERSION="v1.7.7" ARG CONTAINERD_CLONE_URL="https://github.com/containerd/containerd" # we don't build with optional snapshotters, we never select any of these # they're not ideal inside kind anyhow, and we save some disk space