71 ポイント 投稿者 GN⁺ 2026-01-21 | 9件のコメント | WhatsAppで共有
  • 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件のコメント

 
bbulbum 2026-01-22

カーネル + ツール = ディストリビューション
だとしたら、これもまたUbuntuだと言えるのでは..

 
sacredshine 2026-01-21

それで、Linux上で直接Dockerを作ってみて、ディレクトリを分離してユーザーやグループをあれこれ扱うようなチュートリアルもあるみたいでした。

 
dongho42 2026-01-21

参考になります

 
seunggi 2026-01-21

리눅스 네임스페이스, cgroups, 및 chroot를 사용하여 자체 Docker를 구축하세요.

なので、かなり大げさに言えば chroot + cgroup = docker と見てもよさそうでした

 
euphcat 2026-01-21

Acktuallly それは systemd-nspawn のほうが少し近いですね ☝️🤓

 
hohemian 2026-01-22

実際に

 
euphcat 2026-01-22

皮肉 / 自虐

 
crawler 2026-01-21

本当に興味深いですね。

本文はLINUX基準で説明しているようですが、
Windowsで実行する場合は、WSL2で作られた仮想カーネルを記事のように共有することになる、ということですよね?

もしDockerに脆弱性が見つかってカーネルに触れられるようになった場合、Linuxよりも一段仮想化されているWindowsのほうが、セキュリティ面で強いと考えるべきなのかも気になります。

 
minsuchae 2026-01-22

上のコメントの反応のせいで、少し驚きました。
当然みんな分かった上で使っているものだと思っていました。
Linuxカーネルはホスト側のものを使い、それ以外はLinuxディストリビューションで使われるツール群を持ってくる形なので。

WSL2 は、私の知る限りでは Hyper-V 上で仮想化して動きます。
Windows - 仮想マシン上の Linux - その中でさらに Container...

基本的に、Container 内部での root は本当のシステム全体の root ではないため、基本的にはカーネルを任意に操作することはできません。
ただし、脆弱性が見つかると大変なことにはなります。

性能面で見ると、Windows のほうがやや遅い理由は、仮想化をもう一段挟んでいるためです。