Skip to content

Commit 6360e13

Browse files
committed
Move away from oci artifact naming
Signed-off-by: James Sturtevant <[email protected]>
1 parent eff3e0a commit 6360e13

File tree

18 files changed

+158
-119
lines changed

18 files changed

+158
-119
lines changed

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,11 @@ dist/img-oci.tar: target/wasm32-wasi/$(OPT_PROFILE)/img-oci.tar
169169
load: dist/img.tar
170170
sudo ctr -n $(CONTAINERD_NAMESPACE) image import --all-platforms $<
171171

172+
CTR_VERSION := $(shell sudo ctr version | sed -n -e '/Version/ {s/.*: *//p;q;}')
172173
load/oci: dist/img-oci.tar
173-
sudo ../containerd/bin/ctr -n $(CONTAINERD_NAMESPACE) image import --all-platforms $<
174+
@echo $(CTR_VERSION)\\nv1.7.7 | sort -crV || (echo "containerd version must be 1.7.7+ was $(CTR_VERSION)" && exit 1)
175+
@echo using containerd $(CTR_VERSION)
176+
sudo ctr -n $(CONTAINERD_NAMESPACE) image import --all-platforms $<
174177

175178
.PHONY:
176179
target/wasm32-wasi/$(OPT_PROFILE)/img-oci.tar: target/wasm32-wasi/$(OPT_PROFILE)/wasi-demo-app.wasm

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -323,22 +323,22 @@ To kill the process from demo 2, you can run in other session: `sudo ctr task ki
323323

324324
The test binary supports commands for different type of functionality, check [crates/wasi-demo-app/src/main.rs](crates/wasi-demo-app/src/main.rs) to try it out.
325325

326-
## Demo 3 using WASM OCI artifacts
326+
## Demo 3 using OCI Images with custom WASM layers
327327

328-
The previous demos run with an OCI Container image containing the wasm module in the file system. Another option is to provide a cross-platform OCI artifact that that will not have the wasm module or components in the file system of the container that wraps the wasmtime/wasmedge process. This OCI artifact can be run across any platform and provides for de-duplication in the Containerd content store among other benefits.
328+
The previous demos run with an OCI Container image containing the wasm module in the file system. Another option is to provide a cross-platform OCI Image that that will not have the wasm module or components in the file system of the container that wraps the wasmtime/wasmedge process. This OCI Image with custom WASM layers can be run across any platform and provides for de-duplication in the Containerd content store among other benefits.
329329

330330
To learn more about this approach checkout the [design document](https://docs.google.com/document/d/11shgC3l6gplBjWF1VJCWvN_9do51otscAm0hBDGSSAc/edit).
331331

332-
> **Note**: This requires containerd components based on https://github.com/containerd/containerd/pull/8699. Both CTR and containerd need to be build with that patch. If you do not have this patch for both `containerd` and `ctr` you will end up with an error message such as `mismatched image rootfs and manifest layers` at the import and run steps
332+
> **Note**: This requires containerd 1.7.7+ and 1.6.25+ (not yet released). If you do not have these patches for both `containerd` and `ctr` you will end up with an error message such as `mismatched image rootfs and manifest layers` at the import and run steps.
333333
334-
Build and import the OCI artifact image:
334+
Build and import the OCI image with WASM layers image:
335335

336336
```
337337
make test-image/oci
338338
make load/oci
339339
```
340340

341-
Run the image with `sudo ctr run --rm --runtime=io.containerd.[ wasmedge | wasmtime | wasmer ].v1 --label application/vnd.bytecodealliance.wasm.module=oci.wasm ghcr.io/containerd/runwasi/wasi-demo-oci:latest testwasmoci`
341+
Run the image with `sudo ctr run --rm --runtime=io.containerd.[ wasmedge | wasmtime | wasmer ].v1 ghcr.io/containerd/runwasi/wasi-demo-oci:latest testwasmoci`
342342

343343
```
344344
sudo ctr run --rm --runtime=io.containerd.wasmtime.v1 ghcr.io/containerd/runwasi/wasi-demo-oci:latest testwasmoci wasi-demo-oci.wasm echo 'hello'

crates/containerd-shim-wasm/src/container/context.rs

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use std::path::{Path, PathBuf};
22

3+
use oci_spec::image::Platform;
34
use oci_spec::runtime::Spec;
45

5-
use crate::sandbox::oci::OciArtifact;
6+
use crate::sandbox::oci::WasmLayer;
67

