CUBE SUGAR CONTAINER

技術系のこと書きます。

Linux の IPC Namespace について

Linux のコンテナ仮想化を実現する機能の一つに Namespace がある。 Namespace はプロセスが動作する際のリソースをカーネルの中で隔離 (分離) する仕組み。 Namespace は隔離する対象のリソースによって色々とある。

man7.org

今回は、その中でも 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.

もくじ

下準備

あらかじめ必要なパッケージをインストールしておく。

$ 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 を参照する。

man7.org

ただし、今回は 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 に関連するリソースが隔離される様子を観察してみた。