仮想化とコンテナ

目次

主要項目のみを表示しています。詳細な小見出しは本文内で確認できます。

概要

1台の計算機を、どこで分けるか

仮想化とコンテナは、計算資源を複数の実行環境へ分けるための技術です。どちらも「隔離」「再現性」「移動しやすさ」を支えますが、分ける場所が違います。仮想マシンはハードウェアに近い層でOSごと分け、コンテナはOSカーネルを共有しながらプロセスの見える世界を分けます。

要点

VMは「別のマシンがあるように見せる」技術で、コンテナは「同じOS上のプロセスを別環境のように見せる」技術です。軽さだけでなく、隔離の強さ、起動時間、運用責任、セキュリティ境界まで含めて選びます。

この章で重視すること

  • 仮想化とコンテナを、抽象化する境界の違いとして理解する
  • CPU、メモリ、I/O、ネットワークが仮想化される流れを押さえる
  • namespacecgroup、capability、seccompがコンテナを支えることを理解する
  • OCI、イメージ、レジストリ、ランタイム、Kubernetesの役割をつなげる
  • セキュリティと運用を、実行時だけでなくビルドからデプロイまでの流れで見る

全体像

仮想化とコンテナは、どちらもアプリケーションの下に「もう一段の境界」を作ります。境界が下にあるほど隔離は強くなりやすく、上にあるほど軽くなりやすいです。

flowchart TB A["アプリケーション"] --> B["コンテナ境界<br>プロセス、ファイルシステム、ネットワーク、資源制限"] B --> C["ホストOSカーネル"] C --> D["仮想マシン境界<br>仮想CPU、仮想メモリ、仮想デバイス"] D --> E["ハイパーバイザ / VMM"] E --> F["物理ハードウェア"]

この図は厳密な実装階層というより、考え方の地図です。コンテナはOSの機能でプロセスを分け、VMはハイパーバイザがハードウェアらしさを作ります。クラウドでは、VMの上にコンテナを載せ、その上でKubernetesがコンテナ群を管理する構成もよく使われます。

なぜ仮想化が重要になったか

仮想化は、単に「1台のサーバで複数のOSを動かす」ためだけの技術ではありません。現代のクラウドでは、テナントを分け、障害影響を閉じ込め、資源を効率よく詰め込み、運用を自動化するための基盤です。VMがあるから、利用者は物理サーバの調達を待たずに、数分で計算資源を得られます。

歴史的には、メインフレーム時代から仮想化は資源共有の手段でした。その後、x86サーバの普及、ハードウェア支援仮想化、クラウド、コンテナ、Kubernetesへと進みました。コンテナはVMを置き換えたというより、VMの上に載るアプリケーション配布単位として広がりました。

timeline title仮想化とコンテナの流れ メインフレーム時代 : 時分割と仮想マシンの考え方 2000年代 : x86仮想化とサーバ集約 2010年前後 : IaaSとクラウドVM 2013年前後 : Dockerによるコンテナ普及 2014以降 : Kubernetesとクラウドネイティブ運用 現在 : microVM、rootless、confidential computing、サプライチェーン保護

この章では、仮想化を「抽象化」、コンテナを「パッケージングと実行境界」、Kubernetesを「望ましい状態を維持する制御面」として見ると全体がつながります。

VMとコンテナの違い

VMは、ゲストOSから見ると独立した計算機のように見えます。ゲストOSは仮想CPU、仮想メモリ、仮想ディスク、仮想NICを持ち、ハイパーバイザがそれらを物理資源へ対応づけます。Linux、Windows、BSDなど異なるOSを同じ物理ホスト上で動かせるのは、この境界がOSより下にあるためです。

コンテナは、ホストOSカーネルを共有します。コンテナ内のプロセスは、専用のPID空間、専用のマウント空間、専用のネットワーク空間を持つように見えますが、システムコールを処理するカーネルはホストと同じです。そのため起動が速く、イメージも軽くしやすい一方、カーネル境界を共有することを前提に設計する必要があります。

観点 VM コンテナ
分離する単位 マシン / OS プロセス / 実行環境
カーネル ゲストOSごとに持つ ホストカーネルを共有
起動 OS起動が必要で重め プロセス起動に近く軽い
隔離 強い境界を作りやすい 設定とカーネルに依存
配布単位 ディスクイメージ レイヤ化されたコンテナイメージ
主な用途 IaaS、異種OS、強い分離 アプリ配布、スケール、CI/CD
ヒント

「VMかコンテナか」は二者択一ではありません。実際のクラウドでは、VMでテナント境界を作り、その中でコンテナを使ってアプリケーション境界を作ることがよくあります。

隔離モデルで見る

隔離を考えるときは、「何が共有されているか」を見るのが近道です。共有されるものが多いほど軽くなりやすい一方、境界設計を誤ったときの影響範囲も大きくなります。

実行形態 共有するもの 分離されるもの 注意点
通常プロセス OS、カーネル、ファイルシステム アドレス空間、権限 依存関係やポートが衝突しやすい
chroot OS、カーネル、資源 ルートディレクトリの見え方 セキュリティ境界としては弱い
コンテナ カーネル namespacecgroup、rootfs カーネル共有と権限設定が重要
sandboxed container 一部のカーネル面を抽象化 システムコール面、VM境界など 互換性や性能との交換条件
VM 物理ホスト、ハイパーバイザ ゲストOS、仮想ハードウェア 起動と運用は重くなりやすい
ベアメタル ほぼなし 物理マシン単位 集約効率や自動化が課題

この表は「上のほうが安全」と単純に読むものではありません。たとえば、よく管理されたコンテナ基盤のほうが、放置されたVMより安全なこともあります。境界の種類と運用の成熟度をセットで考えます。

仮想化の基本モデル

仮想化の中心にあるのは、ゲストから見える資源と、実際の物理資源の対応づけです。ゲストOSは「自分専用のCPUとメモリとデバイスがある」と思って動きますが、実際にはハイパーバイザが複数のVMの要求を調停しています。

Type 1とType 2

Type 1ハイパーバイザは、ハードウェア上で直接VMを管理します。クラウドやサーバ用途で使われることが多く、KVM、Xen、Hyper-V、ESXiなどが代表例です。Type 2ハイパーバイザは、通常のOS上のアプリケーションとして動きます。開発環境やデスクトップ用途ではVirtualBox、VMware Workstation、Parallelsなどがこの感覚に近いです。

ただし現代の実装は単純な分類だけでは説明しきれません。たとえばKVMはLinuxカーネルの機能としてVM実行を支え、QEMUがデバイスエミュレーションや管理を担当します。つまり「カーネル内の高速な実行機構」と「ユーザー空間のデバイス・管理機構」が組み合わさっています。

完全仮想化と準仮想化

完全仮想化では、ゲストOSを大きく変更せずに動かすことを目指します。ハードウェア支援仮想化が普及する前は、特権命令を捕まえたり変換したりする工夫が重要でした。現在はIntel VT-x、AMD-V、Arm Virtualization Extensionsなどにより、CPUが仮想化を直接支援します。

準仮想化では、ゲストが仮想化環境で動いていることを理解し、ハイパーバイザ向けのインターフェースを使います。代表例が virtio です。完全に実物のNICやディスクを模倣するより、仮想化向けに設計されたデバイスを使うほうが効率的です。

ハイパーバイザの責務

ハイパーバイザは「VMを起動するプログラム」ではなく、複数のゲストOSが同時に動いても安全であるように、CPU、メモリ、I/O、割り込み、時刻、デバイス、管理APIを調停する層です。

代表的な責務は次です。

  • vCPUを物理CPUへスケジュールする
  • ゲスト物理メモリをホスト物理メモリへ対応づける
  • 仮想デバイスを提供し、I/Oを処理する
  • 割り込みやタイマーを仮想化する
  • VMの停止、再開、スナップショット、移行を管理する
  • 管理APIを通じてクラウド基盤や運用ツールと接続する

