前言
我們都知道 Docker 有提供不同的網路模式供我們使用,從預設的 Bridge 到共用本機 Network Namespace 的 Host。我們甚至可以自訂自己的 Bridge 來切割不同網段,如此一來一些網路拓墣都架設都可以用 Docker 來完成。
區別不同網段我們通常會使用不同的 Bridge 來分隔,通常會先建立自訂的 Bridge
docker network create --driver bridge testing-bridge
之後建立 container 時可以指定該 bridge 來使用該網路空間
docker run -it --net=testing-bridge alpine
1 | / # ip a |
這時候可以發現 Docker 幫我建立好了 172.18.0.2/16
網段的介面可以使用,這時候你會發現該介面可以直接出得去外網,因為 Docker 建立好 Bridge,所以封包會從該 Bridge 到本機端出去。
那我們可不可以不要讓 Container 有機會從本機讓封包流出去呢?
(有時候要建立網路拓墣,不希望封包走本機的路由出去,希望封包從該網路路由到其他網路,也就是其他 Container)
解決(使用 veth peer)
要達成這件事,最快的做法就是自己建立 veth 並且將 peer 兩端直接綁在兩個 Container 上
我們打算用 iproute2 直接管理網路,所以建立 containers 時使用 --net=none
docker run -itd --name=left --net=none alpine
docker run -itd --name=right --net=none alpine
接著需要自行建立 Containers 們的 Network Namespace
1 | left_pid=$(docker inspect -f '{{.State.Pid}}' left)` |
接著建立 veth peer 並且命名為 A
以及 B
ip link add A type veth peer name B
1 | ip link set A netns $left_pid # 設定 peer 端 A 到 left container |
新增預設路由的部分註解掉是因為我們將 Containers 用 veth peer 接起來,本來就是通的,所以不需要有預設路由也可以到達另外一個 Container。可以依照情況去設定你的預設路由
接著設定 Container right
1 | ip link set B netns $right_pid |
設定完之後進去 Containers 你會發現他的網路介面為
1 | ubuntu@docker-env:~$ docker exec -it left sh |
並且可以直接 ping 到另外一個 Container
1 | / # ping 10.113.1.2 |
如此一來可以用此概念(搭配 Bridge)完成一些複雜的網路情境
使用 Bridge
實際的網路情況不太可能都用 peer 去接,就達成一個區網內有多台 Host 還是需要使用 Linux Bridge。就像 Docker 預設的 Bridge 模式般,那我們可不可以不要透過 Docker,自己建立 Bridge 來達成網路隔離,答案是可以。自己建立的話你會發現 Docker 除了把 Bridge 建立好以外,還會使用 iptables 把 Container 中的流量從 Host 往外送,我們如果不想要讓封包有機會從 Host 流出,自己建立 Bridge 把 veth peer 一端接上 Container 一端接上 Bridge 也是一個方法!
首先,一樣建立兩個 Containers
1 | docker run -itd --network=none --name left alpine |
接下來手動建立 Linux Bridge
1 | brctl addbr br0 |
建立 veth peer 並且命名為 left-eth0, left-veth0
left-eth0 這端要放入 Container 的 Network Namespace 中
1 | ip link add dev left-eth0 type veth peer name left-veth0 |
此時你的 Host 上會有四個新增的介面
取得 Containers 的 Pid 用來將 peer 加入該 Pid 的 Network Namespace
1 | left_pid=$(docker inspect left -f {{.State.Pid}}) |
加入成功後,在 Host 上可以看到網卡從剛才的四個變成兩個
在 Container 中也會看到裡面的介面多了 eth0
將 veth peer 另一端接上我們自行新增的 Linux Bridge,並且將網卡啟動
1 | brctl addif br0 left-veth0 |
接下來我們要從 Host 上設定 Container 中的介面,因為該已經在不同網路空間,所以我們需要自行 mapping,不然 iproute2 會找不到該網路介面
1 | ln -s /proc/$left_pid/ns/net /var/run/netns/$left_pid |
那為何不在 Container 中做設定?因為這樣子需要 Container 具備特定的 Linux Capability
1 | ip netns exec $left_pid ip addr add 10.113.1.1/24 dev eth0 |
接下來就可以使用 ping 來檢查是否 Containers 之間網路有通
docker exec left ping 10.113.1.2 -c3
docker exec right ping 10.113.1.1 -c3
如果沒通的話,可以看一下 iptables 的 FORWARD chain 是不是被 DROP 掉了,是的話先把 Policy 改成 ACCEPT 即可
通常我們使用 Docker 去建立 Bridge 的話,這些 iptables 規則都是 Docker 幫我們處理的,所以我們現在需要自行修改 iptables。
題外
Docker 提供的網路選項不多,畢竟他主要是用來做資源隔離。除了以上問題沒辦法用內建的網路選項外,一個 Container 假設有多個網路環境,也沒辦法在 Container 啟動時指定預設路由,只能用比較拐彎抹角的方式在 entrypoint 或是 commands 做。