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