KVM/QEMU構成では、KVMがカーネル内でCPU仮想化を支え、QEMUがデバイスモデルやVM管理の多くを担います。libvirtは、その上に管理用の抽象APIを提供します。クラウド基盤では、さらに上にOpenStack、CloudStack、各クラウド事業者の制御面が載ります。

flowchart TB A["クラウド制御面 / 管理API"] --> B["libvirt / 管理レイヤ"] B --> C["QEMU<br>デバイスモデル・プロセス管理"] C --> D["KVM<br>CPU仮想化・VM実行支援"] D --> E["Linuxカーネル"] E --> F["物理CPU / メモリ / I/O"]

CPU仮想化

CPU仮想化では、ゲストOSの命令実行を安全に進めながら、特権操作だけをハイパーバイザが制御します。ゲストアプリケーションは通常のユーザーモードで実行され、ゲストカーネルは「自分が特権モードにいる」と考えますが、実際にはハイパーバイザがより外側で制御しています。

重要なのは、すべての命令をエミュレーションしているわけではない点です。多くの通常命令はCPU上で直接実行されます。ハイパーバイザが介入するのは、I/O、割り込み、ページテーブル、特権レジスタ、VM exitを伴う操作などです。

sequenceDiagram participant Guest asゲストOS participant CPU as CPU仮想化支援 participant VMM asハイパーバイザ participant Host asホスト資源 Guest->>CPU: 通常命令を実行 CPU-->>Guest: 直接実行 Guest->>CPU: 特権操作 / I/O CPU->>VMM: VM exit VMM->>Host: 資源を調停 VMM-->>CPU: VM entry CPU-->>Guest: 実行を再開

VM exitは便利ですが高コストです。仮想化の性能を考えるときは、「どれだけ直接実行できるか」「どれだけexitを減らせるか」が大きな軸になります。

vCPUとオーバーコミット

仮想化基盤では、物理コア数より多いvCPUをVMに割り当てることがあります。これはCPU overcommitです。Webサーバのように待ち時間が多いworkloadでは有効ですが、CPUを常に使い切る計算処理ではcontentionが起きます。

見るべき指標は、単なるCPU使用率だけではありません。steal time、run queue、VM exit回数、ホスト側のCPU ready、NUMAをまたいだメモリアクセスなどが効きます。VM内から見るとCPUが空いているように見えても、ホスト側でvCPUが実行待ちになっていることがあります。

時刻とタイマー

VMでは時刻も難しい対象です。ゲストOSはタイマー割り込みやクロックソースを使いますが、VMが一時停止されたり移行されたりすると、時間の進み方にずれが生じます。NTP、chrony、仮想化向けクロックソース、タイマー割り込みの扱いは、分散システムやログ解析にも影響します。

メモリ仮想化

通常のプロセスでも、仮想アドレスは物理メモリへ変換されます。VMではさらに一段増えます。ゲストOSは「ゲスト物理アドレス」を管理しますが、それは本当の物理アドレスではなく、ホスト側の物理メモリへ対応づけられます。

見えているもの 管理者
ゲストプロセス ゲスト仮想アドレス ゲストOS
ゲストOS ゲスト物理アドレス ゲストOS
ハイパーバイザ ホスト物理アドレス ハイパーバイザ / ホスト

この変換を効率化するために、Intel EPTやAMD NPTのようなsecond-level address translationが使われます。TLBページフォルト、Huge Page、NUMA配置はVMの性能にも影響します。

メモリの過剰割り当て

仮想化基盤では、物理メモリ以上のメモリをVMへ見せることがあります。これをovercommitと呼びます。うまく使えば集約率を上げられますが、実際に多くのVMが同時にメモリを使うと性能劣化やOOMにつながります。

代表的な技術には、未使用ページを回収するballooning、同一内容ページをまとめるdeduplication、スワップ、メモリ圧縮などがあります。ただし、これらは魔法ではありません。低レイテンシ用途やデータベースでは、メモリ過剰割り当てが尾を引く性能問題になることがあります。

NUMAとHuge Page

大きなサーバでは、CPUソケットごとに近いメモリと遠いメモリがあります。これがNUMAです。VMのvCPUとメモリが別々のNUMAノードに散ると、メモリアクセス遅延が増えます。データベースや低レイテンシ処理では、vCPU pinning、NUMA affinity、Huge Pageの設計が重要になります。

Huge Pageはページテーブルの段数やTLBミスを減らす効果がありますが、メモリの断片化や柔軟性との交換条件もあります。仮想化基盤で性能問題を見るときは、ゲストOSのメモリ設定だけでなく、ホスト側のNUMA配置とページサイズも確認します。

I/O仮想化

I/Oは仮想化で特に難しい領域です。CPU命令は直接実行しやすくても、ディスク、NIC、GPU、USB、割り込み、DMAなどは安全性と性能の両方を満たす必要があります。

エミュレーション

エミュレーションでは、ゲストに実在するデバイスのようなものを見せます。互換性は高いですが、実物のデバイス挙動を模倣するためオーバーヘッドが大きくなりやすいです。

virtio

virtio は仮想化向けの準仮想化デバイスです。ゲストドライバとホスト側実装が、仮想化に適したキューを通じてデータをやり取りします。virtio-netvirtio-blkvirtio-scsivirtio-fs などが代表例です。

パススルーとSR-IOV

高性能が必要な場合、デバイスをVMに直接割り当てることがあります。IOMMUを使うことでDMAのアクセス範囲を制御し、安全に近い形でデバイスを渡せます。NICやGPUではSR-IOVやvGPUのような仕組みが使われます。

ただし、パススルーはライブマイグレーション、スナップショット、障害時の復旧、リソース共有を難しくすることがあります。性能だけでなく運用性との交換条件として見る必要があります。

ネットワークI/Oの選択肢

VMのネットワークは、仮想スイッチ、ブリッジ、tapデバイス、vhost-net、SR-IOVなどを組み合わせて実現されます。互換性と観測性を重視するなら仮想スイッチ経由、低レイテンシを重視するならSR-IOVやDPDKを検討します。

ただし、高速化はしばしば管理機能を削ります。パケットキャプチャ、セキュリティグループ、ライブマイグレーション、トラフィック制御がどこまで効くかを確認します。性能だけを最大化すると、障害対応やセキュリティ監査が難しくなることがあります。

ストレージ仮想化

VMのディスクは、ホストから見るとファイルやブロックデバイスです。QCOW2、VMDK、VHDXなどの形式は、スナップショット、差分、圧縮、疎ファイルなどを扱えます。クラウドでは、VMのローカルディスクとネットワークブロックストレージを分けて考えます。

ストレージ仮想化で重要なのは、性能と耐障害性がレイヤをまたいで現れることです。ゲストOSのファイルシステム、仮想ディスク形式、ホストのファイルシステム、物理ストレージ、ネットワークストレージが重なるため、書き込みバリア、キャッシュ、fsync、スナップショットの意味を誤解するとデータ破損や性能問題につながります。

よくある誤解

VMのスナップショットはバックアップそのものではありません。スナップショットはある時点への差分管理や復旧点として便利ですが、別障害領域への退避、整合性確認、復元テストまで含めてバックアップと考えます。

ライブマイグレーション

ライブマイグレーションは、VMを停止時間を短く保ったまま別ホストへ移す技術です。代表的には、実行中のVMのメモリを移行先へコピーし、変更されたページを追いかけ、最後に短時間止めて残りを同期します。

flowchart LR A["移行元ホスト"] --> B["メモリを事前コピー"] B --> C["変更ページを再送"] C --> D["短時間停止"] D --> E["CPU状態と残りページを転送"] E --> F["移行先ホストで再開"]

ライブマイグレーションは、メンテナンスや負荷分散に有効です。一方で、メモリ更新が激しいVM、巨大メモリVM、パススルーデバイスを持つVMでは難しくなります。

