NameSpace 機能を使ったネットワークからインターネットに出れなかった話
コンテナのネットワーク周りを理解するためにカーネルの NameSpace 機能を使って Ubuntu20.04 サーバ上に仮想的なネットワークを構築してみた.その際,作成したネットワークからインターネットに出るのに四苦八苦したのでメモとして残しておく.この記事では,ネットワークの構築の詳細は話さず構築後のトラブルシュートをメインとする.
概要
NameSpace 機能を使ったコンテナネットワークの実験をした.同じような実験についてはインターネットにいくつか記事があるが,今回は以下の記事を参考にした.
構築した環境は図のようになっている.
上記ネットワークを構築するコマンドをまとめておいた.もしよければどうぞ.
クリックすると展開されます
#!/bin/bash ip netns add host1 ip netns add host2 ip netns exec host1 ip link set lo up ip netns exec host2 ip link set lo up ip link add name veth1 type veth peer name br-veth1 ip link add name host1 type veth peer name br-host1 ip link add name host2 type veth peer name br-host2 ip link set host1 netns host1 ip link set host2 netns host2 ip link add br0 type bridge ip link set dev br-veth1 master br0 ip link set dev br-host1 master br0 ip link set dev br-host2 master br0 ip addr add 10.0.0.100/24 dev veth1 ip netns exec host1 ip addr add 10.0.0.1/24 dev host1 ip netns exec host2 ip addr add 10.0.0.2/24 dev host2 ip netns exec host1 ip link set host1 up ip netns exec host2 ip link set host2 up ip link set veth1 up ip link set br-veth1 up ip link set br-host1 up ip link set br-host2 up ip link set br0 up echo 1 > /proc/sys/net/ipv4/ip_forward ip netns exec host1 ip route add default via 10.0.0.100 ip netns exec host2 ip route add default via 10.0.0.100
また,環境としては EC2 の Ubuntu20.04 を使ったので環境構築として以下の記事も参考にさせていただいた.また,EC2 の設定としてインターネットと通信できる状態にしておくことが前提となるのでご注意ください.
こちらも Ubuntu で実行できるようコマンドをまとめておいた.
クリックすると展開されます
#!/bin/bash apt-get update apt-get install -y apt-transport-https ca-certificates curl software-properties-common jq curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" apt-get install -y docker-ce cgdb cgroup-tools uuid-runtime tree iputils-ping make gcc git clone git://git.kernel.org/pub/scm/linux/kernel/git/morgan/libcap.git /usr/src/libcap cd /usr/src/libcap make make install
NameSpace を使って隔離したネットワークからインターネットへ出ていけない
先に紹介した記事 にもある通り,NameSpace 機能で作成した Host1 や Host2 からインターネットに出ていくためには iptables による Nat を設定しなければならないのでホスト Linux 上で以下のように設定した.
iptables -t nat -I POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE
この状態で Host1 から ping を打ってみるが,インターネットへ疎通できなかった.
ip netns exec host1 ping 8.8.8.8 PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data. ...
試しに eth0,veth1,br-veth1,br-host1 のそれぞれで tcpdump をしてみてどこまでパケットが届いているか確認すると br-host1 のキャプチャでしかパケットが表示されなかった.
tcpdump -i eth0 -p icmp -n ... tcpdump -i veth1 -p icmp -n ... tcpdump -i br-veth1 -p icmp -n ... tcpdump -i br-host1 -p icmp -n 01:49:58.420877 IP 10.0.0.1 > 8.8.8.8: ICMP echo request, id 8735, seq 203, length 64 01:49:59.444910 IP 10.0.0.1 > 8.8.8.8: ICMP echo request, id 8735, seq 204, length 64
どうやら bridge 内の通信がまずうまくいっていないので以下の記事を参考にして bridge 内通信を修正.今回は Netfilter で iptables を呼ばない設定をすることにした.記事の最後にこのカーネルパラメータを 1 の状態で解決できなかった理由も書いておく.
sysctl -w net.bridge.bridge-nf-call-iptables = 0
再度各 NIC に対して tcpdump をしてみたところ veth1 までパケットが届いていることがわかった.
tcpdump -i veth1 -p icmp -n 03:06:17.104353 IP 10.0.0.2 > 8.8.8.8: ICMP echo request, id 9766, seq 1, length 64
tcpdump より veth1 から eth0 へのパケットが落ちていることが分かるので,iptables で veth1 から eth0 への FORWARD を設定した.
iptables -t filter -I FORWARD -s 10.0.0.0/24 -d 0.0.0.0/0 -j ACCEPT
eth0 の NIC を tcpdump すると Nat の設定も効いていて,インターネットから eth0 まで通信が返ってきていることを確認できた.
tcpdump -i eth0 -p icmp -n 03:12:11.326420 IP 172.30.20.98 > 8.8.8.8: ICMP echo request, id 9779, seq 1, length 64 03:12:11.328700 IP 8.8.8.8 > 172.30.20.98: ICMP echo reply, id 9779, seq 1, length 64
最後に eht0 から veth1 への戻りの FORWARD 設定をする.セキュリティも考慮してソース IP は 8.8.8.8 だけにしておく.
iptables -t filter -I FORWARD -d 10.0.0.0/24 -s 8.8.8.8/32 -j ACCEPT
そうすると Host1 からインターネットへの接続を確立できる.
ip netns exec host2 ping 8.8.8.8 PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data. 64 bytes from 8.8.8.8: icmp_seq=1 ttl=104 time=2.51 ms 64 bytes from 8.8.8.8: icmp_seq=2 ttl=104 time=2.36 ms
余談
今回は bridge ネットワーク通信を正常にするために net.bridge.bridge-nf-call-iptables
を 0 に設定した対応を取ったが,先の記事 にある iptables -I FORWARD -m physdev --physdev-is-bridged -j ACCEPT
を iptables に設定する方法で進めてみた.
うまくいかない箇所は同じで,まずは veth1 から eth0 への通信が失敗するので iptables に iptables -t filter -I FORWARD -s 10.0.0.0/24 -d 0.0.0.0/0 -j ACCEPT
を設定する.そうすると eth0 にパケットが到達するようになるが,なぜか Nat が効かない状態となる.この Nat をどうにか効かせようと色々調べて設定も変えてみたがうまくいかなかった.結果として net.bridge.bridge-nf-call-iptables=0
にする方法でうまくいったのでそちらをまとめたのだが,もし誰かこの原因をご存知の方がいたらコメント欄に書いていただけると助かります.
まとめ
- トラブルシュートしながらコンテナネットワークからインターネットへ出ていく時の設定を見つけた
- 改めてネットワークトラブルは tcpdump などでパケットを取り続けるしかないことを学んだ
net.bridge.bridge-nf-call-iptables
の設定をそのままにした状態でうまくいかない原因は明確となっていない