在之前的章節中,我們用了偷吃步的方法,將 iptables 的 FORWARD
預設行為改成 ACCEPT
。然而,當我們在上一章將 FORWARD
的預設行為改回 DROP
後,先前建立的網路連線卻斷掉了。這是怎麼回事呢?
測試網路連線:ns1 到 ns0
1sudo ip netns exec ns1 ping -c 1 172.18.0.22# output3PING 172.18.0.2 (172.18.0.2) 56(84) bytes of data.4
5--- 172.18.0.2 ping statistics ---61 packets transmitted, 0 received, 100% packet loss, time 0ms
測試網路連線:ns1 到外網
1sudo ip netns exec ns1 ping -c 1 8.8.8.82# output3PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.4
5--- 8.8.8.8 ping statistics ---61 packets transmitted, 0 received, 100% packet loss, time 0ms
完全斷線了!為什麼會這樣呢?原因就在於 iptables
的預設行為的差異。
iptables 預設行為的差異
iptables
的預設行為,主要體現在三個內建 chains (INPUT, OUTPUT, FORWARD) 的 policy
上:
ACCEPT
(預設允許):採黑名單模式,只有明確被拒絕的流量才會被阻擋,其餘都允許通過。DROP
(預設丟棄):採白名單模式,只有明確被允許的流量才能通過,其餘都會被丟棄。
所以,當我們把 FORWARD
的 policy 改成 DROP
後,所有未被明確允許的流量,包含容器之間的通訊,都被阻擋了。我們必須重新設定 iptables
規則,才能讓網路恢復正常。
分析:安裝 Docker 前後,iptables 條目的變化
為了找出問題所在,我們比較了安裝 Docker 前後 iptables
規則的差異。
Docker 安裝前:
1sudo iptables -t filter -nvL --line-numbers2# output3Chain INPUT (policy ACCEPT)4num target prot opt source destination5
6Chain FORWARD (policy ACCEPT)7num target prot opt source destination8
9Chain OUTPUT (policy ACCEPT)10num target prot opt source destination
Docker 安裝後:
1sudo iptables -t filter -nvL --line-numbers2# output3Chain INPUT (policy ACCEPT 0 packets, 0 bytes)4num pkts bytes target prot opt in out source destination5
6Chain FORWARD (policy DROP 1 packets, 84 bytes)7num pkts bytes target prot opt in out source destination81 664 2569K DOCKER-USER all -- * * 0.0.0.0/0 0.0.0.0/092 664 2569K DOCKER-ISOLATION-STAGE-1 all -- * * 0.0.0.0/0 0.0.0.0/0103 340 2547K ACCEPT all -- * docker0 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED114 0 0 DOCKER all -- * docker0 0.0.0.0/0 0.0.0.0/0125 171 11618 ACCEPT all -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0136 0 0 ACCEPT all -- docker0 docker0 0.0.0.0/0 0.0.0.0/014
15Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)18 collapsed lines
16num pkts bytes target prot opt in out source destination17
18Chain DOCKER (1 references)19num pkts bytes target prot opt in out source destination20
21Chain DOCKER-ISOLATION-STAGE-1 (1 references)22num pkts bytes target prot opt in out source destination231 171 11618 DOCKER-ISOLATION-STAGE-2 all -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0242 664 2569K RETURN all -- * * 0.0.0.0/0 0.0.0.0/025
26Chain DOCKER-ISOLATION-STAGE-2 (1 references)27num pkts bytes target prot opt in out source destination281 0 0 DROP all -- * docker0 0.0.0.0/0 0.0.0.0/0292 171 11618 RETURN all -- * * 0.0.0.0/0 0.0.0.0/030
31Chain DOCKER-USER (1 references)32num pkts bytes target prot opt in out source destination331 664 2569K RETURN all -- * * 0.0.0.0/0 0.0.0.0/0
1sudo iptables-save -t filter2# output3*filter4:INPUT ACCEPT [0:0]5:FORWARD DROP [1:84]6:OUTPUT ACCEPT [0:0]7:DOCKER - [0:0]8:DOCKER-ISOLATION-STAGE-1 - [0:0]9:DOCKER-ISOLATION-STAGE-2 - [0:0]10:DOCKER-USER - [0:0]11-A FORWARD -j DOCKER-USER12-A FORWARD -j DOCKER-ISOLATION-STAGE-113-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT14-A FORWARD -o docker0 -j DOCKER15-A FORWARD -i docker0 ! -o docker0 -j ACCEPT7 collapsed lines
16-A FORWARD -i docker0 -o docker0 -j ACCEPT17-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-218-A DOCKER-ISOLATION-STAGE-1 -j RETURN19-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP20-A DOCKER-ISOLATION-STAGE-2 -j RETURN21-A DOCKER-USER -j RETURN22COMMIT
安裝 Docker 後,多了許多 iptables
規則和 chains,這些都是 Docker 為我們自動建立的,用於管理容器網路。
解釋:Docker 專用 chains 的規則
這些 chains 主要有兩個目的:1. 安全隔離容器與外部網路;2. 集中管理容器流量。Docker 會自動建立必要的 rules,例如允許容器所需的 RELATED
、ESTABLISHED
流量,以確保容器間以及容器與外部網路的正常通訊。
-
DOCKER
- 處理容器 Network Address Translation (NAT) 映射、容器對外/對內連線等規則。
- 例如:
-A FORWARD -o docker0 -j DOCKER
表示從主機對外發送的封包,在通過docker0
介面前,會先經過DOCKER
鏈進行檢查。 - 若封包符合 Docker 建立的允許條件(包含連線狀態、對應容器的埠等),就會被允許通過。
-
DOCKER-ISOLATION-STAGE-1
、DOCKER-ISOLATION-STAGE-2
- 這兩個鏈負責不同網路介面(包含
docker0
與其他 Docker 橋接網路)之間的流量隔離。 STAGE-1
:先判斷封包來源與目的地是否為相同的 Docker 橋接網路,若否,則跳轉到STAGE-2
。STAGE-2
:直接丟棄不允許跨橋接網路的流量。
- 這兩個鏈負責不同網路介面(包含
-
DOCKER-USER
- 提供使用者自訂或覆蓋 Docker 預設規則的空間。
- 例如:若需在容器層新增額外過濾規則,可在
DOCKER-USER
中新增自訂規則。 - 預設最後有一行
-A DOCKER-USER -j RETURN
,表示若無自訂規則,則直接返回FORWARD
鏈。
Docker 未建立容器時,這些鏈並無任何規則。讓我們繼續分析 FORWARD
chain 的規則。
解釋:FORWARD chain 新增的規則
讓我們更仔細看看 FORWARD
chain 中新增的規則:
1# iptables2num pkts bytes target prot opt in out source destination31 664 2569K DOCKER-USER all -- * * 0.0.0.0/0 0.0.0.0/042 664 2569K DOCKER-ISOLATION-STAGE-1 all -- * * 0.0.0.0/0 0.0.0.0/053 340 2547K ACCEPT all -- * docker0 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED64 0 0 DOCKER all -- * docker0 0.0.0.0/0 0.0.0.0/075 171 11618 ACCEPT all -- docker0 !docker0 0.0.0.0/0 0.0.0.0/086 0 0 ACCEPT all -- docker0 docker0 0.0.0.0/0 0.0.0.0/09# iptables-save10-A FORWARD -j DOCKER-USER11-A FORWARD -j DOCKER-ISOLATION-STAGE-112-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT13-A FORWARD -o docker0 -j DOCKER14-A FORWARD -i docker0 ! -o docker0 -j ACCEPT15-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A FORWARD -j DOCKER-USER
- 作用:跳轉到
DOCKER-USER
chain,讓使用者可以在該 chain 裡自行新增或覆蓋 Docker 預設的規則。
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
- 作用:跳轉到
DOCKER-ISOLATION-STAGE-1
,這個 chain 主要用來隔離不同 Docker bridge 或者不同介面之間的流量。 - 流程上:封包先進
DOCKER-USER
,若無規則攔截就 RETURN 回FORWARD
,接著又跳到DOCKER-ISOLATION-STAGE-1
做容器網路隔離檢查。
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
- 作用:對 已有連線 (ESTABLISHED) 或相關連線 (RELATED) 且「流出(-o)docker0」的封包,直接放行。
- 確保容器已建立連線的回應流量能正常到達容器。
-A FORWARD -o docker0 -j DOCKER
- 作用:針對「流出 docker0(-o docker0)」但不符合前面 RELATED,ESTABLISHED的封包,跳轉到
DOCKER
chain 做後續處理。 DOCKER
chain 是 Docker 自動新增的 chain,用來存放針對特定 Port / Protocol 的允許規則 (例如docker run -p 8080:80
時會插入 DNAT、FORWARD 規則等)。
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
- 作用:當封包「從 docker0 進來」,但目的網卡不是 docker0(
! -o docker0
),就直接允許。 - 典型情境:容器想要連到宿主機外部網路、或其他網卡,且不是在同一 docker0 bridge 上。
-A FORWARD -i docker0 -o docker0 -j ACCEPT
- 作用:當封包「從 docker0 進來」且「出去也還是 docker0」,就允許。
- 若有多個容器掛在同一個 docker0 bridge 上,彼此連線就會走這條規則
簡單來說
- 第 1, 2 條對應的
DOCKER-USER
和DOCKER-ISOLATION-STAGE-*
chain 是 Docker 特別設計來讓使用者自訂規則、並且在容器與外部之間做網路隔離與控制。 - 第 3 條是為了實現狀態防火牆(Stateful firewall),不再次檢查回應流量。
- 第 4 條對應的
DOCKER
chain 是設計對特定容器 Port / Protocol 做控制轉發。 - 第 5 條允許 Docker 容器對外連線。
- 第 6 條允許 Docker 容器間的連線。
為 docker1 新增 iptables 規則
現在我們需要為自定義的 docker1
橋接網路新增相應的 iptables
規則,才能讓 ns1
正常連線到 ns0
和外網。
允許容器間的連線
我們新增一條規則,類似於原來的第 6 條規則,但將網路介面替換為 docker1
:
1sudo iptables -t filter -A FORWARD -i docker1 -o docker1 -j ACCEPT
測試連線:
1sudo ip netns exec ns1 ping -c 1 172.18.0.22# output3PING 172.18.0.2 (172.18.0.2) 56(84) bytes of data.464 bytes from 172.18.0.2: icmp_seq=1 ttl=127 time=0.060 ms5
6--- 172.18.0.2 ping statistics ---71 packets transmitted, 1 received, 0% packet loss, time 0ms8rtt min/avg/max/mdev = 0.060/0.060/0.060/0.000 ms
提醒 若新增的規則錯誤,可以使用以下指令刪除:
sudo iptables -t filter -D FORWARD 7
(7
為該條目在FORWARD
表中的編號,使用iptables
命令的--line-numbers
flag 取得)
允許容器到外網的連線
同樣地,我們需要新增規則允許 docker1
網路上的容器訪問外部網路:
1sudo iptables -t filter -A FORWARD -i docker1 ! -o docker1 -j ACCEPT2sudo iptables -t filter -A FORWARD -o docker1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
測試連線:
1sudo ip netns exec ns1 ping -c 1 8.8.8.82# output3PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.464 bytes from 8.8.8.8: icmp_seq=1 ttl=116 time=6.83 ms5
6--- 8.8.8.8 ping statistics ---71 packets transmitted, 1 received, 0% packet loss, time 0ms8rtt min/avg/max/mdev = 6.834/6.834/6.834/0.000 ms
思考 若只新增第 5 條規則,會發生什麼事? 因為第 5 條規則只允許從
docker1
進入,且目的網卡非docker1
的流量通過。但在連線交握時,封包的進出方向會互換,因此無法符合規則,流量將被丟棄。
總結
在本章中,我們更加深入探討 iptables
的運作原理,並成功讓容器間以及從容器到外網的流量得以順利傳輸。那麼,如何從外網存取容器呢?
ns1
的 IP 位址為 172.18.0.3
,對於外網而言,其他主機的 Private IP 位址顯然無法作為封包的目的位址。即使使用主機的 Public IP 位址作為目的位址,也無法精確得知容器內的 IP 位址。
至於怎麼解決,我們下一個章節再繼續。