Dockerの仕組みを原理から理解する|コンテナ隔離・アーキテクチャ・VMとの違い
Dockerは「動いた」「軽い」と言われますが、その軽さや隔離がどの技術で成り立っているかは見えにくい部分です。この記事では、docker CLIからカーネルまでの処理の流れ、Linuxカーネルのnamespacesやcgroupsによるコンテナ隔離の原理、イメージのレイヤ構造、そしてdocker runを打った瞬間に何が起きるかを順に解説します。
「Dockerとは何か」という基本の定義や仮想環境との位置づけを先に押さえたい場合は、姉妹記事のDockerとは何か?仮想環境との違いや特徴をわかりやすく解説を参照してください。本記事はその一歩先、内部で何が起きているかという仕組みに絞ります。
目次
まとめ:Dockerの仕組みの要点
先に結論を整理します。Dockerの仕組みは次の数点に集約できます。
- コンテナはホストOSのカーネルを共有するプロセスです。仮想マシンのようにゲストOSを丸ごと起動しないため、起動が速くメモリ消費が小さくなります。
- 処理は docker CLI → dockerd(デーモン)→ containerd → runc の順に渡されます。実際にコンテナを生成するのはOCI準拠ランタイムのruncです。
- 隔離の正体はLinuxカーネルの機能です。namespacesがプロセス・ネットワーク等の見え方を分離し、cgroupsがCPUやメモリの使用量を制限し、OverlayFSがファイルシステムをレイヤとして重ねます。
- イメージは読み取り専用レイヤの積み重ねで、Dockerfileの命令1つがほぼ1レイヤに対応します。コンテナ起動時には書き込み可能レイヤが上に追加されます。
以降で、アーキテクチャ・カーネル機能・イメージ・起動フローの順に、それぞれの仕組みを具体的に見ていきます。
Dockerの仕組みの全体像とアーキテクチャ
Dockerは単一の巨大なプログラムではなく、役割の異なる複数のコンポーネントが連携して動きます。コンテナ型仮想化(OSレベル仮想化)の一種で、ホストOSのカーネルを共有したまま、アプリケーションを隔離された空間で実行します。
コンテナ型仮想化(OSレベル仮想化)の基本構造
コンテナ型仮想化は、1つのLinuxカーネルの上に隔離された複数の実行環境を作る方式です。各コンテナはホストと同じカーネルを使い、その上で「隔離されたプロセス」として動きます。OSそのものを仮想化しないため、ハードウェアをエミュレートする従来の仮想マシンより層が薄く、これが軽量さの根拠になります。
docker CLIからカーネルまでの処理の流れ
ユーザーが入力するdocker runやdocker buildといったコマンドは、次の順で下位へ渡されます。
| コンポーネント | 役割 |
|---|---|
| docker CLI | コマンドを受け取りREST API経由でdockerdへ送る |
| dockerd(Docker daemon) | イメージ・ネットワーク・ボリュームの管理、レジストリ認証 |
| containerd | コンテナのライフサイクル管理(CNCFプロジェクト) |
| runc | OCI準拠ランタイム。カーネル機能を呼び実際にコンテナを生成 |
docker CLIはUnixソケット(/var/run/docker.sock)を通じてdockerdにAPIリクエストを送ります。dockerdはgRPCでcontainerdを呼び、containerdが各コンテナごとにshimプロセスを介してruncを起動します。この分業のおかげで、dockerdを再起動しても稼働中のコンテナはshimに支えられて生き続けます。
ホストOSとカーネル共有の意味
runcはclone()システムコールで新しいプロセスを作り、そこにnamespacesとcgroupsを適用します。コンテナの中で動くプロセスは、ホストから見れば通常のプロセスの1つです。つまりコンテナは「軽量な仮想マシン」ではなく、カーネルを共有した隔離プロセスだと理解するのが正確です。runcはOCI(Open Container Initiative)仕様に準拠しており、Dockerは既定でこのruncをコンテナランタイムに使います。
コンテナと仮想マシン(VM)の違いと軽量さの理由
「軽い」と言われる理由は、仮想マシンと並べると明確になります。違いはどの層を仮想化するかにあります。
VMとの違い:ゲストOS・ハイパーバイザの有無
| 観点 | コンテナ(Docker) | 仮想マシン |
|---|---|---|
| OSの扱い | ホストのカーネルを共有 | ゲストOSを各VMに丸ごと搭載 |
| 仮想化層 | カーネル機能(namespaces等) | ハイパーバイザ |
| 起動時間 | 秒〜ミリ秒単位 | OS起動ぶん(数十秒〜) |
| サイズの目安 | 数MB〜数百MB | GB単位 |
仮想マシンはハイパーバイザがハードウェアをエミュレートし、その上でゲストOSのカーネルを起動します。一方コンテナはゲストOSもハイパーバイザも持たず、ホストのカーネルをそのまま使います。OSを起動する処理が丸ごと不要になるため、起動が速くメモリも食わない、という関係です。軽さは「ゲストOSを起動しないから」という理由とセットで理解してください。
LXCとの違い
DockerはもともとLinuxの標準的なコンテナ技術であるLXC(Linux Containers)を実行環境として利用していました。両者ともnamespacesとcgroupsを土台にしますが、LXCがOSに近い「軽量サーバ」を作る使い方を想定するのに対し、Dockerは1コンテナ1プロセスを基本とし、イメージのレイヤ管理・ビルド・配布(レジストリ)までを一体化した点が異なります。アプリ単位での再現性ある配布を主目的に据えたのがDockerだと捉えると整理しやすくなります。
仮想マシンを選ぶべき場面
コンテナが常に上位互換というわけではありません。ホストと異なるカーネル(たとえばLinuxホスト上でWindowsカーネルを必要とするワークロード)を動かす、テナント間でカーネルレベルの完全な分離をセキュリティ要件として求める、といった場合は仮想マシンが適します。コンテナはカーネルを共有する以上、カーネルの脆弱性は全コンテナに波及しうるためです。
Linuxカーネル機能で実現するコンテナ隔離の仕組み
ここがDockerの仕組みの核心です。Dockerの隔離はDocker独自の魔法ではなく、Linuxカーネルが備える3つの機能の組み合わせで成り立っています。
namespaces:見え方の隔離
namespacesは、プロセスから見えるシステム資源を分離する仕組みです。コンテナごとに異なるnamespaceを割り当てることで、各コンテナは自分専用のシステムを持っているように振る舞います。主なものは次のとおりです。
- PID:プロセスID空間を分離。コンテナ内ではPID 1から始まる独自のプロセスツリーになる
- NET:ネットワークインターフェース・IPアドレス・ポートを分離
- MNT:マウントポイント(ファイルシステムの見え方)を分離
- UTS:ホスト名・ドメイン名を分離
- IPC:プロセス間通信の資源を分離
- USER:ユーザーID・グループIDを分離(コンテナ内のrootをホストの一般ユーザーに対応づけられる)
コンテナ内でpsを実行しても自分のプロセスしか見えないのは、PID namespaceによる隔離の結果です。
cgroups:資源の制限
cgroups(control groups)は、プロセスグループ単位でCPU・メモリ・ディスクI/O・ネットワーク帯域などの使用量を制限・計測する仕組みです。1つのコンテナがメモリを使い切ってホスト全体を巻き込む事態を防ぐのがこの機能の役割です。たとえばdocker run --memory=512mのような指定は、内部的にcgroupsのメモリ上限として適用されます。現在のディストリビューションではcgroup v2が主流で、Docker Engineも20.10以降はcgroup v2を標準サポートしています(v1から資源管理の構造が見直されました)。
OverlayFS:ファイルシステムのレイヤ化
OverlayFSはユニオンファイルシステムの一種で、複数のディレクトリ層を重ねて1つのファイルシステムに見せます。Dockerはこれを使い、読み取り専用のイメージレイヤの上に、コンテナ専用の書き込み可能レイヤを重ねます。コンテナ内でファイルを変更しても、元のイメージレイヤは変わらず、差分だけが上の層に書かれます。同じイメージから多数のコンテナを起動してもディスクを無駄に消費しないのは、下位レイヤを共有しているためです。
Dockerイメージとレイヤの仕組み
コンテナの実体を準備するのがイメージです。イメージは1枚岩のファイルではなく、読み取り専用レイヤを積み重ねた構造を持ちます。
Dockerfileの命令とレイヤの対応
イメージはDockerfileから作られ、FROMRUNCOPYなどの命令が、ほぼそれぞれ1つのレイヤを生みます。次は最小のDockerfile例です。
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "app.py"]
この例では、ベースイメージの上に作業ディレクトリ指定・依存ファイルのコピー・パッケージインストール・アプリ本体のコピーが順にレイヤとして積まれます。各レイヤは前のレイヤとの差分として保存されます。
ビルドキャッシュと命令の並び順
Dockerはビルド時、変更のないレイヤをキャッシュから再利用します。上の例でrequirements.txtのコピーとインストールをアプリ本体のコピーより前に置いているのは、ソースコードを変えてもライブラリのインストール層をキャッシュから使い回し、再ビルドを短縮するためです。命令の並び順がビルド時間を直接左右する、という点が実務上の勘所になります。Dockerfileの役割をさらに詳しく知りたい場合はDockerfileとは何か?その基本的な役割と重要性についてを参照してください。
コンテナ起動時に何が起きるか
ここまでの部品が、docker runを打った瞬間にどう組み合わさるかを順に追います。
docker run -d -p 8080:80 nginx
このコマンドを実行すると、内部ではおおむね次の処理が走ります。
- 指定イメージがローカルになければレジストリからpull(各レイヤをダウンロード)
- イメージの読み取り専用レイヤをOverlayFSで展開し、上に書き込み可能レイヤを追加
- 新しいnamespacesを生成し、プロセス・ネットワーク・マウントなどを隔離
- cgroupsを割り当て、CPUやメモリの上限を設定
- runcが
clone()で隔離されたプロセスを起動し、イメージで指定されたコマンド(この例ではnginx)を実行
-p 8080:80はホストの8080番ポートをコンテナの80番へ転送する設定で、NET namespaceで分離されたコンテナのネットワークとホストを橋渡しします。コンテナを起動する具体的な操作手順はDockerコンテナを起動するための基本的な手順と実践方法でまとめています。
Docker Composeとレジストリのしくみ
単一コンテナの仕組みを押さえたら、複数コンテナと配布の仕組みも短く確認しておきます。
Docker Composeによる複数コンテナの定義
Docker Composeは、複数のコンテナの構成をcompose.yaml(旧称docker-compose.yml)という設定ファイルに記述し、まとめて起動・管理するツールです。アプリ本体とデータベースのように複数サービスが連携する構成を、1ファイルとして定義できます。
services:
web:
build: .
ports:
- "8080:80"
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: example
docker compose upを実行すると、定義した全サービスが起動します。Dockerfileが「1つのイメージの作り方」を記述するのに対し、Composeファイルは「複数コンテナの組み合わせ方」を記述する、という役割分担になります。
レジストリによるイメージの配布
レジストリはイメージを保管・配布する仕組みで、Docker Hubが代表例です。docker pullでレジストリからイメージを取得し、docker pushで自作イメージを登録します。イメージはレイヤ単位で転送されるため、共通の下位レイヤは再ダウンロードされず、配布が効率化されます。
Dockerの仕組みを踏まえた注意点と向かない場面
仕組みを理解すると、Dockerが不得手な領域も見えてきます。ここは曖昧にせず言い切ります。
第一に、ホストと異なるカーネルを必要とするワークロードには向きません。コンテナはホストのカーネルを共有するため、Linuxホスト上でWindows専用のシステムコールに依存するアプリをネイティブに動かすことはできません。Windowsコンテナを使う場合はWindowsホスト(またはWindows用の実行基盤)が前提になります。
第二に、GUIアプリやデスクトップ用途、シビアなリアルタイム性能を要求する処理は適しません。Dockerはサーバープロセスを隔離して動かす設計で、画面表示やデバイスへの低遅延アクセスを主眼にしていないためです。
よくある失敗パターンが、データをコンテナ内に直接置いてしまうケースです。コンテナを削除すると書き込み可能レイヤごとデータが消えます。永続化が必要なデータは、必ずボリュームを使ってコンテナの外に逃がします。
docker run -v my_volume:/var/lib/postgresql/data postgres:16
カーネル共有という仕組み上、コンテナ間の分離は仮想マシンほど強固ではない点も判断材料になります。マルチテナントで強い分離が要件なら、コンテナ単体に頼らず仮想マシンや専用の隔離技術を併用する設計を選んでください。なお、Docker EngineやDocker Desktopの最新バージョン・ライセンス条件は変動が速いため、導入前に公式ドキュメント(docs.docker.com)で確認することをおすすめします。特にDocker Desktopは、一定規模以上の商用利用で有償サブスクリプションが必要になる場合があります。
よくある質問
Dockerの読み方は?
「ドッカー」と読みます。英語表記Dockerをそのままカタカナにしたもので、イメージは「ドッカーイメージ」と呼ばれます。船荷を規格化されたコンテナで運ぶ港湾労働者(docker)になぞらえた命名で、アプリを規格化されたコンテナとして運ぶ、という発想がそのまま名前に反映されています。
DockerとVM(仮想マシン)の違いは?
最大の違いはOSの扱いです。仮想マシンはハイパーバイザの上でゲストOSを丸ごと起動しますが、DockerのコンテナはホストOSのカーネルを共有し、その上で隔離されたプロセスとして動きます。ゲストOSの起動が不要なぶん、コンテナは起動が秒〜ミリ秒単位と速く、サイズも数MB〜数百MBと小さく収まります。一方、ホストと異なるカーネルを動かす用途や強い分離が要る用途では仮想マシンが適します。
「docker とは」と「docker 仕組み」の違いは?
「とは」はDockerが何をする道具かという定義・概要を指し、「仕組み」は内部でどう動くかという原理を指します。本記事は後者に絞っています。Dockerの定義や仮想環境との位置づけといった基本から知りたい場合は、姉妹記事のDockerとは何か?仮想環境との違いや特徴をわかりやすく解説から読み始めると、本記事の仕組みの解説にスムーズにつながります。
LXCとDockerの違いは?
どちらもLinuxのnamespacesとcgroupsを土台にしたコンテナ技術ですが、目的が異なります。LXCはOSに近い軽量サーバ環境を作る使い方を想定するのに対し、Dockerは1コンテナ1プロセスを基本とし、イメージのレイヤ管理・ビルド・レジストリでの配布までを一体化しています。アプリ単位で再現性ある環境を配布する用途に最適化されているのがDockerです。Docker自身も初期はLXCを実行環境に使っていました。
Dockerはいつ誰が作った?
2013年3月15日、PyCon 2013でSolomon Hykes氏がDockerを公開しました。開発元はPaaS事業を手がけるdotCloud社で、同社はその後Docker, Inc.へ改称しています。公開当初の実行環境にはLXCが使われ、後にruncを中心とした独自のランタイム構成へ移行しました。発表動画が反響を呼び、会社の主軸がDockerへ移ったという経緯があります。