Docker/コンテナ — アプリを箱ごと運んで どこでも同じに動かす

概要 — まず全体をつかむ

中級ではイメージ・レイヤ・namespace・cgroupsを見ました。上級では、その隔離が カーネルのどの機能 で実現されるのか——6つのnamespace・cgroups・overlayfs・コンテナネットワーク・攻撃面の縮小——という「動く仕組み」を掘り下げます。土台は Linux仮想化 です。

詳細 — 1段階ずつ追う

コンテナの正体=隔離されたプロセス

コンテナは「小さな仮想マシン」ではありません。ホストのカーネルをそのまま使う、ただのプロセスです。ただしそのプロセスに対して、カーネルが「見える景色」と「使える資源」を絞っている——それだけです。

  • VM — ハイパーバイザの上で ゲストOS(カーネルごと) を丸ごと動かす。境界はハードウェアの仮想化
  • コンテナ — ホストの1つのカーネルを共有し、namespace(※1)で景色を、cgroups(※2)で資源を 仕切る
アニメーション『VMとコンテナの違い』を開く
仮想マシン(VM)OSをまるごと積む=重いコンテナカーネルを共有=軽い仮想マシン1アプリゲストOS(フルOS)仮想マシン2アプリゲストOS(フルOS)仮想マシン3アプリゲストOS(フルOS)ハイパーバイザハードウェアコンテナ1アプリ+必要な部品だけコンテナ2アプリ+必要な部品だけコンテナ3アプリ+必要な部品だけコンテナエンジンホストOSカーネルを全コンテナで共有ハードウェアVMはゲストOSごと積むので重い/コンテナはホストのカーネルを共有しOSを積まないので軽い

だから起動はプロセス生成と同じ速さ(ミリ秒〜秒)で、メモリのフットプリントも小さい。「軽い」の正体は、OSを2枚重ねていないことにあります。逆に言えば、ホストと違うカーネルが要る環境(別系統のOS)はそのままでは動かせない、という制約もここから来ます。

登場人物メモ:

  • ※1 namespace — プロセスから見える範囲(PID・ネットワーク等)を仕切るカーネル機能
  • ※2 cgroups — CPU・メモリなど使える資源に上限をかけるカーネル機能
  • ※3 overlayfs — 複数の層を1枚に重ねて見せる、コピーオンライトのファイルシステム