VMイメージ、テンプレート、スナップショット

VM運用では、OSインストール済みのイメージをテンプレートとして管理します。クラウドのAMIやカスタムイメージ、オンプレミスのゴールデンイメージはこの考え方です。

イメージ運用で大切なのは、アプリケーションのコンテナイメージと同じく「作った時点で古くなり始める」ことです。OSパッケージ、カーネル、エージェント、証明書、初期ユーザー、SSH鍵、cloud-init設定を継続的に更新します。

ゴールデンイメージと起動時設定

ゴールデンイメージに何でも焼き込むと、変更のたびにイメージ再作成が必要になります。一方で、起動時にすべてを構成すると、起動時間が伸び、失敗点が増えます。実務では、OSと基本エージェントはイメージに入れ、環境ごとの差分はcloud-init、構成管理、ユーザーデータ、コンテナで注入することが多いです。

スナップショットの整合性

スナップショットにはcrash-consistentとapplication-consistentがあります。前者は突然電源が落ちた時点に近く、後者はアプリケーションやファイルシステムが整合した状態で取得します。データベースでは、単にディスクをスナップショットするだけでは不十分な場合があります。

Confidential Computing

通常のVMでは、ゲストOSとアプリケーションは他のVMからは隔離されますが、ホスト側の高権限層を完全には信頼しない、という要件には別の仕組みが必要です。confidential computingは、実行中のデータを保護するために、VMメモリの暗号化やattestationを使う考え方です。

代表例にはAMD SEV / SEV-SNP、Intel TDX、Arm CCAなどがあります。これらは「クラウド事業者やホスト管理者をどこまで信頼するか」という脅威モデルに関わります。

観点 通常VM confidential VM
主な目的 テナント間分離 ホスト側高権限層からの保護も強める
技術 ハイパーバイザ、IOMMU、仮想化支援 メモリ暗号化、測定、attestation
注意点 ホスト管理者を一定信頼 I/O、デバッグ、運用性、対応範囲を確認

Confidential Computingは万能ではありません。サイドチャネル、ゲスト内の脆弱性、アプリケーションの秘密管理、ログ、外部サービスとの通信は別に設計が必要です。

microVMとsandboxed container

コンテナの軽さとVMの隔離を近づけるために、microVMやsandboxed containerが使われます。Firecrackerは、サーバレスやマルチテナントコンテナ向けに設計された軽量VMモニタです。Kata Containersは、Kubernetesから見るとコンテナのように扱いつつ、実行境界として軽量VMを使います。gVisorは、アプリケーションとホストカーネルの間にユーザー空間カーネルのような層を置き、システムコール面を小さくします。

技術 ねらい 向いている場面
通常コンテナ 軽量で高速なアプリ実行 信頼できるワークロード、一般的なサービス
gVisor システムコール境界を狭める 多テナント、未知コード実行
Kata Containers VM境界でコンテナを動かす 強めの隔離が必要なKubernetes
Firecracker microVMを高密度に動かす サーバレス、短命ワークロード

WindowsコンテナとOS差分

コンテナというとLinuxコンテナを指すことが多いですが、Windowsコンテナもあります。Windowsコンテナには、ホストカーネルを共有するWindows Server Containersと、軽量VM境界を使うHyper-V isolationがあります。

重要なのは、コンテナイメージOSカーネルやユーザーランドとの互換性に強く依存することです。LinuxコンテナをWindowsカーネル上でそのまま動かしているわけではなく、多くの場合はLinux VMや互換レイヤが関わります。開発環境で動いたイメージが本番のノードOSやCPUアーキテクチャで動くかを確認します。

コンテナの基本

コンテナは「軽量なVM」ではなく、「隔離されたプロセス」です。コンテナ内でPID 1として見えるプロセスも、ホストカーネルから見ると通常のプロセスです。ただし、そのプロセスには専用の名前空間、資源制限、ファイルシステム、権限設定が組み合わされています。

flowchart TB A["コンテナプロセス"] --> B["namespace<br>見える世界を分ける"] A --> C["cgroup<br>使える資源を制限する"] A --> D["capability<br>root権限を分割する"] A --> E["seccomp / LSM<br>システムコールやアクセスを制限する"] A --> F["rootfs / image layer<br>実行に必要なファイルを提供する"] B --> G["Linuxカーネル"] C --> G D --> G E --> G F --> G

コンテナが分かりにくい理由は、単一の機能ではないからです。コンテナランタイムは、複数のLinux機能をまとめて設定し、「アプリケーションをこの環境で実行する」という単位にしています。

PID 1問題

コンテナ内の最初のプロセスはPID 1になります。LinuxではPID 1はシグナル処理やzombie processの回収で特別な意味を持ちます。アプリケーションをそのままPID 1として動かすと、SIGTERMを正しく処理しなかったり、子プロセスが残ったりすることがあります。

対策として、アプリケーション側でシグナルを正しく処理する、tini のような小さなinitを使う、Kubernetesの terminationGracePeriodSeconds とpreStop hookを理解する、などがあります。

1コンテナ1プロセスの意味

「1コンテナ1プロセス」は絶対規則ではありません。正確には、1つのコンテナに1つの主責務を持たせる、という設計指針です。Webサーバ、ログ転送、メトリクスエージェント、プロキシをすべて同じコンテナに詰めると、更新、監視、権限、障害分離が難しくなります。

Kubernetesでは、密接に協調する補助処理をsidecarとして同じPodに置くことがあります。sidecarは同じネットワーク名前空間やvolumeを共有できるため便利ですが、Podの起動順序、終了順序、リソース配分を意識します。

namespace

namespaceは、プロセスから見える名前空間を分ける機能です。コンテナが「自分だけのプロセス一覧」「自分だけのホスト名」「自分だけのネットワーク」を持っているように見えるのはnamespaceの働きです。

namespace 分離するもの
PID プロセスID空間 コンテナ内でプロセスがPID 1に見える
mount マウントツリー コンテナ専用のrootfs
network NIC、IP、ルーティング、ポート コンテナごとの仮想NIC
UTS hostname、domainname コンテナ内のホスト名
IPC System V IPCPOSIX message queue プロセス間通信の分離
user UID/GIDの対応 コンテナ内rootをホスト非rootに対応づける
cgroup cgroup階層の見え方 コンテナ内から見える資源制御階層
time monotonic / boot time offset 時刻名前空間の分離

特にuser namespaceは重要です。コンテナ内でrootに見えても、ホストでは権限の低いUIDに対応づけられるため、侵害時の影響を下げられます。ただし、ファイル所有権、ボリューム、古いカーネルやランタイムとの相性を考える必要があります。

Rootless Container

Rootless Containerは、root権限なしでコンテナを実行する方式です。PodmanやDocker rootless modeではuser namespaceを使い、コンテナ内rootをホスト側の通常ユーザーに対応づけます。これにより、ランタイムやコンテナが侵害されたときのホストへの影響を下げられます。

rootlessはセキュリティ面で魅力的ですが、すべてが同じように動くわけではありません。低いポートのbind、特定のネットワーク機能、cgroup管理、ボリューム所有権、NFS、FUSE、SELinuxとの組み合わせで差分が出ます。

観点 rootful rootless
ホスト権限 root daemonやroot権限を使いやすい 通常ユーザー権限に寄せる
セキュリティ 設定ミス時の影響が大きい ホスト侵害リスクを下げやすい
互換性 広い 一部機能に制約
運用 伝統的なDocker運用に近い UID/GID mappingとstorageを理解する必要

開発環境ではrootless Podmanが相性のよい場面があります。本番Kubernetesでは、ノードランタイムをrootlessにするか、Podを非rootで動かすか、sandboxed runtimeを使うかを分けて考えます。

cgroup

cgroupは、プロセスを階層的にグループ化し、CPU、メモリ、I/O、pidsなどの資源を制御する仕組みです。コンテナにメモリ上限やCPU quotaを設定できるのはcgroupの働きです。

