鳩小屋

落書き帳

OCIイメージとOCIランタイムバンドル

コンテナイメージ第2弾です。
前回はDockerイメージとコンテナランタイムを紹介しました。

kurobato.hateblo.jp

今回は、下記の構成になります。
・DockerイメージをOCIイメージに変換
・OCIイメージをOCIランタイムバンドルに変換
・runcでOCIランタイムバンドルからコンテナを起動

f:id:FallenPigeon:20210429181504j:plain

参考

github.com

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規格は、コンテナの標準仕様になるため、ここを抑えておけば他のコンテナランタイムへの足掛かりになるはずです。(たぶん)