TenForward

技術ブログ。はてなダイアリーから移転しました

Linux 3.8 で改良された Namespace 機能と lxc-attach コマンド

以前から lxc には lxc-attach というコマンドが含まれていました.このコマンドは OpenVZ やその商用版の Virtuozzo にある vzctl exec というコマンドと同じ目的のもので,コンテナ外のホスト OS 上から直接コンテナ内のコマンドを実行するためのものです.なぜ attach なのかというと,実行しようとするプロセスを既に存在する名前空間 (Namespace) に参加させるためのモノだからですね (コマンド名から動きが想像しにくい気がしますが :p).

このような目的のコマンドはコンテナ環境を運用しようとすると必須のものと言えます.例えばコンテナを起動するのはホスト上から lxc-start コマンドで実行できますが,シャットダウンコマンドはコンテナ内から実行しないといけません (もちろん外から強制終了は可能ですが).その他,運用の色々な場面でコンテナ外からコンテナ内のコマンドを実行したい場面は多いはずです.

ですが,3.7 までの名前空間をサポートした標準のカーネルではこのコマンドは動作しない状態でした.試しに Ubuntu 12.10 (kernel 3.5) でこのコマンドを試してみると以下のようになります.

# uname -a
Linux voyager 3.5.0-22-generic #34-Ubuntu SMP Tue Jan 8 21:47:00 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux
# lxc-attach -n test01 -- /bin/ls / 
lxc-attach: No such file or directory - failed to open '/proc/1657/ns/mnt'
lxc-attach: failed to enter the namespace
lxc-attach: No such file or directory - failed to open '/sys/fs/cgroup/perf_event//lxc/lxc/test01/tasks'
# ls -l /proc/1657/ns
total 0
-r-------- 1 root root 0 Mar  1 15:41 ipc
-r-------- 1 root root 0 Mar  1 15:41 net
-r-------- 1 root root 0 Mar  1 15:41 uts

エラーメッセージにあるように確かに /proc/1657/ns/mnt というファイルはありません.

setns システムコール

前述のようなプロセスを既に存在する名前空間に参加させて実行するには setns() というシステムコールを使います.man setns で確認すると以下のように使うことがわかります.

  : (略)
NAME
       setns - reassociate thread with a namespace

SYNOPSIS
       #define _GNU_SOURCE             /* See feature_test_macros(7) */
       #include <sched.h>

       int setns(int fd, int nstype);
  : (略)

参加させたい名前空間を示すファイルの file descriptor と,名前空間の種類を与えるとその名前空間に参加できるわけです.前述の /proc/PID/ns 以下にあったファイルがその名前空間を表すファイルです.

lxc-0.8.0 の lxc-attach コマンドの該当部分のコードは以下のようになっています.

int lxc_attach_to_ns(pid_t pid)
{
	char path[MAXPATHLEN];
	char *ns[] = { "pid", "mnt", "net", "ipc", "uts" };
	const int size = sizeof(ns) / sizeof(char *);
	int fd[size];
	int i;

	snprintf(path, MAXPATHLEN, "/proc/%d/ns", pid);
	if (access(path, X_OK)) {
		ERROR("Does this kernel version support 'attach' ?");
		return -1;
	}

	for (i = 0; i < size; i++) {
		snprintf(path, MAXPATHLEN, "/proc/%d/ns/%s", pid, ns[i]);
		fd[i] = open(path, O_RDONLY);
		if (fd[i] < 0) {
			SYSERROR("failed to open '%s'", path);
			return -1;
		}
	}

	for (i = 0; i < size; i++) {
		if (setns(fd[i], 0)) {
			SYSERROR("failed to set namespace '%s'", ns[i]);
			return -1;
		}

		close(fd[i]);
	}

	return 0;
}

つまり前述のエラーは lxc が使っている名前空間 (pid, mnt, net, ipc, uts) を参照するファイルのパスを得ようとしていますが,ipc, net, uts しか存在せず,pid(PID名前空間), mnt(マウント名前空間)を示すファイルが存在ないためにエラーになっている事がわかります.3.7 まではこれが実装されていなかったわけですね.

