コンテナとハイパーバイザー:カーネルの「相乗り」か「独占」か、現場で問われる真の境界線
インフラエンジニアの皆さんは、本番環境のデプロイ戦略を練る際、単に「コンテナが軽いから」という理由だけで選定していませんか?
「ハイパーバイザー(VM)よりコンテナの方が起動が速い」「リソース効率が良い」といった謳い文句は、技術書を開けば必ず目にする定説です。しかし、なぜそうなるのか。その裏側でCPUのシステムコールがどう処理され、メモリ空間がどう切り分けられているのか。この「構造的な理解」がないと、いざ本番環境でゾンビプロセスが発生したり、ネットワークスループットが期待値に届かなかったりした際、どこを深掘りすればいいか途方に暮れることになります。
今日は、第一線の現場で培った知見をもとに、コンテナとVMの「魂」とも言えるカーネル共有の仕組みを解剖していきます。
1. カーネルの「共有」対「独占」:システムコールの視点から
ハイパーバイザー型仮想化(KVM, VMwareなど)とコンテナ仮想化(Docker, LXC)の決定的な違いは、「どの階層までをゲストOSが持っているか」にあります。
ハイパーバイザー型の世界
VMはハードウェアをエミュレートし、その上に独立したカーネルを動かします。アプリケーションが read() や socket() といったシステムコールを発行すると、ゲストOSのカーネルが受け取り、仮想デバイスドライバを経由して、ホスト側のハイパーバイザーへと「翻訳」されて渡されます。
- メリット: カーネルレベルでの強力な隔離。万が一ゲストOSがパニックを起こしても、ホストや他のVMには影響が出にくい。
- オーバーヘッド: ゲストOSの起動に伴うカーネル初期化時間と、仮想ハードウェアとのI/O翻訳コストが常にのしかかります。
コンテナ(Docker)の世界
コンテナはホストOSのカーネルを「直接」使います。Dockerがやっているのは、Linuxの namespaces(隔離)と cgroups(リソース制限)を駆使した、OSレベルの「箱庭」作りです。
システムコールはホストOSへ直行します。コンテナの起動が数秒で終わるのは、カーネルが既に動いており、プロセスを一つ隔離して起動するだけだからです。
2. パフォーマンスに直結するオーバーヘッドの正体
実務においてWeb APIのレスポンスタイムが p99 で悪化する際、インフラ側が疑うべきポイントは「コンテキストスイッチ」の頻度です。
- VMの場合: ゲストとホストを行き来するVM Exit/VM Entryが発生し、CPUのパイプラインが乱れます。
- コンテナの場合: カーネル共有のため、ホスト上の他のプロセスと同じ土俵でスケジューリングされます。
APIの負荷試験をする際は、必ずホスト側で perf コマンドを使ってオーバーヘッドを監視してください。
# ホストOS上でコンテナ内のシステムコール発行状況をトレースする例
# コンテナのPIDを特定し、カーネル内での滞留時間を計測する
perf stat -p <コンテナの親プロセスID> -e syscalls:sys_enter_read,syscalls:sys_enter_write
3. 実践:隔離性の代償をどう制御するか
コンテナはカーネルを共有しているため、設定を誤ると隣のコンテナに干渉したり、ホストOSを道連れにしたりします。ここで重要になるのが seccomp プロファイルと capabilities の適切な制御です。
例えば、コンテナ内で特権が必要な操作を制限する設定は、実務では必須の「防御壁」です。
# docker-compose.yml でのセキュリティ強化設定の例
services:
web-api:
image: my-app:latest
# 不要なシステムコールをブロックする(デフォルトのseccompは強力だが、必要に応じて制限)
security_opt:
- no-new-privileges:true
# 必要最小限の能力(Capability)のみ付与
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
4. API通信の「リアル」:パケットはどこを通るのか
コンテナ間の通信を考える際、Kubernetesなどの環境では veth pair という仮想的なLANケーブルが使われます。
1. コンテナ内: eth0 からパケットが出る。
2. veth pair: ホスト側の vethxxxx に到達。
3. ブリッジ: ホストのブリッジ(docker0 等)を通過。
4. iptables/Netfilter: ここでNATやフィルタリングの処理が行われる。
この「経路」に多くのコンテナが密集すると、Netfilterのルール数が肥大化し、通信遅延(Latency)の原因になります。APIのレスポンスが妙に遅い場合、まずはこの iptables のルール数や、conntrack テーブルの枯渇を疑ってください。
# 現在のconntrackテーブルの使用量を確認
# 溢れるとパケットがドロップし、APIの5xxエラーが急増します
sysctl net.netfilter.nf_conntrack_count
sysctl net.netfilter.nf_conntrack_max
最後に:使い分けの哲学
結論として、私はこう考えます。
「強い隔離性と、OSごとの柔軟なチューニングが必要ならVM(またはKubeVirtのようなVM on K8s)」であり、「高密度でスケーラブルなWeb APIを低コストで回すならコンテナ」です。
「とりあえずコンテナ」と決める前に、そのサービスがカーネルの共有によるオーバーヘッドを許容できる構造になっているか、あるいはセキュリティポリシーがコンテナの隔離レベルで本当に担保できるか。そこを考えるのが、シニアエンジニアの矜持です。
皆さんのインフラが、今日も安定してパケットを捌き続けることを願っています。何かトラブルがあれば、まずは dmesg を見て、カーネルが悲鳴を上げていないかを確認するところから始めてみてくださいね。
コメント