Webエンジニアの中田です。
今回は、「Webエンジニアなら知っておきたい!Dockerコンテナを支える技術」と題して、Dockerをはじめとしたコンテナについて少し深掘りしたいと思います。
2013年のDockerの登場以来、Linuxのコンテナ技術は普及し、Web開発の現場からプロダクション環境まで、活用範囲を広げています。
コンテナ化によってアプリケーションの移植性が向上しました。
煩雑な環境構築や構成管理の差異をコンテナが吸収してくれるため、「開発環境では動いたのに」といった問題に悩まされることなく、どこでも安定して実行できるようになりました。
現在ではDocker Desktopをはじめとしたツール・エコシステムも成熟しており、その仕組みについて理解せずとも十分活用できます。
しかし、これらの仕組みについて知っておくことは、単なる知識にとどまらず、トラブルシューティングや、周辺エコシステムへの深い理解に繋がると私は考えています。
Linuxカーネル内部の厳密な挙動や実装の詳細まで踏み込むと、非常に難解です。
この記事では、複雑な仕組みをシンプルに捉えるため、今回はコンテナがどのように「隔離」を実現しているのか、「プロセス・ネットワークの隔離」「リソース制限」「ファイルシステムの差分管理」の3つの観点から見ていきたいと思います。
コンテナ技術についての興味の第一歩になれば幸いです。
コンテナの正体は「丁寧に隔離されたプロセス」
まず初めに、よくコンテナは「軽量な仮想マシン(VM)」と混同されますが、技術的な実態は全く異なります。
| 種別 | 説明 |
|---|---|
| 仮想マシン(VM) | ハードウェアそのものを仮想化し、その上に独立したOS(カーネル)を載せるもの。 |
| コンテナ | ホストOSのカーネルを共有し、「プロセス」を隔離して別の空間にいるように見せかける技術。 |
つまり、ホストOS側から見れば、コンテナの中で動いているWebサーバーも、DBも、実は「ただの1つのプロセス」に過ぎません。
では、なぜ「ただのプロセス」が、あたかも独立したサーバーのように振る舞えるのでしょうか?
それを実現しているのが、Linuxカーネルに備わっている「Linux Namespaces」「cgroups」「UnionFS(OverlayFS)」といった機能群です。
1. プロセス・ネットワークの隔離 (Linux Namespace)
Webエンジニアが一番恩恵を受けているのが、このネットワークの隔離です。 通常、1つのOS上でWebサーバー(ポート80)を複数立ち上げようとすると、「ポート競合」のエラーが起きます。しかし、コンテナならそれぞれが独立したIPアドレスを持つため、競合することなくいくつでも立ち上げられます。
これを実現しているのが Linux Namespaces(名前空間) という機能です。
Linux Namespaceは、プロセスを特定の空間に閉じ込め、プロセスに対して「OSのリソースがあたかも自分専用であるかのように」見せる機能です。*1

特にNetwork Namespaceは、コンテナごとに独立したネットワークスタック(IPアドレス、ポート番号、ルーティングテーブルなど)を持たせます。これにより、コンテナAの80番ポートと、コンテナBの80番ポートは、完全に別のものとして扱うことができるのです。*2

※ Linux Namespacesには他にも、UTS(ホスト名、ドメイン名)、PID(プロセスID)、ユーザー(UID、GID)を隔離する機能も含まれています。
2. リソースの制限 (cgroups)
「あるコンテナがCPUを暴走させても、他のコンテナやホストOSに影響を与えない」 この安全性・安定性を担保しているのが cgroups (Control Groups) です。
Namespacesが「見え方」を隔離するのに対し、cgroupsは物理的な「使用量(リソース)」を制限します。
実は、ホストLinux上のプロセスはすべて cgroups によってグループ分けされています。
コンテナとして実行されるプロセスも例外ではなく、その仕組みの中で管理されています。
各グループには利用可能なリソース(CPUやメモリ)の上限が割り当てられており、特定のプロセスがリソースを食いつぶして他に影響を与えるのを防いでいます。

AWS ECSや、Kubernetesなどのオーケストレーションツールで コンテナごとにCPUやメモリ上限を設定することがありますが、裏側ではこのLinuxのcgroupsに対して「このプロセスグループはCPUを◯%までしか使ってはいけない」と命令が書き込まれているのです。
3. ファイルシステムの差分管理 (UnionFS / Layer構造)
最後に、「なぜコンテナは起動が一瞬で、ディスク容量を圧迫しないのか」という秘密であるファイルシステムです。
コンテナイメージは、「レイヤー(層)」と呼ばれる読み取り専用のファイルシステムを重ね合わせることで構成されています(Union File System)。*3
# イメージの履歴(層)を確認するコマンド例 $ docker history my-web-app:latest IMAGE CREATED CREATED BY SIZE <missing> 2 minutes ago CMD ["python" "app.py"] 0B <layer_id_A> 2 minutes ago COPY . /app 500KB <-- アプリの層 <layer_id_B> 3 weeks ago pip install flask 10MB <-- ライブラリの層 <layer_id_C> 1 month ago /bin/sh -c #(nop) ADD file:7dba... in / 80MB <-- OSの層
普段 docker build をした時、変更していない部分のビルドが一瞬で終わるのは、下層のレイヤーをキャッシュとして再利用しているからです。
また、コンテナを起動するということは、読み取り専用のイメージ層の上に、「薄い書き込み可能な層(コンテナレイヤー)」を1枚被せるようなイメージです。

コンテナ起動後、ファイルを変更しても、それは最上位の薄い層に書き込まれるだけで、下のイメージ自体は変更されません。これが、コンテナが「使い捨て(Disposable)」に適している技術的な理由です。
まとめ
コンテナ技術は、魔法のような新しい発明ではなく、Linuxカーネルが古くから持っていた「プロセス・ネットワークの隔離 (Namespace)」「リソース制限 (cgroups)」「ファイルシステムの差分管理 (UnionFS)」といった機能を、使いやすくパッケージングしたものです。
普段使っているdockerコマンドの裏では、これらのカーネル機能が呼び出され、複雑な設定を私たちの代わりに実行してくれています。
コンテナ = 「リソース制限付きの、隔離されたプロセス」
この理解を持っておくと、「なぜデータが消えるのか?」「なぜポートがつながらないのか?」「なぜコンテナイメージのビルドがおそいのか?」といったトラブルへ対処する際、より解像度の高いアプローチができると思います。
参考情報
- LXCで学ぶコンテナ入門 -軽量仮想化環境を実現する技術 (第2回 コンテナの仕組みとLinuxカーネルのコンテナ機能[1]名前空間とは? )
- LXCで学ぶコンテナ入門 -軽量仮想化環境を実現する技術 (第3回 Linuxカーネルのコンテナ機能[2] ─cgroupとは?(その1) )
- Docker&Kubernetesネットワークのしくみ ――クラウドネイティブに求められる情報通信の知識
- ストレージ ドライバ について
- What is an image?
*1:コンテナ内からは「自分しかいない」ように見えていますが、ホストOSからは全ての通信が見えています。
*2:Windows/Mac OSでのコンテナ環境では、Linux VMを起動しその中でコンテナを動作させています。これらのDockerDesktopなどのツールが ホストOSにポートの転送や名前解決ができるよう工夫してくれています。これによりホストOS側からHTTPリクエストなどが可能になっています。 詳しくはDocker社blogの「How Docker Desktop Networking Works Under the Hood」が参考になります。
*3:Docker-docs-ja「ストレージ ドライバ について」https://docs.docker.jp/storage/storagedriver/index.html#container-and-layers