Kubernetes 解体新書Part 3 ランタイム

ランタイムの栄枯盛衰

Docker で作ったイメージは、Docker Engine がなくても動きます。その仕組みと、dockershim が外れるまでの経緯を追います。

  • L0 ブラウザだけ
  • 更新 2026年10月6日

Docker で作ったイメージは、Docker Engine が入っていない Kubernetes のノードでも動きます。 「イメージを作る道具」と「コンテナを動かす道具」は、同じでなくてもよいからです。

では、かつて Kubernetes から Docker を直接使っていた構成は、どう変わったのでしょうか。 この章では、共通の呼び出し方が作られ、Docker 専用の接続部分が外れるまでを追います。

GOAL この章のゴール

  • ランタイムを交換するために共通の取り決めが必要になった理由が分かる
  • dockershim の削除で変わった部分を説明できる
  • Docker で作ったイメージが引き続き使える理由が分かる

年表

コンテナランタイムの年表 2013〜2026Docker 公開から、OCI と CRI の誕生、rkt の退場、dockershim の削除、containerd 2.x まで。左が Docker / OCI / rkt 側、右が Kubernetes 側の出来事。Docker / OCI / rkt 側Kubernetes 側2013.3Docker 公開2014.6Kubernetes 公開2014.12rkt 公開 (CoreOS)2015.6OCI 設立、runc 寄贈2015.7Kubernetes 1.0 (dockertools と rkt)2016.4Docker 1.11: containerd + runc 内蔵2016.71.3: rktnetes2016.12containerd を独立プロジェクトに2016.121.5: CRI alpha、dockershim2017.3containerd と rkt を CNCF へ2017.4Moby project 発表2017.12containerd 1.02018.4containerd 1.1: CRI plugin 内蔵2019.8rkt アーカイブ (CNCF)2020.121.20: dockershim 非推奨2021.6runc 1.02021.121.23: CRI v1 が安定版2022.51.24: dockershim 削除、cri-dockerd2022.121.26: CRI v1 だけに2024.11containerd 2.02026.9containerd 2.4、runc 1.5.2
図 12013 年の Docker 公開から 2026 年まで。左が Docker / OCI / rkt 側、右が Kubernetes 側。

1.0: ランタイムごとのコードを抱えた kubelet

2015 年 7 月の Kubernetes 1.0 のソースを見ると、kubelet のディレクトリに dockertools と rkt という 2 つのディレクトリが並んでいます。 前者は Docker の API を直接叩くコードで、コンテナの作成、イメージの pull、ログの取得が全部 Docker の Go クライアントへの呼び出しです。 後者は CoreOS 社のランタイム rkt のための同じ種類のコードです。 2015 年にはコンテナランタイムの選択肢がほぼ Docker しか無く、kubelet がランタイムの API を直接呼ぶのは自然な設計でした。

2016 年の CRI の設計文書は、この構造の問題を 3 つ挙げています。 Pod という概念を持たないランタイムのために、ランタイムごとに大きな変換コードが要ること。 ランタイムごとに Pod の同期処理 (SyncPod) が丸ごと実装され、コードを共有できないこと。 Pod の spec が変わるたびに、全ランタイムの実装を直さなければならないことです。

rkt と rktnetes

Docker のデーモン型と rkt のデーモンレス型のプロセスモデルDocker: CLI が常駐する dockerd に REST で頼み、コンテナは dockerd 配下に生まれる。rkt: rkt run コマンド自身が stage1 (systemd ベース、KVM 版もある) を exec して Pod を起動し、常駐デーモンは無い。rkt は 2019 年 8 月に CNCF でアーカイブされた。Docker: デーモン型docker runRESTdockerd (常駐)containerd / runcコンテナデーモンの子孫CLI が終了してもデーモンが残り、コンテナの面倒を見るrkt (2014〜2019): デーモンレス型rkt runexec (stage1)stage1 (systemd ベース)Podrkt のプロセスそのもの常駐プロセス無しPod をネイティブに扱う (複数 app)署名検証が既定KVM (stage1) も選べる2019 年 8 月にCNCF でアーカイブ
図 2Docker はデーモンが常駐し、rkt はコマンド自身が Pod になる。rkt は Pod を最初から複数コンテナの単位として扱った。

