Understanding Docker network through iptables
Docker 為主流的容器化技術之一,而 Docker 則是使用 iptables 做 provide network isolation。 這篇來了解 Docker 預設的 iptables 規則是什麼。
之前有寫過 iptables guide([[iptables-guide]]),知道 iptables 底層是使用 Netfilter 模組作封包的控制。
官方也有相關的資料可以參考 Docker and iptables
On Linux, Docker manipulates
iptables
rules to provide network isolation. While this is an implementation detail and you should not modify the rules Docker inserts into youriptables
policies, it does have some implications on what you need to do if you want to have your own policies in addition to those managed by Docker.
思路
- 建立虛擬機,確認初始 network interface 與 iptables
- 安裝 Docker 並確認 network interface 與 iptables
- 建置 nginx 容器,測試封包走向
環境與版本
OS:
1root@ip-172-31-37-164:/home/ubuntu# cat /etc/os-release
2NAME="Ubuntu"
3VERSION="20.04.2 LTS (Focal Fossa)"
4・・・・・・
Docker:
1root@ip-172-31-37-164:/home/ubuntu# docker version
2Client: Docker Engine - Community
3 Version: 20.10.7
4 API version: 1.41
5・・・・・・
6
7Server: Docker Engine - Community
8 Engine:
9 Version: 20.10.7
10 API version: 1.41 (minimum version 1.12)
11・・・・・・
12 containerd:
13 Version: 1.4.6
14 GitCommit: d71fcd7d8303cbf684402823e425e9dd2e99285d
15 runc:
16 Version: 1.0.0-rc95
17 GitCommit: b9ee9c6314599f1b4a7f497e1f1f856fe433d3b7
18 docker-init:
19 Version: 0.19.0
20 GitCommit: de40ad0
確認初始的 iptables rules
在一個新的虛擬機中,一開始的網路介面有 lo 跟 eth0。 其中 lo 為這虛擬機的 LOOPBACK 使用; 而 eth0 則為可廣播的網路介面使用。
1root@ip-172-31-37-164:/home/ubuntu# ip a
21: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
3 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
4 inet 127.0.0.1/8 scope host lo
5 valid_lft forever preferred_lft forever
6 inet6 ::1/128 scope host
7 valid_lft forever preferred_lft forever
82: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc fq_codel state UP group default qlen 1000
9 link/ether 06:3d:47:e2:09:ba brd ff:ff:ff:ff:ff:ff
10 inet 172.31.37.164/20 brd 172.31.47.255 scope global dynamic eth0
11 valid_lft 3514sec preferred_lft 3514sec
12 inet6 fe80::43d:47ff:fee2:9ba/64 scope link
13 valid_lft forever preferred_lft forever
新的虛擬機中,一開始是沒有配置 Chain、Table 的。
1root@ip-172-31-37-164:/home/ubuntu# iptables -L -n -v --line-numbers -t nat
2Chain PREROUTING (policy ACCEPT 6 packets, 328 bytes)
3num pkts bytes target prot opt in out source destination
4
5Chain INPUT (policy ACCEPT 6 packets, 328 bytes)
6num pkts bytes target prot opt in out source destination
7
8Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
9num pkts bytes target prot opt in out source destination
10
11Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
12num pkts bytes target prot opt in out source destination
1root@ip-172-31-37-164:/home/ubuntu# iptables -L -n -v --line-numbers -t filter
2Chain INPUT (policy ACCEPT 58 packets, 5352 bytes)
3num pkts bytes target prot opt in out source destination
4
5Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
6num pkts bytes target prot opt in out source destination
7
8Chain OUTPUT (policy ACCEPT 48 packets, 6066 bytes)
9num pkts bytes target prot opt in out source destination
安裝 Docker 並且確認 iptables rules
確認網路介面
Docker 安裝完成後,看到新增了一個網路介面 docker0。 其 docker0 的網路介面 ip 位置可以看到被分配到 172.17.0.1/16 的 private ip。而 router 部分則看到 172.17.0.0/16 網段都 link src 172.17.0.1。
1# ip address
2root@ip-172-31-37-164:/home/ubuntu# ip a
3・・・・・・
43: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
5 link/ether 02:42:d3:81:15:1c brd ff:ff:ff:ff:ff:ff
6 inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
7 valid_lft forever preferred_lft forever
8
9# ip route
10root@ip-172-31-37-164:/home/ubuntu# ip r
11default via 172.31.32.1 dev eth0 proto dhcp src 172.31.37.164 metric 100
12172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
13172.31.32.0/20 dev eth0 proto kernel scope link src 172.31.37.164
14172.31.32.1 dev eth0 proto dhcp scope link src 172.31.37.164 metric 100
15
16# ip route show table local
17root@ip-172-31-37-164:/home/ubuntu# ip r show table local
18broadcast 127.0.0.0 dev lo proto kernel scope link src 127.0.0.1
19local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1
20local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1
21broadcast 127.255.255.255 dev lo proto kernel scope link src 127.0.0.1
22broadcast 172.17.0.0 dev docker0 proto kernel scope link src 172.17.0.1
23local 172.17.0.1 dev docker0 proto kernel scope host src 172.17.0.1
24broadcast 172.17.255.255 dev docker0 proto kernel scope link src 172.17.0.1
25broadcast 172.31.32.0 dev eth0 proto kernel scope link src 172.31.37.164
26local 172.31.37.164 dev eth0 proto kernel scope host src 172.31.37.164
27broadcast 172.31.47.255 dev eth0 proto kernel scope link src 172.31.37.164
確認 iptables 的 NAT Table
下面列出了 nat table 的 Chain。看到新增了多個 Chain DOCKER
,而這 Chain 被 references 在 PREROUTING、OUTPUT 中。
而我們知道 iptables 的規則是從上至下順序執行,直至匹配的的規則為止,否則執行預設 policy。
下面說明 nat table 的 Chain:
PREROUTING num 1
是指如果進來的封包目的地址 match LOCAL 都跳到 Chain DOCKER,這邊要注意 LOCAL 並不是指本地,可以使用 "ip route show table local" 命令來確認哪些 ip 為 LOCALDOCKER num 1
指如果封包是從 docker0 網路介面進來的,則結束 Chain DOCKER 然後返回原來的 Chain 繼續跑規則
OUTPUT num 1
是指如果出去封包目的地址 match LOCAL 都跳到 Chain DOCKER,除了 127.0.0.0/8 網段DOCKER num 1
指如果封包是從 docker0 網路介面進來的,則結束 Chain DOCKER 然後返回原來的 Chain 繼續跑規則
POSTROUTING num 1
是指如果出去封包不是 docker0 網路介面和 ip 來源是 172.17.0.0/16 時,不做修改
1root@ip-172-31-37-164:/home/ubuntu# iptables -L -n -v --line-numbers -t nat
2Chain PREROUTING (policy ACCEPT 6 packets, 328 bytes)
3num pkts bytes target prot opt in out source destination
41 14138 743K DOCKER all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
5
6Chain INPUT (policy ACCEPT 6 packets, 328 bytes)
7num pkts bytes target prot opt in out source destination
8
9Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
10num pkts bytes target prot opt in out source destination
111 4 240 DOCKER all -- * * 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
12
13Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
14num pkts bytes target prot opt in out source destination
151 34 2073 MASQUERADE all -- * !docker0 172.17.0.0/16 0.0.0.0/0
16
17Chain DOCKER (2 references)
18num pkts bytes target prot opt in out source destination
191 19 1140 RETURN all -- docker0 * 0.0.0.0/0 0.0.0.0/0
確認 iptables 的 Filter Table
可以看到新增了多個 Chain DOCKER
、DOCKER-ISOLATION-STAGE-1
、DOCKER-ISOLATION-STAGE-2
、DOCKER-USER
,而這些 Chain 都被 references 在 FORWARD 中。
可以看到 Chain FORWARD 的預設 policy 已經被改為 DROP,而我們知道 iptables 的規則是從上至下順序執行,直至匹配的的規則為止,否則執行預設 policy。
看到 FORWARD 的規則:
FORWARD num 1
規則是跳到DOCKER-USER
鏈中DOCKER-USER num 1
規則是 RETURN 封包,回FORWARD
中繼續直執行其他的規則
FORWARD num 2
規則是跳到DOCKER-ISOLATION-STAGE-1
鏈中DOCKER-ISOLATION-STAGE-1 num 1
封包又跳到DOCKER-ISOLATION-STAGE-2
鏈中DOCKER-ISOLATION-STAGE-2 num 1
丟棄所有 docker0 網路介面出去的封包DOCKER-ISOLATION-STAGE-2 num 2
RETURN 所有封包繼續執封包繼續執行DOCKER-ISOLATION-STAGE-1
的其他規則
DOCKER-ISOLATION-STAGE-1 num 2
RETURN 所有封包繼續執封包繼續執行FORWARD
的其他規則
FORWARD num 3
規則為允許由 docker0 網路介面出去的封包,但封包狀態是 RELATED,ESTABLISHEDFORWARD num 4
規則為當封包是 docker0 網路介面出去的,則跳到DOCKER
鏈中DOCKER
暫無規則
FORWARD num 5
、FORWARD num 6
為允許經由 docker0 網路介面近來的所有封包進出- 不在上述規則中的封包全部都 drop
1root@ip-172-31-37-164:/home/ubuntu# iptables -L -n -v --line-numbers -t fillter
2Chain INPUT (policy ACCEPT 385 packets, 28200 bytes)
3num pkts bytes target prot opt in out source destination
4
5Chain FORWARD (policy DROP 0 packets, 0 bytes)
6num pkts bytes target prot opt in out source destination
71 0 0 DOCKER-USER all -- * * 0.0.0.0/0 0.0.0.0/0
82 0 0 DOCKER-ISOLATION-STAGE-1 all -- * * 0.0.0.0/0 0.0.0.0/0
93 0 0 ACCEPT all -- * docker0 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
104 0 0 DOCKER all -- * docker0 0.0.0.0/0 0.0.0.0/0
115 0 0 ACCEPT all -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0
126 0 0 ACCEPT all -- docker0 docker0 0.0.0.0/0 0.0.0.0/0
13
14Chain OUTPUT (policy ACCEPT 304 packets, 59638 bytes)
15num pkts bytes target prot opt in out source destination
16
17Chain DOCKER (1 references)
18num pkts bytes target prot opt in out source destination
19
20Chain DOCKER-ISOLATION-STAGE-1 (1 references)
21num pkts bytes target prot opt in out source destination
221 0 0 DOCKER-ISOLATION-STAGE-2 all -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0
232 0 0 RETURN all -- * * 0.0.0.0/0 0.0.0.0/0
24
25Chain DOCKER-ISOLATION-STAGE-2 (1 references)
26num pkts bytes target prot opt in out source destination
271 0 0 DROP all -- * docker0 0.0.0.0/0 0.0.0.0/0
282 0 0 RETURN all -- * * 0.0.0.0/0 0.0.0.0/0
29
30Chain DOCKER-USER (1 references)
31num pkts bytes target prot opt in out source destination
321 0 0 RETURN all -- * * 0.0.0.0/0 0.0.0.0/0
建置 nginx 容器,測試封包走向
啟動 container nginx
1docker run --name nginx -p 80:80 -d nginx
查看本機 listen port
看到 port 80 被 docker-proxy 監聽著
1root@ip-172-31-37-164:/home/ubuntu# netstat -tlnp
2Active Internet connections (only servers)
3Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
4tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 253681/docker-proxy
5tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN 370/systemd-resolve
6tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 576/sshd: /usr/sbin
7tcp6 0 0 :::80 :::* LISTEN 253686/docker-proxy
8tcp6 0 0 :::22 :::* LISTEN 576/sshd: /usr/sbin
確認網路變化
網路介面新增了 veth3ce1bed
1root@ip-172-31-37-164:~# ip link
21: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
3 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
42: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
5 link/ether 06:3d:47:e2:09:ba brd ff:ff:ff:ff:ff:ff
618: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
7 link/ether 02:42:55:24:1f:ed brd ff:ff:ff:ff:ff:ff
822: veth3ce1bed@if21: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default
9 link/ether ea:ba:b9:7b:48:ea brd ff:ff:ff:ff:ff:ff link-netnsid 1
還有將新增的網路介面 veth3ce1bed
橋接在 docker0
上。
1root@ip-172-31-37-164:~# brctl show
2bridge name bridge id STP enabled interfaces
3docker0 8000.024255241fed no veth3ce1bed
iptables NAT table 新增了兩條規則。
POSTROUTING
封包來源為封包目的為 172.17.0.2 地址為裝封包內容 tcp dpt:80DOCKER num 2
規則為所有非 docker0 網路介面近來且 tcp dpt:80 的封包,改到 172.17.0.2:80
1iptables -L -n -v --line-numbers -t nat
2
3Chain POSTROUTING (policy ACCEPT 266 packets, 17090 bytes)
4num pkts bytes target prot opt in out source destination
52 0 0 MASQUERADE tcp -- * * 172.17.0.2 172.17.0.2 tcp dpt:80
6
7Chain DOCKER (2 references)
8num pkts bytes target prot opt in out source destination
92 136 7472 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:80
1root@ip-172-31-37-164:/home/ubuntu# iptables -L -n -v --line-numbers -t filter
2
3Chain DOCKER (1 references)
4num pkts bytes target prot opt in out source destination
51 123 6692 ACCEPT tcp -- !docker0 docker0 0.0.0.0/0 172.17.0.2 tcp dpt:80
測試封包走向
在不同 client 端嘗試連上 nginx 服務,觀察封包所經過的網路規則。
監控 iptalbes 的 pkts 變化。
1root@ip-172-31-37-164:/home/ubuntu# watch iptables -L -n -v --line-numbers -t nat
2root@ip-172-31-37-164:/home/ubuntu# watch iptables -L -n -v --line-numbers -t filter
監控 docker0 網路介面的封包裝況。
1tcpdump -i docker0 -q -v tcp port 80