#Namespaces
Namespace は、カーネルリソースをプロセスグループ単位で分離する Linux カーネルの機能。 同じホスト上でも、異なる Namespace に属するプロセスは互いに異なるシステムビューを持つ。
これが Docker などのコンテナ技術の根幹となっている。 実際、コンテナ内からホスト側のプロセスは確認できない。
docker pull ubuntu:22.04
docker run -it ubuntu:22.04
# コンテナへ入る
ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 4140 3200 pts/0 Ss 11:37 0:00 /bin/bash
root 11 0.0 0.1 6440 2432 pts/0 R+ 11:39 0:00 ps aux
##Namespace の種類
Linux には以下の 8 種類の Namespace が存在する。
| Namespace | フラグ | 分離対象 |
|---|---|---|
| Mount | CLONE_NEWNS | マウントポイント |
| UTS | CLONE_NEWUTS | ホスト名・ドメイン名 |
| IPC | CLONE_NEWIPC | IPC リソース (メッセージキュー・セマフォ・共有メモリ) |
| PID | CLONE_NEWPID | プロセス ID |
| Network | CLONE_NEWNET | ネットワークインターフェース・ルーティングテーブル |
| User | CLONE_NEWUSER | ユーザー ID・グループ ID |
| Cgroup | CLONE_NEWCGROUP | cgroup ルートディレクトリ |
| Time | CLONE_NEWTIME | システムクロック |
以下のコマンドで確認できる。
lsns
NS TYPE NPROCS PID USER COMMAND
4026531834 time 2 1 root /bin/bash
4026531837 user 2 1 root /bin/bash
4026532379 mnt 2 1 root /bin/bash
4026532380 uts 2 1 root /bin/bash
4026532381 ipc 2 1 root /bin/bash
4026532382 pid 2 1 root /bin/bash
4026532383 cgroup 2 1 root /bin/bash
4026532384 net 2 1 root /bin/bash
##Namespace を操作するシステムコール
Namespace に関連する主なシステムコールは以下の 3 つ。
clone(2)
新しいプロセスを作成する際に、同時に新しい Namespace を作成する。fork() と似ているが、フラグで Namespace の分離を指定できる点が異なる。
unshare(2)
呼び出したプロセス自身を新しい Namespace に移動させる。既存プロセスの Namespace を分離したいときに使う。
# 新しい UTS Namespace でシェルを起動してホスト名を変更する
sudo unshare --uts /bin/bash
hostname container-host
hostname # → container-host (ホスト側には影響しない)
setns(2)
既存の Namespace に参加する。/proc/[pid]/ns/ 配下のファイルディスクリプタを渡すことで、別プロセスの Namespace に入れる。
int fd = open("/proc/1234/ns/net", O_RDONLY);
setns(fd, CLONE_NEWNET); // PID 1234 の Network Namespace に参加
##/proc による Namespace の確認
各プロセスが属する Namespace は /proc/[pid]/ns/ ディレクトリのシンボリックリンクで確認できる。
ls -l /proc/1/ns/
total 0
lrwxrwxrwx 1 root root 0 Mar 21 07:08 cgroup -> 'cgroup:[4026532383]'
lrwxrwxrwx 1 root root 0 Mar 21 07:08 ipc -> 'ipc:[4026532381]'
lrwxrwxrwx 1 root root 0 Mar 21 07:08 mnt -> 'mnt:[4026532379]'
lrwxrwxrwx 1 root root 0 Mar 21 07:08 net -> 'net:[4026532384]'
lrwxrwxrwx 1 root root 0 Mar 21 07:08 pid -> 'pid:[4026532382]'
lrwxrwxrwx 1 root root 0 Mar 21 07:08 pid_for_children -> 'pid:[4026532382]'
lrwxrwxrwx 1 root root 0 Mar 21 07:08 time -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 Mar 21 07:08 time_for_children -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 Mar 21 07:08 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Mar 21 07:08 uts -> 'uts:[4026532380]'
リンク先の数値 (4026531835 など) が Namespace の識別子 (inode 番号) で、同じ値を持つプロセスは同じ Namespace に属している。
##各 Namespace の詳細
Mount Namespace (mnt)
マウントポイントを分離する。ある Namespace 内でのマウント・アンマウント操作が他の Namespace に影響しない。 コンテナが専用のルートファイルシステムを持てるのはこれによる。
chroot と組み合わせることで、完全に独立したファイルシステムツリーを構築できる。
# 新しい Mount Namespace でシェルを起動
sudo unshare --mount /bin/bash
mount --bind /tmp/myroot /mnt
# このマウントはホスト側からは見えない
UTS Namespace (uts)
ホスト名 (hostname) と NIS ドメイン名 (domainname) を分離する。
UTS は “UNIX Time-sharing System” の略。コンテナごとに異なるホスト名を設定するために使われる。
IPC Namespace (ipc)
System V IPC オブジェクト (メッセージキュー・セマフォ・共有メモリ) と POSIX メッセージキューを分離する。 異なる Namespace のプロセス間では IPC オブジェクトの共有ができなくなる。
PID Namespace (pid)
プロセス ID の空間を分離する。新しい PID Namespace 内の最初のプロセスは PID 1 となり、init 相当の役割を担う。
Namespace は入れ子構造 (階層構造) になっており、親 Namespace からは子 Namespace のすべてのプロセスが見えるが、逆は見えない。
# ホスト側から見たプロセスツリー (一部)
# PID 5000 (コンテナの init に相当するプロセス)
# └─ PID 5001 (コンテナ内では PID 2 に見える)
# コンテナ内から見たプロセスツリー
# PID 1 (コンテナの init)
# └─ PID 2
Network Namespace (net)
ネットワークインターフェース・IPアドレス・ルーティングテーブル・iptables ルール・ポート番号空間などを分離する。
各コンテナが独立した仮想 NIC (veth) とループバックインターフェースを持てるのはこれによる。
# 新しい Network Namespace を作成し、veth ペアで接続する例
ip netns add myns
ip link add veth0 type veth peer name veth1
ip link set veth1 netns myns
ip netns exec myns ip link set veth1 up
User Namespace (user)
ユーザー ID (UID) とグループ ID (GID) の空間を分離する。 Namespace 内の UID 0 (root) をホスト側の非特権ユーザー UID にマッピングできるため、コンテナ内では root に見えるが、ホストからは一般ユーザーとして扱われる。これにより特権昇格のリスクを大きく低減できる (rootless コンテナ)。
UID のマッピングは /proc/[pid]/uid_map と /proc/[pid]/gid_map で設定する。
# コンテナ内の UID 0 → ホストの UID 1000 にマッピング
# /proc/[pid]/uid_map の書式: <コンテナ内 UID> <ホスト UID> <個数>
echo "0 1000 1" > /proc/[pid]/uid_map
Cgroup Namespace (cgroup)
cgroup ファイルシステム上のルートビューを分離する。
Namespace 内のプロセスには、自分が属する cgroup がルート (/) に見えるため、ホストの cgroup 階層構造が漏洩しない。
Time Namespace (time)
CLOCK_MONOTONIC と CLOCK_BOOTTIME を分離する (ウォールクロックである CLOCK_REALTIME は対象外)。
コンテナのライブマイグレーション後にモノトニック時刻がリセットされるような問題に対処するために追加された。
##nsenter コマンド
既存のプロセスの Namespace に入るためのユーティリティ。setns(2) のフロントエンド。
# PID 1234 の全 Namespace に参加してシェルを起動
sudo nsenter -t 1234 --all /bin/bash
# Network Namespace だけに参加
sudo nsenter -t 1234 --net /bin/bash
# Docker コンテナに nsenter で入る (コンテナ ID から PID を取得)
PID=$(docker inspect --format '{{.State.Pid}}' <container_id>)
sudo nsenter -t $PID --mount --uts --ipc --net --pid /bin/bash
##Namespace とコンテナの関係
Docker などのコンテナランタイムは、複数の Namespace を組み合わせてプロセスの分離を実現している。
コンテナ = Mount NS + UTS NS + IPC NS + PID NS + Network NS + User NS (+ Cgroup NS)
+ cgroups によるリソース制限
+ seccomp によるシステムコール制限
+ Linux Capabilities による特権の制限
Namespace 単体はあくまで「見え方」の分離であり、CPU・メモリなどのリソース使用量の制限は cgroups が担っている点に注意が必要。