cgroup v2では、統一された階層でコントローラを扱います。CPUでは cpu.max、メモリでは memory.max、プロセス数では pids.max のようなインターフェースが使われます。Kubernetesのresource requests / limitsも、最終的にはノード上のcgroup設定に落ちます。

requestsとlimitsの見方

Kubernetesではrequestはスケジューリング時の予約量、limitは実行時の上限です。CPU limitに達したコンテナはthrottlingされます。メモリlimitに達したコンテナはOOM killされる可能性があります。

よくある誤解

CPU limitは「遅くなる」方向に効きますが、メモリlimitは「落ちる」方向に効きます。レイテンシが重要なサービスでは、CPU throttlingとメモリOOMのどちらも監視対象にします。

QoS classとeviction

Kubernetesはrequestsとlimitsの指定に応じてPodをGuaranteed、Burstable、BestEffortに分類します。ノードがメモリやディスクのpressureを受けると、kubeletはPodを退避または終了してノードを守ります。

QoS 条件のイメージ 性質
Guaranteed すべてのコンテナでCPU/メモリrequestとlimitが等しい 退避されにくい
Burstable requestはあるがlimitと一致しない、または一部だけ指定 多くの通常workload
BestEffort requestもlimitもない 資源逼迫時に落ちやすい

Podが落ちたとき、アプリケーションのバグだけでなく、ノードのMemoryPressure、DiskPressure、PIDPressure、ephemeral storageの使いすぎも疑います。kubectl describe pod のEventsとNode Conditionsを見ると、OOMKilled、Evicted、ImagePullBackOff、CrashLoopBackOffの区別がしやすくなります。

capability、seccomp、LSM

Linuxのroot権限はcapabilityに分割されています。たとえば CAP_NET_ADMIN はネットワーク設定、CAP_SYS_ADMIN は非常に広い管理操作に関わります。コンテナをrootで動かしていても、不要なcapabilityを落とせば攻撃面を減らせます。

seccompは、プロセスが呼び出せるシステムコールを制限します。コンテナ化されたアプリケーションが使わないシステムコールを禁止すれば、カーネル攻撃面を狭められます。AppArmorやSELinuxのようなLSMは、ファイルやプロセスに対するアクセス制御をさらに加えます。

最小権限の設定では、次を基本にします。

  • rootlessまたは非rootユーザーで実行する
  • --privileged を避ける
  • 不要なcapabilityを落とす
  • read-only root filesystemを検討する
  • seccomp、AppArmor、SELinuxの既定プロファイルを有効にする
  • host namespace、hostPath、Docker socketを安易に渡さない

コンテナファイルシステム

コンテナのroot filesystemは、読み取り専用のイメージレイヤと、実行時に書き込むwritable layerから構成されます。Linuxではoverlayfsがよく使われます。アプリケーションがファイルを書き込むと、下位レイヤを直接変更するのではなく、上位のwritable layerに差分が作られます。

flowchart TB A["コンテナから見えるrootfs"] --> B["writable layer<br>実行時の差分"] B --> C["image layer: app"] C --> D["image layer: dependencies"] D --> E["image layer: base OS"]

この仕組みは配布効率に優れますが、永続データの置き場ではありません。ログや一時ファイルを大量にwritable layerに書くと、ディスクpressureやimage garbage collectionの問題につながります。永続データはvolume、オブジェクトストレージ、外部DBなどへ逃がします。

ephemeral storage

Kubernetesでは、コンテナのwritable layer、ログ、emptyDirなどがephemeral storageを消費します。CPUとメモリだけを見ていると、ディスク不足によるevictionを見落とします。ログが多いアプリケーション、キャッシュをローカルに貯める処理、巨大な一時ファイルを作るバッチでは、ephemeral storage request / limitも設計対象です。

コンテナイメージ

コンテナイメージは、実行に必要なファイル群とメタデータをレイヤとしてまとめたものです。イメージはimmutableな配布物であり、コンテナはそのイメージから起動した実行インスタンスです。

レイヤとdigest

イメージは複数のレイヤを持ちます。ベースイメージ、依存関係、アプリケーションコード、設定ファイルなどが順に重なります。同じレイヤは再利用できるため、配布やビルドキャッシュが効きます。

タグは人間に分かりやすい名前ですが、移動することがあります。厳密な再現性が必要な場合はdigestを使います。latest は便利ですが、いつ何を指すかが固定されません。

multi-stage build

multi-stage buildは、ビルド環境と実行環境を分ける手法です。ビルド用ステージにはコンパイラやパッケージマネージャを入れ、最終ステージには実行に必要な成果物だけをコピーします。

FROM golang:1.25 AS build
WORKDIR /src
COPY . .
RUN go build -o /out/app ./cmd/app

FROM gcr.io/distroless/base-debian12
COPY --from=build /out/app /app
USER nonroot:nonroot
ENTRYPOINT ["/app"]

この形にすると、イメージサイズ、脆弱性面、起動時の不要な道具を減らせます。小さいイメージはそれだけで安全になるわけではありませんが、攻撃面と更新対象を減らす効果があります。

Dockerfileの設計

Dockerfileは単なる手順書ではなく、キャッシュ、再現性、セキュリティ、運用性に影響します。

  • .dockerignore で不要なファイルをビルドコンテキストに入れない
  • 依存関係のインストールとアプリコードのコピー順を工夫し、キャッシュを効かせる
  • ADD ではなく意図が明確な COPY を基本にする
  • パッケージインストール後にキャッシュを削除する
  • rootではなく専用ユーザーで実行する
  • 環境変数に秘密情報を焼き込まない
  • OSパッケージと言語パッケージの更新戦略を決める
ヒント

イメージサイズを小さくする目的は、見た目の軽さだけではありません。配布時間、起動時間、脆弱性スキャン対象、攻撃面、古い依存の残存を減らすためです。

distrolessとscratch

scratch は空のベースイメージです。静的リンクされたGoバイナリなどでは非常に小さなイメージを作れます。distrolessは、シェルやパッケージマネージャを含めず、実行に必要な最小限のファイルだけを持つイメージ群です。

ただし、デバッグ用のシェルがないため、障害調査の方法を別に用意します。Kubernetesではephemeral containerを使って調査用コンテナを追加する、ログとメトリクスを十分に出す、ローカルで同等イメージを再現できるようにする、といった設計が必要です。

OCI

OCIは、コンテナの形式と実行方法を標準化する取り組みです。Dockerだけに閉じず、イメージ、ランタイム、レジストリを分けて考えられるようになります。

flowchart LR A["Dockerfile / Buildpacks / BuildKit"] --> B["OCI Image"] B --> C["OCI Registry"] C --> D["containerd / CRI-O"] D --> E["runc / crun / youki"] E --> F["Linux namespace / cgroup"]

OCI Image Specificationはイメージ形式、OCI Runtime Specificationはコンテナ実行時の設定、OCI Distribution SpecificationはレジストリAPIに関わります。実務では、Docker CLI、BuildKit、Podman、containerd、CRI-O、runcなどの役割を分けて理解すると混乱しにくくなります。

コンテナランタイムの階層

Kubernetesから見ると、コンテナを起動する相手はCRIに対応したランタイムです。現在はcontainerdやCRI-Oがよく使われます。さらに下ではruncやcrunのようなOCI runtimeが実際のプロセス起動を担当します。

flowchart TB A["Kubernetes kubelet"] --> B["CRI"] B --> C["containerd / CRI-O"] C --> D["OCI runtime<br>runc / crun / youki"] D --> E["Linuxカーネル機能"]

この分離により、KubernetesはDocker Engineに依存せず、標準インターフェース越しにコンテナを管理できます。

CRIの意味

CRIはkubeletとコンテナランタイムの間のインターフェースです。kubeletはCRIを通じてPod sandboxの作成、コンテナ起動、停止、ログ取得、イメージpullなどを依頼します。これにより、KubernetesはDocker Engineに深く依存せず、containerdやCRI-Oを選べます。

