Linux Network Namespace(netns)でEC2のENIごとに異なるネットワーク設定

2018.01.17

ども、大瀧です。
EC2インスタンスに追加のENI(Elastic Network Interface)を設定すると、ルートテーブルの不整合でうまく通信できなかったり、アプリケーションを追加のENI経由で通信させるのに苦戦したという経験はありませんか。自分は悶々とトラブルシューティングしていた時期があり、極力使わないのがいいなという結論をブログで吐露していました。時を経てひょんなことからnetns(Network Namespace)に触れる機会があり、追加のENIの管理と相性が良かったのでご紹介してみたいと思います。

Linux Network Namespaceとは

Network Namespace(netns)は、OSのネットワークスタック(ルーティングテーブルやiptablesを含むネットワーク設定)を分離して管理できるLinuxカーネルの機能です。古くはXenやKVMなどLinuxをハイパーバイザーのホストOSとして動作させるための仮想ネットワークの中核コンポーネントとして、最近はOpenStackやDockerでの利用が有名ですね。十分枯れた機能と見て良いと思います。

netnsを利用することで、同一のインスタンス/OS上でアプリケーションやNIC単位のネットワーク設定を持たせることができます。サーバーアプリケーションを用途ごとに複数実行するケースや、ルーティングなど複雑な設定になりがちな構成をシンプルにまとめられるのがメリットかなと思っています。

動作確認環境

  • AMI/OS : ami-6be57d0d/Amazon Linux 2 RC
  • インスタンスタイプ : i3.large
  • 追加したENI : eth1,eth2の2つ

特にLinuxディストリビューションやEC2インスタンスタイプの制限はありませんが、インスタンスタイプによって追加のENI数の上限が異なる点にだけ留意ください。

Namespaceの作成とENIの紐づけ

Amazon Linuxでは追加したENIのネットワークインターフェースが自動で活性化、DHCPクライアントが動作するようになっています。今回はコマンドでnetnsへのインターフェースの登録や構成を行うので、追加したENIに対応するインターフェースをダウンさせます。

$ sudo ip link set eth1 down
$ sudo ip link set eth2 down

インターフェース一覧を見てみましょう。

$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP mode DEFAULT qlen 1000
    link/ether 06:d7:23:3e:db:94 brd ff:ff:ff:ff:ff:ff
3: eth1: <BROADCAST,MULTICAST> mtu 9001 qdisc mq state DOWN mode DEFAULT qlen 1000
    link/ether 06:05:a2:18:8f:be brd ff:ff:ff:ff:ff:ff
4: eth2: <BROADCAST,MULTICAST> mtu 9001 qdisc mq state DOWN mode DEFAULT qlen 1000
    link/ether 06:21:b2:b5:ad:16 brd ff:ff:ff:ff:ff:ff
$

では、netnsを構成していきます。netnsでは、Namespaceという論理単位で前述のネットワークスタックを持ちます。今回はeth1とeth2で別のスタックとするために「blue」と「green」の2つのNamespaceを作成します *1。netnsの操作はiproute2に統合されているので、ipコマンドで操作します。設定には/var/run/netns/以下のファイルの書き込み権が必要なので、基本的にはsudoコマンドなどrootの権限で操作します。

$ sudo ip netns add blue
$ sudo ip netns add green

一般的なnetnsの解説ではおもむろにveth(仮想イーサネット)を作成して繋ぐというケースが多いのですが、今回はNamespaceさえ分けられれば要件としては十分なので、ENIに対応するネットワークインターフェース(eth*)をNamespaceに直接割り当てます。blueにeth1、greenにeth2をアサインします。

$ sudo ip link set eth1 netns blue
$ sudo ip link set eth2 netns green

図に書くとこんな感じ。

シンプルですね。ip linkコマンドで再度、インターフェースの居所を確認してみましょう。

$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP mode DEFAULT qlen 1000
    link/ether 06:d7:23:3e:db:94 brd ff:ff:ff:ff:ff:ff
$

eth1eth2が姿を消し、それぞれのNamespaceに移っていることがわかります。Namespaceの状態を確認するためにはip netns exec <Namespace名> <コマンド>で、Namespace上で任意のコマンドを実行するのが簡単です。

$ sudo ip netns exec blue ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
3: eth1: <BROADCAST,MULTICAST> mtu 9001 qdisc noop state DOWN mode DEFAULT qlen 1000
    link/ether 06:05:a2:18:8f:be brd ff:ff:ff:ff:ff:ff
$ sudo ip netns exec green ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
4: eth2: <BROADCAST,MULTICAST> mtu 9001 qdisc noop state DOWN mode DEFAULT qlen 1000
    link/ether 06:21:b2:b5:ad:16 brd ff:ff:ff:ff:ff:ff
$

各Namespaceごとにループバックインターフェースloとアサインしたネットワークインターフェースeth*が確認できますね。ループバックインターフェースは初期状態だと不活性でループバックアドレスも付与されていないので、以下のコマンドで対応しましょう。blueとgreenの両方に行うので、ここでは-allオプション(全Namespaceでのコマンド実行)で実行します。

$ sudo ip -all netns exec ip addr add 127.0.0.1 dev lo
$ sudo ip -all netns exec ip link set lo up

