Vinc3nt's Life

從 Linux 基礎實現 Docker Bridge 網路:一步步理解容器通訊 (5)

2025-01-11
develop
linux
docker
bridge
container
iptable
最後更新:2025-01-26
12分鐘
2250字

在之前的章節中,我們用了偷吃步的方法,將 iptables 的 FORWARD 預設行為改成 ACCEPT。然而,當我們在上一章將 FORWARD 的預設行為改回 DROP 後,先前建立的網路連線卻斷掉了。這是怎麼回事呢?

測試網路連線:ns1 到 ns0

Terminal window
1
sudo ip netns exec ns1 ping -c 1 172.18.0.2
2
# output
3
PING 172.18.0.2 (172.18.0.2) 56(84) bytes of data.
4
5
--- 172.18.0.2 ping statistics ---
6
1 packets transmitted, 0 received, 100% packet loss, time 0ms

測試網路連線:ns1 到外網

Terminal window
1
sudo ip netns exec ns1 ping -c 1 8.8.8.8
2
# output
3
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
4
5
--- 8.8.8.8 ping statistics ---
6
1 packets transmitted, 0 received, 100% packet loss, time 0ms

完全斷線了!為什麼會這樣呢?原因就在於 iptables 的預設行為的差異。

iptables 預設行為的差異

iptables 的預設行為,主要體現在三個內建 chains (INPUT, OUTPUT, FORWARD) 的 policy 上:

  • ACCEPT (預設允許):採黑名單模式,只有明確被拒絕的流量才會被阻擋,其餘都允許通過。
  • DROP (預設丟棄):採白名單模式,只有明確被允許的流量才能通過,其餘都會被丟棄。

default

所以,當我們把 FORWARD 的 policy 改成 DROP 後,所有未被明確允許的流量,包含容器之間的通訊,都被阻擋了。我們必須重新設定 iptables 規則,才能讓網路恢復正常。

分析:安裝 Docker 前後,iptables 條目的變化

為了找出問題所在,我們比較了安裝 Docker 前後 iptables 規則的差異。

Docker 安裝前:

Terminal window
1
sudo iptables -t filter -nvL --line-numbers
2
# output
3
Chain INPUT (policy ACCEPT)
4
num target prot opt source destination
5
6
Chain FORWARD (policy ACCEPT)
7
num target prot opt source destination
8
9
Chain OUTPUT (policy ACCEPT)
10
num target prot opt source destination

Docker 安裝後:

Terminal window
1
sudo iptables -t filter -nvL --line-numbers
2
# output
3
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
4
num pkts bytes target prot opt in out source destination
5
6
Chain FORWARD (policy DROP 1 packets, 84 bytes)
7
num pkts bytes target prot opt in out source destination
8
1 664 2569K DOCKER-USER all -- * * 0.0.0.0/0 0.0.0.0/0
9
2 664 2569K DOCKER-ISOLATION-STAGE-1 all -- * * 0.0.0.0/0 0.0.0.0/0
10
3 340 2547K ACCEPT all -- * docker0 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
11
4 0 0 DOCKER all -- * docker0 0.0.0.0/0 0.0.0.0/0
12
5 171 11618 ACCEPT all -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0
13
6 0 0 ACCEPT all -- docker0 docker0 0.0.0.0/0 0.0.0.0/0
14
15
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
18 collapsed lines
16
num pkts bytes target prot opt in out source destination
17
18
Chain DOCKER (1 references)
19
num pkts bytes target prot opt in out source destination
20
21
Chain DOCKER-ISOLATION-STAGE-1 (1 references)
22
num pkts bytes target prot opt in out source destination
23
1 171 11618 DOCKER-ISOLATION-STAGE-2 all -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0
24
2 664 2569K RETURN all -- * * 0.0.0.0/0 0.0.0.0/0
25
26
Chain DOCKER-ISOLATION-STAGE-2 (1 references)
27
num pkts bytes target prot opt in out source destination
28
1 0 0 DROP all -- * docker0 0.0.0.0/0 0.0.0.0/0
29
2 171 11618 RETURN all -- * * 0.0.0.0/0 0.0.0.0/0
30
31
Chain DOCKER-USER (1 references)
32
num pkts bytes target prot opt in out source destination
33
1 664 2569K RETURN all -- * * 0.0.0.0/0 0.0.0.0/0
Terminal window
1
sudo iptables-save -t filter
2
# output
3
*filter
4
: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-USER
12
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
13
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
14
-A FORWARD -o docker0 -j DOCKER
15
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
7 collapsed lines
16
-A FORWARD -i docker0 -o docker0 -j ACCEPT
17
-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
18
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
19
-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
20
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
21
-A DOCKER-USER -j RETURN
22
COMMIT

