共有と分離を行き来してきた

境界をどこに置くかは、要件で変わる

レンタルサーバーや共有ホスティングでは、多くの利用者を一つのOSへ収容します。ユーザーIDとファイルの権限は利用者を分けますが、カーネルと、カーネルが管理するプロセスやネットワークは共有されます。chrootは見えるディレクトリツリーを狭め、FreeBSD Jailはプロセス、ユーザー、ネットワークにも制約を広げました。

ハードウェア仮想化はゲストカーネルを分けます。その後、Linuxのnamespaceとcgroupは共有カーネルへ戻り、プロセス群を細かく分ける道を発展させました。さらにgVisorやmicroVMは、コンテナの運用方法を活かしながら、ホストカーネルとの間へ別の実行境界を加えています。

複数の利用者を一つのOSへ収容

共有サーバー

ユーザーID、ファイルの所有者と権限、プロセスの実行権限で利用者を分けます。

マルチテナント利用密度
見える範囲とrootの権限を狭める

chroot / FreeBSD Jail

rootディレクトリを切り替え、Jailではプロセス、ユーザー、ネットワークの扱いにも制約を加えます。

権限管理共有カーネル
ゲストOSごとに境界を作る

ハードウェア仮想化 / KVM

各仮想マシンがゲストカーネルを持ちます。OSの互換性と境界を得る代わりに、ゲストOSの起動とメモリが必要です。

独立したカーネル互換性
一つのLinuxカーネル上を細かく分ける

namespacenamespacePID、マウント、ネットワーク、ホスト名などの参照範囲を、プロセス群ごとに分ける仕組み。 / cgroupcgroupプロセス群ごとに、CPU時間、メモリ、I/Oの使用量や優先度を管理する仕組み。

プロセス群から見える状態と、CPUやメモリなどの使用量を項目ごとに管理します。

起動時間資源配分
ファイルと実行設定を持ち運ぶ

コンテナランタイム

rootfs、namespace、cgroupに加え、capabilitycapability従来rootへまとめて与えられていた権限を、操作の種類ごとに分けた単位。、seccompseccompプロセスが呼び出せるシステムコールと引数をfilterで制限する仕組み。、LSMLSMカーネル内の操作へ、アクセス制御を呼び出すhookを用意する共通の枠組み。などを設定してプロセスを起動します。

配置速度イメージ
ホストカーネルへ届く処理を減らす

gVisor

ユーザー空間のapplication kernelがシステムコールを処理し、コンテナとホストカーネルの間へ境界を加えます。

攻撃面互換性との調整
仮想マシンの境界を小さく速く使う

microVM / Firecracker

KVMと最小限のデバイスモデルを使い、仮想マシンの隔離と短い起動時間、少ないメモリ消費を同時に狙います。

起動時間ハードウェア境界
技術は一直線に置き換わっていません。現在も、仮想マシン上でコンテナを動かす構成や、コンテナランタイムへgVisorやmicroVMを組み込む構成が使われています。
07.1
最初に二つの役割を分ける

見える状態と、使える量を別々に管理する

プロセスから見える状態

namespace

PID、マウント構成、ネットワーク構成、ホスト名などを項目ごとに分けます。

プロセス群が使う量

cgroup

CPU、メモリ、I/O、プロセス数などをグループ単位で測り、配分や上限を設定します。

07.2
プロセスから見える状態を分ける

namespaceが変えるもの

Linuxカーネルは、すべてのプロセスとネットワークインターフェース、マウント構成などを管理しています。その一部だけをプロセス群へ見せると、同じカーネルを使いながら、異なる状態を参照させられます。この参照範囲を分ける機能をnamespacenamespacePID、マウント、ネットワーク、ホスト名などの参照範囲を、プロセス群ごとに分ける仕組み。と呼びます。

分ける前

二つのプロセス群が同じ状態を参照

プロセス群 Aプロセス群 B同じPID一覧:1, 426, 8421, 8427
分けた後

各プロセス群から見える値が変わる

プロセス群 A内側:PID 1, 7, 12
プロセス群 B内側:PID 1, 4

PID namespaceは階層構造です。外側のnamespaceからは、内側のプロセスも外側のPIDで参照できます。