sequenceDiagram participant K as kubelet participant R as CRI runtime participant I as Image service participant O as OCI runtime K->>R: RunPodSandbox K->>I: PullImage K->>R: CreateContainer R->>O: OCI specに従ってプロセス起動 O-->>R: container started R-->>K: status

ランタイム問題を調査するときは、kubectl だけでなく、ノード上の crictl、containerdの ctr、ログ、systemd unit、CNI設定も見ることがあります。

レジストリとサプライチェーン

レジストリは、コンテナイメージを保存・配布する場所です。Docker Hub、GitHub Container Registry、Amazon ECR、Google Artifact Registry、Harborなどが代表例です。

コンテナ運用では、アプリケーションコードだけでなく、ベースイメージ、パッケージ、ビルド環境、CI/CD、レジストリ、署名、デプロイ先までがサプライチェーンになります。

重要な観点は次です。

  • ベースイメージの出所を確認する
  • digest pinningで再現性を高める
  • SBOMを生成し、依存関係を把握する
  • 脆弱性スキャンをビルド時と運用時に回す
  • イメージ署名と検証を導入する
  • 秘密情報をイメージやログに入れない
  • 使わなくなったイメージを削除し、古いタグを放置しない

SBOMと署名

SBOMは、イメージやアプリケーションに含まれる依存関係の一覧です。脆弱性が公開されたとき、どのイメージが影響を受けるかを探すために役立ちます。SPDXやCycloneDXが代表的な形式です。

署名は、イメージが信頼できるビルド元から来たこと、途中で改ざんされていないことを確認するために使います。Sigstore cosignのような仕組みを使うと、イメージdigestに対して署名やattestationを紐づけられます。

ただし、SBOMや署名を作るだけでは安全になりません。デプロイ時に検証するadmission policy、脆弱性が見つかったときの再ビルド、古いイメージの退役、例外承認の期限管理まで必要です。

Kubernetesの位置づけ

Kubernetesは、コンテナを単体で起動する道具ではなく、複数ノード上で望ましい状態を保つための制御システムです。ユーザーは「Deploymentのreplicaを3にする」「ServiceでこのPod群へ到達させる」と宣言し、control planeが実際の状態を近づけます。

flowchart TB A["User / CI"] --> B["Kubernetes API Server"] B --> C["etcd"] B --> D["Scheduler"] B --> E["Controller Manager"] D --> F["Node kubelet"] E --> F F --> G["Container Runtime"] G --> H["Pod"]

Kubernetesの強さは、宣言的なAPIとreconciliationにあります。現在の状態を監視し、望ましい状態との差分を埋め続けます。これはSREや分散システムの考え方ともつながります。

Kubernetesの制御ループ

Kubernetesでは、多くのコンポーネントがcontrol loopとして動きます。対象の現在状態を見て、望ましい状態との差分を埋めます。Deployment controllerはReplicaSetを作り、ReplicaSet controllerはPod数を保ち、schedulerはPodをNodeに割り当て、kubeletはNode上でPodを実行します。

flowchart LR A["Desired state<br>manifest"] --> B["API Server"] B --> C["Controller"] C --> D["Observed state"] D --> E{"差分あり?"} E -->|Yes| F["修正操作"] F --> D E -->|No| G["監視継続"]

この性質により、手作業で直接Podを直すより、宣言されたリソースを直すほうが安定します。Podを手で消してもDeploymentが再作成します。したがって、Kubernetesでは「何を直すべきか」をリソース階層で考える必要があります。

Pod、Deployment、Service

Kubernetesでは、コンテナを直接ばらばらに管理するのではなく、Podを最小単位として扱います。Podは1つ以上のコンテナをまとめ、同じネットワーク名前空間と一部のストレージを共有します。

公式ドキュメントでも、Podは「Kubernetesで作成・管理できる最小のデプロイ可能単位」と説明されています。重要なのは、Podが単なるコンテナの別名ではないことです。Podは、IPアドレス、ポート空間、volume、ライフサイクルを共有する小さな論理ホストとして振る舞います。通常は1Podに1つの主コンテナを置き、ログ転送、プロキシ、設定更新のように密接に協調する処理だけをsidecarとして同じPodへ置きます。

リソースの階層構造

リソース 役割 特性
Pod 実行単位。1つ以上のコンテナをまとめる 最小デプロイ単位
ReplicaSet Podの複製数を保つ 宣言的レプリケーション管理
Deployment ReplicaSetを通じてローリング更新を管理する Statelessワークロード向け
Service Pod群への安定したアクセス口を作る クラスタ内外のネットワーク抽象化
Ingress / Gateway HTTP(S) の外部入口を管理する L7ルーティング
ConfigMap 設定を外出しする 非機密設定
Secret 機密値を扱う base64またはencrypted
Job / CronJob 完了する処理や定期処理を扱う バッチ・スケジュール実行
StatefulSet 安定したIDが必要な状態ful workloadを扱う 順序・永続性が必要

Deploymentのローリング更新

Deploymentは新しいReplicaSetを段階的に起動し、古いReplicaSetを停止することでローリング更新を実現します。制御パラメータは次です:

  • replicas: 期待するPod数
  • strategy.type: RollingUpdate または Recreate
  • strategy.rollingUpdate.maxSurge: 期待数を超えてよい最大Pod数
  • strategy.rollingUpdate.maxUnavailable: 準備完了していない最大Pod数

例えば、replicas=3maxSurge=1maxUnavailable=1の場合、最大4つのPodが存在し、常に2つ以上が準備完了状態です。この設定により、ローリング更新中も可用性を保ちながら更新します。

Serviceの種類

Serviceは、Pod群へのアクセス方法を定義します。

Type 用途 アクセス方法
ClusterIP クラスタ内通信 内部DNS / IP
NodePort 外部アクセス(開発用) 全Node:Port
LoadBalancer 外部アクセス(本番) クラウドLB / IP
ExternalName 外部サービス参照 DNS CName

Serviceはkubeプロキシを通じてPodへのロードバランシングを実現します。eBPF対応のCNIを使う場合、kubeプロキシの動作を確認する必要があります。

Stateless vs Stateful

Deploymentはstatelessなサービスに向いています。StatefulSetは、Pod名、永続ボリューム、起動順序などが意味を持つワークロードに使います。ただし、データベースをKubernetesに載せるかどうかは、バックアップ、障害復旧、アップグレード、運用チームの成熟度まで含めて判断します。

StatefulSetの場合、Pod名は序数で安定します(e.g., mysql-0, mysql-1, mysql-2)。ディスク障害でNodeが落ちた場合、別のNodeでPodを再作成しますが、ボリュームが元のデータを指すかは設定に依存します。

スケジューリング

schedulerは、まだNodeに割り当てられていないPodを見つけ、制約を満たすNodeを選びます。単に空いているNodeを選ぶのではなく、resource requests、nodeSelector、affinity、taints/tolerations、topology spread constraints、PodDisruptionBudgetなどが関わります。

スケジューリング制約の種類

仕組み 目的 強制力
nodeSelector 特定ラベルを持つNodeに置く 必須(簡易版)
node affinity Nodeへの配置希望や必須条件を表す 必須 / 希望
pod affinity / anti-affinity 他Podとの近さや離れ方を制御する 必須 / 希望
taints / tolerations 特別なNodeに通常Podを載せない 必須
topology spread constraints ゾーンやNode間でPodを分散する 必須 / 希望
priority / preemption 重要Podを優先する 優先度制御

Node Affinity (詳細)

Node affinityには2つのタイプがあります。

# 必須:このラベルを持つNodeにしか置けない
nodeAffinity:
  requiredDuringSchedulingIgnoredDuringExecution:
    nodeSelectorTerms:
    - matchExpressions:
      - key: disktype
        operator: In
        values:
        - ssd