Linux 3.8 でマージされた /proc/PID/ns 以下のファイルに対する変更

しかし,Linux 3.8 でユーザ名前空間関係のパッチと一緒にこの関係のパッチもマージされ,PID, マウント名前空間ファイルを生成するようになったようです.さらに /proc/PID/ns 以下のファイルも前述では単に /proc 以下の特殊なファイルだっただけなのが,とある inode を指し示す特殊なシンボリックリンクとして実装されるようになりました.

前述のようにこの

  • ファイルの fd を setns に渡すことでその名前空間に参加出来る

というファイルの利用方法の他に,このファイルにはまた別の目的が与えられました.これは,

という風に実装されています.つまり複数のプロセスのこのファイルを stat() で調べて inode 番号を見ると,同じ名前空間に属しているかどうかを調べる事が可能になったわけです.長い間要望されていた機能らしいです.

ちなみにこの辺りの変更が行われたパッチは以下辺りだと思います.

Linux 3.8 で lxc-attach コマンドを実行

ここで 3.8 で起動している環境でコンテナを起動してみましょう.

  • コンテナの起動
    # lxc-start -n test01 -d -o log -c console
    # lxc-info -n test01
    state: RUNNING
    pid: 6061
  • lxc-attach コマンド実行.きちんと実行できています.
    # lxc-attach -n test01 -- uname -a
    Linux test01 3.8.0-plamo64 #1 SMP PREEMPT Fri Feb 22 15:52:08 JST 2013 x86_64 GNU/Linux
  • /proc/PID/ns はこんな感じ
    # ls -l /proc/6061/ns
    合計 0
    lrwxrwxrwx 1 root root 0 3月 1日 14:59 ipc -> ipc:[4026532301]
    lrwxrwxrwx 1 root root 0 3月 1日 15:06 mnt -> mnt:[4026532299]
    lrwxrwxrwx 1 root root 0 3月 1日 15:06 net -> net:[4026532304]
    lrwxrwxrwx 1 root root 0 3月 1日 15:06 pid -> pid:[4026532302]
    lrwxrwxrwx 1 root root 0 3月 1日 15:06 uts -> uts:[4026532300]
    • (比較のために再掲しますが) 3.5 カーネルを使っている Ubuntu 12.10 の場合,ここはこのようになっていました.
      # ls -l /proc/1657/ns
      total 0
      -r-------- 1 root root 0 Mar 1 15:41 ipc
      -r-------- 1 root root 0 Mar 1 15:41 net
      -r-------- 1 root root 0 Mar 1 15:41 uts
  • コンテナ内のプロセスの例えば ipc 名前空間のファイルを確認してみると
    # for p in `lxc-ps -n test01 | tail -n +2 | awk '{print $2}'`; do ruby -e "puts File::Stat.new('/proc/$p/ns/ipc').ino"; done
    4026532301
    4026532301
    4026532301
    4026532301
    4026532301
    4026532301
    4026532301
    4026532301
    4026532301
    4026532301
    • lxc-ps コマンドで起動したコンテナ内の PID を表示させ,/proc/PID/ns/ipc ファイルの参照先の inode を表示させています (念のため解説)

となり,同じ inode 番号を指しているのがわかります.もちろん単に ls -l の出力のリンク先の "ipc:[4026532301]" という部分の [] 内の数字がその inode 番号になっているので,それで簡単にわかりますね.

inode の機能は別にして,既に実装されている名前空間への参照ファイルが /proc 以下に生成されるようになったので lxc-attach もきちんと動くようになったわけです.ここの例のカーネルはユーザ名前空間を有効にしていないカーネルなので /proc/PID/ns 以下は前述のものしかありませんが,もちろんユーザ名前空間を有効にしたカーネルではここに "user" というファイルも生成されます (なはず).