コンテナランタイム事情を整理してみる

## この記事を書いたきっかけ

最近は商用環境でも利用の進んでいるContainerですが, 最近は「Kubernetesがv1.20でDockerを非推奨とした」という文言が一人歩きして(思わず)話題になりました.

(上記ニュースを知らない方は, 落ち着いて [この記事](https://jaco.udcp.info/entry/2020/12/03/172843) を読んでください. Dockerは非推奨ではありません.)

[公式ブログ](https://kubernetes.io/blog/2020/12/02/dont-panic-kubernetes-and-docker/)

上記ニュースはContainer Runtime周りについての知識の有無でかなり反応が分かれたように見受けられました.

そこでこの記事ではContainer Runtimeについてできるだけ広範に動向・事情をまとめてみます.

なおこの記事ではLinux上のContainerのみ取り扱います.

また, この記事内容は参考文献の内容を整理しただけのものになります. 実際に使用したりソースコードを読んで確認しているわけではありません.

## Container Runtimeに関する概念

1. Container Runtime

    広義には, Container image名やContainer実行に関する各種設定情報を受け取ってContainerを作成・実行・停止・削除するソフトウェア全体を指します.

    狭義には, 広義のContainer Runtimeのコンポーネントのことを指す場合もあります.

    この場合は, 後述する通り, コンテナ実行・停止を責務とするLow level Container RuntimeとContainer imageの管理を責務とするHigh level Container Runtimeに分ける場合もあります.

    また, 外部からコンテナ起動・終了をネットワークやソケット経由で受け付ける, 常駐サービス型と, 利用時に起動されるコマンド型があります.
    (「常駐サービス型」「コマンド型」は本記事での独自の呼び方です)

    ただしすべてのContainer Runtimeが上記のような分類に合致するとは限りません.

1. Low level Container Runtime

    Containerの実行を責務とするコンポーネントです. Containerの隔離に関するすべてに責任があります.
    コンテナ使用についてOCI Runtime SpecificationなどContainer実行に関する標準に従うものが多く, 今回取り上げるものはすべてコマンド型Runtimeになります.

    代表例がOCIの所有する [runc](#runc).

1. High level Container Runtime

    Container imageの管理とLow level Runtimeの呼び出しを責務とするコンポーネントです.

    Container imageの管理としてimageの作成や外部Registryへの保存も行えるものと, 外部のRegistryから取得するのみでそれ自体ではimageを作成できないものもあります.

    Container実行についてLow level Runtimeのインターフェースに依存しますが, 最近では標準仕様であるOCI Runtime Spec.に準拠するものが一般的です.

    High level Runtimeはコンテナ実行の管理自動化やオーケストレーションツールとの連携のために常駐サービス型の実装が多いです. 今回紹介する中で例外はPodmanとなります. High level Runtimeのインターフェースを標準化するKubernetesプロジェクトによるContainer Runtime Interface仕様も常駐サービス型であることを前提としています.

    代表例はCNCFが所有するcontainerd.

## Container Runtime

### Low level Runtime

#### runc

![OCI Logo](https://github.com/cncf/landscape/raw/c10495c5c03f1fac8ad03cb7c6e643b2781e26bf/hosted_logos/oci-runc.svg)

現在はOCIに寄贈され, 最も標準的なOCI準拠Low level Runtimeです.

元々はDockerの内部コンポーネントでしたがOCI設立とContainerフォーマットの標準化の中でDocker社から寄贈されました.

Container内のプロセスを直接ホストカーネル上で実行する設計となっており, Container起動が軽量だとされています. 一方で、セキュリティ目的のプロセス隔離の仕組みとしては隔離性が不十分という見方もあります.

- [GitHub](https://github.com/opencontainers/runc)

#### crun

C言語で書かれたOCI Runtime. Go言語とLow level Runtimeのようなソフトウェアは相性が良くないとして, C言語でより効率のよいRuntime実装を目指しています.

リポジトリの[README.md](https://github.com/containers/crun#performance)でも利用PID数やメモリ使用率でruncとの比較が掲載されています.

- [GitHub](https://github.com/containers/crun)
- [RedHatブログによる紹介](https://www.redhat.com/sysadmin/introduction-crun) (2020/8/3投稿)
- [Manページ](https://www.mankier.com/1/crun)(www.mankier.com)

#### runsc(gVisor)

![gVisor Logo](https://github.com/google/gvisor/raw/f18a8f958187aa52d028d1e3cc3dc0c03b742fed/g3doc/logo.png)

Googleが開発する, 軽量なユーザ空間カーネル上でコンテナプロセスを実行するLow level Runtimeです.

アプリケーションのシステムコールを, 独自に開発したユーザ空間カーネル(Sentry, Gofer)を経由して行うことで信頼できないContainer imageを実行する際のContainer内からホストへの攻撃領域(Attack Surface)を減らすことを目的としています.

背景としては, GKEのようなマネージドKubernetesサービスを提供するにあたって, ユーザが稼働するContainer imageのセキュリティを確保することの難しさがあると思われます.

システムコール数について, [2018年の資料](https://medium.com/nttlabs/container-runtime-d3e25189f67a)によると"50種程度"とされていますが, [GitHubの本稿執筆時点のコミット](https://github.com/google/gvisor/blob/08d36b6c630846c4d9d812c25966639513ecd211/runsc/boot/filter/config.go#L28)では, リンク先の配列の要素が59要素あって60種近くなっています. (内容は精査していないので参考程度です)

一般にはこのあと出てくるVMによる隔離やunikernelによる隔離に比べると隔離レベルは低いと考えられていますが, VM隔離と比較するとパフォーマンスが, unikernelと比較すると既存コンテナイメージがそのまま使える点が, それぞれ利点といえます.

一方で, 一部のシステムコールは実装されていないので, アプリケーションが必要としているシステムコールが実装されていない場合はアプリケーションが機能しません. [公式ページ](https://gvisor.dev/docs/user_guide/compatibility/)でよく使われるアプリケーションに関するテスト結果を公開されています.

また, 「既存のものを危険だというのは[FUD](https://ja.wikipedia.org/wiki/FUD)しているのでは？」という[批判](https://blog.jessfraz.com/post/containers-security-and-echo-chambers/)[^4]もありました. ただ批判の中でも触れられている通り, gVisor自体が問題なのではなく, gVisor発表当初の「通常のContainer(おそらくruncによるもの)は危険」という説明がマーケティング上はFUDにあたるのでは, ということです. 実際, ホストとの隔離レベルを向上する方法はKataContainerによる仮想化技術の利用や, SELinux / seccomp / AppArmor などの既存のセキュリティ技術を用いる方法もあります. gVisorだけではなくほかのContainer runtimeも同様ですが, セキュリティを目的に導入する場合は導入する際のトレードオフについてよく検討し, プロジェクトの求めるセキュリティレベルやトレードオフが受け入れ可能か総合的に判断する必要があります.

- [公式ページ](https://gvisor.dev/)
- [GitHub](https://github.com/google/gvisor)
- [今話題のいろいろなコンテナランタイムを比較してみた
[Docker Meetup Tokyo #26発表レポート]](https://medium.com/nttlabs/container-runtime-d3e25189f67a) (2018-11-30公開)

#### runnc(Nabla Containers)

![Nabla Container Logo](https://github.com/nabla-containers/nabla-containers.github.io/raw/8c07c2e2895f720a9099b9fe16e4d8d20e27b0c1/public/img/nabla.png)

unikernel技術を用いてContainerとホストの隔離レベルの向上を狙ったLow level Runtimeです.

unikernelについて詳しくは立ち入りませんが, 基本的なアイディアは汎用OSを使用せずにアプリケーションとOS機能を静的にリンクしてOS部分も含めたマシンイメージ全体を最適化することです.

このunikernel向けに既存アプリケーションをportingし, unikernalアプリケーション用のsandboxをContainerとして稼働することでsandboxからのシステムコールを限定することができ, 結果としてContainerとホストの隔離レベルを向上することができます.

システムコール数について, gVisorと同じく[2018年の資料](https://medium.com/nttlabs/container-runtime-d3e25189f67a)によると7種類と述べられており, [公式サイト](https://nabla-containers.github.io/)にも

> A containerized application can avoid making a Linux system call if it links to a library OS component that implements the system call functionality. Nabla containers use library OS (aka unikernel) techniques, specifically those from the Solo5 project, to avoid system calls and thereby reduce the attack surface. Nabla containers only use **7 system calls**; all others are blocked via a Linux seccomp policy.

と書かれています. (強調追加は筆者)

このRuntimeでアプリケーションを稼働させるにはアプリケーションを専用の環境で再コンパイルする必要があり, その意味で既存Container imageはそのままでは動きません. (OCI標準とは別のレベルの問題だと思われます)

仮想マシンによる隔離に比べて軽量・高速な起動が期待できますが, イメージの再作成など手間がかかることが難点です.

- [公式ページ](https://nabla-containers.github.io/) (2020-12-17閲覧)
- [GitHub](https://github.com/nabla-containers/runnc)
- [今話題のいろいろなコンテナランタイムを比較してみた
[Docker Meetup Tokyo #26発表レポート]](https://medium.com/nttlabs/container-runtime-d3e25189f67a) (2018-11-30公開)
- [Unikernelについての現状調査](https://proc-cpuinfo.fixstars.com/2020/03/unikernel/) (2020-03-12公開)

#### kata-runtime(Kata Containers)

![Kata Containers Logo](https://camo.githubusercontent.com/fc2b272df13c770b08a779c5f96690946039c45998b1bb439eb193b3fcd829ab/68747470733a2f2f7777772e6f70656e737461636b2e6f72672f6173736574732f6b6174612f6b6174612d766572746963616c2d6f6e2d77686974652e706e67)

VMハイパーバイザーと連携してContainer起動時にVMを起動してその中でContainerを実行することで, Containerとホストの隔離レベルをVMと同様のレベルとするLow level Runtime.

intel主導の[Clear Containers](https://nabla-containers.github.io/)プロジェクトとCaaSを提供していたhyper社(?)の[hyper.shプロジェクト](https://github.com/hyperhq/)が合流してできたプロジェクトで, パフォーマンス(Clear Containersが注力)と複数ハイパーバイザーへの対応(hyper.shが注力)の両方を取り込んでいるとされる.

[2018年のベンチマーク](https://www.slideshare.net/KoheiTokunaga/ss-123664087)ではVMが起動されるフェーズであるPodの起動とContainerの停止でruncより時間がかかるようです.

- [公式ページ](https://katacontainers.io/)
- [GitHub](https://github.com/kata-containers)

### High level Runtime

#### containerd

![containerd Logo](https://github.com/cncf/artwork/raw/40e2e8948509b40e4bad479446aaec18d6273bf2/projects/containerd/horizontal/color/containerd-horizontal-color.svg)

もともとDocker社が開発していたHigh level Runtime.

CNCFに寄贈され独立したプロジェクトとして扱われると同時に, mobyプロジェクト内で開発が継続されています.

常駐サービス型のContainer Runtimeであり, CRIに対応するプラグインを内包しているためdockerdを介さずにCRI互換Runtimeとして利用可能です.

また, Low level RuntimeとしてOCI互換なRuntimeを使用可能です. ほとんどのLow level Runtimeの使用方法のドキュメント(Getting Startedなど)でcontainerdを使用する方法が案内されています.

Dockerも現在でも裏側でcontainerdを使っているため、実際はほとんどの利用者が利用しているのではないでしょうか.

CRIよりも豊富な機能を呼び出すためのCLIとしてctrコマンドが用意されており, dockerなしでの利用も可能です.

#### cri-o

![cri-o Logo](https://github.com/cncf/artwork/raw/40e2e8948509b40e4bad479446aaec18d6273bf2/projects/crio/horizontal/color/crio-horizontal-color.svg)

containerdが, その開発経緯からCRIには不要な機能も備えていることから, CRIの実装を中心において, それ以外の機能を含まない最小限なHigh level Runtimeとして開発されています.

containerdと同じく呼び出し元はCRIに対応し, 呼び出し先はOCI互換となっています.

containerdがdockerの様々な機能をサポートするため多機能なのに対して, サポートする機能をCRIに絞っているので軽量な代替Runtimeになっている, とされます.

Container稼働時に一つのContainerごとにconmon(container monitoring)と呼ばれるプロセスでContainerを監視する(logのハンドリングや終了コードの保存など)など, Containerの実行方法でもcontainerd(docker)との違いはあります.

- [公式ページ](https://cri-o.io/)
- [GitHub](https://github.com/cri-o/cri-o)

#### podman

![podman Logo](https://github.com/containers/podman/raw/a655633f2dcf3cce60bfce63d383d4e8e1ae67a2/logo/podman-logo-source.svg)

珍しいコマンド型のHigh level Runtimeです.

常駐サービスプロセスを作らず, コマンド実行プロセスによって処理を行います. それでもOCI互換Container imageの取得や管理を行ってOCI Runtime specに従ってLow level Runtimeを呼び出すことでコンテナを管理するため, High level Runtimeに分類しています.

Container開発ツールとしてのdockerコマンドの代替を目指しており, `alias docker=podman` とするだけで移行できる(CLIインターフェースレベルで互換性がある)ことが一つの特徴です.

コマンド型High level Runtimeのため, 常駐サービス型Runtimeとのインターフェース仕様である CRIは実装していません.

- [公式ページ](https://podman.io/)
- [GitHub](https://github.com/containers/podman)

### その他

#### docker (dockerd)

![docker Logo](https://www.docker.com/sites/default/files/d8/2019-07/horizontal-logo-monochromatic-white.png)

「ContainerといえばDocker」といって過言でないDocker社の製品.

Container実行やContainer imageの管理はrunc/containerdを用いる.

dockerd自体はDockerfileの解釈やvolume/networkなどのリソース管理を担当している. swarm modeなどを使うとContainer runtimeというよりもContainer orchestratorの性格が強くみえる.

デファクトスタンダードだけに様々な立場から問題点を指摘されることがありますが, 徐々に解消されつつあります. 最近では(といってももう2019年の話ですが)dockerd自体をユーザ権限で実行することができるようになるなどかなり基本的な部分についても改善がなされている.

dockerd自体はCRI互換ではありませんが, これまではCRIをdockerd向けに翻訳するdockershimコンポーネントを(歴史的経緯から)Kubernetesコミュニティがメンテナンスし, kubeletに組み込んできました. KubernetesコミュニティはCRI互換High level Runtimeがすでに十分あることやcontainerd自身がCRIを実装していることなどからdockershimをKubernetes v1.23で削除すること, それに向けてv1.20にてdockershimの利用を非推奨とすることにしました. この経緯からわかるように, Container開発のためにdockerを用いることは非推奨になっておらず, ほとんどの開発者にとってのDockerは引き続き利用できます. また, Docker社とMirantis社(Docker社から[Docker Enterprise事業を2019年11月に買収](https://www.mirantis.com/company/press-center/company-news/mirantis-acquires-docker-enterprise/)した)はdockershimに相当するコンポーネントを開発・サポートしていくと発表しています.

- [公式ページ](https://docker.com)
- [GitHub](https://github.com/docker/engine)
- [dockershimのサポートについて](https://www.mirantis.com/company/press-center/company-news/mirantis-acquires-docker-enterprise/)
    - [dims/cri-dockerd](https://github.com/dims/cri-dockerd) (Mirantisがdockershim相当のコンポーネントのベースとすると公表した)
    - [Mirantis/cri-dockerd](https://github.com/Mirantis/cri-dockerd) (まだ何も中身がないが, 今後の開発をこちらで行っていくとされる)

#### rkt

![rkt Logo](https://github.com/cncf/artwork/raw/d8ed92555f9aae960ebd04788b788b8e8d65b9f6/projects/rkt/horizontal/color/rkt-horizontal-color.svg)

rkt[^1]はかなり早い時期[^2]から開発された, Dockerとは非互換のContainerフォーマットを用いるコンテナランタイムです. 2018年4月16日の [v1.30.0]((https://github.com/rkt/rkt/releases/tag/v1.30.0)) リリースを最後にリリースは止まっており, 2020年2月24日にGitHub上で[End of projectが宣言](https://github.com/rkt/rkt/pull/4026)されました. もはやKubernetesなどほとんどのContainer orchestratorでサポートされていませんが, 現在のContainer周辺に大きく影響する取り組みが行われていた, 重要なプロジェクトだと考えるので紹介します.

rktのContainer仕様をもとに[App Container](https://github.com/appc/spec)としてContainerフォーマットの標準化をOCIに先行して[^3]行っていました. 結局OCIへ合流したためv1.0.0には到達しませんでした.

また, rktではContainer imageへの署名と検証をデフォルトで実施するようになっておりSecure-by-defaultになっています.

rktは常駐サービスなしにコマンド型で動作する点も特徴です(現在のpodmanに似た動作). なおKubernetesと連携する場合はCRIを実装したrktletというサービスが常駐して必要に応じてrktを呼び出していました.

そのほかのContainer runtimeとの[比較](https://coreos.com/rkt/docs/latest/rkt-vs-other-projects.html)も公式サイトに掲載されています.

- [公式サイト](https://coreos.com/rkt/)
- [GitHub](https://github.com/rkt/rkt)
    - [App Container](https://github.com/appc)

#### Firecracker

![Firecracker Logo](https://github.com/firecracker-microvm/firecracker/raw/0a83e9ca43bd2f078856daa368cfaddc66ba40a8/docs/images/fc_logo_full.svg)

公式サイトでも

    Firecracker is a virtual machine monitor (VMM) that uses the Linux Kernel-based Virtual Machine (KVM) to create and manage microVMs.

と書かれているように, Firefracker自体は単なるContainer runtimeとして分類しがたいのでここで紹介します. (つまるところ, 筆者がよく知らない)

FirecrackerはAmazon Web ServiceがAWS LambdaやAWS Fargateなどを提供するために使用しているとされます.

Kata Container(kata-runtime)の[ハイパーバイザーとして動作](https://github.com/kata-containers/documentation/wiki/Initial-release-of-Kata-Containers-with-Firecracker-support)することも可能な一方, kata-runtimeの代わりに[firecracker-containerdを用いて動作](https://github.com/firecracker-microvm/firecracker-containerd)することもできます.

- [公式サイト](https://firecracker-microvm.github.io/)
- [GitHub](https://github.com/firecracker-microvm/firecracker)
    - [firecracker-containerd](https://github.com/firecracker-microvm/firecracker-containerd)
- [Kata Containerとの統合](https://github.com/kata-containers/documentation/wiki/Initial-release-of-Kata-Containers-with-Firecracker-support)

## 参考文献

- [今話題のいろいろなコンテナランタイムを比較してみた
[Docker Meetup Tokyo #26発表レポート]](https://medium.com/nttlabs/container-runtime-d3e25189f67a) (2018-11-30公開)
    - [スライド](https://www.slideshare.net/KoheiTokunaga/ss-123664087)
- [コンテナランタイムの動向を整理してみた件](https://qiita.com/mamomamo/items/ed5db2ab1555078f8a24) (2018-07-27公開, 2019-04-09更新)
- [コンテナランタイムの仕組みと、Firecracker、gVisor、Unikernelが注目されている理由。 Container Runtime Meetup #2](https://www.publickey1.jp/blog/20/firecrackergvisorunikernel_container_runtime_meetup_2.html) (2020-09-15公開)
    - [スライド](https://speakerdeck.com/inductor/container-runtime-101)
- [Unikernelについての現状調査](https://proc-cpuinfo.fixstars.com/2020/03/unikernel/) (2020-03-12公開)

- [Open Container Initiative](https://opencontainers.org/)
- [crun](https://github.com/containers/crun)
- [gVisor](https://gvisor.dev/)
- [Nabla Containers](https://nabla-containers.github.io/)
- [Kata Containers](https://katacontainers.io/)

## おわりに

数で勝負だったので仕方ないんですがたくさん調べることがあって大変でした.

もう少し, 参考にした素晴らしい資料のように, いろいろな概念についてわかりやすい図を追加したりしたほうがよかった.

実際に使ってみた記事も書きたい.

[^4]: KataContainerの作者によるもの
[^1]: GitHubのREADME.mdによると `pronounced like a "rocket"` とのことなのであえてカタカナで書くと"rkt container"は「ロケットコンテナ」と読むようです.
[^2]: GitHubでの初期リリース([v0.0.0](https://github.com/rkt/rkt/releases/tag/v0.0.0))が2014年11月27日で, [v1.0.0]((https://github.com/rkt/rkt/releases/tag/v1.0.0))も2016年2月5日です. なお [docker/engine](https://github.com/docker/engine) リポジトリのリリースを見ると [v1.0.0](https://github.com/docker/engine/releases/tag/v1.0.0) は2014年6月9日でcontainrdの[v1.0.0](https://github.com/containerd/containerd/releases/tag/v1.0.0)も2017年12月13日です.
[^3]: GitHub上の履歴では[appc/specの初期リリース](https://github.com/appc/spec/releases/tag/v0.1.1)が2014年12月12日なのに対して[opencontainers/runtime-specの初期リリース](https://github.com/opencontainers/runtime-spec/releases/tag/v0.1.1)は2015年12月18日, [opencontainers/image-spec](https://github.com/opencontainers/image-spec/releases/tag/v0.1.0)は2016年5月7日です.