Linux のコンテナ仮想化を実現する機能の一つに Namespace がある。 Namespace はプロセスが動作する際のリソースをカーネルの中で隔離 (分離) する仕組み。 Namespace は隔離する対象のリソースによって色々とある。
今回は、その中でも IPC (Inter Process Communication) に関するリソースを隔離する仕組みの IPC Namespace について扱う。 ここでいう IPC には、たとえば SystemV IPC と POSIX IPC がある。 今回は、unshare(1) と unshare(2) を使って SystemV IPC に関するリソースが Namespace によって隔離される様子を観察してみる。
使った環境は次のとおり。
$ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 22.04 LTS Release: 22.04 Codename: jammy $ uname -srm Linux 5.18.0-051800-generic aarch64 $ unshare --version unshare from util-linux 2.37.2 $ gcc --version gcc (Ubuntu 11.2.0-19ubuntu1) 11.2.0 Copyright (C) 2021 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
もくじ
- もくじ
- 下準備
- SystemV IPC について
- unshare(1) を使って IPC Namespace を操作する
- unshare(2) を使って IPC Namespace を操作する
- まとめ
下準備
あらかじめ必要なパッケージをインストールしておく。
$ sudo apt-get update $ sudo apt-get install \ util-linux \ build-essential \ python3-sysv-ipc
SystemV IPC について
今回は IPC Namespace の動作確認のために SystemV IPC を使う。
SystemV IPC は、その名のとおり UNIX System V で導入された IPC の仕組み。
SystemV IPC はメッセージキュー、共有メモリ、セマフォという 3 種類の機能を提供している。
操作するためには msgget(2) や msgsnd(2) といったシステムコールを使う。
詳細は man 7 sysvipc
を参照する。
ただし、今回は SystemV IPC 自体を詳しく解説したいわけではない。 そこで、操作には util-linux に含まれる ipcs(1) と Python ラッパーの sysv-ipc を使う。
たとえば ipcs(1) をオプションなしで実行すると、SystemV IPC に関するリソースの利用状況がわかる。 初期状態では、特に何も作られていない。
$ ipcs ------ Message Queues -------- key msqid owner perms used-bytes messages ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status ------ Semaphore Arrays -------- key semid owner perms nsems
ここで、試しにメッセージキューを作ってみよう。 まずは、Python のインタプリタを起動する。
$ python3
sysv_ipc
パッケージをインポートして、0x100
というキーでメッセージキューを作る。
>>> import sysv_ipc >>> q = sysv_ipc.MessageQueue(key=0x100, flags=sysv_ipc.IPC_CREAT, mode=0o644)
別のターミナルから ipcs(1) を実行すると、メッセージキューができていることがわかる。
-q
オプションをつけるとメッセージキューに関する情報だけ表示できる。
$ ipcs -q ------ Message Queues -------- key msqid owner perms used-bytes messages 0x00000100 0 vagrant 644 0 0
キューに対してオブジェクトを送ってみよう。
>>> q.send("Hello, World!")
ここで、別のプロセスから Python のインタプリタを起動する。
$ python3
そして、先ほどと同じキー 0x100
を指定してメッセージキューを参照する。
>>> import sysv_ipc >>> q = sysv_ipc.MessageQueue(key=0x100, mode=0o644)
キューのオブジェクトに対して receive()
メソッドを実行すると、先ほど送ったメッセージが取得できる。
>>> q.receive() (b'Hello, World!', 1)
結果はタプルになっていて、2 番目の要素はメッセージを送るときにつけた type
を表している。
デフォルトでは 1
になっており、これはキューの中をさらに細分化して扱うための仕組みのようだ。
unshare(1) を使って IPC Namespace を操作する
さて、SystemV IPC の基本的な説明が終わったので、ここから本題の IPC Namespace を扱っていく。 まずは unshare(1) を使って IPC Namespace を操作してみよう。
現在のプロセスが所属する IPC Namespace は /proc/self/ns/ipc
で確認できる。
以下であれば 4026531839
という識別子に所属している。
$ file /proc/self/ns/ipc /proc/self/ns/ipc: symbolic link to ipc:[4026531839]
ここで unshare(1) を --ipc
オプションをつけて実行してみよう。
同時に bash(1) を起動する。
$ sudo unshare --ipc bash
すると、所属する IPC Namespace が 4026532177
へと変化したことがわかる。
# file /proc/self/ns/ipc /proc/self/ns/ipc: symbolic link to ipc:[4026532177]
ipcs(1) を実行すると、先ほどまで見えていたメッセージキューも表示されなくなっている。 これが正に IPC Namespace の機能であり、IPC に関するリソースを隔離できている。
# ipcs ------ Message Queues -------- key msqid owner perms used-bytes messages ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status ------ Semaphore Arrays -------- key semid owner perms nsems
試しに 0x1000
というキーでメッセージキューを作ると、ちゃんと作成できる。
# python3 -c "import sysv_ipc; sysv_ipc.MessageQueue(key=0x1000, flags=sysv_ipc.IPC_CREAT, mode=0o644)" # ipcs ------ Message Queues -------- key msqid owner perms used-bytes messages 0x00001000 0 root 644 0 0 ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status ------ Semaphore Arrays -------- key semid owner perms nsems
元々のターミナル、つまりシステムにおいて ipcs(1) を実行するとキーが 0x100
のメッセージキューが見える。
$ ipcs ------ Message Queues -------- key msqid owner perms used-bytes messages 0x00000100 0 vagrant 644 0 0 ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status ------ Semaphore Arrays -------- key semid owner perms nsems
ちゃんとメッセージキューが Namespace ごとに隔離されている様子が確認できた。
unshare(1) で起動した bash(1) は一旦終了しておく。
# exit
unshare(2) を使って IPC Namespace を操作する
続いては unshare(2) のシステムコールを使って IPC Namespace を操作してみよう。
下記のサンプルコードでは unshare(2) の引数に CLONE_NEWIPC
を指定することで新しく IPC Namespace を作成している。
その上で execvp(3) を使ってシェルを起動している。
#define _GNU_SOURCE #include <sched.h> #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <unistd.h> int main(int argc, char *argv[]) { // unshare(2) で IPC Namespace を作成する if (unshare(CLONE_NEWIPC) != 0) { fprintf(stderr, "Failed to create a new IPC namespace: %s\n", strerror(errno)); exit(EXIT_FAILURE); } // execvp(3) でシェルを起動する char* const args[] = {"bash", NULL}; if (execvp(args[0], args) != 0) { fprintf(stderr, "Failed to exec \"%s\": %s\n", args[0], strerror(errno)); exit(EXIT_FAILURE); } return EXIT_SUCCESS; }
上記をビルドする。
$ gcc -Wall example.c
ビルドしたら実行しよう。
$ sudo ./a.out
実行して起動されるシェルからは、先ほどと同じようにシステムのメッセージキューが表示されない。
# ipcs ------ Message Queues -------- key msqid owner perms used-bytes messages ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status ------ Semaphore Arrays -------- key semid owner perms nsems
/proc/self/ns/ipc
の識別子についても、システムとは異なっている。
# file /proc/self/ns/ipc /proc/self/ns/ipc: symbolic link to ipc:[4026532177]
今回も、試しにメッセージキューを作ってみよう。
# python3 -c "import sysv_ipc; sysv_ipc.MessageQueue(key=0x1000, flags=sysv_ipc.IPC_CREAT, mode=0o644)" # ipcs ------ Message Queues -------- key msqid owner perms used-bytes messages 0x00001000 0 root 644 0 0 ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status ------ Semaphore Arrays -------- key semid owner perms nsems
やはり、システムで実行する ipcs(1) とは SystemV IPC のリソースが隔離されていることが分かる。
$ ipcs ------ Message Queues -------- key msqid owner perms used-bytes messages 0x00000100 0 vagrant 644 0 0 ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status ------ Semaphore Arrays -------- key semid owner perms nsems
まとめ
今回は IPC Namespace を使って、SystemV IPC に関連するリソースが隔離される様子を観察してみた。