78
pub trait RuntimeContext {
89
// ctx.args() returns arguments from the runtime spec process field, including the
@@ -23,7 +24,9 @@ pub trait RuntimeContext {
2324
// "#init" -> { path: "", func: "init" }
2425
fn wasi_entrypoint(&self) -> WasiEntrypoint;
2526

26-
fn oci_artifacts(&self) -> &[OciArtifact];
27+
fn wasm_layers(&self) -> &[WasmLayer];
28+
29+
fn platform(&self) -> &Platform;
2730
}
2831

2932
pub struct WasiEntrypoint {
@@ -33,7 +36,8 @@ pub struct WasiEntrypoint {
3336

3437
pub(crate) struct WasiContext<'a> {
3538
pub spec: &'a Spec,
36-
pub oci_artifacts: &'a [OciArtifact],
39+
pub wasm_layers: &'a [WasmLayer],
40+
pub platform: &'a Platform,
3741
}
3842

3943
impl RuntimeContext for WasiContext<'_> {
@@ -59,8 +63,12 @@ impl RuntimeContext for WasiContext<'_> {
5963
}
6064
}
6165

62-
fn oci_artifacts(&self) -> &[OciArtifact] {
63-
self.oci_artifacts
66+
fn wasm_layers(&self) -> &[WasmLayer] {
67+
self.wasm_layers
68+
}
69+
70+
fn platform(&self) -> &Platform {
71+
self.platform
6472
}
6573
}
6674