個別のNamespace上でいろいろ試すのであればip netns execのコマンドにbashを指定してシェルを起動するのが便利です。sudoコマンドで実行するので、rootユーザーの権限でのコマンド操作になることに注意してください。

$ sudo ip netns exec blue bash
#

先ほどアサインしたネットワークインターフェースを活性化しましょう。IPアドレスはDHCPもしくは静的に付与すればOKです。今回はdhclientコマンドでDHCPを動作させます。DHCPではデフォルトルートが設定されなかったので、デフォルトルートを追加で付与しました。

# dhclient eth1
^C
# ip route add default via <ENIのデフォルトゲートウェイのIPアドレス>
# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/32 scope host lo
       valid_lft forever preferred_lft forever
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP qlen 1000
    link/ether 06:05:a2:18:8f:be brd ff:ff:ff:ff:ff:ff
    inet 172.31.31.158/20 brd 172.31.31.255 scope global dynamic eth1
       valid_lft 3593sec preferred_lft 3593sec
    inet6 fe80::405:a2ff:fe18:8fbe/64 scope link
       valid_lft forever preferred_lft forever
# exit
$

これでblue Namespaceのインターフェースの疎通はOKです。greenも同様に対応します。

$ sudo ip netns exec green bash
# dhclient eth2
^C
# ip route add default via <ENIのデフォルトゲートウェイのIPアドレス>
# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/32 scope host lo
       valid_lft forever preferred_lft forever
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
4: eth2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP qlen 1000
    link/ether 06:21:b2:b5:ad:16 brd ff:ff:ff:ff:ff:ff
    inet 172.31.19.81/20 brd 172.31.31.255 scope global dynamic eth2
       valid_lft 3547sec preferred_lft 3547sec
    inet6 fe80::421:b2ff:feb5:ad16/64 scope link
       valid_lft forever preferred_lft forever

OKですね。ルーティングテーブルやNetfilter(iptables)がNamespaceごと別々になっている様子を確認します。

# ip route
default via 172.31.16.1 dev eth2
172.31.16.0/20 dev eth2 proto kernel scope link src 172.31.19.81
# iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
#

今回は特に必要なかったのですが、netnsにはresolv.confhostsをNamespaceごとに/etc/netns/<Netspace名>/{resolv.conf|hosts}というファイル名で設定することもできます。デフォルトでは/etc/resolv.conf/etc/hostsを参照します。

アプリケーションの実行

では、用意したNamespaceでサーバーアプリケーションを実行してみましょう。やり方自体は、前述のip netns <Namespace名> execコマンドをそのまま利用します。今回はWebサーバーのNginxを実行してみました。

$ sudo amazon-linux-extras install nginx1.12
$ sudo ip -all netns exec nginx

サーバーアプリケーションの種類によって、実行するコマンドラインやオプションは調整してください。では、cURLで試してみます。

非Namespace→Namespace(ENI間通信)

$ curl -I <eth1のPrivate IP>
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Wed, 17 Jan 2018 08:23:55 GMT
Content-Type: text/html
Content-Length: 3520
Last-Modified: Wed, 13 Dec 2017 18:36:55 GMT
Connection: keep-alive
ETag: "5a317347-dc0"
Accept-Ranges: bytes
$ curl -I <eth2のPrivate IP>
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Wed, 17 Jan 2018 08:24:07 GMT
Content-Type: text/html
Content-Length: 3520
Last-Modified: Wed, 13 Dec 2017 18:36:55 GMT
Connection: keep-alive
ETag: "5a317347-dc0"
Accept-Ranges: bytes
$

ローカルホスト

非NamespaceではNginxを実行していないのでエラー、各Namespace上でローカルホストへのリクエストが通るはずです。

$ curl -I localhost
curl: (7) Failed to connect to localhost port 80: Connection refused
$ sudo ip netns exec blue curl -I localhost
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Wed, 17 Jan 2018 08:26:34 GMT
Content-Type: text/html
Content-Length: 3520
Last-Modified: Wed, 13 Dec 2017 18:36:55 GMT
Connection: keep-alive
ETag: "5a317347-dc0"
Accept-Ranges: bytes

$ sudo ip netns exec green curl -I localhost
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Wed, 17 Jan 2018 08:26:40 GMT
Content-Type: text/html
Content-Length: 3520
Last-Modified: Wed, 13 Dec 2017 18:36:55 GMT
Connection: keep-alive
ETag: "5a317347-dc0"
Accept-Ranges: bytes

想定通りですね。今回はわかりやすくするためにサーバーアプリケーションを動作させる例でしたが、NATやVPNなどネットワークサービスをNamespaceごとに提供するユースケースにも有効だと思います。

OSの起動に合わせてnetnsおよびアプリケーションを稼働させる場合は、SysVinitやsystemdでの設定を行うことになります(ざっくりrc.localでもいいかもしれませんが)。systemdについてはGitHubにテンプレートが公開されているので、参考になるかもしれません。

まとめ

netnsを使ってENIごとにネットワーク設定を持たせる構成をご紹介しました。EC2であれば複数インスタンス構成にしてしまうのが最もシンプルですが、なんらかの事情で複数ENIでえんやこらとやるときに参考になれば幸いです。

参考URL

脚注

  1. ネーミングは、Blue/Greenデプロイメントとは特に関係ありません。