読者です 読者をやめる 読者になる 読者になる

TenForward

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

Linux 3.8 の User Namespace 機能 (2)

Container Linux

ブログのテーマを変えてみました.数日間,今のとはまた別のでしたが,コードの表示部分が見にくかったのでまた変えました ^^;

閑話休題

Linux 3.8 の機能と書きましたが,今回はそれを利用している側を少し見てみました.lxc の開発中のコードが github にありますが,ここにあるコードを見てみました.

これをご覧の皆さんは想像が付くと思いますが,特にすごく複雑な事もなく実装されていますので,今回もまたあまり情報のないブログエントリとなります (^_^;) (今まで情報が充分に含まれてるエントリ自体殆どありませんが :p)

先のブログエントリに書いたマッピングを作っている部分ですが,これは設定ファイルに書かれたマッピングの設定に従い,src/lxc/conf.c 内で普通に文字列処理とファイル入出力の処理がされているだけです.

int add_id_mapping(enum idtype idtype, pid_t pid, uid_t host_start, uid_t ns_start, int range)
{
	char path[PATH_MAX];
	int ret;
	FILE *f;

	ret = snprintf(path, PATH_MAX, "/proc/%d/%cid_map", pid, idtype == ID_TYPE_UID ? 'u' : 'g');
	if (ret < 0 || ret >= PATH_MAX) {
		fprintf(stderr, "%s: path name too long", __func__);
		return -E2BIG;
	}
	f = fopen(path, "w");
	if (!f) {
		perror("open");
		return -EINVAL;
	}
	ret = fprintf(f, "%d %d %d", ns_start, host_start, range);
	if (ret < 0)
		perror("write");
	fclose(f);
	return ret < 0 ? ret : 0;
}

int lxc_map_ids(struct lxc_list *idmap, pid_t pid)
{
	struct lxc_list *iterator;
	struct id_map *map;
	int ret = 0;

	lxc_list_for_each(iterator, idmap) {
		map = iterator->elem;
		ret = add_id_mapping(map->idtype, pid, map->hostid, map->nsid, map->range);
		if (ret)
			break;
	}
	return ret;
}

/proc/PID/uid_map or gid_map を開いて文字列書いてるだけですね.ちなみに設定ファイルには

 lxc.id_map = U 100000 0 10000
 lxc.id_map = G 100000 0 10000

こんな感じに書きます.これで名前空間内では 0-10000 の ID が,ホスト上では 100000-110000 となります.この設定は複数書けるようです.

lxc.id_map = U 9800 0 100
lxc.id_map = U 9900 1000 100

名前空間内で 0-100, 1000-1100 の uid が 9800-9900, 9900-10000 に割り当てられます.

さて,lxc のコードに話を戻して,lxc_map_ids 関数ですが,src/lxc/start.c 内の lxc_spawn 関数内で呼ばれています.この関数ですが,まさにコンテナの名前空間を作ってコンテナをスタートさせるような関数です.

int lxc_spawn(struct lxc_handler *handler)
{
    : (略)
	handler->clone_flags = CLONE_NEWUTS|CLONE_NEWPID|CLONE_NEWIPC|CLONE_NEWNS;
	if (!lxc_list_empty(&handler->conf->id_map)) {
		INFO("Cloning a new user namespace");
		handler->clone_flags |= CLONE_NEWUSER;
	}
	if (!lxc_list_empty(&handler->conf->network)) {

		handler->clone_flags |= CLONE_NEWNET;
    : (略)

ここまでで clone のフラグとして UTS, PID, IPC, マウントの名前空間を指定して,設定ファイルでマッピングの指定がある場合はユーザ名前空間の指定 (CLONE_NEWUSER) を指定し,コンテナ独自のネットワークを作成するように設定されている場合は,それも指定 (CLONE_NEWNET) しています.

その後,指定した名前空間でコンテナのプロセスを作成します.

	/* Create a process in a new set of namespaces */
	handler->pid = lxc_clone(do_start, handler, handler->clone_flags);
	if (handler->pid < 0) {
		SYSERROR("failed to fork into a new namespace");
		goto out_delete_net;
	}

で,その後,その pid に対してユーザ名前空間マッピングを先ほどの lxc_map_ids を呼ぶことによって作成しています.

	if (lxc_map_ids(&handler->conf->id_map, handler->pid)) {
		ERROR("failed to set up id mapping");
		goto out_delete_net;
	}

あとは処理に必要な chown とか

	/* If child is in a fresh user namespace, chown his ptys for
	 * it */
	if (uid_shift_ttys(handler->pid, handler->conf))
		DEBUG("Failed to chown ptys.\n");

カーネルは色々実装が複雑ですが,使う側は特に大規模な変更がされているわけでもありません (もちろん,これ以外にも変更は加わっています).

(Linux 3.8 の User Namespace 機能 (3) - TenForwardの日記へ続く)