# 希望:このラベルを持つNodeを優先(重み付け)
nodeAffinity:
  preferredDuringSchedulingIgnoredDuringExecution:
  - weight: 100
    preference:
      matchExpressions:
      - key: disktype
        operator: In
        values:
        - ssd

名前の「IgnoredDuringExecution」は、一度スケジュール後にラベルが削除されてもPodを削除しないことを意味します。

Pod Affinity / Anti-affinity

他のPodとの配置関係を制御します。「Pod AがNode Xで動いているなら、Pod BもNode Xに置く」といった制約が可能です。service meshやデータキャッシュなど、低レイテンシ通信が必要な場面で有効です。

podAffinity:
  requiredDuringSchedulingIgnoredDuringExecution:
  - labelSelector:
      matchExpressions:
      - key: app
        operator: In
        values:
        - database
    topologyKey: kubernetes.io/hostname  # 同じNode

ただし、pod affinityは強力な制約でもあるため、スケジューリング失敗のリスクが高まります。必須(required)より希望(preferred)の方が安全な場面が多いです。

Topology Spread Constraints (詳細)

topology spread constraintsは、Podを障害ドメイン(ゾーン、Node、ラック等)に分散させます。「3 replicasがすべて同じNodeにいるなら、冗長化としては弱い」という問題を防ぎます。

topologySpreadConstraints:
- maxSkew: 1
  topologyKey: topology.kubernetes.io/zone
  whenUnsatisfiable: DoNotSchedule
  labelSelector:
    matchLabels:
      app: myapp

maxSkewは、各トポロジードメインのPod数の最大差です。上の例では、各ゾーンのPod数が1以上異なってはいけません。whenUnsatisfiableは、制約を満たせない場合に予約(DoNotSchedule)か改善(ScheduleAnyway)かを制御します。

高可用性の設計

高可用性を考えるなら、replica数だけでなく、異なるNode、異なるゾーン、異なる障害ドメインに広がっているかを確認します。3 replicasがすべて同じNodeにいるなら、冗長化としては弱いです。

効果的なパターン:

replicas: 3
topologySpreadConstraints:
- maxSkew: 1
  topologyKey: topology.kubernetes.io/zone
  whenUnsatisfiable: DoNotSchedule
  labelSelector:
    matchLabels:
      app: critical

このパターンで、3つのPodが異なるゾーンに分散します。ゾーン障害でも、少なくとも2つのPodが生き残ります。

Kubernetesネットワーク

Kubernetesの基本モデルでは、Podはクラスタ内で一意のIPを持ち、Pod間通信はNATなしに到達できることを前提にします。実装はCNI pluginが担当します。Calico、Cilium、FlannelなどはCNIの世界にあります。

Serviceは、入れ替わるPod群に対して安定した宛先を提供します。ClusterIPはクラスタ内、NodePortは各ノードのポート、LoadBalancerはクラウドのロードバランサ、IngressやGateway APIはL7の入口を扱います。

NetworkPolicyは、Pod間通信を制御するための仕組みです。ただし、NetworkPolicyはそれを実装するCNIが対応していなければ効きません。ポリシーを書いたつもりでも実際には制御されていない、という失敗を避けるために確認が必要です。

重要

NetworkPolicyはデフォルトではすべてのトラフィックを許可します。明示的に制御するには、policyを追加し、かつCNIが実装していることを確認します。

CNIとeBPF

CNIは、コンテナやPodのネットワークを構成するためのインターフェースです。Pod作成時にIPアドレスを割り当て、仮想インターフェースを作り、ルーティングやポリシーを設定します。CNI Specificationにより、異なるCNIの相互互換性が実現しています。

sequenceDiagram participant K as kubelet participant CNI as CNI plugin participant N as Network infra K->>CNI: AddNetwork CNI->>N: IP割り当て CNI->>N: インターフェース作成 CNI->>N: ルーティング設定 CNI-->>K: IP / DNS返却

近年はeBPFを使うCNIが増えています。eBPFにより、カーネル内でパケット処理、ロードバランシング、ネットワークポリシー、観測を効率よく行えます。Ciliumはその代表例です。ただし、eBPFを使うほど、カーネルバージョン、権限、デバッグ方法を理解する必要が出ます。

IngressとGateway API

IngressはHTTP(S) の入口を定義するリソースとして広く使われています。Gateway APIは、より役割分担と拡張性を意識した新しいAPI群です。インフラ担当がGatewayを管理し、アプリ担当がRouteを管理する、といった分担を表しやすくなります。

Ingressの制御は、Ingress Controllerと呼ばれるコンポーネントが行います。Nginx Ingress Controller、Istio Ingress Gateway、AWS ALB Controllerなどが代表例です。Gateway APIはより宣言的に書けるため、新規プロジェクトでの採用が増えています。

重要な設計検討事項:

  • TLS終端:ロードバランサ、Ingress Controller、アプリケーションのどこで行うか
  • バックエンド選択:Service、Pod、外部エンドポイントへのルーティング規則
  • トラフィック管理:rate limit、timeout、リトライ、circuitbreaker
  • 監視:ロードバランサレベルのメトリクス、ログ、トレース

ロードバランサ、TLS終端、WAF、認証、rate limit、service meshとの関係をどこで持つかは、組織の運用境界にも関わります。

Kubernetesストレージ

コンテナのファイルシステムは短命です。Podが再作成されるとローカルの変更は失われます。永続データにはPersistentVolumeとPersistentVolumeClaimを使います。クラウドでは、EBS、Persistent Disk、Azure Disk、NFS、Ceph、EFSのような外部ストレージが対応先になります。

ストレージは「マウントできるか」だけでは足りません。読み書きモード、レイテンシ、スループット、スナップショット、バックアップ、ゾーン障害、拡張、暗号化、データ整合性まで考えます。

CSI

CSIは、Kubernetesとストレージ実装をつなぐインターフェースです。クラウドのブロックストレージ、ファイルストレージ、オンプレミスの分散ストレージなどは、CSI driverを通じてPersistentVolumeを提供します。

StorageClassは、どの種類のストレージをどう作るかを表します。高速SSD、安価なHDD、マルチゾーン対応、暗号化、バックアップ対応などの違いをStorageClassに分けると、アプリケーション側はPVCで必要な性質を要求できます。

Kubernetesセキュリティ

Kubernetesセキュリティは、コンテナ単体の安全設定だけでは完結しません。API server、RBAC、ノード、イメージ、Pod設定、ネットワーク、Secret、監査ログをまとめて見ます。

Pod Security

Pod Security Standardsでは、Privileged、Baseline、Restrictedという考え方でPodの安全度を整理します。実務では、少なくとも通常workloadはRestrictedに近づけるのが目標になります。

チェックする項目は次です。

  • runAsNonRoot を使う
  • allowPrivilegeEscalation: false を設定する
  • readOnlyRootFilesystem: true を検討する
  • capabilities.drop: ["ALL"] を基本にする
  • seccomp profileを RuntimeDefault にする
  • host network、host PID、host IPCを避ける
  • hostPath volumeを強く制限する

RBACとSecret

RBACは「誰が何をできるか」を制御します。ServiceAccountに過大な権限を与えると、Pod侵害からクラスタ全体へ横展開される恐れがあります。Secretはbase64エンコードされているだけでは暗号化ではありません。etcd暗号化、外部secret manager、短命credential、監査ログを組み合わせます。

Admission controlとpolicy

Kubernetesでは、API serverに届いたリクエストを保存前に検査・変更できます。これがadmission controlです。Pod Security Admission、ValidatingAdmissionPolicy、OPA Gatekeeper、Kyvernoなどを使うと、危険なPod設定や未署名イメージのデプロイを防げます。

ポリシー例は次です。

  • privileged: true を禁止する
  • runAsNonRoot を必須にする
  • latest タグを禁止する
  • 許可されたregistryからのimageだけを認める
  • resource requests / limitsを必須にする
  • hostPath volumeを特定パス以外禁止する
  • image署名が検証できない場合は拒否する

