はじめに
OCI Runtime Specificationを読んだので概要を書きました。読んだのは仕様を理解して、簡単なところだけ実装しようと思ったからです。
Dockerとの関連を簡単に説明します*1。Docker Clientはdocker
コマンドを提供するCLIです。Docker ClientはDocker Engineに要求を出してDocker Engineがcontainedを操作し、containedはruncを操作します。runcというのはLinuxの機能を利用してコンテナを実際に作ったり操作するプログラムです。今回はコンテナを実際に作るruncにあたるレイヤーの仕様の話です。コンテナを作るレイヤーの仕様なので、コンテナとは?のような話が含まれます。
OCI (Open Container Initiative)
OCIとはOpen Container InitiativeのことでLinux Foundationの傘下の組織です。この組織はコンテナのイメージ形式やランタイムなどについて業界標準を作る管理機構です。公式サイトはこちらです。
現在以下の仕様が決められています。
Runtime Specificationはコンテナのランタイムについて、Image Specificationはコンテナのイメージ形式、Distribution Specificationはコンテナのレジストリの仕様です。それぞれの関係は次のような感じです。
- OCI RegistryからOCI Imageをダウンロードします
- 次にOCI Imageをディスク上に展開します。
- 展開されたディレクトリにはコンテナ実行に必要なファイル群を含みます。これをOCI Runtime Filesystem Bundleと呼びます。
- 最後にOCI RuntimeでOCI Runtime Filesystem Bundleを実行するとコンテナが動きます。
The 5 principles of Standard Containers
Runtime specではStandard Containersという概念を定義しています。これはコンテナを実行するインフラやコンテナの中身に関係なくコンテナを実行するための定義です。
Standard Containersの5つの原則は以下のとおりです。
- Standard operations - 基本的な操作ができること。
- Content-agnostic - コンテナの中身に関わらず同じ操作は同じ影響を与える。
- Infrastructure-agnostic - インフラに関わらず同じように操作を行える。
- Designed for automation - 自動化のためにデザインされている。コンテナの中身やインフラに関わらず同じ操作が行えるというのは自動化に向いている。
- Industrial-grade delivery - 企業で実運用ができるレベルのソフトウェアデリバリができる。
Standard Containersのための仕様というのは以下を定義します。
- Runtime Specification - 3つの仕様
- 設定ファイルのフォーマット
- 基本的な操作
- 実行環境
Standard Containersのための仕様というのはOCI Runtime Specification自体のことです。つまり、OCI Runtime Specificationで定義されるコンテナというのがStandard Containersということでしょう。
OCI Runtime Filesystem Bundle
コンテナのイメージをレジストリからダウンロードして、ディスクに展開したとき、展開されたディレクトリにはコンテナを実行するのに必要なデータが含まれます。このディレクトリをOCI Runtime Filesystem Bundleと呼びます。コンテナはプロセスとして動いているものですが、OCI Runtime Filesystem Bundleというのはコンテナのファイルシステム上における形式です。以降ではOCI bundleもしくはバンドルと呼びます。バンドルには次のものを含みます。
- config.json
- 設定ファイルです。このファイルはバンドルのディレクトリのルートに配置する必要があります。ファイル名は
config.json
です。
- 設定ファイルです。このファイルはバンドルのディレクトリのルートに配置する必要があります。ファイル名は
- container's root filesystem
- コンテナのルートファイルシステムで、設定ファイルの
root.path
プロパティで指定されます。
- コンテナのルートファイルシステムで、設定ファイルの
では実際にOCI Bundleを作りましょう。OCI Runtime Specificationの実装であるruncのREADMEを参考にします。runcのインストールはREADMEを参考にしてください。
以下rootユーザで実行しています。プロンプトを>
で表します。
# create the top most bundle directory > mkdir /mycontainer > cd /mycontainer # create the rootfs directory > mkdir rootfs # export busybox via Docker into the rootfs directory > docker export $(docker create busybox) | tar -C rootfs -xvf -
以上のコマンドで、コンテナ(busybox)のルートファイルシステムが/mycontainer/rootfs
に展開されました。ここではmycontainer
がバンドルということになります。config.json
を配置します。
> runc spec > ls config.json rootfs
自分で作成してもいいですがrunc spec
でconfig.jsonが作成されます。デフォルトでroot.path
プロパティはrootfs
という名前のディレクトリが指定されています。
> cat config.json | jq .root { "path": "rootfs", "readonly": true }
これでOCI bundleの作成が完了です。
OCI bundleの実行
ではこのバンドルを実行しましょう*2。
> runc run mycontainerid / # ls bin dev etc home proc root sys tmp usr var
これでバンドルが実行されました。コンテナ*3の中に入ってプロンプト#
が表示されます。
Operations
これはRuntime Specification - 3つの仕様
の2. 基本的な操作
のことです。Runtimeがサポートすべき操作は以下の通りです。
- Query State -
state <container-id>
- Create -
create <container-id> <path-to-bundle>
- Start -
start <container-id>
- Kill -
kill <container-id> <signal>
- Delete -
delete <container-id>
runc run mycontainerid
で使ったrun
がないので、run
はcreate
とstart
の組み合わせということでしょう。以下にrunc --help
の一部抜粋を載せます。定義されたものは全部ありますね。
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
Hooks
基本的な操作において、操作前と後に何かしらのアクションを実行できます。この仕組みをHooksと呼びます。
Lifecycle
コンテナの生成から停止、削除までのライフサイクルの定義です。ドキュメントに詳細に書いてあります。
Errors・Warnings
State
コンテナは以下のプロパティを持ちます
- ociVersion (REQUIRED)
- id (REQUIRED)
- status (REQUIRED)
- pid (REQUIRED)
- bundle (REQUIRED)
- annotations (OPTIONAL)
annotationsだけはなくても良いようです。runc run mycontainerid
でコンテナを実行して別のターミナルでStateを確認してみます。
> runc state mycontainerid { "ociVersion": "1.0.0", "id": "mycontainerid", "pid": 2231, "status": "running", "bundle": "/mycontainer", "rootfs": "/mycontainer/rootfs", "created": "2020-01-19T16:30:29.441186722Z", "owner": "" }
REQUIREDであるociVersion,id,status,pid,bundleは全部揃っています。
ここまでのOCI bundle、Hooks、Lifecycle、Errors・Warnings、StateがRuntime Specification - 3つの仕様
の3. 実行環境
にあたるところでしょうか。
Configuration
Runtime Specification - 3つの仕様
の1. 設定ファイルのフォーマット
にあたる部分です。設定ファイルであるconfig.jsonについてです。詳細はドキュメントを参照ください。見るとわかると思いますが、共通の設定と、プラットフォーム依存の設定があります。恐らくStandard Containersの理想的には共通の設定だけですましたいところですが、現実的には各プラットフォーム依存の設定が必要になってきてしまうようです。
まとめ
仕様だけを読むとイメージがつきにくいのですが、runcを動かしながらだと「あぁ、なるほど。」と理解できました。runcはOCIが開発元なので実装を見て仕様の解釈がわかるのは良いですね。しばらくランタイムを触って、次にImage Specificationを見ていこうと思います。