在 Docker 的網路規則中,由於 DOCKER-ISOLATION-STAGE-1
、DOCKER-ISOLATION-STAGE-2
兩個 iptables chain 的關係,因此不同 Docker network 連結的 container 是無法互相溝通的。不過,我們可以透過建立 router 的手段來規避掉 chain 的限制,讓 container 收到其他 container 來的packages。此方式涉及到 Linux Bridge
處理封包的方式和基本網路知識,希望透過此介紹,來更了解 network stack 與 packages 處理流程。
Docker Container Network
我們知道,Docker 的網路實現是基於 Linux network namespace,Linux network bridge,與 virtual ethernet device 所架構而成的(可參閱 Linux Network Fundamentals)。
假設我們建立兩個 container,一個連結自定義網路(User-Defined Bridge Networks) host1 與連結預設網路的 host2:
1sudo docker network create h1r1br
2sudo docker run -d -it --network h1r1br --rm --name h1 ubuntu:16.04
3sudo docker run -d -it --rm --name h2 ubuntu:16.04
其結構圖示如下:
我們可以使用 brctl
來查看 bridge 和 veth 關係
1yu-shuan@:~$ brctl show
2bridge name bridge id STP enabled interfaces
3br-0e510c1eeb44 8000.02429a48a474 no veth8b7aaa8
4docker0 8000.024288b35644 no vethfa6f0c4
也可以用 ifconfig
來查看 bridge 與 veth 的資訊
1yu-shuan@:~$ ifconfig br-0e510c1eeb44
2br-0e510c1eeb44: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
3 inet 172.22.0.1 netmask 255.255.0.0 broadcast 172.22.255.255
4 inet6 fe80::42:9aff:fe48:a474 prefixlen 64 scopeid 0x20<link>
5 ether 02:42:9a:48:a4:74 txqueuelen 0 (Ethernet)
我們進入 host1
container,來看 routing 設定:
1root@fb1431179a56:/# ip route
2default via 172.22.0.1 dev eth0
3172.22.0.0/16 dev eth0 proto kernel scope link src 172.22.0.2
由於 host1 只有一個 interface,因此無論 Dst IP 是什麼,都會從 eth0 出去,而 host2 也同理。
Connection Test
Case 1: host1 connects to the internet
接著我們看一下從 host1 container 是如何與外部 internet 聯繫的。我們從 host1 container ping google server (169.254.169.254)。
從 host1 發送 icmp 封包,由於沒有 169.254.169.254 的 MAC 資訊,因此將封包轉送到 default gateway(dst_Mac 改成 gatyeway 的 mac address),也就是 bridge br-0e510c1eeb44(h1r1br),從 eth0 出去。
當封包抵達 bridge br-0e510c1eeb44 後,就是在本機端處理此封包,因此會使用本機端的 iptable 設定。同樣地,由於沒有 169.254.169.254 的資訊,因此會轉發到本機設定的 default gateway,並檢查 filter table 中的對應 rules,最後發送到外部網路。
Case 2: host1(172.22.0.2) ping host2(172.17.0.2)
再來討論 host1 ping host2 的場景~我們第一直覺就能夠判斷一定不能通。那為什麼不能通呢?
封包處理流程跟上面第一個例子相似, icmp 封包會被轉發到本機端,其中最大的差別在於:此 icmp 封包會被 filter.FORWARD table Drop 掉,因此就無法將封包發送到目標 host2 container 中。
透過 iptables command-lin tool 來查看 table 設定:
1yu-shuan@:~$ sudo iptables -nL --line-numbers -v -t filter
2Chain INPUT (policy ACCEPT 1921 packets, 227K bytes)
3num pkts bytes target prot opt in out source destination
41 199K 1215M sshguard all -- * * 0.0.0.0/0 0.0.0.0/0
5
6Chain FORWARD (policy DROP 0 packets, 0 bytes)
7num pkts bytes target prot opt in out source destination
81 49568 61M DOCKER-USER all -- * * 0.0.0.0/0 0.0.0.0/0
92 49568 61M DOCKER-ISOLATION-STAGE-1 all -- * * 0.0.0.0/0 0.0.0.0/0
10
11Chain DOCKER-ISOLATION-STAGE-1 (1 references)
12num pkts bytes target prot opt in out source destination
131 9119 499K DOCKER-ISOLATION-STAGE-2 all -- br-0e510c1eeb44 !br-0e510c1eeb44 0.0.0.0/0 0.0.0.0/0
142 4729 252K DOCKER-ISOLATION-STAGE-2 all -- br-5004af5170da !br-5004af5170da 0.0.0.0/0 0.0.0.0/0
153 78 18422 DOCKER-ISOLATION-STAGE-2 all -- br-a0789b166030 !br-a0789b166030 0.0.0.0/0 0.0.0.0/0
164 901 48486 DOCKER-ISOLATION-STAGE-2 all -- br-e7d606a081d9 !br-e7d606a081d9 0.0.0.0/0 0.0.0.0/0
175 0 0 DOCKER-ISOLATION-STAGE-2 all -- br-e0e7a31ff7d4 !br-e0e7a31ff7d4 0.0.0.0/0 0.0.0.0/0
186 70549 8287K DOCKER-ISOLATION-STAGE-2 all -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0
197 1161K 805M RETURN all -- * * 0.0.0.0/0 0.0.0.0/0
20
21Chain DOCKER-ISOLATION-STAGE-2 (6 references)
22num pkts bytes target prot opt in out source destination
231 0 0 DROP all -- * br-0e510c1eeb44 0.0.0.0/0 0.0.0.0/0
242 0 0 DROP all -- * br-5004af5170da 0.0.0.0/0 0.0.0.0/0
253 0 0 DROP all -- * br-a0789b166030 0.0.0.0/0 0.0.0.0/0
264 31 15348 DROP all -- * br-e7d606a081d9 0.0.0.0/0 0.0.0.0/0
275 6 504 DROP all -- * br-e0e7a31ff7d4 0.0.0.0/0 0.0.0.0/0
286 2 168 DROP all -- * docker0 0.0.0.0/0 0.0.0.0/0
297 85337 9089K RETURN all -- * * 0.0.0.0/0 0.0.0.0/0
封包會進入 DOCKER-ISOLATION-STAGE-1
,接著 match 到其中 number 1 的 rule
1Chain DOCKER-ISOLATION-STAGE-1 (1 references)
2num pkts bytes target prot opt in out source destination
31 9119 499K DOCKER-ISOLATION-STAGE-2 all -- br-0e510c1eeb44 !br-0e510c1eeb44 0.0.0.0/0 0.0.0.0/0
最後進入 DOCKER-ISOLATION-STAGE-2
,match 到 number 6 的 rule,封包被 Drop 掉。
1Chain DOCKER-ISOLATION-STAGE-2 (6 references)
2num pkts bytes target prot opt in out source destination
36 2 168 DROP all -- * docker0 0.0.0.0/0 0.0.0.0/0
Cross-Network Container Communication via a Router
如果要讓兩個分別在不同 bridge 的 container 互相溝通,最重要的是如何符合 docker 本身在 iptables 所設定的規則。而在實體網路中,我們會使用 router 來連結兩個網段的網路,相同的概念也可以應用在 docker container 上,以實現跨 network bridge 溝通的目的。
首先,我們先起一個 router container,並且使用不同的 network 把 host1、host2、與 router 連結在一起。
如此一來,host1 的封包可以透過 h1r1br Bridge 傳送到 router1 ,接著再經過 docker0 bridge 將封包從 router1 傳送到 host2。
當然,只是將 router 1 連結 host1、host2 還不夠,我們還要設定 host1、host2 的 default gateway,這樣才能確保封包能正確地走到 router1 再傳出去。
Host1
1ip route del default
2ip route add default via 172.22.0.3
Host2
1ip route del default
2ip route add default via 172.17.0.3
經過這樣的調整後,封包處理流程會變成:
- host1 ping
172.17.0.2
(host2) - host1 routing table 沒有 172.17.0.0/16 的資訊,走 default gatway
- default gatway 被設定為
172.22.0.3
因此 dst_mac 會變成 router1 eth0 的 mac address(如果 host1 的 arp table 沒有紀錄172.22.0.3
mac address,則 host1 會發送 ARP request 去詢問,而因為172.22.0.3
在同一個 bridge 上,因此可以得到 ARP response) - 封包傳送至 h1r1br Bridge,而由於 dst_mac 不是 h1r1br Bridge 的 mac address,且
dst_mac
src_mac
interface 皆在同一個 bridge 上,因此符合 docker 網路規範,封包不會被 drop 掉。
The importance of bridge’s decision
這裡有一個非常重要的概念,就是封包的 dst_mac mac address 會影響 Linux Bridge 處理封包的流程。根據 ebtables/iptables interaction on a Linux-based bridge 中有提到:
The bridge’s decision for a frame can be one of these:
- bridge it, if the destination MAC address is on another side of the bridge;
- flood it over all the forwarding bridge ports, if the position of the box with the destination MAC is unknown to the bridge;
- pass it to the higher protocol code (the IP code), if the destination MAC address is that of the bridge or of one of its ports;
- ignore it, if the destination MAC address is located on the same side of the bridge.
如果我們沒有設定 host1 的 default gateway 是 router1 的 eth0,那麼 default gateway 的 mac address 就會變成 h1r1br Bridge 的 mac address,並且根據上面 bridge’s decision 所描述,會將封包 pass 到 IP Network Layer, 接著根據 routing table 來決定 output interface,此時 dst_mac mac address 就會變成 docker0,最後就會在 filter.FORWARD 階段被 Drop 掉。
etable/iptable Logs
我們從 log 來確認剛剛的敘述是否正確。
- Default Gateway is router1 eth0
1Jul 6 06:56:45 sdn-experi-project kernel: [313896.817044] [raw.PREROUTING]IN=br-0e510c1eeb44 OUT= PHYSIN=veth8b7aaa8 MAC=02:42:ac:16:00:03:02:42:ac:16:00:02:08:00 SRC=172.22.0.2 DST=172.21.0.2 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=64082 DF PROTO=ICMP TYPE=8 CODE=0 ID=1164 SEQ=1
2Jul 6 06:56:45 sdn-experi-project kernel: [313896.817061] TRACE: eb:filter:FORWARD IN=veth8b7aaa8 OUT=veth6d5ff81 MAC source = 02:42:ac:16:00:02 MAC dest = 02:42:ac:16:00:03 proto = 0x0800 IP SRC=172.22.0.2 IP DST=172.21.0.2, IP tos=0x00, IP proto=1
3Jul 6 06:56:45 sdn-experi-project kernel: [313896.817070] [filter.FORWARD]IN=br-0e510c1eeb44 OUT=br-0e510c1eeb44 PHYSIN=veth8b7aaa8 PHYSOUT=veth6d5ff81 MAC=02:42:ac:16:00:03:02:42:ac:16:00:02:08:00 SRC=172.22.0.2 DST=172.21.0.2 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=64082 DF PROTO=ICMP TYPE=8 CODE=0 ID=1164 SEQ=1
4Jul 6 06:56:45 sdn-experi-project kernel: [313896.817130] [raw.PREROUTING]IN=br-5004af5170da OUT= PHYSIN=veth83ddfd8 MAC=02:42:ac:15:00:02:02:42:ac:15:00:03:08:00 SRC=172.22.0.2 DST=172.21.0.2 LEN=84 TOS=0x00 PREC=0x00 TTL=63 ID=64082 DF PROTO=ICMP TYPE=8 CODE=0 ID=1164 SEQ=1
5Jul 6 06:56:45 sdn-experi-project kernel: [313896.817134] TRACE: eb:filter:FORWARD IN=veth83ddfd8 OUT=veth56a6af1 MAC source = 02:42:ac:15:00:03 MAC dest = 02:42:ac:15:00:02 proto = 0x0800 IP SRC=172.22.0.2 IP DST=172.21.0.2, IP tos=0x00, IP proto=1
6Jul 6 06:56:45 sdn-experi-project kernel: [313896.817141] [filter.FORWARD]IN=br-5004af5170da OUT=br-5004af5170da PHYSIN=veth83ddfd8 PHYSOUT=veth56a6af1 MAC=02:42:ac:15:00:02:02:42:ac:15:00:03:08:00 SRC=172.22.0.2 DST=172.21.0.2 LEN=84 TOS=0x00 PREC=0x00 TTL=63 ID=64082 DF PROTO=ICMP TYPE=8 CODE=0 ID=1164 SEQ=1
7Jul 6 06:56:45 sdn-experi-project kernel: [313896.817209] TRACE: eb:filter:FORWARD IN=veth56a6af1 OUT=veth83ddfd8 MAC source = 02:42:ac:15:00:02 MAC dest = 02:42:ac:15:00:03 proto = 0x0800 IP SRC=172.21.0.2 IP DST=172.22.0.2, IP tos=0x00, IP proto=1
8Jul 6 06:56:45 sdn-experi-project kernel: [313896.817220] TRACE: eb:filter:FORWARD IN=veth6d5ff81 OUT=veth8b7aaa8 MAC source = 02:42:ac:16:00:03 MAC dest = 02:42:ac:16:00:02 proto = 0x0800 IP SRC=172.21.0.2 IP DST=172.22.0.2, IP tos=0x00, IP proto=1
- Default Gateway is h1r1br Bridge
1Jul 6 07:06:23 sdn-experi-project kernel: [314474.517447] [raw.PREROUTING]IN=br-0e510c1eeb44 OUT= PHYSIN=veth8b7aaa8 MAC=02:42:9a:48:a4:74:02:42:ac:16:00:02:08:00 SRC=172.22.0.2 DST=172.21.0.2 LEN=84 TOS=0x00 PREC=0x00 TTL=64 ID=52580 DF PROTO=ICMP TYPE=8 CODE=0 ID=1180 SEQ=1
2Jul 6 07:06:23 sdn-experi-project kernel: [314474.517594] TRACE: eb:filter:INPUT IN=veth8b7aaa8 OUT= MAC source = 02:42:ac:16:00:02 MAC dest = 02:42:9a:48:a4:74 proto = 0x0800 IP SRC=172.22.0.2 IP DST=172.21.0.2, IP tos=0x00, IP proto=1
3Jul 6 07:06:23 sdn-experi-project kernel: [314474.517622] [filter.FORWARD]IN=br-0e510c1eeb44 OUT=br-5004af5170da PHYSIN=veth8b7aaa8 MAC=02:42:9a:48:a4:74:02:42:ac:16:00:02:08:00 SRC=172.22.0.2 DST=172.21.0.2 LEN=84 TOS=0x00 PREC=0x00 TTL=63 ID=52580 DF PROTO=ICMP TYPE=8 CODE=0 ID=1180 SEQ=1
比對 netfilter package flow:
可以看到最重要的差異在於 default gateway 是 router1 eth0 的時候,會走 bridge-level filter.FORWARD
,反之如果是 h1r1br Bridge, 則會走 bridge-level filter.INPUT
並經過 routing decision,而造成此差異的原因就在於 bridge’s decision。
結論
我們使用 router 的方式來實現 cross-network container communication,由於 Docker container network 結合 linux bridge 來實作網路模型,因此上述透過實驗來驗證封包在 bridge 的流向和處理方式,了解這樣的流程,對於未來在 debug 相關議題上都非常有幫助。