不論是Kubernetes還是Docker本身,我們在一個節點上暴露端口對外提供服務的時候,總是少不了端口轉發。例如以下方式:
docker run -d -p 8080:80 nginx:latest
就是用最簡單的方法,在本機暴露8080端口,轉發到容器內的80端口,這樣一來外界可以通過本機IP+8080端口就可以訪問容器內提供的nginx服務了。
大家有沒有想過,這是怎麼做到的呢?
其實不難,Docker實現本身就是高度依賴Linux內核的若干模塊,這裏的端口轉發也不例外,也是用了Linux iptables的小把戲,接下來我們就來分析下這個小把戲。
一. 從log入手
既是知道Docker的端口轉發與iptables有關,那麼究竟有什麼樣的關係呢?我覺得從log入手,嘗試記錄下數據包通過iptables的軌跡,然後再作分析吧。
在不確定具體的涉及哪些iptables表的情況下,我想最好的方法是把所有的可能的表都加上適當的log,從而記錄數據包在各個表和鏈之間的流轉。
接下來,我們就來照此思路去探究iptables如何做到端口轉發。
1. 開啓iptables的log
在CentOS7中打開 /etc/rsyslog.conf ,並添加如下配置:
kern.* /var/log/iptables.log
並重啓rsyslog:
systemctl restart rsyslog.service
這樣,就可以在 /var/log/iptables.log 中看到iptables的log了,當然,前提是你得有log輸出。
2. 在nat表中增加log規則
在操作以前,首先保存原先的iptables配置,以免後續操作帶有破壞性,也便於還原:
iptables-save > iptb_origin
然後,分別在所有可能的表的所有的可能涉及的鏈中增加對於源目端口爲80或者8080的DEBUG log,可以用如下腳本方便進行:
#!/bin/bash
for table in raw filter nat mangle
do
for chain in PREROUTING OUTPUT FORWARD INPUT POSTROUTING
do
for port in 8080 80
do
iptables -t ${table} -A ${chain} -p tcp -m tcp --dport $port -j LOG --log-prefix "[debug]-${table}-${chain}:" --log-level 7 || true
iptables -t ${table} -A ${chain} -p tcp -m tcp --sport $port -j LOG --log-prefix "[debug]-${table}-${chain}:" --log-level 7 || true
done
done
done
可能某些表無法加入某些鏈,不過,我們暫且忽略,爲了簡化邏輯,直接等以上腳本執行完,而忽略其中的錯誤。
3. 發起請求
從外部地址 1.2.3.4 發起請求:
curl 172.20.242.183:8080
我們再看
1 Jan 21 18:09:52 centos7 kernel: [debug]-raw-PREROUTING:IN=eth0 OUT= MAC=00:16:3e:01:ae:15:ee:ff:ff:ff:ff:ff:08:00 SRC=1.2.3.4 DST=172.20.242.183 LEN=64 TOS=0x14 PREC=0x00 TTL=48 ID=0 DF PROTO=TCP SPT=10656 DPT=8080 WINDOW=65535 RES=0x00 SYN URGP=0 2 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-PREROUTING:IN=eth0 OUT= MAC=00:16:3e:01:ae:15:ee:ff:ff:ff:ff:ff:08:00 SRC=1.2.3.4 DST=172.20.242.183 LEN=64 TOS=0x14 PREC=0x00 TTL=48 ID=0 DF PROTO=TCP SPT=10656 DPT=8080 WINDOW=65535 RES=0x00 SYN URGP=0 3 Jan 21 18:09:52 centos7 kernel: [debug]-nat-PREROUTING:IN=eth0 OUT= MAC=00:16:3e:01:ae:15:ee:ff:ff:ff:ff:ff:08:00 SRC=1.2.3.4 DST=172.20.242.183 LEN=64 TOS=0x14 PREC=0x00 TTL=48 ID=0 DF PROTO=TCP SPT=10656 DPT=8080 WINDOW=65535 RES=0x00 SYN URGP=0 4 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-FORWARD:IN=eth0 OUT=docker0 MAC=00:16:3e:01:ae:15:ee:ff:ff:ff:ff:ff:08:00 SRC=1.2.3.4 DST=172.17.0.2 LEN=64 TOS=0x14 PREC=0x00 TTL=47 ID=0 DF PROTO=TCP SPT=10656 DPT=80 WINDOW=65535 RES=0x00 SYN URGP=0 5 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-POSTROUTING:IN= OUT=docker0 SRC=1.2.3.4 DST=172.17.0.2 LEN=64 TOS=0x14 PREC=0x00 TTL=47 ID=0 DF PROTO=TCP SPT=10656 DPT=80 WINDOW=65535 RES=0x00 SYN URGP=0 6 Jan 21 18:09:52 centos7 kernel: [debug]-nat-POSTROUTING:IN= OUT=docker0 SRC=1.2.3.4 DST=172.17.0.2 LEN=64 TOS=0x14 PREC=0x00 TTL=47 ID=0 DF PROTO=TCP SPT=10656 DPT=80 WINDOW=65535 RES=0x00 SYN URGP=0 7 Jan 21 18:09:52 centos7 kernel: [debug]-raw-PREROUTING:IN=docker0 OUT= PHYSIN=veth57b521b MAC=02:42:96:cd:15:f7:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=1.2.3.4 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=80 DPT=10656 WINDOW=28960 RES=0x00 ACK SYN URGP=0 8 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-PREROUTING:IN=docker0 OUT= PHYSIN=veth57b521b MAC=02:42:96:cd:15:f7:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=1.2.3.4 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=0 DF PROTO=TCP SPT=80 DPT=10656 WINDOW=28960 RES=0x00 ACK SYN URGP=0 9 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-FORWARD:IN=docker0 OUT=eth0 PHYSIN=veth57b521b MAC=02:42:96:cd:15:f7:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=1.2.3.4 LEN=60 TOS=0x00 PREC=0x00 TTL=63 ID=0 DF PROTO=TCP SPT=80 DPT=10656 WINDOW=28960 RES=0x00 ACK SYN URGP=0 10 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-POSTROUTING:IN= OUT=eth0 PHYSIN=veth57b521b SRC=172.17.0.2 DST=1.2.3.4 LEN=60 TOS=0x00 PREC=0x00 TTL=63 ID=0 DF PROTO=TCP SPT=80 DPT=10656 WINDOW=28960 RES=0x00 ACK SYN URGP=0 11 Jan 21 18:09:52 centos7 kernel: [debug]-raw-PREROUTING:IN=eth0 OUT= MAC=00:16:3e:01:ae:15:ee:ff:ff:ff:ff:ff:08:00 SRC=1.2.3.4 DST=172.20.242.183 LEN=52 TOS=0x14 PREC=0x00 TTL=48 ID=0 DF PROTO=TCP SPT=10656 DPT=8080 WINDOW=2061 RES=0x00 ACK URGP=0 12 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-PREROUTING:IN=eth0 OUT= MAC=00:16:3e:01:ae:15:ee:ff:ff:ff:ff:ff:08:00 SRC=1.2.3.4 DST=172.20.242.183 LEN=52 TOS=0x14 PREC=0x00 TTL=48 ID=0 DF PROTO=TCP SPT=10656 DPT=8080 WINDOW=2061 RES=0x00 ACK URGP=0 13 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-FORWARD:IN=eth0 OUT=docker0 MAC=00:16:3e:01:ae:15:ee:ff:ff:ff:ff:ff:08:00 SRC=1.2.3.4 DST=172.17.0.2 LEN=52 TOS=0x14 PREC=0x00 TTL=47 ID=0 DF PROTO=TCP SPT=10656 DPT=80 WINDOW=2061 RES=0x00 ACK URGP=0 14 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-POSTROUTING:IN= OUT=docker0 SRC=1.2.3.4 DST=172.17.0.2 LEN=52 TOS=0x14 PREC=0x00 TTL=47 ID=0 DF PROTO=TCP SPT=10656 DPT=80 WINDOW=2061 RES=0x00 ACK URGP=0 15 Jan 21 18:09:52 centos7 kernel: [debug]-raw-PREROUTING:IN=eth0 OUT= MAC=00:16:3e:01:ae:15:ee:ff:ff:ff:ff:ff:08:00 SRC=1.2.3.4 DST=172.20.242.183 LEN=134 TOS=0x14 PREC=0x00 TTL=48 ID=0 DF PROTO=TCP SPT=10656 DPT=8080 WINDOW=2061 RES=0x00 ACK PSH URGP=0 16 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-PREROUTING:IN=eth0 OUT= MAC=00:16:3e:01:ae:15:ee:ff:ff:ff:ff:ff:08:00 SRC=1.2.3.4 DST=172.20.242.183 LEN=134 TOS=0x14 PREC=0x00 TTL=48 ID=0 DF PROTO=TCP SPT=10656 DPT=8080 WINDOW=2061 RES=0x00 ACK PSH URGP=0 17 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-FORWARD:IN=eth0 OUT=docker0 MAC=00:16:3e:01:ae:15:ee:ff:ff:ff:ff:ff:08:00 SRC=1.2.3.4 DST=172.17.0.2 LEN=134 TOS=0x14 PREC=0x00 TTL=47 ID=0 DF PROTO=TCP SPT=10656 DPT=80 WINDOW=2061 RES=0x00 ACK PSH URGP=0 18 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-POSTROUTING:IN= OUT=docker0 SRC=1.2.3.4 DST=172.17.0.2 LEN=134 TOS=0x14 PREC=0x00 TTL=47 ID=0 DF PROTO=TCP SPT=10656 DPT=80 WINDOW=2061 RES=0x00 ACK PSH URGP=0 19 Jan 21 18:09:52 centos7 kernel: [debug]-raw-PREROUTING:IN=docker0 OUT= PHYSIN=veth57b521b MAC=02:42:96:cd:15:f7:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=1.2.3.4 LEN=52 TOS=0x00 PREC=0x00 TTL=64 ID=22142 DF PROTO=TCP SPT=80 DPT=10656 WINDOW=227 RES=0x00 ACK URGP=0 20 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-PREROUTING:IN=docker0 OUT= PHYSIN=veth57b521b MAC=02:42:96:cd:15:f7:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=1.2.3.4 LEN=52 TOS=0x00 PREC=0x00 TTL=64 ID=22142 DF PROTO=TCP SPT=80 DPT=10656 WINDOW=227 RES=0x00 ACK URGP=0 21 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-FORWARD:IN=docker0 OUT=eth0 PHYSIN=veth57b521b MAC=02:42:96:cd:15:f7:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=1.2.3.4 LEN=52 TOS=0x00 PREC=0x00 TTL=63 ID=22142 DF PROTO=TCP SPT=80 DPT=10656 WINDOW=227 RES=0x00 ACK URGP=0 22 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-POSTROUTING:IN= OUT=eth0 PHYSIN=veth57b521b SRC=172.17.0.2 DST=1.2.3.4 LEN=52 TOS=0x00 PREC=0x00 TTL=63 ID=22142 DF PROTO=TCP SPT=80 DPT=10656 WINDOW=227 RES=0x00 ACK URGP=0 23 Jan 21 18:09:52 centos7 kernel: [debug]-raw-PREROUTING:IN=docker0 OUT= PHYSIN=veth57b521b MAC=02:42:96:cd:15:f7:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=1.2.3.4 LEN=290 TOS=0x00 PREC=0x00 TTL=64 ID=22143 DF PROTO=TCP SPT=80 DPT=10656 WINDOW=227 RES=0x00 ACK PSH URGP=0 24 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-PREROUTING:IN=docker0 OUT= PHYSIN=veth57b521b MAC=02:42:96:cd:15:f7:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=1.2.3.4 LEN=290 TOS=0x00 PREC=0x00 TTL=64 ID=22143 DF PROTO=TCP SPT=80 DPT=10656 WINDOW=227 RES=0x00 ACK PSH URGP=0 25 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-FORWARD:IN=docker0 OUT=eth0 PHYSIN=veth57b521b MAC=02:42:96:cd:15:f7:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=1.2.3.4 LEN=290 TOS=0x00 PREC=0x00 TTL=63 ID=22143 DF PROTO=TCP SPT=80 DPT=10656 WINDOW=227 RES=0x00 ACK PSH URGP=0 26 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-POSTROUTING:IN= OUT=eth0 PHYSIN=veth57b521b SRC=172.17.0.2 DST=1.2.3.4 LEN=290 TOS=0x00 PREC=0x00 TTL=63 ID=22143 DF PROTO=TCP SPT=80 DPT=10656 WINDOW=227 RES=0x00 ACK PSH URGP=0 27 Jan 21 18:09:52 centos7 kernel: [debug]-raw-PREROUTING:IN=docker0 OUT= PHYSIN=veth57b521b MAC=02:42:96:cd:15:f7:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=1.2.3.4 LEN=664 TOS=0x00 PREC=0x00 TTL=64 ID=22144 DF PROTO=TCP SPT=80 DPT=10656 WINDOW=227 RES=0x00 ACK PSH URGP=0 28 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-PREROUTING:IN=docker0 OUT= PHYSIN=veth57b521b MAC=02:42:96:cd:15:f7:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=1.2.3.4 LEN=664 TOS=0x00 PREC=0x00 TTL=64 ID=22144 DF PROTO=TCP SPT=80 DPT=10656 WINDOW=227 RES=0x00 ACK PSH URGP=0 29 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-FORWARD:IN=docker0 OUT=eth0 PHYSIN=veth57b521b MAC=02:42:96:cd:15:f7:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=1.2.3.4 LEN=664 TOS=0x00 PREC=0x00 TTL=63 ID=22144 DF PROTO=TCP SPT=80 DPT=10656 WINDOW=227 RES=0x00 ACK PSH URGP=0 30 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-POSTROUTING:IN= OUT=eth0 PHYSIN=veth57b521b SRC=172.17.0.2 DST=1.2.3.4 LEN=664 TOS=0x00 PREC=0x00 TTL=63 ID=22144 DF PROTO=TCP SPT=80 DPT=10656 WINDOW=227 RES=0x00 ACK PSH URGP=0 31 Jan 21 18:09:52 centos7 kernel: [debug]-raw-PREROUTING:IN=eth0 OUT= MAC=00:16:3e:01:ae:15:ee:ff:ff:ff:ff:ff:08:00 SRC=1.2.3.4 DST=172.20.242.183 LEN=52 TOS=0x14 PREC=0x00 TTL=48 ID=0 DF PROTO=TCP SPT=10656 DPT=8080 WINDOW=2047 RES=0x00 ACK URGP=0 32 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-PREROUTING:IN=eth0 OUT= MAC=00:16:3e:01:ae:15:ee:ff:ff:ff:ff:ff:08:00 SRC=1.2.3.4 DST=172.20.242.183 LEN=52 TOS=0x14 PREC=0x00 TTL=48 ID=0 DF PROTO=TCP SPT=10656 DPT=8080 WINDOW=2047 RES=0x00 ACK URGP=0 33 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-FORWARD:IN=eth0 OUT=docker0 MAC=00:16:3e:01:ae:15:ee:ff:ff:ff:ff:ff:08:00 SRC=1.2.3.4 DST=172.17.0.2 LEN=52 TOS=0x14 PREC=0x00 TTL=47 ID=0 DF PROTO=TCP SPT=10656 DPT=80 WINDOW=2047 RES=0x00 ACK URGP=0 34 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-POSTROUTING:IN= OUT=docker0 SRC=1.2.3.4 DST=172.17.0.2 LEN=52 TOS=0x14 PREC=0x00 TTL=47 ID=0 DF PROTO=TCP SPT=10656 DPT=80 WINDOW=2047 RES=0x00 ACK URGP=0 35 Jan 21 18:09:52 centos7 kernel: [debug]-raw-PREROUTING:IN=eth0 OUT= MAC=00:16:3e:01:ae:15:ee:ff:ff:ff:ff:ff:08:00 SRC=1.2.3.4 DST=172.20.242.183 LEN=52 TOS=0x14 PREC=0x00 TTL=48 ID=0 DF PROTO=TCP SPT=10656 DPT=8080 WINDOW=2048 RES=0x00 ACK FIN URGP=0 36 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-PREROUTING:IN=eth0 OUT= MAC=00:16:3e:01:ae:15:ee:ff:ff:ff:ff:ff:08:00 SRC=1.2.3.4 DST=172.20.242.183 LEN=52 TOS=0x14 PREC=0x00 TTL=48 ID=0 DF PROTO=TCP SPT=10656 DPT=8080 WINDOW=2048 RES=0x00 ACK FIN URGP=0 37 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-FORWARD:IN=eth0 OUT=docker0 MAC=00:16:3e:01:ae:15:ee:ff:ff:ff:ff:ff:08:00 SRC=1.2.3.4 DST=172.17.0.2 LEN=52 TOS=0x14 PREC=0x00 TTL=47 ID=0 DF PROTO=TCP SPT=10656 DPT=80 WINDOW=2048 RES=0x00 ACK FIN URGP=0 38 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-POSTROUTING:IN= OUT=docker0 SRC=1.2.3.4 DST=172.17.0.2 LEN=52 TOS=0x14 PREC=0x00 TTL=47 ID=0 DF PROTO=TCP SPT=10656 DPT=80 WINDOW=2048 RES=0x00 ACK FIN URGP=0 39 Jan 21 18:09:52 centos7 kernel: [debug]-raw-PREROUTING:IN=docker0 OUT= PHYSIN=veth57b521b MAC=02:42:96:cd:15:f7:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=1.2.3.4 LEN=52 TOS=0x00 PREC=0x00 TTL=64 ID=22145 DF PROTO=TCP SPT=80 DPT=10656 WINDOW=227 RES=0x00 ACK FIN URGP=0 40 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-PREROUTING:IN=docker0 OUT= PHYSIN=veth57b521b MAC=02:42:96:cd:15:f7:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=1.2.3.4 LEN=52 TOS=0x00 PREC=0x00 TTL=64 ID=22145 DF PROTO=TCP SPT=80 DPT=10656 WINDOW=227 RES=0x00 ACK FIN URGP=0 41 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-FORWARD:IN=docker0 OUT=eth0 PHYSIN=veth57b521b MAC=02:42:96:cd:15:f7:02:42:ac:11:00:02:08:00 SRC=172.17.0.2 DST=1.2.3.4 LEN=52 TOS=0x00 PREC=0x00 TTL=63 ID=22145 DF PROTO=TCP SPT=80 DPT=10656 WINDOW=227 RES=0x00 ACK FIN URGP=0 42 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-POSTROUTING:IN= OUT=eth0 PHYSIN=veth57b521b SRC=172.17.0.2 DST=1.2.3.4 LEN=52 TOS=0x00 PREC=0x00 TTL=63 ID=22145 DF PROTO=TCP SPT=80 DPT=10656 WINDOW=227 RES=0x00 ACK FIN URGP=0 43 Jan 21 18:09:52 centos7 kernel: [debug]-raw-PREROUTING:IN=eth0 OUT= MAC=00:16:3e:01:ae:15:ee:ff:ff:ff:ff:ff:08:00 SRC=1.2.3.4 DST=172.20.242.183 LEN=52 TOS=0x14 PREC=0x00 TTL=48 ID=0 DF PROTO=TCP SPT=10656 DPT=8080 WINDOW=2048 RES=0x00 ACK URGP=0 44 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-PREROUTING:IN=eth0 OUT= MAC=00:16:3e:01:ae:15:ee:ff:ff:ff:ff:ff:08:00 SRC=1.2.3.4 DST=172.20.242.183 LEN=52 TOS=0x14 PREC=0x00 TTL=48 ID=0 DF PROTO=TCP SPT=10656 DPT=8080 WINDOW=2048 RES=0x00 ACK URGP=0 45 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-FORWARD:IN=eth0 OUT=docker0 MAC=00:16:3e:01:ae:15:ee:ff:ff:ff:ff:ff:08:00 SRC=1.2.3.4 DST=172.17.0.2 LEN=52 TOS=0x14 PREC=0x00 TTL=47 ID=0 DF PROTO=TCP SPT=10656 DPT=80 WINDOW=2048 RES=0x00 ACK URGP=0 46 Jan 21 18:09:52 centos7 kernel: [debug]-mangle-POSTROUTING:IN= OUT=docker0 SRC=1.2.3.4 DST=172.17.0.2 LEN=52 TOS=0x14 PREC=0x00 TTL=47 ID=0 DF PROTO=TCP SPT=10656 DPT=80 WINDOW=2048 RES=0x00 ACK URGP=0
以上涉及raw,mangle和nat幾個iptables表,選取第1行到第22行的log,數據包的基本軌跡可以歸納爲如下
- [1] raw-PREROUTING: IN=eth0 (1.2.3.4:10656 -> 172.20.242.183:8080)
- [2] mangle-PREROUTING: IN=eth0 (1.2.3.4:10656 -> 172.20.242.183:8080)
- [3] nat-PREROUTING: IN=eth0 (1.2.3.4:10656 -> 172.20.242.183:8080)
- [4] mangle-FORWARD: IN=eth0 OUT=docker0 (1.2.3.4:10656 -> 172.17.0.2:80)
- [5] mangle-POSTROUTING: OUT=docker0 (1.2.3.4:10656 -> 172.17.0.2:80)
- [6] nat-POSTROUTING: OUT=docker0 (1.2.3.4:10656 -> 172.17.0.2:80)
- [7] raw-PREROUTING: IN=docker0 (172.17.0.2:80 -> 1.2.3.4:10656)
- [8] mangle-PREROUTING: IN=docker0 (172.17.0.2:80 -> 1.2.3.4:10656)
- [9] mangle-FORWARD: IN=docker0 OUT=eth0 (172.17.0.2:80 -> 1.2.3.4:10656)
- [10] mangle-POSTROUTING: OUT=eth0 (172.17.0.2:80 -> 1.2.3.4:10656)
- [11] raw-PREROUTING: IN=eth0 (1.2.3.4:10656 -> 172.20.242.183:8080)
- [12] mangle-PREROUTING: IN=eth0 (1.2.3.4:10656 -> 172.20.242.183:8080)
- [13] mangle-FORWARD: IN=eth0 OUT=docker0 (1.2.3.4:10656 -> 172.17.0.2:80)
- [14] mangle-POSTROUTING: OUT=docker0 (1.2.3.4:10656 -> 172.17.0.2:80)
- [15] raw-PREROUTING: IN=eth0 (1.2.3.4:10656 -> 172.20.242.183:8080)
- [16] mangle-PREROUTING: IN=eth0 (1.2.3.4:10656 -> 172.20.242.183:8080)
- [17] mangle-FORWARD: IN=eth0 OUT=docker0 (1.2.3.4:10656 -> 172.17.0.2:80)
- [18] mangle-POSTROUTING: OUT=docker0 (1.2.3.4:10656 -> 172.17.0.2:80)
- [19] raw-PREROUTING: IN=docker0 (172.17.0.2:80 -> 1.2.3.4:10656)
- [20] mangle-PREROUTING: IN=docker0 (172.17.0.2:80 -> 1.2.3.4:10656)
- [21] mangle-FORWARD: IN=docker0 OUT=eth0 (172.17.0.2:80 -> 1.2.3.4:10656)
- [22] mangle-POSTROUTING: OUT=eth0 (172.17.0.2:80 -> 1.2.3.4:10656)
以上分成5個色塊,分別爲如下表示:
- 粉色:1.2.3.4發送SYN包
- 橘色:172.17.0.2發送SYN + ACK包
- 黃色:1.2.3.4發送ACK包
- 綠色:1.2.3.4發送ACK + PSH包
- 藍色:172.17.0.2發送ACK包
從以上的log可以看到,從nat表的PREROUTING鏈開始後,目的端口就從8080變成了80,那這個會不會就是其端口轉發的根源呢?我們接下來再分析下。
二. NAT表的魔法
1. 初始的Docker的NAT表
上面我們說到,數據包從NAT表出來後,目的端口發生了變化。我們將iptables回退到增加log之前的狀態,看看這幾個iptables的表都有什麼變化。
與安裝docker前相比,其他幾個表都沒有什麼相關的變化,唯獨nat表,看上去有些不一樣:
[root@centos7 ~]# iptables -t nat -nvL Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination 60 3132 DOCKER all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL Chain INPUT (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination 0 0 DOCKER all -- * * 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination 0 0 MASQUERADE all -- * !docker0 172.17.0.0/16 0.0.0.0/0 Chain DOCKER (2 references) pkts bytes target prot opt in out source destination 0 0 RETURN all -- docker0 * 0.0.0.0/0 0.0.0.0/0
我們可以看到,它在nat表中增加了一個自定義鏈DOCKER,而這個鏈被兩處引用,一處是在PREROUTING,另一處在OUTPUT,爲什麼在這兩處呢?
爲了回答這個問題,我們有必要先談談iptables的基本知識。
2. iptables的處理端口轉發的過程
簡單地講,Linux iptables一共有5條內置鏈,分別爲PREROUTING,FORWARDING,POSTROUTING,INPUT和OUTPUT。
此外,iptables還有5個表,分別爲filter,nat,mangle,raw和security。
數據包經過這些鏈是有順序的,經過某個鏈時,如果鏈上有對應的表在作用,則去執行該表中對應的鏈裏的rule,大致如下所示:
即數據進入網卡接口後,到達協議棧,首先經過的iptables鏈是PREROUTING,這裏給數據包一些預處理的機會,譬如修改目的地址等,把所有的表都擼一遍,如果看到某個表有PREROUTING鏈的規則,則進去執行,如果沒有就跳過,多說一句,不是每個表都有PREROUTING鏈,只有raw、mangle和nat表纔有的,以下鏈也是的,後續也就不一一而足了。
數據包經過PREROUTING鏈的處理後,然後交給Routing決策,結合路由表看看該數據包是繼續在協議棧中往下走進入本地進程,還是直接把本網卡所在的機器當路由,直接通過FOWARD出該網卡去往其他網卡,甚至其他機器。
到達了INPUT就表示肯定是去往本地的某個進程了,不過再給一次處理的機會再進入進程,於是乎,再查看下具有INPUT鏈的幾張表(即mangle、nat和filter),看看其中有沒有能夠匹配的規則,有就執行,對數據包作送入進程socket前的最後一次處理。
一旦到達了進程socket,進入進程用戶空間,這個數據包的生命就該到達終點了。
本來到了這裏,故事也就結束了,然而實際使用的協議都是有來有回的,回包的起點就是本地進程了,因而出現了上圖中的右邊部分,即當本地進程發起一個數據包(這裏可能是一個回包)時,首先就要交給OUTPUT鏈,在該鏈上先給個機會對進程發出的數據包做個處理,然後再經過路由決策決定發往哪張網卡。
經由上面的分析,Docker對於入向包在PREROUTING鏈中處理,而出向包在OUTPUT鏈中處理,也就順理成章了吧。也就是說,要趕在讓路由策略決定往哪裏發前,先處理下,這樣保證如向包能夠順利進入相應的進程,而出向包能夠達到相應的網卡接口。
我們先來解讀下以上的nat表rule的作用:
- 在PREROUTING中,對於所有源地址、目的地址、網絡協議的包進行地址匹配,如果匹配本地地址類的,就進入自定義的DOCKER鏈,注意,本地地址類可以不侷限於127.0.0.0/8,而是包括所有本地監聽的、分配的地址,例如docker容器中常用的172開頭的那些地址,具體可以通過 ip route show table local type local 看到
- 在OUTPUT鏈中,對所有源地址、網絡協議、目的地址不是127.0.0.0/8的包進行匹配,如果屬於本地地址類的,進入自定義的DOCKER鏈
- 在自定義的DOCKER鏈中,對於docker0網橋進入的包(即容器中發出的),啥也不做,直接返回到剛剛跳轉到DOCKER鏈的上級鏈
- 在MASQUERADE鏈中對於所有源自172.17.0.0/16、但不是由docker0發出的包(由容器發往本機之外的)都進行地址僞裝,即將源地址替換爲本地地址,也就是做SNAT
以上就是docker安裝好的nat表,啓動了以上nginx的端口轉發後,
3. 啓動端口轉發後NAT表的變化
既然我們談的是Docker的端口轉發,那我們真的來一個轉發看看,瞧瞧iptables中有什麼變化:
docker run -d -p 8080:80 nginx:latest
看iptables變化:
[root@centos7 ~]# iptables -t nat -nvL Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination 60 3132 DOCKER all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL Chain INPUT (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination 0 0 DOCKER all -- * * 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination 0 0 MASQUERADE all -- * !docker0 172.17.0.0/16 0.0.0.0/0 0 0 MASQUERADE tcp -- * * 172.17.0.2 172.17.0.2 tcp dpt:80 Chain DOCKER (2 references) pkts bytes target prot opt in out source destination 0 0 RETURN all -- docker0 * 0.0.0.0/0 0.0.0.0/0 0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.2:80
好傢伙,偷偷在POSTROUTING和DOCKER中增加了兩條rule:
- 在自定義的DOCKER鏈中,對於不是由docker0接受到的tcp包,進行目的地址轉換,轉換的規則爲: 如果目的端口是8080的tcp包,則修改爲172.17.0.2:80(172.17.0.2爲剛生成的container的地址)
- 在MASQUERADE鏈中,增加了了一條地址僞裝規則,對於所有的來自172.17.0.2和發往172.17.0.2且目的端口爲80的TCP包,都進行源地址僞裝(SNAT),改爲該網絡接口對外地址,如果是eth0,那我們這裏就是172.20.242.183
4. 數據包的基本流向
看到這裏,我們或許已經大體上有了一個包的基本流向的概念了,結合已經的iptables的log,我們來分析下具體的流向。
首先,查看了本地的路由:
[root@centos7 ~]# ip route default via 172.20.255.253 dev eth0 169.254.0.0/16 dev eth0 scope link metric 1002 172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 172.20.240.0/20 dev eth0 proto kernel scope link src 172.20.242.183
我們從這臺機器去inspect這個nginx container,拿到部分IP信息如下:
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "a2d0aec96e15c5cb8ead04d0d95fd00d71b72b17a8214ce78b4af9a4c71b6248",
"EndpointID": "c1faabc8c50e3a703219900e029ebf813cfcd619030865c2d8991ac966f56b95",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:11:00:02"
}
}
並查看本地的所有的網卡接口的地址:
[root@centos7 ~]# ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 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 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000 link/ether 00:16:3e:01:ae:15 brd ff:ff:ff:ff:ff:ff inet 172.20.242.183/20 brd 172.20.255.255 scope global dynamic eth0 valid_lft 315273420sec preferred_lft 315273420sec inet6 fe80::216:3eff:fe01:ae15/64 scope link valid_lft forever preferred_lft forever 3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:c0:8e:53:0e brd ff:ff:ff:ff:ff:ff inet 172.17.0.1/16 scope global docker0 valid_lft forever preferred_lft forever inet6 fe80::42:c0ff:fe8e:530e/64 scope link valid_lft forever preferred_lft forever 11: veth02d2a0d@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default link/ether da:75:64:82:95:10 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet6 fe80::d875:64ff:fe82:9510/64 scope link valid_lft forever preferred_lft forever
那麼從外網1.2.3.4發出的第一個SYN包(log總結中粉色部分)的走向是這樣的:
- TCP包(1.2.3.4:10656 -> 172.20.242.183:8080)由eth0接口進入TCP協議棧
- 進入PREROUTING鏈,分別過濾raw、mangle及nat表,發現只有nat表有rule存在,則進入逐條過濾
- 發現其目的地址屬於本地地址類,跳轉到自定義DOCKER鏈
- 在DOCKER鏈中發現第一條規則不匹配,因爲其不是docker0接收到的包;第二條匹配,非docker0接收,且目的端口爲8080,則通過DNAT轉換爲(1.2.3.4:10656 -> 172.17.0.2:80)
- PREROUTING鏈中過濾完成後,進入路由決策,路由決策根據路由 172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 決定該包發往docker0,如果沒有源地址則填充源地址爲172.17.0.1(即本機發起的包,我們這裏顯然不是),則該包維持(1.2.3.4:10656 -> 172.17.0.2:80),發往docker0,走向FORWARRD鏈,相當於轉發到另外一個網卡接口了,這裏得益於打開了上面提及的ip_forward內核參數,eth0和docker0之間可以互相轉發包
- 於是經過FORWARD鏈,數據包由eth0直接轉發到docker0,數據包維持(1.2.3.4:10656 -> 172.17.0.2:80)
- (1.2.3.4:10656 -> 172.17.0.2:80)到達POSTROUTING鏈,經過nat表過濾,因爲它的源地址不屬於172.17.0.0/16,因而不匹配第一條rule;到第二條rule,源地址目的地址都不是172.17.0.2,因而也不匹配,從docker0出協議棧,由於docker0是Linux網橋,該網橋與容器內的"eth0"(容器自己看,它自己有個eth0,實際是veth pair的一端)通過一對veth pair直連,這樣一來,容器內的nginx進程的socket就收到該SYN包了
那從容器裏返回的SYN+ACK包該怎麼辦呢?結合上面的log總結中的橘色部分,我們可以作如下推演:
- TCP包(172.17.0.2:80 -> 1.2.3.4:10656)從容器內的nginx進程的socket通過docker0接口發出返回包SYN+ACK,進入TCP協議棧
- 首先到達PREROUTING鏈,這裏比較奇怪,只經過了raw和mangle表的過濾,卻沒有經過nat,爲什麼呢?因爲iptables對於數據包也是記錄狀態的,如果前面有了一個ACK了,那麼從iptables看來,同樣四元組的SYN+ACK包就是ESTABLISHED狀態了,因此不需要經過nat表去浪費時間了,需要怎麼轉換iptables已經知道了,直接做掉就好了,於是進入路由決策
- 在路由決策階段,由於是外部地址,匹配 default via 172.20.255.253 dev eth0路由規則,通過eth0發送,當前是在docker0,所以需要從FORWARD鏈出去
- TCP包(172.17.0.2:80 -> 1.2.3.4:10656)包到達POSTROUTING鏈,這時需要在mangle表處理下,因爲iptables的記憶功能,它知道這個四元組曾經從eth0來的時候是從172.20.242.183:8080轉換來的,這裏回去的話還要轉換爲本地地址和端口即數據包變成(172.20.242.183:8080 -> 1.2.3.4:10656)發出,至此,一個回包就好了。
接下來的部分,就顯得雷同了,大差不差,只是少了進入nat表了,原因之前說了,iptables是有狀態的。那我們就不在這裏贅述了。
參考:
[1] https://www.cnblogs.com/yum777/articles/8514636.html
[2] https://tonybai.com/2016/01/15/understanding-container-networking-on-single-host/
[3] https://www.rigacci.org/wiki/lib/exe/fetch.php/doc/appunti/linux/sa/iptables/conntrack.html