namespaceは、カーネルが管理する対象について、プロセスが参照する範囲を種類ごとに分ける機能です。
CLONE_NEWNSLinux 2.4.19

mount namespace

どのファイルシステムが、ディレクトリツリーのどこへ接続されているかを分けます。ある範囲で行ったmountやunmountを、別の範囲から切り離せます。

CLONE_NEWUTSLinux 2.6.19

UTS namespace

hostnameとNIS domain nameを分けます。同じカーネル上でも、プロセス群ごとに異なるホスト名を参照できます。

CLONE_NEWIPCLinux 2.6.19

IPC namespace

System V IPCとPOSIX message queueを分けます。識別子やキューを、そのnamespaceに属するプロセス同士で使います。

CLONE_NEWPIDLinux 2.6.24

PID namespace

プロセス番号の見え方を階層的に分けます。新しく作ったPID namespaceでは最初のプロセスがPID 1になり、親側のPID namespaceからは別のPIDで見えます。

CLONE_NEWNETLinux 2.6.24

network namespace

ネットワークインターフェース、IPアドレス、経路、ポート、socketなどを分けます。各範囲に独立したネットワーク構成を用意できます。

CLONE_NEWUSERLinux 3.8

user namespace

UID、GID、capabilityの対応を分けます。内側のUID 0を、外側の一般ユーザーのUIDへ対応づける構成も作れます。

CLONE_NEWCGROUPLinux 4.6

cgroup namespace

プロセスが参照するcgroup階層の起点を分けます。内側からは、割り当てられた位置をcgroup階層のルートとして扱えます。

CLONE_NEWTIMELinux 5.6

time namespace

起動後の経過時間などに使うmonotonic clockとboottime clockの値へ、namespaceごとのずれを設定できます。

プロセスとnamespaceの関係を変える

namespaceの作成と切り替え

新しいnamespaceに属する子プロセスを作る、自分用のnamespaceを作る、既存のnamespaceへ所属を切り替える、といった操作をシステムコールで行います。

  1. 新しいnamespaceに属する子プロセスを作るclone() / clone3()
  2. 自分用のnamespaceを新しく作るunshare()
  3. 既存のnamespaceへ所属を切り替えるsetns()
  4. プロセスが属するnamespaceを確認する/proc/PID/ns/

PID namespaceとtime namespaceをunshare()で作る場合、呼び出したプロセス自身ではなく、その後に作る子プロセスが新しいnamespaceへ入ります。

07.3
CPUやメモリをプロセス群ごとに管理する

cgroupの階層と設定

CPU時間やメモリは、同じコンピューター上のプロセスが共有します。ある処理が大量に使うと、ほかの処理へ割り当てられる量が減ります。そこでLinuxは、複数のプロセスをグループにまとめ、使用量を測り、配分や上限を設定できます。このグループをcgroupcgroupプロセス群ごとに、CPU時間、メモリ、I/Oの使用量や優先度を管理する仕組み。と呼びます。

cgroupは親子関係を持つ階層として作ります。親に設定した制約は、その下のグループにも及びます。cgroup v2では、一つの階層でCPU、メモリ、I/Oなどをまとめて管理します。

cgroup v2の階層

ルート

この下へ、管理したい単位ごとのグループを作ります。

Webサービス

web.service

所属するプロセスPID 8421PID 8427
CPUの配分
cpu.weight = 200
メモリ上限
memory.max = 1 GiB
バッチ処理

batch.service

所属するプロセスPID 9104PID 9118
CPUの配分
cpu.weight = 50
メモリ上限
memory.max = 512 MiB
設定値は構成例です。親に設定した制約は子孫にも及び、同じ親を持つグループのCPU配分は重みを使って調整できます。systemdがservice unitごとにこの階層を作る流れを見る
設定と現在値をファイルとして扱う

cgroupfs

Linuxは、cgroupの階層と設定項目をファイルシステムとして公開します。グループのディレクトリを作り、cgroup.procsへPIDを書いてプロセスを所属させ、cpu.weightやmemory.maxなどのファイルを通して値を設定します。

CPUやメモリなど、管理する対象ごとに使用量を測り、設定を適用するカーネル側の機能をcontrollerと呼びます。cgroup v2の主なcontrollerには、次のものがあります。

CPU

CPU時間を配分する