安裝 Docker 後,多了許多 iptables 規則和 chains,這些都是 Docker 為我們自動建立的,用於管理容器網路。

解釋:Docker 專用 chains 的規則

這些 chains 主要有兩個目的:1. 安全隔離容器與外部網路;2. 集中管理容器流量。Docker 會自動建立必要的 rules,例如允許容器所需的 RELATEDESTABLISHED 流量,以確保容器間以及容器與外部網路的正常通訊。

  1. DOCKER

    • 處理容器 Network Address Translation (NAT) 映射、容器對外/對內連線等規則。
    • 例如:-A FORWARD -o docker0 -j DOCKER 表示從主機對外發送的封包,在通過 docker0 介面前,會先經過 DOCKER 鏈進行檢查。
    • 若封包符合 Docker 建立的允許條件(包含連線狀態、對應容器的埠等),就會被允許通過。
  2. DOCKER-ISOLATION-STAGE-1DOCKER-ISOLATION-STAGE-2

    • 這兩個鏈負責不同網路介面(包含 docker0 與其他 Docker 橋接網路)之間的流量隔離。
    • STAGE-1:先判斷封包來源與目的地是否為相同的 Docker 橋接網路,若否,則跳轉到 STAGE-2
    • STAGE-2:直接丟棄不允許跨橋接網路的流量。
  3. DOCKER-USER

    • 提供使用者自訂或覆蓋 Docker 預設規則的空間。
    • 例如:若需在容器層新增額外過濾規則,可在 DOCKER-USER 中新增自訂規則。
    • 預設最後有一行 -A DOCKER-USER -j RETURN,表示若無自訂規則,則直接返回 FORWARD 鏈。

Docker 未建立容器時,這些鏈並無任何規則。讓我們繼續分析 FORWARD chain 的規則。

解釋:FORWARD chain 新增的規則

讓我們更仔細看看 FORWARD chain 中新增的規則:

