コンテナは Namespace と cgroups という Linux の仕組みで構成されます1。
コンテナ毎にプロセスやファイルシステムを分離したり、
CPU やメモリなどのリソースを分け合えたりできるのは、これらの技術のおかげです。
この記事では、Namespace を取り上げ、概要説明を交えつつ、実際の動きにも触れてみます。
概要
コンテナを複数立てたとき、プロセスはコンテナ間で分離されます。
コンテナ A で動くプロセスは、コンテナ B では見ることはできません。
同様にファイルシステムにおいても、コンテナ A からコンテナ B のファイルを見ることはできません。
これを実現しているのが Namespace という機能です。
Docker がコンテナを作成すると、下記のような Namespace を作成します。
- IPC Namespace
- Mount Namespace
- Network Namespace
- PID Namespace
- User Namespace
- UTS Namespace
以下では、ubuntu のドキュメント2を参考に Namespace を掘り下げてみます。
Namespace とは
名前空間は、 グローバルシステムリソースを抽象化層で覆うことで、 名前空間内のプロセスに対し
て、 自分たちが専用の分離されたグローバルリソースを持っているかのように見せる仕組みであ
る。 グローバルリソースへの変更は、 名前空間のメンバーである他のプロセスには見えるが、 そ
れ以外のプロセスには見えない。 名前空間の一つの利用方法はコンテナーの実装である。
「グローバルシステムリソース」がホストマシンが持つリソースのことになります。
Namespace という”部屋”を分けることによって、互いに独立したリソースを持つことができます。
これはシェアハウスのイメージに近いかもしれません。
互いの部屋の壁が厚いので、まるで一軒家と勘違いしてしまうような感じです。
Namespace の種類
Namespace | 分離対象 |
---|---|
IPC | System V IPC, POSIX メッセージキュー |
Network | ネットワークデバイス、スタック、ポートなど |
Mount | マウントポイント |
PID | プロセス ID |
User | ユーザー ID とグループ ID |
UTS | ホスト名と NIS ドメイン名 |
中でも PID は直感的に理解しやすい Namespace かと思います。
コンテナ上のプロセスは基本的に PID1 が振られますが、コンテナ A でも コンテナ B でも PID1 を利用できます。
Namespace が別であるため、通常一意であるべき PID が重複していても問題がないわけです。
Namespace を操作する API
システムコール | 内容 |
---|---|
clone(2) | プロセスを生成し呼び出し元プロセスと同じ Namespace に所属させる。フラグを使って新しい Namespace を作成することもできる。 |
setns(2) | 呼び出したプロセスを指定した既存 Namespace に参加させることができる。 |
unshare(2) | 呼び出したプロセスを新しい Namespace に移動する。 |
Namespace の確認方法
/proc/[pid]/ns/
ディレクトリを見に行くことで、
現在のプロセスが所属している Namespace を確認できます。
$ ls -l /proc/$$/ns
total 0
lrwxrwxrwx. 1 mtk mtk 0 Jan 14 01:20 ipc -> ipc:[4026531839]
lrwxrwxrwx. 1 mtk mtk 0 Jan 14 01:20 mnt -> mnt:[4026531840]
lrwxrwxrwx. 1 mtk mtk 0 Jan 14 01:20 net -> net:[4026531956]
lrwxrwxrwx. 1 mtk mtk 0 Jan 14 01:20 pid -> pid:[4026531836]
lrwxrwxrwx. 1 mtk mtk 0 Jan 14 01:20 user -> user:[4026531837]
lrwxrwxrwx. 1 mtk mtk 0 Jan 14 01:20 uts -> uts:[4026531838]
実際に触ってみる
試しに PID Namespace を分離してみます。
環境準備
Linux 環境を用意します。
今回は ubuntu の公式イメージを利用します。
$ docker pull ubuntu
$ docker run --privileged --name ubuntu -it ubuntu /bin/bash # 特権モードで起動
現在のプロセスの状態を確認してみる
bash が起動したら、現在のプロセスが所属している Namespace を確認します。
/proc/self/ns
配下のファイルを参照して確認できます。
$ cd /proc/self/ns
$ ls -la
total 0
dr-x--x--x 2 root root 0 May 1 06:04 .
dr-xr-xr-x 9 root root 0 May 1 05:46 ..
lrwxrwxrwx 1 root root 0 May 1 06:04 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 May 1 06:04 ipc -> 'ipc:[4026532376]'
lrwxrwxrwx 1 root root 0 May 1 06:04 mnt -> 'mnt:[4026532374]'
lrwxrwxrwx 1 root root 0 May 1 06:04 net -> 'net:[4026532379]'
lrwxrwxrwx 1 root root 0 May 1 06:04 pid -> 'pid:[4026532377]'
lrwxrwxrwx 1 root root 0 May 1 06:04 pid_for_children -> 'pid:[4026532377]'
lrwxrwxrwx 1 root root 0 May 1 06:04 time -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 May 1 06:04 time_for_children -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 May 1 06:04 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 May 1 06:04 uts -> 'uts:[4026532375]'
pid に注目すると 4026532377
という Namespace に所属していると分かります。
続いて、現在のプロセスを確認します。
$ ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 05:46 pts/0 00:00:00 /bin/bash
root 15 1 0 06:05 pts/0 00:00:00 ps -ef
PID1 で /bin/bash が動いています。
PID Namespace を分離してみる
次に、PID Namespace を分離してみます3。
分離には unshare4 という新しい Namespace を作るためのコマンドを使用します5。
$ unshare --pid --fork --mount-proc sh
先程と同様に Namespace を確認します。
$ ls -la /proc/self/ns
total 0
dr-x--x--x 2 root root 0 May 1 06:04 .
dr-xr-xr-x 9 root root 0 May 1 06:04 ..
lrwxrwxrwx 1 root root 0 May 1 06:04 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 May 1 06:04 ipc -> 'ipc:[4026532376]'
lrwxrwxrwx 1 root root 0 May 1 06:04 mnt -> 'mnt:[4026532455]'
lrwxrwxrwx 1 root root 0 May 1 06:04 net -> 'net:[4026532379]'
lrwxrwxrwx 1 root root 0 May 1 06:04 pid -> 'pid:[4026532456]'
lrwxrwxrwx 1 root root 0 May 1 06:04 pid_for_children -> 'pid:[4026532456]'
lrwxrwxrwx 1 root root 0 May 1 06:04 time -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 May 1 06:04 time_for_children -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 May 1 06:04 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 May 1 06:04 uts -> 'uts:[4026532375]'
すると、pid は 4026532456
となっており、さっきと異なっていることが分かります。
プロセスも見てみましょう。
$ ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 06:04 pts/0 00:00:00 sh
root 3 1 0 06:05 pts/0 00:00:00 ps -ef
すると、こちらでは PID1 で sh が動いており、元々あった bash が見えなくなっています。
以上から、PID Namespace を分離することで
- 別 Namespace のプロセスが見えないこと
- Namespace 間で PID を重複して利用できること
の2点を確認できました。
-
https://github.com/docker/docker.github.io/blob/v17.06-release/engine/docker-overview.md#the-underlying-technology ↩
-
http://manpages.ubuntu.com/manpages/jammy/ja/man7/namespaces.7.html ↩
-
docker run に
--privileged
を付けて特権モードで起動したのはこのコマンドを打つためです。 ↩