This repository contains a golang library and CLI for VM images, manage disk images, kernel, initrd and other bootable artifacts in OCI registries. It can build, push and pull those images.
It is inspired directly by ORAS and leverages it, but is opinionated to the ECI use case. As such, it uses elements of OCI Artifacts.
It can store the images in multiple formats:
artifacts
(default): leverage full artifacts mime types, with each layer a different artifactlegacy
: standard mime-types and configs, with each layer optionally a different artifact; if the standard type is.tar
, then the single file is tarred; if the standard type istar+gzip
, then the single file is tarred and gzipped.
Note that the legacy
format actually looks identical to putting the artifacts in a filesystem in an OCI container
image. We simply leverage annotations to indicate where each artifact is. Ideally, each artifact - kernel,
initrd, root disk, other disks, etc. - are their own layers. However, this is not required.
Because the legacy
format replicates a standard OCI container image, you can create it using standard docker
tools as well. An example is shown below.
In all cases, annotations are used as well.
To push an ECI to a registry, you need the following items in a directory:
- a root disk image in any supported format: raw, vhd, vmdk, iso
- a Linux kernel (optional)
- a Linux initrd (optional)
- additional disks (optional)
- a config file, whose contents provide the desired OCI manifest config
Note: If you do not provide a config file, a default will be created, using the following
information. It can be overridden via options; run with --help
to see the options.
- OS: current platform OS
- Arch: current platform arch
- Author:
lfedge/edge-containers
You can push the image as follows:
eci push --root path/to/root.img:raw --kernel path/to/kernel --initrd path/to/initrd --disk path/to/disk1:iso --disk path/to/disk2:vmdk ... --config path/to/config lfedge/eci-nginx:ubuntu-1804-11715
The above uses the default artifacts
format, which assumes that the registry fully supports Artifacts
and will use specialized mime types.
If you wish to use one of the other formats, or do not have a choice as you are using a registry that does
not yet support artifacts, select an alternate format with the --format
flag.
For the legacy
format:
eci push --format legacy --root path/to/root.img:raw --kernel path/to/kernel --initrd path/to/initrd --disk path/to/disk1:iso --disk path/to/disk2:vmdk ... --config path/to/config lfedge/eci-nginx:ubuntu-1804-11715
The eci
command will take care of setting the correct mime types and annotations on all of the objects.
Note that disks, both root and additional, must have the file name, following by a :
and the disk type,
so that consumers know how to interpret them, e.g. to send a disk file whose name is mydisk
and
is of type qcow2:
--disk mydisk:qcow2
Standard docker tools do not support the artifacts
format. However, you can build and push
using the legacy
format with standard docker tools. However, docker does not support
adding annotations to the manifest, except using experimental tools.
To support standard docker tools, we support reading the annotations from the image labels,
which are stored in the config that is generated by docker build
.
To use standard docker tools, you need to do two things:
- Place the artifacts, such as disks or kernel, in your container image filesystem
- Add appropriate labels to the container image
The following Dockerfile is equivalent to the above:
FROM scratch
COPY path/to/root.img /root.img
COPY path/to/kernel /kernel
COPY path/to/initrd /initrd
COPY path/to/disk1 /disk1.iso
COPY path/to/disk2 /disk2.vmdk
The above Dockerfile
places each artifact in its own layer. You can place them all in a single layer as well:
FROM scratch
COPY path/to/ /
In order to inform any consumer where the artifacts are in the filesystem, aply the appropriate labels when building the image. Run:
docker build -t lfedge/eci-nginx:ubuntu-1804-11715 --label "org.lfedge.eci.artifact.root"="/root.img" --label "org.lfedge.eci.artifact.kernel"="/kernel" --label "org.lfedge.eci.artifact.initrd"="/initrd" --label "org.lfedge.eci.artifact.disk-1"="/disk1.iso" --label "org.lfedge.eci.artifact.disk-2"="/disk2.vmdk" .
The above line is somewhat messy, so you can include the labels in the Dockerfile, which makes sense, by adding LABEL
commands:
FROM scratch
COPY path/to/root.img /root.img
COPY path/to/kernel /kernel
COPY path/to/initrd /initrd
COPY path/to/disk1 /disk1.iso
COPY path/to/disk2 /disk2.vmdk
LABEL "org.lfedge.eci.artifact.root"="/root.img"
LABEL "org.lfedge.eci.artifact.kernel"="/kernel"
LABEL "org.lfedge.eci.artifact.initrd"="/initrd"
LABEL "org.lfedge.eci.artifact.disk-1"="/disk1.iso"
LABEL "org.lfedge.eci.artifact.disk-2"="/disk2.vmdk"
And then run:
docker build -t lfedge/eci-nginx:ubuntu-1804-11715 .
Note: if you use the legacy
format, your config file needs to be in a specific format
for docker to recognize it. This utility builds it for you, and it is recommended you accept
the default. However, if you provide --config
, you can override it. Use at your own risk.
To pull an ECI, you simply need a registry where the components will be downloaded:
eci pull lf-edge/eci-nginx:ubuntu-1804-11715
The above will default to placing artifacts in the current directory. To place them in a different directory:
eci pull --dir foo/bar/ lf-edge/eci-nginx:ubuntu-1804-11715
The eci
command knows how to read the manifest and annotations and determine how to extract the data.
Note that whatever format it is in, it can be pulled "as is" by docker, containerd, go-containerregistry, img or any other tool that knows how to pull OCI images.
The specific standard media types are at docs/mediatypes.md.
In addition to the types, eci
always will add annotations to the layer and config in the manifest describing its purpose.
The specific standard annotations are at docs/annotations.md.
ECI is highly opinionated about the file names. No matter what names you pass to it, it will give the files particular names. These are listed in docs/filenames.md.
A sample manifest for an actual pushed image is below. This is a manifest on docker hub, so the media types are the legacy types, while the annotations provide the purpose.
{
"schemaVersion": 2,
"config": {
"mediaType": "application/vnd.oci.image.config.v1+json",
"digest": "sha256:ffb3941df4fe37f22165b124d66e966d93b3dbf2765b736818b57a4516aed94e",
"size": 14,
"annotations": {
"org.lfedge.eci.mediaType": "application/vnd.lfedge.eci.config.v1+json",
"org.opencontainers.image.title": "config.json"
}
},
"layers": [
{
"mediaType": "application/vnd.oci.image.layer.v1.tar",
"digest": "sha256:edeaaff3f1774ad2888673770c6d64097e391bc362d7d6fb34982ddf0efd18cb",
"size": 4,
"annotations": {
"org.lfedge.eci.mediaType": "application/vnd.lfedge.eci.kernel.layer.v1+kernel",
"org.lfedge.eci.role": "kernel",
"org.opencontainers.image.title": "kernel"
}
},
{
"mediaType": "application/vnd.oci.image.layer.v1.tar",
"digest": "sha256:da1464fd7ceaf38ff56043bc1774af4fb5cb83ef5358981d78de0b8be5a6fbcb",
"size": 4,
"annotations": {
"org.lfedge.eci.mediaType": "application/vnd.lfedge.eci.initrd.layer.v1+cpio",
"org.lfedge.eci.role": "initrd",
"org.opencontainers.image.title": "initrd"
}
},
{
"mediaType": "application/vnd.oci.image.layer.v1.tar",
"digest": "sha256:deb055d836e44a1dcf0317b0cacac2dbdd36301f82abf787f7849d3f5b916750",
"size": 5,
"annotations": {
"org.lfedge.eci.mediaType": "application/vnd.lfedge.disk.layer.v1+raw",
"org.lfedge.eci.role": "disk-root",
"org.opencontainers.image.title": "disk-root-root.raw"
}
}
]
}
How does the pull client - and anything else that might want to pull the artifacts - know what it has in hand? It could be one of:
- an artifacts format image from this library/utility
- a legacy format image from this library/utility, with each artifact as a layer but in tar+gzip format
- a normal OCI container image, with VM artifacts inside at arbitrary paths
The parsing process is as follows:
- Retrieve the manifest.
- If the layers have the special
mediaType
, it is an ECI in artifacts format; use them as is. - If the layers have the special annotations, it is an ECI in legacy format, one artifact per layer; extract using
tar
/gzip
- If the config has the special annotations, it is an ECI in legacy format, with files at arbitrary locations; extract using
tar
/gzip
- It is a regular OCI image.
The go library is github.com/lf-edge/edge-containers/pkg/registry
. Docs are available at godoc.org/github.com/lf-edge/edge-containers/pkg/registry.
The eci
tool can be built via make build
, which will deposit the build artifact in dist/bin/eci-<os>-<arch>
, e.g. dist/bin/eci-darwin-amd64
or dist/bin/eci-linux-arm64
. To build it for alternate OSes or architectures, run:
make build OS=<target> ARCH=<target>
e.g.
make build OS=linux ARCH=amd64
make build OS=linux ARCH=amd64