ポリシーは強すぎると開発を止め、弱すぎると守れません。最初はauditで影響を見てからenforceへ移すと導入しやすいです。

マルチテナント設計

Kubernetesのnamespaceは便利な管理単位ですが、それだけで強いセキュリティ境界になるわけではありません。複数チームや複数顧客を同じクラスタに載せるなら、RBAC、NetworkPolicy、ResourceQuota、LimitRange、Pod Security、専用ノード、runtimeClass、監査ログを組み合わせます。

強い分離が必要なら、クラスタを分ける、Node poolを分ける、VM境界を使う、Kata Containersのようなsandboxed runtimeを使う、といった選択肢があります。

コンテナの観測

コンテナは短命で、ノード間を移動し、IPも変わります。そのため、サーバにSSHしてログを見る運用だけでは追いつきません。標準出力ログ、メトリクス、トレース、イベント、監査ログを集約します。

観測すべき代表的な指標は次です。

  • CPU使用率とthrottling
  • メモリ使用量、working set、OOM kill
  • コンテナ再起動回数
  • image pull失敗
  • Pod pending / crashloop
  • request latency、error rate、saturation
  • node pressure、disk pressure、network error

コンテナやKubernetesの問題は、アプリケーション、ランタイム、ノード、クラスタ制御面、クラウド基盤のどこでも起きます。観測データは、層をまたいで相関できる形にしておくと調査が速くなります。

トラブルシュートの型

Kubernetesの障害調査では、まず「desired stateとobserved stateの差」を見ます。Podが作られないのか、作られたが起動しないのか、起動したがReadyにならないのか、Readyだが通信できないのかで見る場所が変わります。

代表的な入口は次です。

kubectl get pod -A
kubectl describe pod <pod>
kubectl logs <pod> -c <container>
kubectl get events --sort-by=.lastTimestamp
kubectl describe node <node>
kubectl top pod
kubectl top node
症状 見る場所 典型原因
Pending Pod Events、scheduler、Node resources requestが大きすぎる、taint、PVC未束縛
ImagePullBackOff Pod Events、registry、Secret image名誤り、認証、タグ不在
CrashLoopBackOff logs、exit code、probe アプリ例外、設定不足、起動遅延
OOMKilled describe、metrics、cgroup memory limit不足、リーク
Evicted Events、Node Conditions MemoryPressure、DiskPressure、ephemeral storage
Readyにならない readiness probe、Service endpoints probe設定誤り、依存先未接続
通信できない Service、EndpointSlice、NetworkPolicy、CNI selector誤り、ポリシー、DNS

ノード内部まで見る場合は、journalctl -u kubelet、container runtimeのログ、crictl pscrictl logs、CNI pluginのログ、iptables/nftables/eBPFの状態を確認します。

オートスケーリング

コンテナ基盤では、負荷に応じてPodやNodeを増減できます。ただし、何を増やすかで意味が違います。

仕組み 増減するもの 使う指標 特徴
HPA (水平スケーリング) Pod数 CPU、メモリ、カスタムメトリクス 複数Pod間で負荷分散
VPA (垂直スケーリング) Podのrequest 過去の使用量 単一Pod内でリソース拡大
Cluster Autoscaler Node数 unschedulable Pod インフラレイヤの調整
KEDA Pod数 キュー長、イベント数など イベント駆動のスケーリング

HPA (Horizontal Pod Autoscaler)

HPAはPodのreplicaをメトリクスに基づいて自動調整します。代表的な指標はCPUとメモリですが、Metrics Serverが必要です。スケーリング判定の基本式は次です:

desired_replicas = ceil[current_replicas * (current_metric_value / target_metric_value)]

ただし、1分以上のクールダウン期間を設けるため、トラフィックの急変への反応は即座ではありません。CPUでスケールする場合、Pod内で複数スレッドが必要であり、単純な同期処理ではあまり効果がありません。メモリベースのスケーリングは、メモリ使用量が安定しにくいため、容易ではありません。

カスタムメトリクスやPrometheus連携により、アプリケーション固有の指標(キュー長、レスポンスタイム、マシン学習モデルのバッチサイズなど)でスケーリングできます。

VPA (Vertical Pod Autoscaler)

VPAは過去のリソース使用量に基づいてrequestを自動調整します。requestが大きすぎると、スケジューリングが失敗しやすく、費用も増えます。requestが小さすぎると、throttlingやevictionが起きます。VPAはこの見積もりの手間を減らします。

VPAの動作モードは4つあります。

モード 機能 用途
off 推奨値を計算するが適用しない 監視用途、手作業で確認
initial 新規Pod作成時だけ適用 既存Podへの影響を最小化
recreate 既存Podを再作成して適用 安定性を優先
auto 再作成とin-place更新を組み合わせ Kubernetes 1.35以降での推奨

In-place pod vertical scalingにより、Pod再作成なしにメモリやCPU limitを増やせるようになりました。ただし、Podが再起動されないため、メモリ解放の機会がなく、fragmentationが起きる場合があります。

Cluster Autoscaler

Cluster AutoscalerはunschedulableなPodを検出したときにNodeを増やします。一方、使用率の低いNodeを検出したときにNodeを減らします。

重要なのは、Cluster Autoscalerはスケールダウンの判定が保守的な点です。以下の場合はNodeを削除しません:

  • システムPod(kube-dns、kube-proxyなど)が走っている
  • ローカルストレージ(emptyDir、local volume)を持つPodがある
  • PodDisruptionBudgetが設定されていて、削除がbudgetを超える

スケールダウンの意図しない遅延を避けるには、undeployable Podの自動削除(--skip-nodes-with-system-pods=false)や、タイムアウト値の調整を検討します。

KEDA (Kubernetes Event Driven Autoscaling)

KEDAは、キューの深さ、メッセージ数、Webhookトリガーなど、外部イベント指標に基づいてスケーリングします。メッセージキューシステム(RabbitMQ、Kafka、AWS SQS)やデータベース(PostgreSQL、MongoDB)などとの連携が容易です。

スケーリング設計の実務ポイント

スケール設計では、アプリケーションが水平分割できるか、起動時間が短いか、ウォームアップが必要か、DBや外部APIがボトルネックにならないかを確認します。

重要な考慮事項:

  • 起動時間:新しいPodが完全に準備できるまでの時間。これが長いと、スケールアップの効果が遅れます。ヘルスチェック、ウォームアップ、依存サービス接続時間を見積もります。startup probeを使い、readiness probeとは分ける。
  • 接続プール:データベース接続やRedis接続が限られている場合、むやみにPodを増やすとコネクション枯渇になります。接続プールサイズとPod数の関係を設計します。
  • ステートレス性:Podが再配置されても安全なように設計します。セッション状態、ローカルキャッシュはNode再起動で失われます。
  • ノード容量Cluster Autoscalerスケジューリング失敗で初めてNodeを増やすため、受動的です。事前にリザーブ容量を用意するか、priority classを使って重要Podを優先します。

デプロイ戦略

コンテナ化されたアプリケーションは、イメージを差し替えることでデプロイできます。ただし、単に新しいイメージを流すだけでは安全なデプロイにはなりません。

代表的な戦略は次です。

  • rolling update 少しずつ新バージョンへ置き換える
  • blue-green deployment 新旧環境を並べて切り替える
  • canary release 一部のトラフィックだけ新バージョンへ流す
  • feature flag デプロイと機能有効化を分ける

readiness probeは「トラフィックを受けてよいか」、liveness probeは「再起動すべきか」を表します。ここを混同すると、起動直後のアプリケーションへ早すぎるトラフィックを流したり、依存先障害でPodを無意味に再起動し続けたりします。

Probeとgraceful shutdown

Kubernetesのprobeは、アプリケーションの状態をkubeletに伝える重要な契約です。liveness、readiness、startupは目的が違います。

