コンテナはなぜ VM より「軽い」のか? 手放しているものは何か
docker run は数秒でシェルに入れるのに、クラウドで新しい VM を 1 台立ち上げると起動完了まで数十秒〜数分待たされる。同じ「隔離された実行環境」のはずなのに、この差はどこから生まれるのか。そして「軽い」という言葉の裏で、コンテナは VM が持っていた何を手放しているのか。
- VM はハイパーバイザー上にカーネルごとゲスト OS を丸ごと積むが、コンテナはホストのカーネルを他のコンテナと共有し、名前空間と cgroup でプロセスを区切るだけである
- 軽さの正体は「新しいカーネルを起動しない」ことにあり、起動はプロセス起動と同程度に速く、イメージにもカーネルを含める必要がない
- カーネルを 1 つ共有する代わりに隔離境界は弱く、macOS/Windows では結局 1 つの Linux VM を裏で起動してその中でコンテナを動かしている
VM — ハイパーバイザーの上に、もう一つのカーネルを積む
VM(仮想マシン)はハイパーバイザー(物理ハードウェアを仮想化し、複数のゲスト OS を並行して動かす基盤ソフトウェア)が仮想 CPU・仮想ネットワークカードなどの「仮想ハードウェア」を作り、その上でゲスト OS(VM 内で動く、独自のカーネルを持つ OS)を丸ごと起動する構成を取る。ゲスト OS は自分専用のカーネルを持つため、ホスト OS の種類に関わらず Linux でも Windows でも自由に選べる。
この構成の代償が起動コストだ。VM を 1 台立ち上げるたびに、ブートローダーからカーネル初期化までを含む OS の起動シーケンスを丸ごと 1 回実行する必要がある。イメージのサイズもカーネルとシステムライブラリ一式を含むため数百 MB〜数 GB になりやすい。
コンテナ — 名前空間で「見える範囲」を、cgroup で「使える量」を区切る
コンテナはゲスト OS を持たない。ホスト OS のカーネルをそのまま共有し、Linux カーネルの 2 つの機能だけで「隔離された環境らしく見せる」仕組みだ。1 つ目が名前空間(namespace)で、PID・ネットワーク・マウントポイント・ホスト名などについて「そのプロセスから何が見えるか」を区切る。PID 名前空間を分ければコンテナ内では自分が PID 1 に見え、ネットワーク名前空間を分ければ自分専用のネットワークインターフェースを持っているように見える。2 つ目が cgroup(control group)で、そのプロセス群が使える CPU・メモリなどのリソース量に上限をかける。
どちらもカーネルを新たに起動する処理ではない。この設定を実際に行うのが Docker や containerd といったコンテナランタイム(イメージから名前空間・cgroup を組み立ててコンテナプロセスを起動・管理するソフトウェア)で、コンテナ起動とは実質的には「ホストカーネル上で名前空間と cgroup を設定してから 1 個のプロセスを fork/exec する」だけの操作だ。この違いが起動速度とイメージサイズの両方に直結する。
VM ┌────────────────────┐ │App + Libs │ │Guest Kernel │ ├────────────────────┤ │Hypervisor │ ├────────────────────┤ │Host OS / Host HW │ └────────────────────┘ Container(Host Kernel を共有) ┌────────────────────┐ │App + Libs │ ├────────────────────┤ │Container Runtime │ ├────────────────────┤ │Host OS / Host HW │ └────────────────────┘
VM は敷地に一戸建てを何棟も建てるようなものだ。各棟が基礎・水道管・電気配線を自前で持ち、隣の工事の影響を受けない代わりに、1 棟ごとに丸ごとの建築期間がかかる。コンテナは 1 棟のマンションの各部屋に近い。壁(名前空間)で見える範囲を区切り、契約(cgroup)で電気・水道の使用量を制限するが、建物の基礎や配管そのものは 1 つ(ホストカーネル)を全戸で共有している。だから入居(起動)は一瞬で済むが、建物の基礎に亀裂(カーネルの脆弱性)が入れば全戸に影響が及びうる。
手放しているもの — カーネル 1 つを共有するリスク
VM の隔離はハイパーバイザーによるハードウェアレベルの仮想化に支えられており、ゲスト OS のカーネルがどれだけ壊れても、その影響は基本的に自分の VM の中に閉じる。一方コンテナは全員が同じホストカーネルの上で動いているため、そのカーネル自体の脆弱性は隔離の前提ごと崩す。例えば 2022 年に報告された CVE-2022-0492 は、cgroup v1 の release_agent という通知の仕組みを悪用し、コンテナ内の一般ユーザーでもホスト上で root 権限のスクリプトを実行できてしまう欠陥だった。名前空間で「見えない」はずのホストに、カーネルの穴を突いて到達できてしまう。
この種のリスクへの現実的な緩和策が seccomp(許可するシステムコールを絞る)や AppArmor・SELinux(プロセスの権限をポリシーで縛る)であり、それでも隔離をハイパーバイザー相当まで引き上げたい場合は、KVM を使ってワークロードごとに専用の軽量ゲストカーネルを与える Kata Containers や、システムコールをユーザー空間で横取りしてホストカーネルに直接渡さない gVisor のような、コンテナのインターフェースのまま VM に近い隔離を提供するランタイムを選ぶ、という判断になる。
余談 — Mac の Docker Desktop はなぜ VM を使うのか
ここまでの説明からすると意外に思えるかもしれないが、Mac や Windows で Docker Desktop を使う場合、コンテナは結局 1 つの Linux VM の中で動いている。名前空間や cgroup は Linux カーネルの機能であり、macOS や Windows のカーネルには存在しない。そのため Docker Desktop は裏で軽量な Linux VM(Mac では Apple Virtualization Framework または HyperKit、Windows では WSL2 上の Linux)を 1 つ起動し、コンテナ群はその Linux カーネルを共有する形で動く。「コンテナは VM を使わない」という理解は Linux ホスト上の話であって、非 Linux ホストでは VM が 1 段挟まっている。
ローカルの docker compose up や Kubernetes の Pod が秒単位でスケールアウトできるのは、コンテナがカーネルを起動し直さないからだ。逆に、他社のワークロードと同じホストにコンテナを同居させるマルチテナント SaaS や、信頼できないコードを実行するサービスでは、カーネル共有のリスクを重く見て Kata Containers や gVisor のようなランタイムを選ぶ判断が出てくる。GitHub Actions のホスト型ランナーのように、CI が毎回まっさらな VM を 1 台起動してからその中でコンテナを動かす構成も、同じ「コンテナだけでは隔離が弱い場面がある」という判断の表れだ。
見た目の違う Linux ディストリのコンテナを 3 つ起動しても、カーネルのバージョンは全部同じ(ホストと共有している)ことを確かめる。
docker run --rm ubuntu:22.04 uname -r docker run --rm alpine uname -r docker run --rm fedora uname -r
ディストリはバラバラなのに、3 つとも同じバージョン文字列が返ってくるはずだ。これは各コンテナが自分専用のカーネルを積んでいるのではなく、Docker を動かしているホスト(Mac の場合は Docker Desktop が裏で起動している Linux VM)のカーネルをそのまま覗いているために起きる。
- コンテナは「軽量な VM」だ — VM はゲスト OS ごと自分専用のカーネルを新たに起動するが、コンテナは新しいカーネルを一切起動しない。ゲスト OS という概念自体が無く、ホストカーネルの上で名前空間により区切られたプロセスに過ぎない。
- Docker Desktop for Mac はコンテナをネイティブに動かしている — 実際には裏で 1 つの軽量 Linux VM を起動し、その中の Linux カーネル上でコンテナを動かしている。macOS のカーネルは Linux ではなく、Linux 専用機能である名前空間・cgroup をそのままでは使えないためだ。
- コンテナは VM と同等かそれ以上に安全に隔離されている — 全コンテナがホストカーネルを 1 つ共有しているため、カーネル自体の脆弱性は隔離の前提ごと崩れうる(例: CVE-2022-0492)。VM はハイパーバイザーによるハードウェアレベルの隔離があるため、この種の脆弱性の影響範囲がより狭い。
- 名前空間(namespace)
- プロセスから見える PID・ネットワーク・マウントポイントなどの範囲を区切る Linux カーネルの機能。
- cgroup
- プロセス群が使える CPU・メモリなどのリソース量を制限・計測する Linux カーネルの機能。
- ハイパーバイザー
- 物理ハードウェアを仮想化し、複数のゲスト OS を並行して動かす基盤ソフトウェア。
- ゲスト OS
- ハイパーバイザー上で動く、独自のカーネルを持つ仮想マシン内の OS。
- コンテナランタイム
- イメージから名前空間・cgroup を設定してコンテナプロセスを起動・管理するソフトウェア。
- What is a container? | Docker Docs — コンテナが「隔離されたプロセス」であり VM とは異なる点を公式ドキュメントが説明する。
- namespaces(7) — Linux manual page — PID・ネットワーク・マウントなど各名前空間の種類と役割を定義する一次ソース。
- Virtual Machine Manager for Docker Desktop on Mac | Docker Docs — Mac の Docker Desktop が裏で Linux VM を管理している仕組みの公式解説。
- New Linux Kernel Vulnerability: Escaping Containers by Abusing Cgroups | Aqua Security — CVE-2022-0492 の仕組みと、カーネル共有が隔離境界に与える影響の具体例。