Terminal window
1
# iptables
2
num pkts bytes target prot opt in out source destination
3
1 664 2569K DOCKER-USER all -- * * 0.0.0.0/0 0.0.0.0/0
4
2 664 2569K DOCKER-ISOLATION-STAGE-1 all -- * * 0.0.0.0/0 0.0.0.0/0
5
3 340 2547K ACCEPT all -- * docker0 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
6
4 0 0 DOCKER all -- * docker0 0.0.0.0/0 0.0.0.0/0
7
5 171 11618 ACCEPT all -- docker0 !docker0 0.0.0.0/0 0.0.0.0/0
8
6 0 0 ACCEPT all -- docker0 docker0 0.0.0.0/0 0.0.0.0/0
9
# iptables-save
10
-A FORWARD -j DOCKER-USER
11
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
12
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
13
-A FORWARD -o docker0 -j DOCKER
14
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
15
-A FORWARD -i docker0 -o docker0 -j ACCEPT
  1. -A FORWARD -j DOCKER-USER
  • 作用:跳轉到 DOCKER-USER chain,讓使用者可以在該 chain 裡自行新增或覆蓋 Docker 預設的規則。
  1. -A FORWARD -j DOCKER-ISOLATION-STAGE-1
  • 作用:跳轉到 DOCKER-ISOLATION-STAGE-1,這個 chain 主要用來隔離不同 Docker bridge 或者不同介面之間的流量。
  • 流程上:封包先進 DOCKER-USER,若無規則攔截就 RETURN 回 FORWARD,接著又跳到 DOCKER-ISOLATION-STAGE-1 做容器網路隔離檢查。
  1. -A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
  • 作用:對 已有連線 (ESTABLISHED) 或相關連線 (RELATED) 且「流出(-o)docker0」的封包,直接放行。
  • 確保容器已建立連線的回應流量能正常到達容器。
  1. -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 規則等)。
  1. -A FORWARD -i docker0 ! -o docker0 -j ACCEPT
  • 作用:當封包「從 docker0 進來」,但目的網卡不是 docker0(! -o docker0),就直接允許。
  • 典型情境:容器想要連到宿主機外部網路、或其他網卡,且不是在同一 docker0 bridge 上。
  1. -A FORWARD -i docker0 -o docker0 -j ACCEPT
  • 作用:當封包「從 docker0 進來」「出去也還是 docker0」,就允許。
  • 若有多個容器掛在同一個 docker0 bridge 上,彼此連線就會走這條規則

簡單來說

  • 第 1, 2 條對應的 DOCKER-USERDOCKER-ISOLATION-STAGE-* chain 是 Docker 特別設計來讓使用者自訂規則、並且在容器與外部之間做網路隔離與控制。
  • 第 3 條是為了實現狀態防火牆(Stateful firewall),不再次檢查回應流量。
  • 第 4 條對應的 DOCKER chain 是設計對特定容器 Port / Protocol 做控制轉發。
  • 第 5 條允許 Docker 容器對外連線。
  • 第 6 條允許 Docker 容器間的連線。

為 docker1 新增 iptables 規則

現在我們需要為自定義的 docker1 橋接網路新增相應的 iptables 規則,才能讓 ns1 正常連線到 ns0 和外網。

允許容器間的連線

我們新增一條規則,類似於原來的第 6 條規則,但將網路介面替換為 docker1

Terminal window
1
sudo iptables -t filter -A FORWARD -i docker1 -o docker1 -j ACCEPT

測試連線:

Terminal window
1
sudo ip netns exec ns1 ping -c 1 172.18.0.2
2
# output
3
PING 172.18.0.2 (172.18.0.2) 56(84) bytes of data.
4
64 bytes from 172.18.0.2: icmp_seq=1 ttl=127 time=0.060 ms
5
6
--- 172.18.0.2 ping statistics ---
7
1 packets transmitted, 1 received, 0% packet loss, time 0ms
8
rtt min/avg/max/mdev = 0.060/0.060/0.060/0.000 ms

提醒 若新增的規則錯誤,可以使用以下指令刪除: sudo iptables -t filter -D FORWARD 77 為該條目在 FORWARD 表中的編號,使用 iptables 命令的 --line-numbers flag 取得)

允許容器到外網的連線

同樣地,我們需要新增規則允許 docker1 網路上的容器訪問外部網路:

Terminal window
1
sudo iptables -t filter -A FORWARD -i docker1 ! -o docker1 -j ACCEPT
2
sudo iptables -t filter -A FORWARD -o docker1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

測試連線:

Terminal window
1
sudo ip netns exec ns1 ping -c 1 8.8.8.8
2
# output
3
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
4
64 bytes from 8.8.8.8: icmp_seq=1 ttl=116 time=6.83 ms
5
6
--- 8.8.8.8 ping statistics ---
7
1 packets transmitted, 1 received, 0% packet loss, time 0ms
8
rtt 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 位址。

至於怎麼解決,我們下一個章節再繼續。

參考

本文標題:從 Linux 基礎實現 Docker Bridge 網路:一步步理解容器通訊 (5)
文章作者:Vincent Lin
發布時間:2025-01-11