処理が競合したときの相対的な配分をcpu.weightで指定し、一定期間に使えるCPU時間の上限をcpu.maxで指定します。

MEMORY

メモリ使用量を測り、上限を設ける

現在の使用量をmemory.currentで確認します。memory.highは使用量を抑える目安、memory.maxは超えられない上限として働きます。

I/O

ストレージI/Oを配分する

ブロックデバイスへの読み書きについて、io.weightで相対的な配分を、io.maxで帯域や回数の上限を設定します。

PIDS

作成できるプロセス数を制限する

現在のプロセス数をpids.currentで確認し、pids.maxで上限を設定します。プロセスを無制限に増やす動作を抑えられます。

CPUSET

使えるCPUとメモリノードを指定する

プロセス群を実行できるCPUと、メモリを割り当てられるNUMA nodeの範囲を指定します。

Linux 2.6.24

cgroup v1

controllerごとに異なる階層を組める設計です。構成の自由度が高い一方、controller間でプロセスのグループ分けが一致しない構成も作れました。

Linux 4.5から

cgroup v2

統合階層が正式に導入されました。v2で使えるcontrollerはその後も増え、cpuは4.15、cpusetは5.0、プロセス群を一時停止するfreezerは5.2、hugetlbは5.6で加わりました。

07.4
二つの機能を同じプロセス群へ適用する

コンテナを構成する機能

アプリを実行するには、実行ファイルとライブラリ、設定ファイルなどが並んだディレクトリツリーも必要です。コンテナを起動するソフトウェアは、これらのファイルからプロセスの/として使うディレクトリツリーを用意します。このツリーをrootファイルシステム、略してrootfsと呼びます。複数のnamespaceとcgroupを設定した後、このrootfsにある実行ファイルからアプリのプロセスを起動します。

HOST FILESYSTEM
/
  • bin
  • home
  • var
    • log
    • lib/containers/web
      • lower
      • upper
      • merged
  • tmp
図では、コンテナ用ファイルを/var/lib/containers/webへ置く構成を例にしています。
PROCESS VIEW
基本となるファイル/bin /lib /etc
アプリのファイル/app/server
書き込み領域/tmp/cache
MOUNT NAMESPACE
/
  • bin
  • etc
  • lib
  • app
    • server
  • tmp
    • cache
pivot_root()などで、このツリーをプロセス側の/にする
見える状態PID 110.0.1.2web-01PID、network、UTS namespace
使用量CPU weightmemory.max 512 MiBcgroup
execve("/app/server", ...)アプリのプロセス
01

ホストのツリー

Linuxは、rootディレクトリから枝分かれする一つのディレクトリツリーとしてファイルを扱います。

図はOverlayFSを使う構成例です。OCIイメージの各layerはファイルの追加、変更、削除を記録します。コンテナランタイムが使う保存場所とrootfsの組み立て方は、実装や設定によって異なります。
07.5
二つのコンテナを切り替えて確認する

同じカーネル上でも設定は分かれる

コンテナコンテナ同じカーネルを共有しながら、プロセスを分離して動かす環境。ごとに、プロセスから見える値と、cgroupが記録する使用量や設定値を切り替えて確認できます。

同じLinuxカーネル上で動く二つのプロセス群host
NAMESPACE

webプロセス群から見える状態

PID 1, 7, 1210.0.1.0/24/appを中心としたマウント構成

このプロセス群に設定されたPID、ネットワーク、マウントの各namespaceを通して見える値です。

CGROUP

使用状況と設定

直近のCPU使用率38%
メモリ使用量296 MiB / 512 MiB
cpu.max = 200000 100000memory.max = 512 MiB

現在の使用量を測る値と、配分や上限を決める設定値は別々に管理されます。

namespaceで見える状態を分ける+cgroupで使用量を管理する=同じプロセス群へ二つの機能を適用
見える状態を分ける
namespace
使用量を管理する
cgroup
実行ファイルとライブラリを用意する
rootfs
同じLinuxカーネル上で動く、分離されたプロセス群

コンテナの権限を狭める場合は、capability、seccomp、LSMなども同じプロセス群へ設定します。これらはアクセス制御とカーネル防御で扱います。PID namespace内のPID 1が担う終了状態の回収とシグナル処理は、systemdとPID 1で説明します。