コンテナイメージ第2弾です。
前回はDockerイメージとコンテナランタイムを紹介しました。
今回は、下記の構成になります。
・DockerイメージをOCIイメージに変換
・OCIイメージをOCIランタイムバンドルに変換
・runcでOCIランタイムバンドルからコンテナを起動
参考
OCI image(image-spec)
skopeoというツールを使ってubuntuのDockerイメージをociイメージに変換します。
$skopeo copy docker://ubuntu:latest oci:ubuntu:latest $cd ubuntu $ tree . ├── blobs │ └── sha256 │ ├── 345e3491a907bb7c6f1bdddcf4a94284b8b6ddd77eb7d93f09432b17b20f2bbe │ ├── 57671312ef6fdbecf340e5fed0fb0863350cd806c92b1fdd7978adbd02afc5c3 │ ├── 5e9250ddb7d0fa6d13302c7c3e6a0aa40390e42424caed1e5289077ee4054709 │ ├── 687ea84638c380e1856de9f1d923412efd27fe95d781e3fff07258b7f75cd9b7 │ └── 7c6bc520685c7c84faf88a21861b77456900f20ef4e31d0ee387595951f990de ├── index.json └── oci-layout
OCIイメージはindex.json→image manifest→config,layersで構成されています。
index.jsonにマニフェストへの参照が書かれています。
$ jq . index.json { "schemaVersion": 2, "manifests": [ { "mediaType": "application/vnd.oci.image.manifest.v1+json", "digest": "sha256:687ea84638c380e1856de9f1d923412efd27fe95d781e3fff07258b7f75cd9b7", "size": 658, "annotations": { "org.opencontainers.image.ref.name": "latest" } } ] } $ jq . blobs/sha256/687ea84638c380e1856de9f1d923412efd27fe95d781e3fff07258b7f75cd9b7 { "schemaVersion": 2, "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "digest": "sha256:7c6bc520685c7c84faf88a21861b77456900f20ef4e31d0ee387595951f990de", "size": 2423 }, "layers": [ { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "digest": "sha256:345e3491a907bb7c6f1bdddcf4a94284b8b6ddd77eb7d93f09432b17b20f2bbe", "size": 28539626 }, { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "digest": "sha256:57671312ef6fdbecf340e5fed0fb0863350cd806c92b1fdd7978adbd02afc5c3", "size": 851 }, { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "digest": "sha256:5e9250ddb7d0fa6d13302c7c3e6a0aa40390e42424caed1e5289077ee4054709", "size": 187 } ] }
マニフェストにはblobに格納されたconfigとlayersの参照情報が格納されています。
Dockerイメージとほぼ一緒ですね。
$ jq . blobs/sha256/7c6bc520685c7c84faf88a21861b77456900f20ef4e31d0ee387595951f990de { "created": "2021-04-23T22:21:37.49442735Z", "architecture": "amd64", "os": "linux", "config": { "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ], "Cmd": [ "/bin/bash" ] }, "rootfs": { "type": "layers", "diff_ids": [ "sha256:ccdbb80308cc5ef43b605ac28fac29c6a597f89f5a169bbedbb8dec29c987439", "sha256:63c99163f47292f80f9d24c5b475751dbad6dc795596e935c5c7f1c73dc08107", "sha256:2f140462f3bcf8cf3752461e27dfd4b3531f266fa10cda716166bd3a78a19103" ] }, "history": [ { "created": "2021-04-23T22:21:34.1865992Z", "created_by": "/bin/sh -c #(nop) ADD file:5c44a80f547b7d68b550b0e64aef898b361666857abf9a5c8f3f8d0567b8e8e4 in / " }, { "created": "2021-04-23T22:21:35.354865637Z", "created_by": "/bin/sh -c set -xe \t\t&& echo '#!/bin/sh' > /usr/sbin/policy-rc.d \t&& echo 'exit 101' >> /usr/sbin/policy-rc.d \t&& chmod +x /usr/sbin/policy-rc.d \t\t&& dpkg-divert --local --rename --add /sbin/initctl \t&& cp -a /usr/sbin/policy-rc.d /sbin/initctl \t&& sed -i 's/^exit.*/exit 0/' /sbin/initctl \t\t&& echo 'force-unsafe-io' > /etc/dpkg/dpkg.cfg.d/docker-apt-speedup \t\t&& echo 'DPkg::Post-Invoke { \"rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true\"; };' > /etc/apt/apt.conf.d/docker-clean \t&& echo 'APT::Update::Post-Invoke { \"rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true\"; };' >> /etc/apt/apt.conf.d/docker-clean \t&& echo 'Dir::Cache::pkgcache \"\"; Dir::Cache::srcpkgcache \"\";' >> /etc/apt/apt.conf.d/docker-clean \t\t&& echo 'Acquire::Languages \"none\";' > /etc/apt/apt.conf.d/docker-no-languages \t\t&& echo 'Acquire::GzipIndexes \"true\"; Acquire::CompressionTypes::Order:: \"gz\";' > /etc/apt/apt.conf.d/docker-gzip-indexes \t\t&& echo 'Apt::AutoRemove::SuggestsImportant \"false\";' > /etc/apt/apt.conf.d/docker-autoremove-suggests" }, { "created": "2021-04-23T22:21:36.274883825Z", "created_by": "/bin/sh -c [ -z \"$(apt-get indextargets)\" ]", "empty_layer": true }, { "created": "2021-04-23T22:21:37.334286535Z", "created_by": "/bin/sh -c mkdir -p /run/systemd && echo 'docker' > /run/systemd/container" }, { "created": "2021-04-23T22:21:37.49442735Z", "created_by": "/bin/sh -c #(nop) CMD [\"/bin/bash\"]", "empty_layer": true } ] }
OCI runtime bundle(runtime-spec)
ここまでで OCI image(image-spec)の構成を確認しました。しかしながら、OCI imageのままではコンテナを生成できません。
低レベルコンテナランタイムがコンテナを生成するためには、OCI imageの形式からOCI runtime bundleという形式に変換する必要かあります。
今度はumociというツールを使ってubuntuのOCI imageをOCI runtime bundleに変換します。
$cd .. $umoci unpack --image ubuntu:latest ubuntu-bundle $ ls ubuntu-bundle/ config.json rootfs sha256_687ea84638c380e1856de9f1d923412efd27fe95d781e3fff07258b7f75cd9b7.mtree umoci.json
rootfsにubuntuのルートファイルシステムが含まれています。
$ ls ubuntu-bundle/rootfs bin boot dev etc home lib lib32 lib64 libx32 media mnt opt proc root run sbin srv sys tmp usr var
config.jsonには、コンテナ実行時の設定が書かれています。
namespacesやcgroupなどコンテナ関連の用語があちこちに見つかると思います。
ホストOSの侵害につながる/procや/sysを読取り専用にしたり隠蔽したりする設定も確認できますね。
runcはconfig.jsonにしたがってコンテナを生成して実行します。
$cat ubuntu-bundle/config.json { "ociVersion": "1.0.0", "process": { "terminal": true, "user": { "uid": 0, "gid": 0 }, "args": [ "/bin/bash" ], "env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "TERM=xterm", "HOME=/root" ], "cwd": "/", "capabilities": { "bounding": [ "CAP_AUDIT_WRITE", "CAP_KILL", "CAP_NET_BIND_SERVICE" ], "effective": [ "CAP_AUDIT_WRITE", "CAP_KILL", "CAP_NET_BIND_SERVICE" ], "inheritable": [ "CAP_AUDIT_WRITE", "CAP_KILL", "CAP_NET_BIND_SERVICE" ], "permitted": [ "CAP_AUDIT_WRITE", "CAP_KILL", "CAP_NET_BIND_SERVICE" ], "ambient": [ "CAP_AUDIT_WRITE", "CAP_KILL", "CAP_NET_BIND_SERVICE" ] }, "rlimits": [ { "type": "RLIMIT_NOFILE", "hard": 1024, "soft": 1024 } ], "noNewPrivileges": true }, "root": { "path": "rootfs" }, "hostname": "umoci-default", "mounts": [ { "destination": "/proc", "type": "proc", "source": "proc" }, { "destination": "/dev", "type": "tmpfs", "source": "tmpfs", "options": [ "nosuid", "strictatime", "mode=755", "size=65536k" ] }, { "destination": "/dev/pts", "type": "devpts", "source": "devpts", "options": [ "nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5" ] }, { "destination": "/dev/shm", "type": "tmpfs", "source": "shm", "options": [ "nosuid", "noexec", "nodev", "mode=1777", "size=65536k" ] }, { "destination": "/dev/mqueue", "type": "mqueue", "source": "mqueue", "options": [ "nosuid", "noexec", "nodev" ] }, { "destination": "/sys", "type": "sysfs", "source": "sysfs", "options": [ "nosuid", "noexec", "nodev", "ro" ] }, { "destination": "/sys/fs/cgroup", "type": "cgroup", "source": "cgroup", "options": [ "nosuid", "noexec", "nodev", "relatime", "ro" ] } ], "annotations": { "org.opencontainers.image.architecture": "amd64", "org.opencontainers.image.author": "", "org.opencontainers.image.created": "2021-04-23T22:21:37.49442735Z", "org.opencontainers.image.exposedPorts": "", "org.opencontainers.image.os": "linux", "org.opencontainers.image.stopSignal": "" }, "linux": { "resources": { "devices": [ { "allow": false, "access": "rwm" } ] }, "namespaces": [ { "type": "pid" }, { "type": "network" }, { "type": "ipc" }, { "type": "uts" }, { "type": "mount" } ], "maskedPaths": [ "/proc/kcore", "/proc/latency_stats", "/proc/timer_list", "/proc/timer_stats", "/proc/sched_debug", "/sys/firmware", "/proc/scsi" ], "readonlyPaths": [ "/proc/asound", "/proc/bus", "/proc/fs", "/proc/irq", "/proc/sys", "/proc/sysrq-trigger" ] } }
runcでコンテナを起動
最後に、作成したOCIランタイムバンドルをruncで実行してみましょう。
$ sudo runc -h NAME: runc - Open Container Initiative runtime runc is a command line client for running applications packaged according to the Open Container Initiative (OCI) format and is a compliant implementation of the Open Container Initiative specification. runc integrates well with existing process supervisors to provide a production container runtime environment for applications. It can be used with your existing process monitoring tools and the container will be spawned as a direct child of the process supervisor. Containers are configured using bundles. A bundle for a container is a directory that includes a specification file named "config.json" and a root filesystem. The root filesystem contains the contents of the container. To start a new instance of a container: # runc run [ -b bundle ]Where " " is your name for the instance of the container that you are starting. The name you provide for the container instance must be unique on your host. Providing the bundle directory using "-b" is optional. The default value for "bundle" is the current directory. USAGE: runc [global options] command [command options] [arguments...] VERSION: 1.0.0-rc93 commit: 12644e614e25b05da6fd08a38ffa0cfe1903fdec spec: 1.0.2-dev go: go1.13.15 libseccomp: 2.5.1 COMMANDS: checkpoint checkpoint a running container create create a container delete delete any resources held by the container often used with detached container events display container events such as OOM notifications, cpu, memory, and IO usage statistics exec execute new process inside the container init initialize the namespaces and launch the process (do not call it outside of runc) kill kill sends the specified signal (default: SIGTERM) to the container's init process list lists containers started by runc with the given root pause pause suspends all processes inside the container ps ps displays the processes running inside a container restore restore a container from a previous checkpoint resume resumes all processes that have been previously paused run create and run a container spec create a new specification file start executes the user defined process in a created container state output the state of a container update update container resource constraints help, h Shows a list of commands or help for one command GLOBAL OPTIONS: --debug enable debug output for logging --log value set the log file path where internal debug information is written --log-format value set the format used by logs ('text' (default), or 'json') (default: "text") --root value root directory for storage of container state (this should be located in tmpfs) (default: "/run/runc") --criu value path to the criu binary used for checkpoint and restore (default: "criu") --systemd-cgroup enable systemd cgroup support, expects cgroupsPath to be of form "slice:prefix:name" for e.g. "system.slice:runc:434234" --rootless value ignore cgroup permission errors ('true', 'false', or 'auto') (default: "auto") --help, -h show help --version, -v print the version
OCIランタイム仕様ではコンテナの作成と実行がそれぞれ独立した操作として定義されていて、runcにもそれに対応するcreate、startサブコマンドが実装されています。
今回はこのサブコマンドをまとめたrun サブコマンドを使います。
runサブコマンドに--bundleオプションを付与し、ランタイムバンドルを指定します。
下記のコマンドを実行すると、コンテナを起動してそのシェルを利用することができます。
$sudo runc run --bundle ubuntu-bundle ubuntu-container root@umoci-default:/# cat /etc/os-release //コンテナ内 NAME="Ubuntu" VERSION="20.04.2 LTS (Focal Fossa)" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 20.04.2 LTS" VERSION_ID="20.04"
runcでコンテナが起動できることを確認しました。
listサブコマンドでコンテナを一覧表示することもできます。
$ sudo runc list ID PID STATUS BUNDLE CREATED OWNER ubuntu-container 17925 running /home/user/ubuntu-bundle 2021-04-29T08:42:50.57736707Z root
実行したコンテナはkillサブコマンドで停止できます。
$ sudo runc kill ubuntu-container KILL $ sudo runc list ID PID STATUS BUNDLE CREATED OWNER
今回はOCIイメージの生成からruncによるコンテナ実行まで紹介しました。
OCI規格は、コンテナの標準仕様になるため、ここを抑えておけば他のコンテナランタイムへの足掛かりになるはずです。(たぶん)