docker run ubuntu を実行しても ホストのLinuxカーネルを共有し、Ubuntuは ユーザー空間ツールのみを提供する
uname -r の結果には ホストのカーネルバージョン が表示され、Ubuntuの情報として現れるのは /etc/os-release のみ
- VMはそれぞれ固有のカーネルを持ち、起動に数分かかるが、コンテナは ミリ秒単位で起動し、ハードウェア仮想化なしのOSレベル分離によってホストカーネルを共有するためオーバーヘッドが低い
- Linuxの システムコールABIの安定性 により、さまざまなディストリビューションのコンテナが同一カーネル上で動作できる
- 16GB RAM環境では、軽量コンテナは50〜100個、中規模は10〜30個、大型コンテナは5〜10個 程度が実用的な上限
- このアーキテクチャの理解が重要な理由は、カーネル脆弱性がすべてのコンテナに影響し、ベースイメージの選択 が互換性とセキュリティに直接影響するため
「Ubuntuを実行する」とは何を意味するのか
docker run ubuntu:22.04 を実行すると、Ubuntuのように見えるbashプロンプトが得られ、apt update やパッケージのインストールが可能
- しかしコンテナ内で
uname -r を実行すると ホストのカーネルバージョン(例: 6.5.0-44-generic)が表示される
/etc/os-release ファイルはUbuntu 22.04と表示するが、カーネルはホストマシンのもの であり、「Ubuntu」の部分は ユーザー空間を構成するファイルシステム にすぎない
コンテナ vs 仮想マシン: アーキテクチャ比較
- VMはハードウェアを仮想化し、コンテナは OSを仮想化する
- 主な違い:
- カーネル: VMはそれぞれ固有のカーネルを保有、コンテナはホストカーネルを共有
- 起動時間: VMは数分、コンテナは ミリ秒
- メモリオーバーヘッド: VMは512MB〜4GB、コンテナは1〜10MB
- ディスク使用量: VMは10〜100GB、コンテナイメージは10〜500MB
- 分離レベル: VMはハードウェアレベル、コンテナは プロセスレベル
- 性能: VMは約5〜10%のオーバーヘッド、コンテナは ネイティブに近い性能
ベースイメージの実際の構成要素
ubuntu:22.04 をpullした際にダウンロードされるtarballの内容:
-
1. 必須バイナリ (/bin, /usr/bin)
/bin/bash(シェル)、/bin/ls(ファイル一覧)、/bin/cat(ファイル表示)
/usr/bin/apt(パッケージマネージャー)、/usr/bin/dpkg(Debianパッケージツール)
-
2. 共有ライブラリ (/lib, /usr/lib)
- glibc やその他プログラムがリンクする共有ライブラリ
/lib/x86_64-linux-gnu/libc.so.6(Cライブラリ - すべてのCプログラムの基盤)
libpthread.so.0, libm.so.6 などの必須ライブラリ
-
3. 設定ファイル (/etc)
/etc/apt/sources.list(パッケージリポジトリ)
/etc/passwd(ユーザーデータベース)
/etc/resolv.conf(DNS設定、通常はホストからマウント)
-
4. パッケージデータベース
/var/lib/dpkg/status(インストール済みパッケージ)
/var/lib/apt/lists/(利用可能なパッケージキャッシュ)
- カーネル・ブートローダー・ドライバーは 含まれない
カーネルはそのまま、すべてが変わる
- Linuxカーネルが提供する機能: プロセススケジューリング、メモリ管理、ファイルシステム操作、ネットワークスタック、デバイスドライバー、システムコール
- コンテナプロセスが
open(), read(), fork() を呼び出すと ホストカーネルへ直接渡される
- カーネルはそのプロセスが「Ubuntuコンテナ」なのか「Alpineコンテナ」なのかを 知りもせず気にもしていない
-
システムコールインターフェースの安定性
- Linux syscall ABIは非常に安定している
- glibc 2.31(Ubuntu 20.04)でコンパイルされたバイナリがUbuntu 24.04カーネルでも動作する理由:
- カーネルが 下位互換性 を維持している
- システムコール番号に変更がない
- 新機能は追加されても既存機能が削除されることはほとんどない
- カーネル6.5を動かしているホストでUbuntu 18.04コンテナを実行できる理由
実際に試してみる: 同じカーネル、異なるユーザー空間
- 複数のベースイメージで同じカーネル問い合わせを実行すると、すべてのイメージが ホストカーネルを共有していること がわかる
- ubuntu:22.04、debian:12、alpine:3.19、fedora:39、archlinux:latest はすべて同じカーネルバージョン(6.5.0-44-generic)である
- コンテナごとの差は
uname バイナリやlibcなどの ユーザーランド構成 にある
コンテナが非常に効率的な理由
-
1. カーネルの重複がない
- VMはそれぞれ完全なカーネルをメモリにロードする(約100〜500MB)
- 10台のVMでは10個のカーネルがメモリを消費するが、10個のコンテナでは 1つのカーネルしか使わない
-
2. 即時起動
- VMの起動順序: BIOS → ブートローダー → カーネル → initシステム → サービス
- コンテナは
fork() と exec() の呼び出しだけで ミリ秒以内にプロセスとして存在
- 一般的なVM起動: 30〜60秒 / コンテナ起動: 約0.347秒
-
3. 共有イメージレイヤー
ubuntu:22.04 で100個のコンテナを実行しても、ベースイメージレイヤーは ディスク上に一度だけ存在 する
- 各コンテナが取得するのは変更のための 薄いcopy-on-writeレイヤー のみ
-
4. カーネルによるメモリ共有
- カーネルの ページキャッシュが共有 される
- 50個のコンテナが同じファイルを読んでも、カーネルは一度だけキャッシュする
- 同じ共有ライブラリを使う場合、copy-on-writeによるメモリページ共有 も可能
コンテナ実行可能数の上限計算
-
メモリ分析(16GB RAMのVM基準)
- 総RAM: 16,384 MB
- ホストOSオーバーヘッド: -1,024 MB
- Dockerデーモン: -256 MB
- コンテナランタイムオーバーヘッド: -512 MB
- コンテナ利用可能量: 14,592 MB
-
コンテナ種類ごとのメモリ使用量
- 最小(sleep): 約1MB
- Alpine + 小規模アプリ: 約25MB
- Ubuntu + Pythonアプリ: 約120MB
- Ubuntu + Javaアプリ: 約500MB
- Node.jsサービス: 約200MB
-
理論上の最大値
- 最小コンテナ(1MB): 14,592個
- Alpine + 小規模アプリ(25MB): 583個
- Ubuntu + Python(120MB): 121個
- Javaマイクロサービス(500MB): 29個
-
実際の上限
- メモリ以外の考慮事項:
- CPUスケジューリング: コンテナが多すぎて競合すると遅延スパイクが発生
- ファイルディスクリプタ: デフォルトのulimitは1024
- ネットワークポート: ポートマッピングに使えるのは65,535個まで
- PIDs:
/proc/sys/kernel/pid_max の制限(デフォルト: 32,768)
- ディスクI/O: OverlayFSのオーバーヘッド、多数のレイヤー探索が必要
- 16GBのVMで実ワークロードを実行する場合の実用上限:
- 軽量コンテナ(API、ワーカー): 50〜100個
- 中規模コンテナ(DB、キャッシュ): 10〜30個
- 大型コンテナ(MLモデル、JVMアプリ): 5〜10個
Linuxディストリビューションの互換性
-
カーネルABIの約束
- Linuxは安定したsyscallインターフェースを維持している
- 古いカーネル向けにコンパイルされたバイナリが新しいカーネルでも動作する
- Ubuntu 18.04バイナリがカーネル6.5で正常に実行される
-
互換性が壊れる場合
- カーネル機能要件: コンテナがカーネルにない機能を必要とする場合(例: io_uring はカーネル5.1+が必要)
- カーネルモジュール依存性: Wireguardはwireguardカーネルモジュールが必要、NVIDIAコンテナはnvidiaカーネルドライバが必要
- Seccomp/capability制限: ホストがコンテナに必要なsyscallをブロックする場合(例: ptraceを使うには
--cap-add SYS_PTRACE が必要)
ベースイメージ選択ガイド
| ベースイメージ |
サイズ |
パッケージマネージャー |
用途 |
scratch |
0 MB |
なし |
静的コンパイルされたGo/Rustバイナリ |
alpine |
7 MB |
apk |
最小コンテナ、musl libc |
distroless |
20 MB |
なし |
セキュリティ重視、シェル・パッケージマネージャーなし |
debian-slim |
80 MB |
apt |
サイズと互換性のバランス |
ubuntu |
78 MB |
apt |
開発しやすさ |
fedora |
180 MB |
dnf |
最新パッケージ、SELinux |
-
各イメージを使うタイミング
- scratch: 静的コンパイル済みバイナリ向け、OSをまったく含まずバイナリだけを含む
- alpine: シェルアクセスが必要な最小イメージ、glibcの代わりに musl libc を使うため一部で互換性問題が起こりうる
- distroless: セキュリティ重視の本番用イメージ、シェルとパッケージマネージャーがないためデバッグは難しいがより安全
ユーザー空間とカーネルの境界
-
ベースイメージ由来のもの(ユーザー空間)
- シェル(
/bin/bash, /bin/sh)
- Cライブラリ(glibc, musl)
- パッケージマネージャー(apt, apk, yum)
- コアユーティリティ(ls, cat, grep)
- initシステム設定(通常はsystemdそのものではない)
- 基本ユーザーとグループ(
/etc/passwd)
- ライブラリパスと設定
-
ホスト由来のもの(カーネル)
- プロセススケジューリングとメモリ管理
- ネットワークスタック(TCP/IP、ルーティング)
- ファイルシステム操作(読み取り、書き込み、マウント)
- セキュリティ機能(名前空間、cgroups、seccomp)
- デバイスドライバー(GPU、ネットワーク、ストレージ)
- 時刻とクロック管理
- 暗号化と乱数生成
-
名前空間が生み出す錯覚
- カーネルが名前空間を提供することで、コンテナは分離されているように感じられる
- コンテナ内ではPID 1に見えるプロセスも、ホストではより大きいPID(例: 45678)として存在する
- カーネルがマッピングを維持する: コンテナPID 1 → ホストPID 45678
- 仮想化なしで 分離が機能する仕組み
本番環境での意味
-
1. カーネル脆弱性はすべてのコンテナに影響する
- ホストカーネルに脆弱性があれば、すべてのコンテナが露出する
- ホストへのパッチ適用を維持することが必須
-
2. ホストカーネルがコンテナ機能を制限する
- io_uringを使うにはホストカーネル5.1+が必要
- eBPF機能には特定オプションが有効なカーネル4.15+が必要
-
3. glibc vs muslの重要性
- Alpineは musl libc を使用する
- glibc向けにコンパイルされた一部バイナリは動作しない可能性がある
- 例: Alpineでglibcバイナリを実行すると
/lib/x86_64-linux-gnu/libc.so.6 ファイルがないというエラーが発生する可能性がある
-
4. コンテナの「OS」は純粋に整理上の概念
- カーネルの観点では「Ubuntuコンテナ」と「Debianコンテナ」に 違いはない
- どちらもsyscallを発行するプロセスにすぎない
よくある誤解
- ❌ 「コンテナは軽量VM」: コンテナは高度に分離されたプロセスであり、VMはハードウェアを仮想化して別個のカーネルを実行する
- ❌ 「各コンテナが固有のカーネルを持つ」: すべてのコンテナはホストカーネルを共有し、コンテナの「OS」はユーザー空間のファイルにすぎない
- ❌ 「Ubuntuコンテナを実行する = Ubuntuを実行する」: 実際にはホストカーネルとUbuntuツールを実行しており、ホストがDebianなら、実行しているカーネルはDebianのもの
- ❌ 「ベースイメージに完全なOSが含まれている」: ベースイメージに含まれるのは最小限のユーザー空間ツールだけで、カーネル・ブートローダー・ドライバーはない
- ❌ 「コンテナが増えるほどメモリも増える」: 共有レイヤーとカーネルのページキャッシュにより、コンテナはしばしば 効率よくメモリを共有 する
要点まとめ
- Dockerベースイメージは、Linuxディストリビューションの ユーザー空間構成要素のファイルシステムスナップショット である
- UbuntuをUbuntuらしく感じさせるバイナリ、ライブラリ、設定を含む
- 実際のOSである カーネルはホストと共有 される
- このアーキテクチャが可能にすること:
- ミリ秒単位の起動時間(カーネル起動がない)
- 最小限のメモリオーバーヘッド(1つのカーネル、共有ページ)
- 高密度運用(ホストあたり数百コンテナ)
- ネイティブに近い性能(カーネルへの直接syscall)
- トレードオフはVMより分離が弱いこと。コンテナはカーネルを共有するため、カーネルエクスプロイトがすべてのコンテナに影響 する
- ほとんどのワークロードでは、このトレードオフには価値がある
9件のコメント
カーネル + ツール = ディストリビューション
だとしたら、これもまたUbuntuだと言えるのでは..
それで、Linux上で直接Dockerを作ってみて、ディレクトリを分離してユーザーやグループをあれこれ扱うようなチュートリアルもあるみたいでした。
参考になります
리눅스 네임스페이스, cgroups, 및 chroot를 사용하여 자체 Docker를 구축하세요.
なので、かなり大げさに言えば
chroot + cgroup = dockerと見てもよさそうでしたAcktuallly それは
systemd-nspawnのほうが少し近いですね ☝️🤓実際に
皮肉 / 自虐
本当に興味深いですね。
本文はLINUX基準で説明しているようですが、
Windowsで実行する場合は、WSL2で作られた仮想カーネルを記事のように共有することになる、ということですよね?
もしDockerに脆弱性が見つかってカーネルに触れられるようになった場合、Linuxよりも一段仮想化されているWindowsのほうが、セキュリティ面で強いと考えるべきなのかも気になります。
上のコメントの反応のせいで、少し驚きました。
当然みんな分かった上で使っているものだと思っていました。
Linuxカーネルはホスト側のものを使い、それ以外はLinuxディストリビューションで使われるツール群を持ってくる形なので。
WSL2 は、私の知る限りでは Hyper-V 上で仮想化して動きます。
Windows - 仮想マシン上の Linux - その中でさらに Container...
基本的に、Container 内部での root は本当のシステム全体の root ではないため、基本的にはカーネルを任意に操作することはできません。
ただし、脆弱性が見つかると大変なことにはなります。
性能面で見ると、Windows のほうがやや遅い理由は、仮想化をもう一段挟んでいるためです。