rktロケットCoreOS 社が 2014 年 12 月に公開したコンテナランタイム。デーモンを置かず、systemd の仕組みで Pod を直接起動する。2016 年に Kubernetes 1.3 で対応 (rktnetes)、2019 年 8 月に CNCF でアーカイブ。用語集で見る は、Docker と正反対の設計でした。 Kubernetes のブログによると、rkt は常駐デーモンを持たず、Pod を最初から起動の単位として扱い、systemd と統合され、実行環境の部分 (stage1) を差し替えれば KVM の VM として起動することもできました。 2016 年 7 月の Kubernetes 1.3 で、--container-runtime=rkt で選べる形で正式に組み込まれ、「rktnetes」と呼ばれました。

rkt が kubelet に入ったことで、「ランタイムを差し替えられる kubelet」が初めて現実になりました。 同時に、前の節の問題も現実になります。 2 つのランタイムを抱えた kubelet には、それぞれの API を呼ぶコードが二重にあり、どちらかが変わるたびに Kubernetes 側の修正が必要でした。

CRI の誕生 (1.5)

2016 年 12 月の 1.5 で、Kubernetes は kubelet がランタイムに頼む方法を仕様として決め、アルファとして出しました。 それが前の章で見た CRI です。 設計文書と発表のブログから、決まったことを 3 つ拾います。

  1. gRPC over UNIX socket:kubelet は全ノードにいて、ランタイムも同じノードに常駐する。だからネットワーク越しの RPC は要らず、同じノードのソケット 1 本で話す。
  2. 2 つのサービス:イメージを扱う ImageService と、Pod とコンテナを扱う RuntimeService に分ける。
  3. 命令型でコンテナ単位の API と sandbox:「sandbox を作れ」「コンテナを起動しろ」という手続きを kubelet が順に呼ぶ。設計文書は、Pod 単位の依頼にすると Pod の意味づけや再起動の規則を全ランタイムが実装し直すことになる、という理由でこの形を選んでいる。Pod の概念を持たないランタイムでも実装できるように、Pod の土台だけを sandbox という単位にした。

この取り決めのおかげで、ランタイムごとの変換コードは kubelet の中心部から切り離されました。 発表のブログは、Docker 用の dockershim と rkt 用の rktlet を、CRI とランタイムの間に挟む変換層 (shim) として紹介しています。

rkt の退場

rkt 用の変換層 rktlet は作られましたが、2019 年 12 月にアーカイブされました。 それより前の 2019 年 8 月に、CNCF は rkt プロジェクト自体をアーカイブしています。 CNCF の発表が挙げた理由は、利用者の採用が大きく減ったこと、プロジェクトの活動と貢献者が減り続けたこと、未修正の CVE が残ったことで、利用者は同じ CNCF の containerd と CRI-O に移っていました。 rkt は、CNCF が新しく作ったアーカイブ手続きの最初の対象でした。

rkt が目指した方向は、別のプロジェクトに残っています。 Kubernetes からの依頼だけを受けるランタイムとしては、Red Hat が 2016 年に始めた CRI-O があります。 常駐デーモンを持たない設計としては、Docker の代わりに手元で使う Podman が、自分を「デーモンレス」のコンテナエンジンと名乗っています。

dockershim

dockershim があった頃の経路、cri-dockerd の経路、containerd 直結の経路2020 年まで: kubelet → dockershim (kubelet 内蔵) → dockerd → containerd → runc。1.24 以降に Docker を使い続けるなら: kubelet → cri-dockerd (別プロセス) → dockerd → containerd → runc。標準: kubelet → containerd → runc。〜1.23 (dockershim)1.24〜 Docker を残す1.24〜 標準kubeletdockershim (kubelet 内蔵)dockerdcontainerdshimrunckubeletcri-dockerd (Mirantis)dockerdcontainerdshimruncCRIkubeletcontainerd (CRI plugin)shimruncCRIどの経路でも最後は同じ runc。違うのは途中の段数と、誰が CRI → Docker API の変換を持つか。
図 3dockershim は kubelet の中にあった CRI → Docker API の変換層。削除後に Docker を使い続けたいなら cri-dockerd を別プロセスで置く。

