このページの目次
名前空間
Namespace は Linux コンテナの基本的なプリミティブです。各 namespace はプロセスに対してリソースの分離という幻覚を提供します。PID namespace はコンテナ内のプロセスが自分自身を PID 1 だと認識させ、user namespace はコンテナ内の root をホスト上の通常ユーザーにマッピングし、network namespace は各コンテナが独立したネットワークインターフェースとルーティングテーブルを見るようにします。nsproxy は複数の namespace を 1 つのプロセスの「分離されたビュー」として結合します。 カーネルバージョン: 2.6 ~ 6.x
概要
Namespace は Linux コンテナの基本的なプリミティブです。各 namespace はその中のプロセスに対してリソース分離の幻覚を提供します。あるプロセスは、PID namespace では自分自身を PID 1 として見つけ、mount namespace では独立したファイルシステムツリーを、network namespace では独立したネットワークインターフェースとルーティングテーブルを見ます。
Namespace と cgroup の補完関係:namespace は可視性(何が見えるか)を分離し、cgroup はリソース量(どれだけ使えるか)を制限します。Docker/LXC/Podman は、namespace + cgroup + seccomp のスタックの上に構築されています。
8 種類の Namespace
| 種類 | Clone フラグ | 分離対象 | 導入 |
|---|---|---|---|
| Mount | CLONE_NEWNS | ファイルシステムのマウントポイント。それぞれが mount/umount 可能 | 2.4.19 |
| UTS | CLONE_NEWUTS | ホスト名、ドメイン名 | 2.6.19 |
| IPC | CLONE_NEWIPC | SysV IPC (shm/sem/msg)、POSIX mq | 2.6.19 |
| PID | CLONE_NEWPID | プロセス ID。ns 内の PID 1 は init として自分自身を見る | 2.6.24 |
| Network | CLONE_NEWNET | ネットワークインターフェース、ルーティング、netfilter、ソケット | 2.6.24 (2.6.29 で完成) |
| User | CLONE_NEWUSER | UID/GID のマッピング (ns 内の root = ホストの通常ユーザー) | 3.8 |
| Cgroup | CLONE_NEWCGROUP | cgroup のルートディレクトリ (コンテナ内ではホストの cgroup ツリーが見えない) | 4.6 |
| Time | CLONE_NEWTIME | CLOCK_MONOTONIC/BOOTTIME のオフセット (各 ns で独立した時間) | 5.6 |
カーネル実装: nsproxy
// include/linux/nsproxy.h
;
各 task_struct → nsproxy は、そのプロセスが属する各 namespace を指します。fork() 時に子プロセスは親プロセスの nsproxy を共有します(count++)。unshare() 時には nsproxy をクローンし、指定された種類の namespace を新しく作成したものに置き換えます。setns(fd) は、現在のプロセスを既存の namespace に参加させます(fd は /proc/<pid>/ns/<type> から取得)。
PID Namespace: ネストと init プロセス
PID namespace はネストされた木構造を形成します。親 ns は子 ns のプロセスを見ることができます(親 ns 内では通常の PID として扱われますが)、子 ns は親 ns のプロセスを見ることはできません。
各 PID namespace には独自の init プロセス(PID 1)があります。PID 1 の特別な責任:
- 孤児の養子縁組: 親プロセスが子プロセスより先に終了した場合 → 子プロセスは PID 1 にリペアレント(親変更)される
- wait() ループ: PID 1 は積極的に
wait()を呼び出してゾンビを回収しなければならない。そうしないと、ゾンビが ns 内に蓄積し、PID が枯渇するまで続く - kill できない: SIGKILL は PID 1 に対して無効です(先祖 ns から送信されない限り)。ただし、PID 1 は SIGSTOP によって停止させることは可能です
- PID 1 の終了 → 全体の ns が破棄される: カーネルは ns 内の全プロセスに SIGKILL を送信します
コンテナ内の PID 1 は通常、アプリケーション自体です(例: CMD ["nginx", "-g", "daemon off;"])。これはシグナル(SIGTERM)の適切な処理と孤児の回収を行う必要があります。これを行わないと、ゾンビプロセスが蓄積します。
User Namespace: ns 内の root = ホスト上の nobody
User namespace はコンテナセキュリティの基盤です。これにより、非特権ユーザーが ns 内で root 権限を持つことができます。
/proc/<pid>/uid_map:
ns_uid 0 → host_uid 1000 # ns 内の root = ホストの通常ユーザー
ns_uid 1 → host_uid 100000 # ns 内の通常ユーザー = ホストの 100000 以降のサブ UID
# 非特権ユーザーが user ns を作成 (kernel.unprivileged_userns_clone=1):
$ unshare --user --map-root-user
# → ns 内では root として表示されるが、ホスト上では通常ユーザーのまま
重要なセキュリティ特性として、user ns 内の「root」はその user ns が所有するリソースのみを操作できます。ホストのファイルにアクセスしたり、カーネルモジュールをロードしたり、ホストのプロセスを kill したりすることはできません。この「root のように見えるが、実際には監禁されている」という状態により、rootless コンテナ(Podman rootless モード)が可能になります。
Mount Namespace: 独立したマウントツリー
各 mount namespace には独立したファイルシステムマウントポイントのリストがあります。ある ns での mount は他の ns に影響を与えません。CLONE_NEWNS で作成されると、子 ns は親 ns のマウントポイントのコピー(共有ではない)を受け取ります。その後、それぞれが独立して変更を行います。
共有サブツリー (shared subtrees, 2.6.15+): mount --make-shared /path を実行すると、マウントイベントが複数の ns 間で伝播します。Systemd はこのメカニズムを使用して、ホストとコンテナ間で /run を共有します。
Network Namespace: 独立したネットワークスタック
netns は最もよく使われる namespace です。各コンテナには 1 つ必要です。各 netns には独立した以下のリソースがあります:ネットワークインターフェースのリスト、ルーティングテーブル、netfilter ルール、ソケットバインド。ip netns add myns で新しい netns を作成します(実際には /var/run/netns/myns への bind mount)。ip link set eth0 netns myns でネットワークインターフェースを netns に移動します。
ユーザーランド API
# 新しい PID + mount + net namespace の作成:
# --fork: 必須。これがないと、新しい PID ns の init は元のシェルになってしまう
# --mount-proc: 新しい ns で procfs をマウント → ps コマンドは ns 内のプロセスのみを表示
# 既存の namespace への参加:
# namespace の一覧表示:
参考
- ソースコード:
kernel/nsproxy.c,kernel/pid_namespace.c,kernel/user_namespace.c,net/core/net_namespace.c,fs/namespace.c - カーネルドキュメント:
Documentation/admin-guide/namespaces/ - LWN: 「Namespaces in Linux」シリーズ
Keywords: namespace, nsproxy, PID namespace, user namespace, UID mapping, mount namespace, network namespace, CLONE_NEWNS, container, setns, unshare