やさしく言うと(中級

Docker の世界は、イメージコンテナの2語で回っています。

  • イメージ(※1)— 箱の設計図。不変のテンプレートで、これ自体は動かない
  • コンテナ — イメージを起動して動いている実体(実行インスタンス)

イメージは1枚岩ではなく、レイヤ(※2)=差分の積み重ねでできています。下からベースOS層・ランタイム/ライブラリ層・アプリ層…と重なり、同じ層は共有・キャッシュされます。だから配布もビルドも速く済みます。

アニメーション『イメージとコンテナのレイヤ』を開く
イメージ(設計図)読み取り専用レイヤの積み重ね・不変コンテナ(実行中)同じイメージ+薄い読み書き層1つの設計図アプリ層自分のアプリランタイム/ライブラリ層必要な部品ベースOS層土台の最小環境起動アプリ層(共有)ランタイム/ライブラリ層(共有)ベースOS層(共有)コンテナ1読み書き層(薄い)コンテナ2読み書き層(薄い)下位レイヤは1つを共有・各コンテナは薄い読み書き層だけを持つ同じ層は再ダウンロードしない=ディスクもキャッシュも節約でき、配布も速い変えたい所だけ差分レイヤを積み替えればよいので、作り直しも軽い

登場人物メモ:

  • ※1 イメージ — アプリと環境を固めた不変のテンプレート
  • ※2 レイヤ — イメージを構成する差分の層。共有・再利用でき、キャッシュが効く
  • ※3 レジストリ — イメージを保管・配布する倉庫(Docker Hub など)

6つの名前空間と cgroups

中級では「namespaceで見える範囲を仕切る」と一言でまとめました。実際には 種類ごとに別々のnamespace があり、組み合わせて1つのコンテナの隔離を作ります。

  • pid — プロセス番号を独立させる。コンテナ内の最初のプロセスがPID 1に見える
  • net — ネットワーク(インターフェース・ルーティング表・ポート)を独立させる
  • mnt — マウント点(見えるファイルシステムの木)を独立させる
  • uts — ホスト名・ドメイン名を独立させる
  • ipc — プロセス間通信(共有メモリ等)を独立させる
  • user — ユーザ/グループIDを対応づけ直す。コンテナ内のroot(0)をホストの非特権IDへ写せる

一方 cgroups は「見える/見えない」ではなく を扱います。CPU時間の割り当て、メモリ上限(超えるとOOMで停止)、ブロックI/Oやプロセス数の制限。1つのコンテナが暴走してもホストごと道連れにしにくいのは、この資源の壁があるからです。namespaceが隔離、cgroupsが制限——この二本立てが腑に落ちれば、コンテナの体は見えたも同然です。

やさしく言うと(中級

なぜ「軽いのに隔離されている」のか。土台の OSの仕組み が鍵です。

  • namespace(名前空間) — プロセスから見える範囲を仕切る。他のコンテナやホストが見えないので、独立して見える
  • cgroups — CPUやメモリなど使える資源に上限をかける。1つが暴走しても巻き込まれにくい
  • カーネルは共有 — 別OSを積まないので、起動は秒単位で軽い

つまりコンテナは「小さな仮想マシン」ではなく、隔離して資源制限したプロセスだと捉えると腑に落ちます。

OCIイメージとoverlayfs

イメージが「レイヤの積み重ね」であることは中級で見ました。上級の勘所は、その層を 実行時にどう1枚へ重ねるか です。仕様は OCI(Open Container Initiative)で標準化され、各層はtarで固めた差分の集まりです。

  • lower — イメージ由来の 読み取り専用 の層(複数枚を下から重ねる)
  • upper — コンテナ起動時に付く 書き込み可能 な層
  • merged — lowerとupperを重ねて見せる、実際に中で見えるファイル木
アニメーション『イメージとコンテナのレイヤ』を開く
イメージ(設計図)読み取り専用レイヤの積み重ね・不変コンテナ(実行中)同じイメージ+薄い読み書き層1つの設計図アプリ層自分のアプリランタイム/ライブラリ層必要な部品ベースOS層土台の最小環境起動アプリ層(共有)ランタイム/ライブラリ層(共有)ベースOS層(共有)コンテナ1読み書き層(薄い)コンテナ2読み書き層(薄い)下位レイヤは1つを共有・各コンテナは薄い読み書き層だけを持つ同じ層は再ダウンロードしない=ディスクもキャッシュも節約でき、配布も速い変えたい所だけ差分レイヤを積み替えればよいので、作り直しも軽い

肝は コピーオンライト(※3 overlayfsの働き)。読むだけならlowerをそのまま参照し、書き換えるときだけupperへコピー して変更を載せます。だから同じベースイメージから100個コンテナを立てても、共有される下層は1回分のディスクで済む。イメージが不変(immutable)で、差分だけが書き込み層に溜まる——この分離が、配布の速さと使い捨てのしやすさを同時に生みます。コンテナを消すとupperごと消えるので、残したいデータはボリュームへ逃がす、という中級の作法もここに根拠があります。

コンテナネットワーク

netのnamespaceで「独立したネットワーク」を持つと言いましたが、では外とどうつながるのか。標準的なブリッジ構成はこうです。

  • veth ペア — 仮想的なLANケーブルの両端。片端をコンテナのnamespace内に、もう片端をホスト側に置く
  • bridge(docker0など) — ホスト上の仮想スイッチ。各コンテナのveth端をここに挿し、コンテナ同士を同じ内部ネットワークに乗せる
  • NAT — 外へ出る通信はホストのIPへ アドレス変換。外から特定コンテナへ入れたいときは、ホストのポートをコンテナのポートへ転送(ポートマッピング)する

つまりコンテナは、ホストの中に作られた小さなLANの住人です。IPは起動のたびに変わり得るので、宛先を名前で解決する仕組み(内部DNS)や、複数の入口をまとめる ロードバランサ が上に乗ってきます。この「入れ替わる相手をどう束ねるか」という課題は、そのまま Kubernetes 側の主題へ続きます。

攻撃面を狭める

カーネルを共有するということは、カーネルの穴がホスト全体の穴になりうるということです。だからコンテナは「隔離できている」で満足せず、権限をどこまで削れるかを詰めます。

  • capabilities — rootの全能をこまかい権限に分割したもの。コンテナには本当に要る一握りだけ残し、残りを落とす(例 生のパケットを扱う権限を外す)
  • seccomp — プロセスが呼べる システムコールを絞る フィルタ。使わない危険な呼び出しを禁止し、カーネルへの攻撃面を狭める
  • rootless — userのnamespaceを使い、コンテナ内のrootをホストの非特権ユーザへ写す。万一抜け出されてもホストではただの一般ユーザなので、被害の天井が下がる

「動く」だけなら特権を全部与えても動きます。しかし共有カーネルという前提では、与えない設計(最小権限) こそが隔離の質を決めます。ここは Linux の権限モデルと地続きです。

⚠️ 深いところの落とし穴

  • --privileged の乱用 — capabilitiesもseccompも実質無効化され、隔離はほぼ名ばかりになる。抜け出されればホストごと危ない
  • 共有カーネルの脆弱性 — VMより境界が薄い。カーネルの穴は全コンテナに響くので、ホストのパッチ運用が命綱
  • 書き込み層への溜め込み — 使い捨て前提のupperにデータを書き続けると肥大化し、コンテナ削除で消える。永続化はボリュームへ
  • latestとキャッシュの罠 — タグ固定を怠ると、同じDockerfileでも下層イメージが変わって再現性を失う
やさしく言うと(中級
  • イメージの肥大化 — 不要な物まで層に積むと重くなる(最小限の土台を選ぶ)
  • 状態の消失 — コンテナは使い捨て前提。消えて困るデータは外部(ボリューム等)へ
  • カーネル依存 — ホストと大きく異なるOSは共有カーネルでは動かせない
  • latest 頼み — バージョンを固定しないと、環境ごとに中身がずれて「動かない」が再発する

理解度チェック

そのまま解けます(成績は保存されません)。無料アカウントを作ると、学習の記録と進捗の山登りが始まります。

1. namespaceとcgroupsの役割分担として正しいのは?

2. overlayfsのコピーオンライト(copy-on-write)の説明として正しいのは?

3. user namespace の働きとして正しいのは?

4. seccompの役割として正しいのは?

5. コンテナ実行で `--privileged` を乱用したときに起きる問題として正しいのは?

6. コンテナを外とつなぐ標準的なブリッジ構成で、仮想的なLANケーブルの両端に相当する「ペア」を何と呼びますか。

7. コンテナイメージや実行時の仕様を標準化する取り組み(Open Container Initiative)の略語を、英字3文字で答えてください。

8. コンテナが「他のコンテナやホストが見えない」ように隔離される中心の仕組みは?