@@ -85,7 +93,8 @@ mod tests {
8593

8694
let ctx = WasiContext {
8795
spec: &spec,
88-
oci_artifacts: &[],
96+
wasm_layers: &[],
97+
platform: &Platform::default(),
8998
};
9099

91100
let args = ctx.args();
@@ -104,7 +113,8 @@ mod tests {
104113

105114
let ctx = WasiContext {
106115
spec: &spec,
107-
oci_artifacts: &[],
116+
wasm_layers: &[],
117+
platform: &Platform::default(),
108118
};
109119

110120
let args = ctx.args();
@@ -131,7 +141,8 @@ mod tests {
131141

132142
let ctx = WasiContext {
133143
spec: &spec,
134-
oci_artifacts: &[],
144+
wasm_layers: &[],
145+
platform: &Platform::default(),
135146
};
136147

137148
let args = ctx.args();
@@ -152,7 +163,8 @@ mod tests {
152163

153164
let ctx = WasiContext {
154165
spec: &spec,
155-
oci_artifacts: &[],
166+
wasm_layers: &[],
167+
platform: &Platform::default(),
156168
};
157169

158170
let path = ctx.wasi_entrypoint().path;
@@ -179,7 +191,8 @@ mod tests {
179191

180192
let ctx = WasiContext {
181193
spec: &spec,
182-
oci_artifacts: &[],
194+
wasm_layers: &[],
195+
platform: &Platform::default(),
183196
};
184197

185198
let WasiEntrypoint { path, func } = ctx.wasi_entrypoint();
@@ -207,7 +220,8 @@ mod tests {
207220

208221
let ctx = WasiContext {
209222
spec: &spec,
210-
oci_artifacts: &[],
223+
wasm_layers: &[],
224+
platform: &Platform::default(),
211225
};
212226

213227
let WasiEntrypoint { path, func } = ctx.wasi_entrypoint();

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

Lines changed: 70 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ use containerd_client::services::v1::{GetContainerRequest, GetImageRequest, Read
1010
use containerd_client::tonic::transport::Channel;
1111
use containerd_client::{tonic, with_namespace};
1212
use futures::TryStreamExt;
13-
use oci_spec::image::{ImageManifest, MediaType};
13+
use oci_spec::image::{Arch, ImageManifest, MediaType, Platform};
1414
use tokio::runtime::Runtime;
1515
use tonic::Request;
1616

1717
use crate::sandbox::error::{Error as ShimError, Result};
18-
use crate::sandbox::oci::{self, OciArtifact, COMPONENT_ARTIFACT_TYPE, MODULE_ARTIFACT_TYPE};
18+
use crate::sandbox::oci::{self, WasmLayer};
1919

20-
pub struct Client {
20+
pub(crate) struct Client {
2121
inner: Channel,
2222
rt: Runtime,
2323
namespace: String,
@@ -33,7 +33,7 @@ impl Client {
3333

3434
let inner = rt
3535
.block_on(containerd_client::connect(address))
36-
.map_err(|err| ShimError::Others(err.to_string()))?;
36+
.map_err(|err| ShimError::Containerd(err.to_string()))?;
3737

3838
Ok(Client {
3939
inner,
@@ -53,12 +53,12 @@ impl Client {
5353
ContentClient::new(self.inner.clone())
5454
.read(req)
5555
.await
56-
.map_err(|err| ShimError::Others(err.to_string()))?
56+
.map_err(|err| ShimError::Containerd(err.to_string()))?
5757
.into_inner()
5858
.map_ok(|msg| msg.data)
5959
.try_concat()
6060
.await
61-
.map_err(|err| ShimError::Others(err.to_string()))
61+
.map_err(|err| ShimError::Containerd(err.to_string()))
6262
})
6363
}
6464

@@ -70,10 +70,22 @@ impl Client {
7070
let digest = ImagesClient::new(self.inner.clone())
7171
.get(req)
7272
.await
73-
.map_err(|err| ShimError::Others(err.to_string()))?
73+
.map_err(|err| ShimError::Containerd(err.to_string()))?
7474
.into_inner()
75-
.image.ok_or(ShimError::Others(format!("failed to get image content sha for image {}", image_name.to_string())))?
76-
.target.ok_or(ShimError::Others(format!("failed to get image content sha for image {}", image_name.to_string())))?
75+
.image
76+
.ok_or_else(|| {
77+
ShimError::Containerd(format!(
78+
"failed to get image content sha for image {}",
79+
image_name.to_string()
80+
))
81+
})?
82+
.target
83+
.ok_or_else(|| {
84+
ShimError::Containerd(format!(
85+
"failed to get image content sha for image {}",
86+
image_name.to_string()
87+
))
88+
})?
7789
.digest;
7890
Ok(digest)
7991
})
@@ -87,50 +99,74 @@ impl Client {
8799
let image = ContainersClient::new(self.inner.clone())
88100
.get(req)
89101
.await
90-
.map_err(|err| ShimError::Others(err.to_string()))?
102+
.map_err(|err| ShimError::Containerd(err.to_string()))?
91103
.into_inner()
92104
.container
93-
.ok_or(ShimError::Others(format!("failed to get image for container {}", container_name.to_string())))?
105+
.ok_or_else(|| {
106+
ShimError::Containerd(format!(
107+
"failed to get image for container {}",
108+
container_name.to_string()
109+
))
110+
})?
94111
.image;
95112
Ok(image)
96113
})
97114
}
98115

99-
// load module will query the containerd store to find an image that has an ArtifactType of WASM OCI Artifact
116+
// load module will query the containerd store to find an image that has an OS of type 'wasm'
100117
// If found it continues to parse the manifest and return the layers that contains the WASM modules
101-
// and possibly other configuration artifacts
102-
pub fn load_modules(&self, containerd_id: impl ToString) -> Result<Vec<oci::OciArtifact>> {
118+
// and possibly other configuration layers.
119+
pub fn load_modules(
120+
&self,
121+
containerd_id: impl ToString,
122+
) -> Result<(Vec<oci::WasmLayer>, Platform)> {
103123
let image_name = self.get_image(containerd_id.to_string())?;
104124
let digest = self.get_image_content_sha(image_name)?;
105125
let manifest = self.read_content(digest)?;
106126
let manifest = manifest.as_slice();
107127
let manifest = ImageManifest::from_reader(manifest)?;
108128

109-
let artifact_type = manifest
110-
.artifact_type()
111-
.as_ref()
112-
.ok_or(ShimError::Others("manifest is not an OCI Artifact".to_string()))?;
129+
let image_config_descriptor = manifest.config();
130+
let image_config = self.read_content(image_config_descriptor.digest())?;
131+
let image_config = image_config.as_slice();
113132

114-
match artifact_type {
115-
MediaType::Other(s) if s == COMPONENT_ARTIFACT_TYPE || s == MODULE_ARTIFACT_TYPE => {
116-
log::info!("manifest with OCI Artifact of type {s}");
117-
}
118-
_ => {
119-
log::info!("manifest is not a known OCI Artifact: {artifact_type}");
120-
return Ok([].to_vec());
121-
}
122-
}
133+
// the only part we care about here is the platform values
134+
let platform: Platform = serde_json::from_slice(image_config)?;
135+
let Arch::Wasm = platform.architecture() else {
136+
log::info!("manifest is not in WASM OCI image format");
137+
return Ok((vec![], platform));
138+
};
139+
log::info!("found manifest with WASM OCI image format.");
123140

124-
Ok(manifest
141+
let layers = manifest
125142
.layers()
126143
.iter()
144+
.filter(|x| !is_image_layer_type(x.media_type()))
127145
.map(|config| {
128-
self.read_content(config.digest())
129-
.map(|module| OciArtifact {
130-
config: config.clone(),
131-
layer: module,
132-
})
146+
self.read_content(config.digest()).map(|module| WasmLayer {
147+
config: config.clone(),
148+
layer: module,
149+
})
133150
})
134-
.collect::<Result<Vec<_>>>()?)
151+
.collect::<Result<Vec<_>>>()?;
152+
Ok((layers, platform))
153+
}
154+
}
155+
156+
fn is_image_layer_type(media_type: &MediaType) -> bool {
157+
match media_type {
158+
MediaType::ImageLayer
159+
| MediaType::ImageLayerGzip
160+
| MediaType::ImageLayerNonDistributable
161+
| MediaType::ImageLayerNonDistributableGzip
162+
| MediaType::ImageLayerNonDistributableZstd
163+
| MediaType::ImageLayerZstd => true,
164+
MediaType::Other(s)
165+
if s.as_str()
166+
.starts_with("application/vnd.docker.image.rootfs.") =>
167+
{
168+
true
169+
}
170+
_ => false,
135171
}
136172
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ pub enum Error {
4646
#[cfg(unix)]
4747
#[error("{0}")]
4848
Libcontainer(#[from] libcontainer::error::LibcontainerError),
49+
#[error("{0}")]
50+
Containerd(String),
4951
}
5052

5153
pub type Result<T> = ::std::result::Result<T, Error>;

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,8 @@ pub use oci_spec::runtime::Spec;
1212

1313
use super::error::Result;
1414

15-
pub const COMPONENT_ARTIFACT_TYPE: &str = "application/vnd.bytecodealliance.component.v1+wasm";
16-
pub const MODULE_ARTIFACT_TYPE: &str = "application/vnd.bytecodealliance.module.v1+wasm";
17-
1815
#[derive(Clone)]
19-
pub struct OciArtifact {
16+
pub struct WasmLayer {
2017
pub config: Descriptor,
2118
pub layer: Vec<u8>,
2219
}

crates/containerd-shim-wasm/src/sys/unix/container/executor.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ use libcontainer::workload::{
1010
Executor as LibcontainerExecutor, ExecutorError as LibcontainerExecutorError,
1111
ExecutorValidationError,
1212
};
13+
use oci_spec::image::Platform;
1314
use oci_spec::runtime::Spec;
1415

1516
use crate::container::{Engine, PathResolve, RuntimeContext, Stdio, WasiContext};
16-
use crate::sandbox::oci::OciArtifact;
17+
use crate::sandbox::oci::WasmLayer;
1718

1819
#[derive(Clone)]
1920
enum InnerExecutor {
@@ -27,7 +28,8 @@ pub(crate) struct Executor<E: Engine> {
2728
engine: E,
2829
stdio: Stdio,
2930
inner: OnceCell<InnerExecutor>,
30-
oci_artifacts: Vec<OciArtifact>,
31+
wasm_layers: Vec<WasmLayer>,
32+
platform: Platform,
3133
}
3234

3335
impl<E: Engine> LibcontainerExecutor for Executor<E> {
@@ -64,27 +66,30 @@ impl<E: Engine> LibcontainerExecutor for Executor<E> {
6466
}
6567

6668
impl<E: Engine> Executor<E> {
67-
pub fn new(engine: E, stdio: Stdio, oci_artifacts: Vec<OciArtifact>) -> Self {
69+
pub fn new(engine: E, stdio: Stdio, wasm_layers: Vec<WasmLayer>, platform: Platform) -> Self {
6870
Self {
6971
engine,
7072
stdio,
7173
inner: Default::default(),
72-
oci_artifacts,
74+
wasm_layers,
75+
platform,
7376
}
7477
}
7578

7679
fn ctx<'a>(&'a self, spec: &'a Spec) -> WasiContext<'a> {
77-
let oci_artifacts = &self.oci_artifacts;
80+
let wasm_layers = &self.wasm_layers;
81+
let platform = &self.platform;
7882
WasiContext {
7983
spec,
80-
oci_artifacts,
84+
wasm_layers,
85+
platform,
8186
}
8287
}
8388

8489
fn inner(&self, spec: &Spec) -> &InnerExecutor {
8590
self.inner.get_or_init(|| {
8691
// if the spec has oci annotations we know it is wasm so short circuit checks
87-
if !self.oci_artifacts.is_empty() {
92+
if !self.wasm_layers.is_empty() {
8893
InnerExecutor::Wasm
8994
} else if is_linux_container(&self.ctx(spec)).is_ok() {
9095
InnerExecutor::Linux

0 commit comments

Comments
 (0)