🐡

【Linux】名前空間(Namespaces)の隔離をmacOS(Docker)で検証する

に公開

概要

Linuxの名前空間(Namespaces)について、概念の整理と、macOS上での検証方法のメモです。
名前空間は、システム全体のリソース(プロセスID、ネットワーク、マウント等)を、特定のプロセスに対して「そこが全世界である」かのように見せる隔離技術です。いわゆるコンテナ技術(Docker等)の正体とも言えます。

今回は、macOS上にLinuxのコンテナを立て、そのコンテナの中で unshare コマンドを使って名前空間を分離し、挙動を確認しました。

環境

  • M2 macOS (Host)
  • Docker Desktop
  • Ubuntu 22.04 (コンテナ)

※ macOSのカーネルには名前空間の機能がないため、Dockerコンテナ内でLinux環境を再現して検証します。

準備

実験用のUbuntuコンテナを特権モード(--privileged)で起動します。
これがないと、コンテナ内で名前空間を操作する権限がありません。

# (ホストOSで実行)コンテナを特権モードで起動
$ docker run -it --rm --privileged ubuntu:24.04 /bin/bash

# (コンテナ内で実行)必要なツールのインストール(ipコマンド、pingコマンド等)
apt-get update && apt-get install -y iproute2 iputils-ping

検証1:PID(プロセスID)の隔離

プロセス空間を分離し、自分以外のプロセスが見えなくなることを確認します。

実験と結果

まず、比較のためにホスト側(コンテナ直下)でバックグラウンドプロセスをいくつか起動し、プロセス数が複数ある状態とします。

# 比較用:ダミープロセスを起動
$ sleep 10000 &
$ sleep 20000 &

# プロセスを確認
$ ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.0   4296  3632 pts/0    Ss   00:33   0:00 /bin/bash
root         269  0.0  0.0   2268   740 pts/0    S    00:34   0:00 sleep 10000
root         270  0.0  0.0   2268   724 pts/0    S    00:34   0:00 sleep 20000
root         271  100  0.0   7628  3648 pts/0    R+   00:34   0:00 ps aux

# hostnameを確認
$ hostname
339038be1d76

次に、unshare コマンドで新しいPID名前空間を作成して入ります。

# --fork: 新しいプロセスとして分離
# --pid: PID名前空間を分離
# --mount-proc: /procをマウントし直してpsコマンドを正常化
$ unshare --fork --pid --mount-proc --uts /bin/bash

# プロセスを確認
$ ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.0   4296  3588 pts/0    S    00:36   0:00 /bin/bash
root           4 66.6  0.0   7628  3648 pts/0    R+   00:37   0:00 ps aux

# hostnameを変更して確認する
$ hostname
339038be1d76
$ hostname secret-room
$ hostname
secret-room
  • 直前まで動いていた sleep プロセスが見えなくなっています。
  • 自分の bash のPIDが 1 になっています。
# 外にでる
$ exit

# 中で変更したhostnameを確認する
$ hostname
339038be1d76
  • 外に出ると、hostnameは元に戻っています。

検証2:ネットワーク(NET)の隔離

ネットワーク名前空間を分離し、外部との通信を遮断できるか確認します。
※一度 exit して前の実験から抜けた状態で実施。

手順

  • まず親の状態を確認
$ ip -a
# => eth0などが出力される

# 応答がある
$ ping -c 2 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
  • --net オプションを使用して新しい名前空間に入る。
$ unshare --net --fork --map-root-user /bin/bash

$ ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
3: ip6tnl0@NONE: <NOARP> mtu 1452 qdisc noop state DOWN group default qlen 1000
    link/tunnel6 :: brd :: permaddr 4640:b8cf:6528::

$ ping -c 2 8.8.8.8
ping: connect: Network is unreachable
  • eth0 が消滅しています。
  • ループバック(lo)も DOWN 状態です。

検証3:マウント(MNT)の隔離

特定のディレクトリへのマウント操作が、隔離空間の外には影響しないことを確認します。

手順

  • 準備: 実験用の空ディレクトリを作成します。
$ mkdir /tmp/secret_room
  • マウント名前空間を分離して入ります。
$ unshare --mount --fork /bin/bash
  • 隔離空間内でマウントを行います。
    ※マウント情報の伝播を防ぐため、ルート以下をプライベート化します。(これを行わなければ、以下の結果とはならず、親と教諭される)
$ mount --make-rprivate /

# tmpfs(メモリ領域)をマウント
$ mount -t tmpfs tmpfs /tmp/secret_room

# ファイルを作成してみる
$ echo "ここは隔離された世界です" > /tmp/secret_room/message.txt
  • 隔離空間を抜けて、元の世界でディレクトリを確認します。
$ exit
$ ls -l /tmp/secret_room
# -> total 0
  • 中ではファイルを作成しましたが、外から見るとディレクトリは空のままでした。マウント操作が名前空間内に閉じ込められていることが確認できました。

まとめ

  • PID名前空間: プロセス番号を1から振り直し、他のプロセスを見えなくする。
  • NET名前空間: ネットワークインターフェースを独立させる。
  • MNT名前空間: ファイルシステムのマウント状況を独立させる。

Dockerなどのコンテナランタイムは、これらの機能を組み合わせて隔離環境を実現していることが、コマンドレベルで追跡できました。

参考

Discussion