refactor(driver-vm): replace OCI registry client with containerd shim#2313
refactor(driver-vm): replace OCI registry client with containerd shim#2313ericcurtin wants to merge 1 commit into
Conversation
|
There are only two implementations of the OCI protocol that have been widely battle-tested across the bazillion OCI registries in the world: https://github.com/containerd/containerd and everything that wraps these like docker, podman, skopeo, k8s, etc. The protocol is highly ossified, hard to test many of these incantations as they are in private data-centre's we cannot access. Using oci-client crate is a mistake. I propose a little bit of golang just for this transport component. containerd as a library gets the nod in this PR because it's a CNCF graduated project, it's the default runtime under most Kubernetes distributions (GKE, EKS, AKS), it sits inside Docker Engine, and its image-handling code paths (the remotes resolver and the newer transfer service) are exercised billions of times a day. containerd also supports Windows, Linux, macOS. Last I tried "containers/image" it isn't buildable or functioning on Windows. |
|
Thanks for expanding on the motivation here. I agree that registry compatibility At the same time, I do not see a concrete registry failure or required use case Before replacing the current implementation, however, I would prefer that we The current code combines several distinct responsibilities inside the VM
PR #2313 changes several of these responsibilities at once while they remain Conceptually, I would like the VM rootfs pipeline to look more like: The exact interface names are less important than keeping image acquisition and The existing We should also evaluate whether more of the acquisition and unpacking path can If we confirm compatibility gaps in the current Rust implementation and decide This separation is also relevant to PR #2312. I therefore suggest staging this work as follows:
My request is not to retain the existing OCI implementation indefinitely. It is |
b05d386 to
4025144
Compare
|
Agreed the boundary should exist eventually. I'll open a follow-up issue to extract ImageSource/ImageUnpacker once #2312 lands, so the interface is designed against two real implementations rather than speculatively. In the meantime, I've documented why umoci and the containerd archive unpacker coexist (host vs. guest trust boundary) so that's no longer an open question in this PR. |
The VM driver's registry pull and layer-unpack logic was a hand-rolled OCI client (auth, manifest parsing, per-layer download, digest verification) plus a from-scratch tar-layer merge that only understood plain files, dirs, symlinks, and OCI whiteouts. Replace it with containerd's own Go client libraries (core/remotes/docker, core/content/local, pkg/archive) for OCI-correct registry resolve/fetch/auth and layer application, including opaque dirs, xattrs, and device/fifo entries. No containerd daemon is involved: the new goshim/ Go module links those packages directly and builds into a small cgo shared library (libopenshell_containerd_shim), loaded at runtime via libloading the same way this crate already loads libkrun. - goshim/: exports ContainerdResolveDigest, ContainerdPullImage, and ContainerdUnpackLayout. - src/containerd_shim.rs: dynamic loader and safe Rust wrappers, mirroring ffi.rs's LibKrun pattern. - src/driver.rs: removed the manual registry client, layer-merge, and OCI layout writer (~1000 net lines); both pull paths now call the shim. - build.rs / embedded_runtime.rs: embed the shim like libkrun/libkrunfw/gvproxy. - tasks/scripts/vm/build-containerd-shim.sh: builds the shim natively or cross-compiled, wired into vm:setup and CI. - Dropped oci-client, flate2, sha2; added go to mise.toml/mise.lock. Registry auth env vars are unchanged. Per-layer pull progress is now a single event instead of one per layer, since the whole pull happens in one blocking call into the shim. Verified against a live registry (docker.io/library/busybox) through the built shared library via libloading, and cross-built for macOS with docker buildx/osxcross. Signed-off-by: Eric Curtin <eric.curtin@docker.com>
4025144 to
f80f1a0
Compare
|
I think identifying these seams should be done before we land #2312. Working against an abstraction will allow us to review the compute contributions of #2312 more cleanly -- even if they are currently using containerd (or some other mechanism) for realising the rootfs. (I did create #2388 as a POC / demonstrator to start discussion. It's still a draft, but we can dig into it further if we think it's valuable). With regards to this PR. Although the approach is interesting, I have concerns with introducing a GoLang-based library into our build and release process at this stage. Although this misgivings ot the |
FWIW, I ❤️ Rust. If the whole OCI ecosystem was being written in 2026, it probably would probably be written in Rust. But OCI and golang are heavily intertwined, it's a multi-year effort to reimplement all of it in Rust. We can take subsets and re-implement those in Rust bit by bit like in the case of #2312 especially in the performance critical cases, it's just something to be mindful of. I would say pushing/pulling images isn't performance critical because it's network bound, but at the same time happy to drop this if needs be. |
Summary
The VM driver's registry pull and layer-unpack logic was a hand-rolled OCI
client (auth, manifest parsing, per-layer download, digest verification)
plus a from-scratch tar-layer merge that only understood plain files,
dirs, symlinks, and OCI whiteouts.
Replace it with containerd's own Go client libraries (
core/remotes/docker,core/content/local,pkg/archive) for OCI-correct registry resolve/fetch/authand layer application, including opaque dirs, xattrs, and device/fifo entries.
No containerd daemon is involved: the new
goshim/Go module links thosepackages directly and builds into a small cgo shared library
(
libopenshell_containerd_shim), loaded at runtime vialibloadingthe sameway this crate already loads
libkrun.Changes
goshim/: exportsContainerdResolveDigest,ContainerdPullImage, andContainerdUnpackLayout.src/containerd_shim.rs: dynamic loader and safe Rust wrappers, mirroringffi.rs'sLibKrunpattern.src/driver.rs: removed the manual registry client, layer-merge, and OCIlayout writer (~1000 net lines); both pull paths now call the shim.
build.rs/embedded_runtime.rs: embed the shim likelibkrun/libkrunfw/gvproxy.tasks/scripts/vm/build-containerd-shim.sh: builds the shim natively orcross-compiled, wired into
vm:setupand CI.oci-client,flate2,sha2; addedgotomise.toml/mise.lock.Registry auth env vars are unchanged. Per-layer pull progress is now a single
event instead of one per layer, since the whole pull happens in one blocking
call into the shim.
Testing
cargo fmt --all -- --check/cargo clippy --workspace --all-targets -- -D warningspasscargo test -p openshell-driver-vmpassesopenshell-driver-vmwith real runtime artifacts embeddedmarkdownlint-cli2passes on the changed READMEdocker.io/library/busybox) through the builtshared library via
libloading: resolve digest, pull into an OCI layout, unpacka layer, and error propagation for a nonexistent image. This caught and fixed a
real bug:
platforms.Onlydoesn't fail closed for cross-arch requests the wayplatforms.OnlyStrictdoes.docker buildx/osxcross, producing areal Mach-O arm64 binary with the shim cross-compiled and embedded
driver's own image-pull path is exercised by the verification above instead
Design notes (from review)
rootfs boots the guest, so no VM exists yet to unpack it inside of. Every other
(less trusted, user-requested) image still goes through the existing guest-side
umoci raw unpackpath, so archive extraction of untrusted registry content neverruns against host-owned storage. Documented in the README and in
pull_and_unpack_registry_image's doc comment.ImageSource/ImageUnpacker/VmRootfsMaterializerinterfaces before landing this, and to stage this behind docs(vm): clarify MicroVM driver use cases and limitations #2332, is a real
architectural question I want to answer in review rather than pre-empt here.
Known follow-ups
cargo-about/syftyet; this isthe first Go code in the repo.
from the shim if finer-grained CLI watch detail is needed later.
Checklist