veth pair 不夠用了#

veth pair 只能連通兩個 Network Namespace。想要三個、四個容器互相看得到,就需要一個「集線器」般的元件,把多條 veth 都接上去。這個角色由 Linux bridge 擔任。

bridge 的本質是核心裡的虛擬交換機(virtual switch):

  • 接上 bridge 的介面叫做這座 bridge 的 port
  • 任何從某個 port 進來的封包,bridge 會依 MAC 表決定要從哪個 port 送出
  • 行為很接近一台 L2 switch

Docker 預設的 bridge 模式,就會建立一座叫做 docker0 的 Linux bridge,每個容器透過 veth pair 把一端接到 docker0 上、另一端塞進容器自己的 namespace。

建立 bridge 的兩種方式#

老派工具是 brctl(bridge-utils):

sudo brctl addbr br0
sudo brctl show

新派寫法走 iproute2:

sudo ip link add name br0 type bridge
sudo ip link set br0 up
ip -d link show br0

兩種方式建出來的東西是同一樣東西,後續操作建議統一用 ip 命令。

用 bridge 連通兩個 namespace#

接下來重做一次上一章的實驗,但改用 bridge 當中介。

1. 建立 namespace 與 bridge#

sudo ip netns add ns1
sudo ip netns add ns2
sudo ip link add name br0 type bridge
sudo ip link set br0 up

2. 為每個 namespace 拉一對 veth#

ns1veth-h1(host 端)和 veth-c1(容器端);ns2veth-h2veth-c2

sudo ip link add veth-h1 type veth peer name veth-c1
sudo ip link add veth-h2 type veth peer name veth-c2

3. host 端接上 bridge#

sudo ip link set veth-h1 master br0
sudo ip link set veth-h2 master br0
sudo ip link set veth-h1 up
sudo ip link set veth-h2 up

master br0 表示這個介面成為 br0 的 port。

4. 容器端塞進各自 namespace#

sudo ip link set veth-c1 netns ns1
sudo ip link set veth-c2 netns ns2

5. 在 namespace 內設 IP 並 up#

sudo ip netns exec ns1 ip addr add 10.0.0.1/24 dev veth-c1
sudo ip netns exec ns1 ip link set veth-c1 up
sudo ip netns exec ns1 ip link set lo up

sudo ip netns exec ns2 ip addr add 10.0.0.2/24 dev veth-c2
sudo ip netns exec ns2 ip link set veth-c2 up
sudo ip netns exec ns2 ip link set lo up

6. 互 ping 驗證#

sudo ip netns exec ns1 ping -c 3 10.0.0.2

應該收得到回覆。封包路徑是:

  • ns1veth-c1 出 → host 的 veth-h1
  • br0 依 MAC 表把封包轉到 veth-h2
  • veth-h2 出 → ns2veth-c2

bridge 自己不需要 IP,只負責 L2 轉送。要不要給 bridge 一個 IP,取決於是否打算讓 bridge 同時當 default gateway,這留到下一章談。

看一下 bridge 的 port#

bridge link show
ip -d link show master br0

會看到 veth-h1veth-h2 都標示為 master br0

對應到 Docker#

啟動 Docker 之後,docker0 就是一座一模一樣的 Linux bridge:

ip link show docker0
bridge link show

每跑一個 bridge 模式的容器,Docker 就會:

  • 建一對 veth pair
  • 把 host 端接到 docker0
  • 把容器端塞進容器的 Network Namespace 並命名為 eth0
  • docker0 對應的子網(預設 172.17.0.0/16)配一個 IP 給容器

整個流程跟手動拼出來的版本一樣,只是被 Docker 自動化了。

還少什麼#

到這裡,namespace 之間互通沒問題了,但容器還沒辦法連到外面的網站。下一章要補上 routing table(路由表)、default gateway 與 ip route,讓封包知道「不認識的地址要往哪送」。

延伸閱讀#

  • man 8 bridge
  • man 8 brctl
  • iproute2 的 ip link ... type bridge