probe 目的 失敗時の挙動
startup 起動完了まで待つ 成功までliveness/readinessを遅らせる
readiness トラフィックを受けてよいか Service endpointから外す
liveness 再起動すべき停止状態か コンテナを再起動する

livenessに依存先DBの疎通を入れると、DB障害時に全Podが再起動し続けることがあります。readinessには依存先状態を含めてもよい場面がありますが、livenessは「プロセスが自力回復不能か」を見るのが基本です。

終了時は、SIGTERMを受けて新規リクエストを止め、処理中リクエストを終え、接続を閉じ、terminationGracePeriodSeconds 内に終了します。readinessを先にfalseにする、preStop hookで少し待つ、ロードバランサ側のdrain時間を合わせる、といった調整が必要です。

状態を持つワークロード

コンテナはstatelessなアプリケーションと相性が良いですが、状態を持つワークロードも扱えます。ただし、難易度は上がります。

データベース、キュー、検索エンジン、分散ストレージをコンテナで動かす場合は、次を確認します。

  • データの永続化先
  • バックアップと復元手順
  • スナップショットの整合性
  • ノード障害時の再配置
  • バージョンアップ手順
  • quorumやleader election
  • ストレージ性能とゾーン配置
  • 運用チームが復旧できるか

「コンテナで動く」と「本番で安全に運用できる」は別の話です。状態を持つシステムでは、障害時にどう壊れ、どう戻すかを先に設計します。

コストと容量設計

仮想化とコンテナは資源効率を上げますが、設計を誤るとコストが見えにくくなります。Kubernetesではrequestを大きく見積もりすぎるとNodeが余り、小さすぎるとthrottlingやevictionが増えます。

容量設計では、平均使用率ではなくピーク、バースト、冗長化、メンテナンス時の余裕を見ます。たとえばNodeを1台drainしても必要なreplicaが残るか、ゾーン障害時に残りゾーンへPodが載るか、Cluster Autoscalerが間に合うかを考えます。

観点 確認すること
CPU request、limit、throttling、ピーク時run queue
メモリ working set、OOM、ページキャッシュ、GC
ディスク imagefs、nodefs、ephemeral storage、ログ量
ネットワーク east-west traffic、egress cost、帯域、DNS
可用性 zone spread、PodDisruptionBudget、drain可能性

コンテナ基盤は「小さく詰める」ほど効率は上がりますが、障害時の余白が減ります。コスト最適化は、可用性と復旧時間とのバランスです。

開発環境としてのコンテナ

コンテナは本番運用だけでなく、開発環境の再現にも役立ちます。言語ランタイム、ミドルウェア、DB、メッセージキューをコンテナでそろえると、ローカル環境差分を減らせます。

ただし、開発環境と本番環境を完全に同じにすることは難しいです。CPUアーキテクチャ、OSカーネル、ファイルシステム、ネットワーク、クラウドIAM、マネージドサービスの差分は残ります。開発では便利さを優先し、本番に近い検証はCIやステージングで補うのが現実的です。

代表的な設計パターン

sidecar

sidecarは、主コンテナの隣に補助コンテナを置くパターンです。ログ転送、プロキシ、証明書更新、設定同期、service meshのdata planeなどで使われます。同じPod内なのでlocalhost通信やvolume共有ができます。

init container

init containerは、主コンテナ起動前に一度だけ実行されるコンテナです。DB migration、設定ファイル生成、依存サービス待ち、権限調整などに使います。ただし、失敗するとPod全体が起動しないため、冪等性とタイムアウトが重要です。

ambassadorとadapter

ambassadorは外部サービスへの通信を代理するコンテナ、adapterは既存アプリケーションの出力を標準的な形式に変換するコンテナです。古いアプリケーションをクラウドネイティブな観測や通信方式へつなぐときに役立ちます。

operator

operatorは、Kubernetes APIを拡張し、特定アプリケーションの運用知識をcontrollerとして実装するパターンです。データベースクラスタ、メッセージキュー、証明書管理、バックアップなどで使われます。

operatorは便利ですが、導入すると「そのoperator自体を運用する」責任が増えます。アップグレード、CRD互換性、権限、障害時の挙動を確認します。

VM、コンテナ、サーバレスの比較

VM、コンテナ、サーバレスは優劣ではなく、責任範囲の違いです。

観点 VM コンテナ サーバレス
主な抽象化 マシン アプリ実行環境 関数 / マネージド実行単位
OS管理 利用者側が大きく担当 ホストは基盤側、イメージは利用者側 基盤側が大きく担当
起動時間 秒から分 ミリ秒から秒 実装に依存
隔離 強い 中程度 基盤に依存
運用自由度 高い 高い 低め
スケール VM単位 Pod / コンテナ単位 リクエスト / イベント単位
向く用途 既存システム、強い分離、異種OS Web API、バッチ、CI/CD、マイクロサービス イベント処理、短命処理、運用負荷削減

使い分けの判断

VMが向くのは、強い隔離、異なるOS、既存システム移行、カーネルやデバイスに近い制御が必要な場面です。コンテナが向くのは、アプリケーションを小さく配布し、頻繁にデプロイし、スケールやロールバックをしやすくしたい場面です。サーバレスが向くのは、運用負荷を下げ、イベント駆動で短命な処理を動かしたい場面です。

一方で、規模が小さいシステムではKubernetesが過剰なこともあります。数個のサービスなら、マネージドPaaS、サーバレス、単純なVM、Docker Composeのほうが保守しやすい場合があります。技術選定では「できること」ではなく「運用し続けられること」を基準にします。

失敗しやすいポイント

コンテナをセキュリティ境界だと過信する

コンテナは隔離を提供しますが、VMと同等の境界ではありません。ホストカーネルを共有するため、カーネル脆弱性、過剰なcapability、hostPath、Docker socket、privileged containerが大きなリスクになります。

イメージを作ったら終わりにする

ベースイメージは更新され、脆弱性情報も変わります。イメージは一度作って終わりではなく、定期的に再ビルドし、スキャンし、署名し、古いものを退役させます。

Kubernetesを導入すれば運用が楽になると思う

Kubernetesは強力ですが、運用対象そのものが増えます。クラスタ、ノード、ネットワーク、ストレージ、権限、監視、アップグレードを理解する必要があります。小規模なチームでは、マネージドKubernetesを使ってもアプリケーション運用の複雑性は残ります。

ローカルディスクや固定IPに依存する

コンテナは作り直される前提です。ローカルファイルや固定IPに依存すると、スケール、再配置、障害復旧で壊れます。永続化、サービス発見、設定注入を明確に分けます。

YAMLを直接増やし続ける

Kubernetes manifestは増えやすいです。環境差分、Secret、Ingress、RBAC、HPA、NetworkPolicyを手で複製すると、差分が読めなくなります。Kustomize、Helm、Jsonnet、Cue、GitOpsなどで構成管理します。ただし、テンプレート化しすぎると逆に読めなくなるため、生成物をレビューできる形にします。

requestとlimitを設定しない

requestsがないPodはスケジューリング上の予約がなく、BestEffortになりやすいです。limitsがないと暴走時にノード全体へ影響することがあります。一方でCPU limitを厳しくしすぎるとthrottlingで遅くなります。最初は実測に基づくrequestを置き、観測しながら調整します。

まとめ

仮想化とコンテナは、計算資源を分けて使うための層の違う道具です。VMはハードウェアに近い境界でOSごと分け、コンテナはOSカーネルを共有しながらプロセスの見える世界と資源を分けます。

この違いを理解すると、KVM、QEMU、virtionamespacecgroupOCI、containerd、Kubernetesが一本の線でつながります。実務では、軽さだけでコンテナを選ぶのではなく、隔離の強さ、権限、イメージの出所、サプライチェーン、観測、復旧、運用チームの負荷まで含めて設計することが大切です。

参考文献

公式・標準

論文

解説・補助