iT邦幫忙

2

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

  • 分享至 

  • xImage
  •  

部落格好讀版


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

測試網路連線:ns1 到 ns0

sudo ip netns exec ns1 ping -c 1 172.18.0.2
# output
PING 172.18.0.2 (172.18.0.2) 56(84) bytes of data.

--- 172.18.0.2 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms

測試網路連線:ns1 到外網

sudo ip netns exec ns1 ping -c 1 8.8.8.8
# output
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.

--- 8.8.8.8 ping statistics ---
1 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 安裝前:

sudo iptables -t filter -nvL --line-numbers
# output
Chain INPUT (policy ACCEPT)
num  target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
num  target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
num  target     prot opt source               destination

Docker 安裝後:

sudo iptables -t filter -nvL --line-numbers
# output
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination

Chain FORWARD (policy DROP 1 packets, 84 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1      664 2569K DOCKER-USER  all  --  *      *       0.0.0.0/0            0.0.0.0/0
2      664 2569K DOCKER-ISOLATION-STAGE-1  all  --  *      *       0.0.0.0/0            0.0.0.0/0
3      340 2547K ACCEPT     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
4        0     0 DOCKER     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0
5      171 11618 ACCEPT     all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0
6        0     0 ACCEPT     all  --  docker0 docker0  0.0.0.0/0            0.0.0.0/0

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination

Chain DOCKER (1 references)
num   pkts bytes target     prot opt in     out     source               destination

Chain DOCKER-ISOLATION-STAGE-1 (1 references)
num   pkts bytes target     prot opt in     out     source               destination
1      171 11618 DOCKER-ISOLATION-STAGE-2  all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0
2      664 2569K RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0

Chain DOCKER-ISOLATION-STAGE-2 (1 references)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 DROP       all  --  *      docker0  0.0.0.0/0            0.0.0.0/0
2      171 11618 RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0

Chain DOCKER-USER (1 references)
num   pkts bytes target     prot opt in     out     source               destination
1      664 2569K RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0
sudo iptables-save -t filter
# output
*filter
:INPUT ACCEPT [0:0]
:FORWARD DROP [1:84]
:OUTPUT ACCEPT [0:0]
:DOCKER - [0:0]
:DOCKER-ISOLATION-STAGE-1 - [0:0]
:DOCKER-ISOLATION-STAGE-2 - [0:0]
:DOCKER-USER - [0:0]
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
-A DOCKER-USER -j RETURN
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 中新增的規則:

# iptables
num   pkts   bytes  target                       prot opt in     out       source         destination
1      664   2569K  DOCKER-USER                 all  --  *      *         0.0.0.0/0      0.0.0.0/0
2      664   2569K  DOCKER-ISOLATION-STAGE-1    all  --  *      *         0.0.0.0/0      0.0.0.0/0
3      340   2547K  ACCEPT                      all  --  *      docker0   0.0.0.0/0      0.0.0.0/0    ctstate RELATED,ESTABLISHED
4        0       0  DOCKER                      all  --  *      docker0   0.0.0.0/0      0.0.0.0/0
5      171   11618  ACCEPT                      all  --  docker0 !docker0 0.0.0.0/0      0.0.0.0/0
6        0       0  ACCEPT                      all  --  docker0 docker0  0.0.0.0/0      0.0.0.0/0
# iptables-save
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-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

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

測試連線:

sudo ip netns exec ns1 ping -c 1 172.18.0.2
# output
PING 172.18.0.2 (172.18.0.2) 56(84) bytes of data.
64 bytes from 172.18.0.2: icmp_seq=1 ttl=127 time=0.060 ms

--- 172.18.0.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
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 網路上的容器訪問外部網路:

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

測試連線:

sudo ip netns exec ns1 ping -c 1 8.8.8.8
# output
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=116 time=6.83 ms

--- 8.8.8.8 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
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 位址。

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

參考


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言