CRI ができた 2016 年の時点で、dockerd が受け付けるのは Docker 独自の REST API だけでした。 kubelet が Docker を使い続けるには、CRI の呼び出しを Docker API に変換する層が要ります。 その変換層が、kubelet の中に置かれた dockershimドッカーシムkubelet に内蔵されていた CRI → Docker API の変換層。1.20 で非推奨、1.24 (2022 年) で削除された。用語集で見る です。 Docker を使うクラスタは kubelet → dockershim → dockerd → containerd → runc と 5 段を経由していました。

一方の containerd は、Docker から独立していきます。 Docker 1.11 (2016 年 4 月) で dockerd の内部に入り、2016 年 12 月に独立プロジェクトになり、2017 年 3 月に rkt と一緒に CNCF に寄贈されました。 2017 年 4 月には Docker 社が、Docker を組み立てる部品 (containerd など) を公開する Moby project を発表しています。 2018 年 4 月の containerd 1.1 で CRI plugin が containerd 本体に入り、kubelet が containerd に直接話せるようになりました。 Kubernetes のブログはこのとき、kubelet → dockershim → dockerd → containerd → runc と、kubelet → containerd → runc の 2 つの経路を並べて、後者には中間のデーモンが無いことを示しています。

2020 年 12 月の 1.20 で dockershim は非推奨になり、2022 年の 1.24 で削除されました。 Kubernetes の FAQ によると、非推奨の理由は、dockershim の保守が kubelet に大きな負担をかけていたことと、Docker が CRI を実装していないことです。 削除されたのは kubelet 内の変換層だけで、Docker で作ったイメージはそのまま動きます (OCI Image Spec だからです)。 Docker を使い続けたい場合は、Mirantis が保守する cri-dockerd を別プロセスとして置けば、CRI の口ができます。

dockerd が持つ 3 つの機能

Docker の 3 つのサービスと Kubernetes のインターフェース。Kubernetes が使うのはランタイムの部分だけdockerd はランタイム (containerd)、ネットワーク (docker network)、ボリューム (docker volume) を持つ。Kubernetes は CRI でランタイムだけを使い、ネットワークは CNI、ストレージは CSI で別のものを使う。dockerd が持つものランタイムサービスcontainerd + runc でコンテナを動かすネットワークサービスdocker network、docker0、libnetworkボリュームサービスdocker volume、volume pluginKubernetes のインターフェースCRIcontainerd / CRI-OCNICilium / Calico / VPC CNI …CSIEBS / Ceph / kubevirt-csi …使う使わない使わない
図 4dockerd はランタイム、ネットワーク、ボリュームの 3 つを持つ。Kubernetes が使うのはランタイムだけで、残り 2 つは CNI と CSI で別のものを使う。

Docker を外せた理由がもう 1 つあります。 Kubernetes のブログ「Don’t Panic」は、Docker を「人間が使うために作られたもので、ネットワーク、ボリューム、UX を含む」と説明し、それらは CRI が求めるものに入っていない、と述べています。 dockerd を 3 つのサービスに分けると、コンテナを動かすランタイムサービス、docker network のネットワークサービス、docker volume のボリュームサービスです。 Kubernetes が dockerd に頼むのは、このうちランタイムだけです。 ネットワークは CNI、ストレージは CSI と、それぞれ別のインターフェースを持っているからです (Part 2 で「Kubernetes は docker0 を使っていない」と確かめたのはこのことです)。

Docker を Kubernetes のノードに置くと、使わない 2 つのサービスが常駐し、使うランタイムの部分も dockerd 経由の遠回りになります。 containerd を直接使えば、必要な部分だけが残ります。

ふりかえり

QKubernetes 1.24 で削除されたのは?

削除されたのは CRI → Docker API の変換層だけです。OCI 形式のイメージはそのまま動きます。

QCRI が gRPC over UNIX socket を選んだ理由として適切なのは?

同じノードの中でしか話さないので、ソケット 1 本で済みます。

QCNCF が rkt をアーカイブした理由として挙げたのは?

利用者は containerd と CRI-O に移っていました。CNCF の新しいアーカイブ手続きの最初の対象でした。

参考