TenForward

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

cgroup の SUBSYS マクロ

Linux カーネルの cgroup 関連のコードのお話。

cgroup_subsys.h というヘッダがあって、cpuset の部分だけ抜き出すと

#if IS_ENABLED(CONFIG_CPUSETS)
SUBSYS(cpuset)
#endif

という風に SUBSYS マクロの中に cpuset のようなサブシステム名を与えているだけのヘッダファイルがあります。

この SUBSYS マクロは cgroup.h と cgroup.c で計 4 回ほど定義されています。

まず最初に、cgroup.h で

#define SUBSYS(_x) _x ## _cgrp_id,

と定義されています。サブシステム名と _cgrp_id という文字列を連結します。

直後に enum の定義があり、

enum cgroup_subsys_id {
#include <linux/cgroup_subsys.h>
        CGROUP_SUBSYS_COUNT,
};

上記のように、そこで cgroup_subsys.h が include されているので、さきほどの SUBSYS(cpuset)cpuset_cgrp_id という風に展開されます。

つまり、

enum cgroup_subsys_id {
        cpuset_cgrp_id,

となります。cgroup_subsys.h には、他に存在するサブシステムが (kernel の config で有効になっている場合に) SUBSYS(サブシステム名) という風に定義されているので、(サブシステム名)_cgrp_id という値をサブシステム分持つ enum が定義されます。そして、最後が CGROUP_SUBSYS_COUNT となるわけですね。つまり、

enum cgroup_subsys_id {
        cpuset_cgrp_id,
        cpu_cgrp_id,
        cpuacct_cgrp_id,
        blkio_cgrp_id,
        memory_cgrp_id,
        devices_cgrp_id,
        freezer_cgrp_id,
        net_cls_cgrp_id,
        perf_event_cgrp_id,
        net_prio_cgrp_id,
        hugetlb_cgrp_id,
        debug_cgrp_id,
        CGROUP_SUBSYS_COUNT,
};

という enum が完成します。直後に

#undef SUBSYS

と SUBSYS は undef されているので、ここで上記の enum を作って役割は終わりです。この CGROUP_SUBSYS_COUNT はサブシステム分ループする場合に使えますね。例えばこんなふうに

#define for_each_subsys(ss, ssid)                   \
   for ((ssid) = 0; (ssid) < CGROUP_SUBSYS_COUNT &&      \
        (((ss) = cgroup_subsys[ssid]) || true); (ssid)++)

そして、サブシステムの配列などで、それぞれのサブシステムの ID を表す (サブシステム名)_cgrp_id という値が利用できます。あとは配列の領域確保のときに使われています。


もう 1 か所、cgroup.h に SUBSYS の定義があります。

#define SUBSYS(_x) extern struct cgroup_subsys _x ## _cgrp_subsys;
#include <linux/cgroup_subsys.h>
#undef SUBSYS

サブシステム名を _cgrp_subsys と連結して、extern struct cgroup_subsys の後に書くので、例えば cpuset なら

extern struct cgroup_subsys cpuset_cgrp_subsys;

となります。cpuset_cgrp_subsys 変数は kernel/cpuset.c に定義がありますので、それの extern 宣言になりますね。同様に、有効になっている各サブシステムの cgroup_subsys 構造体ごとに宣言されます。


kernel/cgroup.c にも 2 ヶ所ほどあります。

/* generate an array of cgroup subsystem pointers */
#define SUBSYS(_x) [_x ## _cgrp_id] = &_x ## _cgrp_subsys,
static struct cgroup_subsys *cgroup_subsys[] = {
#include <linux/cgroup_subsys.h>
};
#undef SUBSYS

最初に紹介した、サブシステム名と _cgrp_id を連結した enum の id が使われていますね。これも同様に

[cpuset_cgrp_id] = &cpuset_cgrp_subsys,

という行が生成されますね。つまり cgroup_subsys という配列の (サブシステム名)_cgrp_id 番目は (サブシステム名)_cgrp_subsys へのポインタということです。

これは先に紹介した for_each_subsys マクロで使われていましたね。


さいごに kernel/cgroup.c にあるのが、サブシステム名を命名するマクロです。

/* array of cgroup subsystem names */
#define SUBSYS(_x) [_x ## _cgrp_id] = #_x,
static const char *cgroup_subsys_name[] = {
#include <linux/cgroup_subsys.h>
};
#undef SUBSYS

これもさきほどと同様の処理でサブシステム名の配列を作っています。配列の (サブシステム名)_cgrp_id 番目は (サブシステム名)、つまり SUBSYS マクロに指定した文字列となります。

[cpuset_cgrp_id] = cpuset,

つまり、カーネルが使うサブシステムに関わる構造体の配列とか ID とか名前の配列なんかは、カーネルの config 時 (make config とか make menuconfig とか) の時点で定義され、コンパイル時点で作成されるってことですね。

いやー、さすがカーネル開発するような人は賢いなあ (←言ってる人アホっぽい)

cgroup のデフォルトルート cgrp_dfl_root

このエントリはほぼ個人的なメモで、色々唐突です。

cgroupのコアは kernel/cgroup.c に色々処理があります。

その中に「デフォルトヒエラルキ(のルート)」という変数があります。

4.1 kernel のコードです。

/*
 * The default hierarchy, reserved for the subsystems that are otherwise
 * unattached - it never has more than a single cgroup, and all tasks are
 * part of that cgroup.
 */
struct cgroup_root cgrp_dfl_root;

cgroup の初期化処理は start_kernel から呼び出される cgroup_init_early と cgroup_init で行われますが、cgrp_dfl_root は cgroup_init で初期化されているようです。

int __init cgroup_init(void)
{
	struct cgroup_subsys *ss;
	unsigned long key;
	int ssid, err;

	BUG_ON(cgroup_init_cftypes(NULL, cgroup_dfl_base_files));
	BUG_ON(cgroup_init_cftypes(NULL, cgroup_legacy_base_files));

	mutex_lock(&cgroup_mutex);

	/* Add init_css_set to the hash table */
	key = css_set_hash(init_css_set.subsys);
	hash_add(css_set_table, &init_css_set.hlist, key);

	BUG_ON(cgroup_setup_root(&cgrp_dfl_root, 0));

ここで cgroup_setup_root に cgrp_dfl_root が渡されます。この中で渡された root (つまり cgrp_dfl_root) の初期化処理がなされていきます。

ここで root = cgrp_dfl_root の場合は cgroup_dfl_base_files

static int cgroup_setup_root(struct cgroup_root *root, unsigned int ss_mask)
{

    : (snip)

	if (root == &cgrp_dfl_root)
		base_files = cgroup_dfl_base_files;
	else
		base_files = cgroup_legacy_base_files;

    : (snip)

	ret = cgroup_addrm_files(root_cgrp, base_files, true);

ここで設定した base_files を cgroup_addrm_files に渡して、cgroup コアが各 cgroup (つまり cgroup を表す各ディレクトリ) に作成するファイルを設定します。

cgroup_dfl_base_files ってのは

/* cgroup core interface files for the default hierarchy */
static struct cftype cgroup_dfl_base_files[] = {
	{
		.name = "cgroup.procs",
		.seq_start = cgroup_pidlist_start,
		.seq_next = cgroup_pidlist_next,
		.seq_stop = cgroup_pidlist_stop,
		.seq_show = cgroup_pidlist_show,
		.private = CGROUP_FILE_PROCS,
		.write = cgroup_procs_write,
		.mode = S_IRUGO | S_IWUSR,
	},
	{
		.name = "cgroup.controllers",
		.flags = CFTYPE_ONLY_ON_ROOT,
		.seq_show = cgroup_root_controllers_show,
	},
	{
		.name = "cgroup.controllers",
		.flags = CFTYPE_NOT_ON_ROOT,
		.seq_show = cgroup_controllers_show,
	},
	{
		.name = "cgroup.subtree_control",
		.seq_show = cgroup_subtree_control_show,
		.write = cgroup_subtree_control_write,
	},
	{
		.name = "cgroup.populated",
		.flags = CFTYPE_NOT_ON_ROOT,
		.seq_show = cgroup_populated_show,
	},
	{ }	/* terminate */
};

という内容ですが、ここで .name で指定されているファイルって cgroup-v2 の各 cgroup (ディレクトリ) に作成されるファイルです。

つまり start_kernel -> cgroup_init で起動時に設定される「デフォルトのヒエラルキ(のルート)」は v2 ?

確かに v2 は単一階層構造なのでここで初期化しておけば、あとはそれを利用すれば良いのに対して、v1 はいくつでもマウントできるので後で設定してもいいのかな? と思えますが、もしかして今後は v2 をデフォルトにしたいという考えの現れ?

3.15 kernel では

ちなみに v2 のコードがまだそれほど入っていないころの 3.15 だと、cgroup_init 内で cgroup_setup_root が呼ばれているのは同じですが、cgroup_setup_root は

static int cgroup_setup_root(struct cgroup_root *root, unsigned long ss_mask)
{

    :(snip)

	ret = cgroup_addrm_files(root_cgrp, cgroup_base_files, true);

と条件文はなくて直接 cgroup_base_files が渡されており、cgroup_base_files は

static struct cftype cgroup_base_files[] = {
	{
		.name = "cgroup.procs",
    : (snip)

	},
	{
		.name = "cgroup.clone_children",
		.flags = CFTYPE_INSANE,
    : (snip0)
	},
	{
		.name = "cgroup.sane_behavior",
		.flags = CFTYPE_ONLY_ON_ROOT,
    : (snip)
	},

	/*
	 * Historical crazy stuff.  These don't have "cgroup."  prefix and
	 * don't exist if sane_behavior.  If you're depending on these, be
	 * prepared to be burned.
	 */
	{
		.name = "tasks",
		.flags = CFTYPE_INSANE,		/* use "procs" instead */
    : (snip)

	},
	{
		.name = "notify_on_release",
		.flags = CFTYPE_INSANE,
    : (snip)

	},
	{
		.name = "release_agent",
		.flags = CFTYPE_INSANE | CFTYPE_ONLY_ON_ROOT,

    : (snip)

	},
	{ }	/* terminate */
};

というふうに v1 のファイルは flags に CFTYPE_INSANE ってのが指定されています。追加の際に v1, v2 を判定して追加するかどうかを決めています。

static int cgroup_addrm_files(struct cgroup *cgrp, struct cftype cfts[],
			      bool is_add)
{

    : (snip)

	for (cft = cfts; cft->name[0] != '\0'; cft++) {
		/* does cft->flags tell us to skip this file on @cgrp? */
		if ((cft->flags & CFTYPE_ONLY_ON_DFL) && !cgroup_on_dfl(cgrp))
			continue;
		if ((cft->flags & CFTYPE_INSANE) && cgroup_sane_behavior(cgrp))
			continue;
		if ((cft->flags & CFTYPE_NOT_ON_ROOT) && !cgrp->parent)
			continue;
		if ((cft->flags & CFTYPE_ONLY_ON_ROOT) && cgrp->parent)
			continue;

		if (is_add) {
			ret = cgroup_add_file(cgrp, cft);
    : (snip)
}

という風に v1, v2 混在したようなコードになってます。

Plamo Linux で Let's Encrypt

はじめに (読まなくても問題ありません)

自前で Plamo Linux で運用しているサーバはずっと StartSSL を使って証明書を取得していました。

Let's Encrypt が出てきた時は、いろいろ調べてみるとコマンド著名ディストリビューション前提で、Plamo には対応していないようなので、更新時期がきた証明書も引き続き StartSSL で取得しました。

その後、Zenlogicで運用している趣味のブログをZenlogicの機能で Let's Encrypt を使った SSL/TLS 化をしたのをきっかけに少し調べてみると、Plamo Linux でも Let's Encrypt の証明書が取得できそうなので、試してみました。

最近は certbot というコマンドを使うようですね。

まず最初に考えたのが、Plamo 用のパッケージを作ってしまうことです。SlackwareでもSlackbuildsにパッケージがありますし、ArchLinuxのスクリプトを見てもそれほど難しくはなさそうです。

しかし、依存パッケージが多い! いくら python setup.py を実行するだけとはいえ、これだけのためにいくつものパッケージを自作するのは面倒だし、それだけのためにたくさんパッケージを入れるのもなんか無駄な気がします。

というわけで、certbot-auto コマンドを実行すると、virtualenv を使った環境を作って、そこで必要なことはやってくれるようなので、お気楽にやってみようということで試しました。

準備とインストール

まずは必要最低限準備するパッケージをインストールしました。(私の作ったオレオレパッケージを公開しているのでリンクしておきます)

pip, virtualenv は Plamo 標準ではパッケージが準備されていませんので、適当にインストールするなり、私の作ったオレオレビルドスクリプト(python2_pip)、(python2_virtualenv)でパッケージを作成するなりしてインストールします (pip パッケージの作成には他にもパッケージインストールが必要かもしれません)。

以下では standalone プラグインしか使ってないので、他のプラグインを使う場合は他にも準備が必要かもしれません (試してません)。

さて、ここまできたらおもむろに certbot を取得します。

$ git clone https://github.com/certbot/certbot.git

準備は OK です。clone した certbot ディレクトリに移動して、virtualenv 環境をします。

$ cd certbot
$ sh tools/venv.sh

これでいろいろと準備が行われて環境が構築されます。

動作テスト

きちんと動作するか確認してみましょう。activate を実行して virtualenv 環境に入り (?)、

# source certbot/venv/bin/activate
(venv) # certbot --help all
usage: 
  certbot [SUBCOMMAND] [options] [-d DOMAIN] [-d DOMAIN] ...

Certbot can obtain and install HTTPS/TLS/SSL certificates.  By default,
it will attempt to use a webserver both for obtaining and installing the
cert. The most common SUBCOMMANDS and flags are:
  :(略)

とりあえず help を表示させてみました。うまくセットアップされていないと、ここでエラーになります。

証明書発行のテスト

これであとは証明書を発行すれば良いのですが、スクリプト化をしたり、テスト実行したりしたいでしょうから、何回も証明書の発行を行うこともあるかもしれません。

Let's Encrypt は一定期間中に発行できる証明書数の上限がありますので、とりあえずテストの証明書を発行してみましょう。そのためにステージング環境が準備されています。これで発行した証明書はブラウザでは警告が出てページがそのままでは表示されませんが、証明書の発行テストにはなります。

ステージング環境で実行するには、本番と同じコマンドに "--staging" とつけるだけなので簡単です。さらにまずは "--dry-run" でオプションに間違いがないかを確認してみましょう。

"--standalone" をつけて standalone プラグインを使う場合は、ローカルで Web サーバが起動しますので、Apache や nginx など、他の Web サーバが起動している場合は停止しましょう。

# apachectl stop
# certbot certonly --dry-run --standalone --domain www.example.com --email myaddress@example.com --non-interactive --staging
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Obtaining a new certificate
Performing the following challenges:
tls-sni-01 challenge for www.example.com
Waiting for verification...
Cleaning up challenges
Generating key (2048 bits): /etc/letsencrypt/keys/0001_key-certbot.pem
Creating CSR: /etc/letsencrypt/csr/0001_csr-certbot.pem

IMPORTANT NOTES:
 - The dry run was successful.

うまくいきそうなら、"--dry-run" を外して実行しましょう。

発行のテストが済んだら証明書更新のテストもやってみましょう。有効な証明書があると言われるので、強制的に新しい証明書を発行するために "--force-renewal" をつけます。

certbot renew --force-renewal --staging

発行

"--staging" で発行できたら、いよいよ本番です。

手元でステージングで発行した証明書がある場合、本番でも有効な証明書があるから発行できないと怒られるかもしれません (いろいろ試してたのでどうだったか忘れたw)。その場合は先と同様に "--force-renewal" をつけます。

# certbot certonly --standalone --domain www.example.com --email myaddress@example.com --non-interactive --agree-tos
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Obtaining a new certificate
Performing the following challenges:
  :(snip)

更新は "renew" コマンドを実行するだけですが、cron なんかで自動実行する場合は、起動中の Web サーバの停止・起動が必要ですので、

certbot renew --force-renewal --pre-hook "apachectl stop" --post-hook "apachectl start"

みたいにしておくと、確認の前後で hook を実行してくれます。

certbot 実行の詳しくは以下が参考になります (以上で書いたのは適当です)。

Apache HTTP server の mod_proxy_html メモ

最近、結構音楽ブログを更新しています。昔は音楽レビューといえばひたすら言葉でレビューするだけだったのが、最近は動画の埋め込みを行うようになって表現力のない私でも気楽にレビューができるようになってきました。

動画の埋め込みというと YouTube がまず思い浮かびますね。私の音楽ブログでもよく使っています。他に音楽ブログの性格上、V LIVEの動画を埋め込むこともあります。どちらも iframe を使った埋め込みコードが動画ページから生成できますので、気楽に貼り付けられます。

さて、私の音楽ブログは最近常時 SSL 化の流れで Let's Encrypt を使って SSL 化を行いました (Zenlogicの機能を使っただけ!!)。http でアクセスしても https に飛ばされます。https でアクセスするページに YouTube のコードを埋め込むのは何の問題もないのですが、V LIVE は埋め込む URL が http なので https でアクセスするページに埋め込みコードを貼り付けてもブロックされてしまいます。

となると、自分で運用している VPS でリバースプロキシの設定をやって、それを貼り付けたらいいんじゃないか? という考えが思い浮かぶのでやってみました。まあ例えば

ProxyPass "/vlive" "http://www.vlive.tv"
ProxyPassReverse "/vlive" "http://www.vlive.tv"

ってな感じですぐに設定できました。早速喜んで埋め込みコードとして、自 VPS の URL を使ってみましたが、これだと iframe の src に書かれた先を読み込むのは https 化されましたが、その中でさらに呼ばれる js や css が相変わらず http なので、動画はブロックされたままでした。残念...

そこで確かそういう場合に使うモジュールあったよなあと思ってググってみると、proxy 通した場合のコンテンツ内の URL を変換する場合は

  • mod_substitute
  • mod_proxy_html

なんかを使うようですね。

そこでmod_proxy_htmlを試すかということで、mod_proxy_htmlの公式マニュアルを見ながら設定してみました (mod_proxy_html は以前使ったことあるような記憶があったので)。

ProxyHTMLEnable On
ProxyHTMLExtended On
ProxyHTMLURLMap http://www.vlive.tv/ https://www.example.com/

# 以下は mod_proxy_html と一緒にインストールされる proxy-html.conf 内で書かれている設定もあります
ProxyHTMLLinks meta content
ProxyHTMLLinks script src
ProxyHTMLLinks link href
  : (snip)

とするけど、ブラウザでエラーになってしまいます(Chrome だと "ERR_CONTENT_DECODING_FAILED")。うーむ、としばらくいろいろ設定を試したのですがうまくいかずに諦めようかなという時にふと目に付いたのが公式マニュアルのコメント欄

RequestHeader unset Accept-Encoding

これを加えると見事動作しました。マニュアル部分に注釈としてでも書いておいてほしいなー。

SetOutputFilter proxy-html

を設定しろみたいに書いてあるページもいくつか見つけましたが、コメント欄にあるように UTF-8 の部分が文字化けしてしまいますので不要です。

ハマったのでメモ代わりに残しておこうと思ってブログ書いたけど…、ふと今日検索してみると…

こんなページがひっかかった!! (^_^;) mod_substitute でも同じ問題が起きるようですね。なるほど、ブラウザの送るヘッダをそのまま送って圧縮した応答なんかが帰ってきた場合にうまく処理できないってことですか。

なぜハマってる最中にググった時に気づかなかったんだ!? というわけで書くほどでもなかったネタのようですが、せっかく書いたのでそのまま公開しておきます。

せっかく問題は解決したのに、V LIVE で貼り付けようとしていた動画を早速貼り付けようとしたら、YouTube でも公開されていたので、まだ音楽ブログでは活用できてません。

ip netns コマンドが意外にきめ細やかにコンテナを作ってくれる

(2016-07-26: 誤記修正しました "setns -> nsenter")

お手軽にシェルスクリプトなんかでコンテナを作る場合の強い味方といえば

  • util-linux の unshare/nsenter コマンド
  • iproute2 の ip netns コマンド

が代表的でしょう。"ip netns" は Network Namespace 作ってコマンド実行するだけの単純なコマンドかと思ったら、中では意外に色々細かくやってくれていることがわかったのでちょっと紹介しておきます。

以下は iproute2 4.2.0 で試しています。

/var/run/netns

まずよく知られているのが /var/run/netns 以下に Namespace 名のファイルを作ってくれることですね。これは、Namespace 内で動いているプロセスがなくなったら Namespace が消滅してくれるのを防ぐためです。だって ip netns add が終了したら、Namespace 内にはプロセスいなくなりますから。

まずはこの辺りで /var/run/netns を MS_SHARED にして、自身を bind mount して、ココをマウントポイントにしています。

   636        while (mount("", NETNS_RUN_DIR, "none", MS_SHARED | MS_REC, NULL)) {
        :(snip)
   644               /* Upgrade NETNS_RUN_DIR to a mount point */
   645               if (mount(NETNS_RUN_DIR, NETNS_RUN_DIR, "none", MS_BIND, NULL)) {
        :(snip)
   651        }
   (ip/ipnetns.cより)

そして

   667        /* Bind the netns last so I can watch for it */
   668        if (mount("/proc/self/ns/net", netns_path, "none", MS_BIND, NULL) < 0) {
   (ip/ipnetns.cより)

ip netns add コマンドのプロセスディレクトリの ns/net ファイルを /var/run/netns 以下に bind mount していますね。このように Namespace を示す特殊なファイルを残したままにしておけば Namespace が消えません。

Namespace と veth 作成

ここで Network Namespace として "netns01" を作成し、ホストとコンテナ間は veth インターフェースを作成します。

# export NETNS="netns01"
# export VETH="veth0"

それでは Network Namespace を作りましょう。

# ip netns add $NETNS
# ip netns list
netns01

簡単ですね。

それでは veth インターフェースを作りましょう。

# ip link add name $VETH-host type veth peer name $VETH-ns
# ip link set $VETH-ns netns $NETNS
# ip addr add 10.10.10.10/24 dev $VETH-host
# ip link set $VETH-host up
# ip netns exec $NETNS ip addr add 10.10.10.11/24 dev $VETH-ns
# ip netns exec $NETNS ip link set $VETH-ns up

ホスト側は "veth-host"、コンテナ側は "veth-ns" という名前のペアを作りました。それぞれアドレスを与えて、インターフェースを up します。

では、この作成した Namespace で bash を実行してコンテナを作成してみましょう。

# ip netns exec $NETNS /bin/bash
# echo $$
11945
# ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
11: veth0-ns@if12: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether ba:aa:99:70:46:ab brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.10.10.11/24 scope global veth0-ns
       valid_lft forever preferred_lft forever
    inet6 fe80::b8aa:99ff:fe70:46ab/64 scope link 
       valid_lft forever preferred_lft forever

"veth0-ns" インターフェースがあって、ちゃんとアドレスが割りあたってますね。まあ、これはそうなるようにコマンド実行していますので当たり前です。

コンテナ内の sysfs

ここで、Namespace 内で /sys 以下を見てみましょう。

# ls /sys/class/net -l
合計 0
lrwxrwxrwx 1 root root 0  7月 26日  20:01 lo -> ../../devices/virtual/net/lo/
lrwxrwxrwx 1 root root 0  7月 26日  20:01 veth0-ns -> ../../devices/virtual/net/veth0-ns/

ちゃんと Namespace 内のインターフェースのみ見えてますね。これは、この辺りの処理ですね。

    66        if (unshare(CLONE_NEWNS) < 0) {
    67               fprintf(stderr, "unshare failed: %s\n", strerror(errno));
    68               return -1;
    69        }
    70        /* Don't let any mounts propagate back to the parent */
    71        if (mount("", "/", "none", MS_SLAVE | MS_REC, NULL)) {
    72               fprintf(stderr, "\"mount --make-rslave /\" failed: %s\n",
    73                      strerror(errno));
    74               return -1;
    75        }
    76        /* Mount a version of /sys that describes the network namespace */
    77        if (umount2("/sys", MNT_DETACH) < 0) {
    78               fprintf(stderr, "umount of /sys failed: %s\n", strerror(errno));
    79               return -1;
    80        }
    81        if (mount(name, "/sys", "sysfs", 0, NULL) < 0) {
    82               fprintf(stderr, "mount of /sys failed: %s\n",strerror(errno));
    83               return -1;
    84        }
    (lib/namespace.cより)
  • 66行目で Mount Namespace を作ってます
  • 71行目で他の Mount Namespace に自身の Mount が伝播しないようにしています
  • 77行目で現在の /sys を umount して
  • 81行目で sysfs を改めて mount します

まあ、当たり前といえば当たり前ですが、ちゃんとここまで面倒見ててくれたんですね、ip netns。

/etc/netns

そして、以上の辺りのソースをつらつらと眺めていたら、気になる関数を見つけました。

    86        /* Setup bind mounts for config files in /etc */
    87        bind_etc(name);
    (lib/namespace.cより)

これを見てみると

    35               snprintf(netns_name, sizeof(netns_name), "%s/%s", etc_netns_path, entry->d_name);
    36               snprintf(etc_name, sizeof(etc_name), "/etc/%s", entry->d_name);
    37               if (mount(netns_name, etc_name, "none", MS_BIND, NULL) < 0) {
    (lib/namespace.cより)

"etc_netns_path" は /etc/netns/[namespace名] が入っていますので、その下のファイルを作成した Namespace 内に bind mount してくれるようです。

早速、ホスト上で /etc/netns/netns01/{hosts,resolv.conf} を作ってみました。

# cat /etc/netns/$NETNS/hosts 
10.10.10.10	host01
10.10.10.11	ns01
# cat /etc/netns/$NETNS/resolv.conf 
nameserver 8.8.8.8

以上を準備してから、再度コンテナを作成します。

# ip netns exec $NETNS /bin/bash
# cat /etc/resolv.conf 
nameserver 8.8.8.8
# cat /etc/hosts
10.10.10.10	host01
10.10.10.11	ns01

見事に作成した Namespace で実行されるコンテナでは準備したファイルが見えていますね。こんなとこまで面倒見てくれてたとは。(ちゃんと man ip-netns に書いてあるやん)

ip netns コマンドのソース、シンプルでコメントもちゃんと書かれてあって読みやすいのでコンテナ作成の勉強になる気がします。