前言
上篇文章 Docker Network - 網路模型 中跟大家分享了 Docker 內幾種基本網路模型,從 None, Host, Bridge 到 Contianer 共享
而今天這篇文章會繼續往下看,我們的最終目的是想要理解預設情況下,我們的 Container 是如何擁有對外網路存取能力的。
介紹
事實上,上篇文章介紹的那些模型,彼此之間是可以互相轉換的,只要你夠熟悉 Linux 上相關指令的操作與用法,你就有辦法自己打造這些模型,甚至轉換。
今天這篇文章探討如何打造一個 Bridge 的網路模型,最後我們會基於基於該網路模型上使用多個容器,並且確保這些容器彼此可以互相存取
本篇文章中還不會探討到容器是如何對外上網的,譬如存取 Google DNS server,而是先著重於底層的架構,以及容器間同網段的存取。
環境
為了更好展示整個過程的變化,我們會先透過 None 的網路模型創造一個完全乾淨的容器,接下來則會透過 Linux 指令的方式將其改造為 Bridge 網路模型。
最後,我們會根據上述步驟,打造兩個容器並且都採用 Bridge 網路模型,讓這兩個容器可以互通,但是還沒有辦法擁有對外上網的能力。
相關程式碼都可以於這邊找到 technologynoteniu/bloglab-source code
步驟
接下來的步驟會伴隨著跟上次一樣的圖例,從不同角度來慢慢觀察每個步驟對系統產生的變化,同時每個步驟也會附上相關的程式碼,有興趣的人也可以於自己環境中測試。
下述環境基於
1 | $ lsb_release -a |
創建兩個 None 容器
首先,我們要先創造兩個乾淨的容器,最後目標是打造成 Bridge 的網路模型並確保這兩個容器能夠互相存取。
這邊會先使用 –network=none 的形式要求 Docker 不要對該網路模型動手腳,讓我們自己處理即可。
這邊我們創造容器的時候,我們會特別給予一個參數 –privileged 來要求特別權限,原因後面會解釋,這邊先直接使用
示範程式碼
透過下列的程式碼,我們會於系統中創造兩個容器,分別名為 c1 及 c2.
之後透過 docker 指令進去看,確認兩個容器內除了 lo 之外沒有其他任何網卡。
1 | $ docker run --privileged -d --network=none --name c1 hwchiu/netutils |
模型分析
這邊用兩張不同視角來看待這個行為,由於本篇文章都會採用相同的概念去解讀不同網路模型,因此這邊詳細介紹兩種視角的概念。
圖左:這邊想要介紹的是以系統底層的角度去觀察網路,中間的灰色線將其分為上半部分的 UserSpace, 以及下半部分的 Kernel Space。
本範例中下圖會有不同顏色變化,代表不同的網路空間,每個網路空間彼此網路隔離。
圖右:這邊提供一個比較簡略的介紹,主要會從使用者的角度去觀察,由圖例來說明網路元件的關係上有什麼變化。
- 當我們透過 None 創建兩個容器時,同時也會於系統內創建兩個全新的 netns(Network Namespace),如下圖黃色及淺綠色所述,而淺藍色則是原生宿主機所使用的。
- 預設情況下,這些 netns 裡面都只會有 lo 這張預設網卡
- 假設 eth0 是宿主機本身擁有的網卡
創建 Linux Bridge
接下來我們先於系統中創建一個 Linux Bridge,這也是 Docker 預設網路模型的作法。
這部份會需要使用 brctl 這個工具, Ubuntu 系統可以透過安裝 brctl-utils 來取得。
範例程式碼
這邊我們會使用兩個 brctl 的指令來處理,分別是
- brctl add-br $name
- brctl show
第一個指令會於系統中創建一個名為 $name 的 Linux Bridge
第二個指令則是會顯示目前系統中有多少 Linux Bridge,以及其相關資訊
範例中我們會創立一個名為 hwchiu0
的 Linux Bridge,最後透過 ifconfig 這個指令讓 Bridge 給叫起來,讓他處於一個可運行的狀態。
1 | $ sudo brctl addbr hwchiu0 |
網路模型
執行完這個階段後,我們系統會於宿主機的 netns (Network Namespace) 中創造一個全新的 Linux Bridge (hwchiu0)。
這時候的系統架構圖如下,基本上跟上述沒有太多變化,單純就是多了一個元件。
創建 Veth Pair
目前我們擁有
- 兩個空蕩蕩的容器,包含其本身的 netns
- 一個 Linux Bridge
因此接下來我們要做的就是想辦法將其串連起來,這邊我們會使用一個名為 veth 的特殊網路設備,透過該設備我們可以於系統中創造一條特殊的連結。
該連結會有兩個端口,分別都會有對應的網卡名稱,從一端進去的封包都會馬上從另外一端出來,可以想像成一個雙向水管的概念。
因此這個步驟我們要先於宿主機上面創造兩條雙向水管,也就是兩條 veth pair.
程式碼示範
這個範例中,我們需要透過 ip 這個指令來創建該 veth 水管,指令變化很多,這邊示範一種用法
ip link add dev ${name} type veth
上述指令會要求 kernel 幫忙創造一條基於 veth 型態的連結,並且其中一個端頭命名為 ${name},至於另一端則讓 kernel 幫忙處理
當該指令完畢後,系統中就會多出兩張虛擬網卡,分別是 ${name} 以及 kernel 幫忙創造的,通常是 veth….
1 | $ sudo ip link add dev c1-eth0 type veth |
透過上述的指令,我們創建了兩條基於 veth 的網卡,因此系統中產生了四個虛擬網卡,其配對分別為
- veth1 –> c2-eth0
- veth0 –> c1-eth0
網路模型
網卡建立完畢後,這時候的網路模型如下
左圖: 當前都是於宿主機內操作,所以四張虛擬網卡都坐落於宿主機的 netns 裡面,圖中用兩種不同顏色的連線來代表這些網卡的關係
右圖: 系統創建完畢後,宿主機多出四張虛擬網卡,這四張虛擬網卡跟容器無關,跟 Linux 無關,可以想成四個孤兒
移動 Veth 到容器
當我們創建好相關的 veth 水管後,下一個步驟就是要將 veth 的一個端頭給放到容器之中,這樣我們就可以利用 veth 的特性於不同 netns 之中傳遞封包。
上述創造的配對如下
- veth1 –> c2-eth0
- veth0 –> c1-eth0
因此我們的目標就是
- 將 c1-eth0 這個虛擬網卡放到 c1 容器
- 將 c2-eth0 這個虛擬網卡放到 c2 容器
然而,準確的說,我們其實不是要放到容器內,而是要放到容器所屬的 netns (network namespace) 內,所以我們要先有辦法接觸到這些容器所使用的 netns.
這邊我們會使用 ip netns 指令來進行操作,這個指令預設會去讀取 /var/run/netns 底下的資料來顯示相關的 netns。
然而 docker 其本身設計預設則是會避開 /var/run/netns 可能是怕有人直接用系統指令 ip netns 來操作導致容器崩壞。
因此透過 docker 所創造的容器其所屬的 netns 都會放到 /var/run/docker/netns。
這邊我們只需要透過一個 soft link 的方式將這兩個位置串接起來,我們就可以使用 ip netns 來觀察 c1,c2 兩個容器的 netns 了
程式碼範例
根據上述概念,我們先執行 ln 指令來創造 soft link,然後透過 ip netns show 來展示出系統當前看到的 netns 名稱
1 | $ sudo ln -s /var/run/docker/netns /var/run/netns |
這邊的兩個名稱其實是 docker 容器裡面的 NetworkSettings.SnadboxID
我們可以透過下列指令來觀察
1 | $ docker inspect c1 | jq '.[0].NetworkSettings.SandboxID' |
因此當前範例中, 792fedcf97d8 代表是的 c2 容器的 netns,而 1bb2e0141544 則是 c1 容器的 netns。
接下來終於要進入正題,把我們事先創建好的虛擬網卡(veth的一端)給放到對應的 netns 中,這邊要借助 ip link set 這指令,該指令可以把虛擬網卡給放到不同的 netns 內,同時也可以重新命名
1 | $ sudo ip link set c1-eth0 netns 1bb2e0141544 name eth0 |
執行完畢上述指令後,我們接下來可以透過 docker 指令再次觀察是否有任何變化
1 | $ sudo docker exec c2 ifconfig -a |
最後我們可以再次透過 ip link 指令觀察,可以發現宿主機上面的四張虛擬網卡只剩下兩張了,因為有兩張都被搬移到容器內。
網路模型
當相關的虛擬網卡都被移動到對應的容器後,這時候的模型有一點點小小變化
左圖: veth 的一端都被移動到所屬容器的 netns 內,並且重新命名為 eth0。
右圖: 這邊的改變就是 veth 的端口被搬移到容器中,並且重新命名為 eth0。
veth 綁定 Bridge
接下來,我們要將 veth 跟 Linux Bridge 給整合一起,希望藉由 Linux Bridge 的功能來幫我們轉發封包
所以這邊的概念很簡單,就是把宿主機上面關於 veth 的網卡通通都綁到我們事先創造好的 Linux Bridge hwchiu0 身上
程式碼範例
這邊我們會使用 brctl addif 這個指令來達成目標,該指令用法如下
brctl addif ${bridge_name} ${nic_name}
該指令會將名為 ${nic_name} 的網卡給加入到 ${bridge_name} 的 Linux Bridge 上。
當加入完畢後,我們也順便透過 ifconfig (其實 ip link 也可以) 將 veth 虛擬網卡給叫起來讓其運作
1 | $ sudo brctl addif hwchiu0 veth0 |
最後我們可以透過 brctl show 來顯示當前系統上面關於 Linux Bridge 的資訊,可以觀察到 veth0 以及 veth1 都已經綁定上去。
網路模型
這個步驟主要針對的是 veth 於宿主機內的搬移,將其綁到對應的 Linux Bridge 上。
左圖/右圖: 差異就是 veth 的兩個端頭現在都不流浪,而是歸屬於 Linux Bridge 底下
設定 Container IP
現在我們幾乎已經將整個路線給串通了!剩下最後一個簡單步驟就是設定 IP
我們兩個容器內的 eth0 網卡都還沒有 IP,因此這個步驟我們就來幫忙設定 IP,
IP 本身議題也很多,這邊我們為了避免更多問題,因此這兩個容器的 IP 我們設定同網段,就如同我們使用 Docker 容器般,大家都是同網段
目標:
- c1 容器使用 10.55.66.2 的IP, 網段為 10.55.66.0/24
- c2 容器使用 10.55.66.3 的IP, 網段為 10.55.66.0/24
程式碼範例
還記得一開始創造容器時有特別要求 --privileged
這個參數嗎?
這是因為我們接下來要於容器內透過一些網路工具去修改網卡資訊,這部份我們會需要相關的權限,否則執行的時候會得到下列錯誤1
2
3
4SIOCSIFADDR: Operation not permitted
SIOCSIFFLAGS: Operation not permitted
SIOCSIFNETMASK: Operation not permitted
SIOCSIFFLAGS: Operation not permitted
1 | $ sudo docker exec c1 ifconfig eth0 10.55.66.2 netmask 255.255.255.0 up |
最後透過 ifconfig 等指令再次觀察,可以看到相關的網卡都起來了,同時 IP 等資訊也都有了,可以準備邁入最後階段
Ping 測試
這個階段,我們要來透過 PING 的指令來打測試我們的連線,整個封包流程會是
c1 容器內的 ping -> c1 容器內的網卡 eth0 -> 宿主機內的 veth0
-> 宿主機內的 Linux Bridge -> 宿主機內 Bridge 上的 veth1
-> c2 容器內的網卡 eth0
這部份我們直接進入到容器內執行 ping 10.55.66.3 指令即可
程式碼模擬
1 | $ docker exec -it c1 ping 10.55.66.3 -c5 |
這時候你會發現網路不通, ping 都沒有反應,這部份我們執行下面的神祕指令
1 | $ sudo iptables --policy FORWARD ACCEPT |
你就會發現網路通了,一切都正常了,至於到底上面那個神祕指令做了什麼手腳,為什麼會影響封包,這部份等我們下一章節再來慢慢細談 iptables 的概念
網路模型
最後這邊就用來顯示一下封包的流程,基本上就是概括了本章節提到的所有元件,包含了 veth, Linux Bridge, 容器 IP 等資訊。
Summary
到這邊為止,我們已經瞭解如何將一個什麼都沒有的容器給打造為使用 Bridge 模型的容器,然而現在這種情況下,我們的兩個容器只能彼此互相存取,還沒有辦法對外上網,譬如 ping 8.8.8.8 。
所以下篇文章我們會來探討這後半部分,來把整個 Docker Bridge 網路模型給摸熟,透過這些步驟來瞭解到底每個容器起來時實際上系統做了什麼手腳
題外話:
上述的流程中有一個部分其實不好處理,稱為 IPAM,也就是 IP 分配管理的相關議題,我們要如何對每個容器去分配一個不重複的 IP 地址,同時當容器掛掉時,也要能夠回收這些 IP。
另外本篇文章的詳細內容其實跟 Bridge CNI 的步驟大概有 80% 一樣, 差異只剩下 IP 分配問題,以及後續會探討到的 IP 路由表管理。
個人資訊
我目前於 Hiskio 平台上面有開設 Kubernetes 相關課程,歡迎有興趣的人參考並分享,裡面有我從底層到實戰中對於 Kubernetes 的各種想法
組合包
https://hiskio.com/packages/7ey2vdnyN
疑難雜症除錯篇
https://hiskio.com/courses/440/about?promo_code=LG28Q5G
單堂(CI/CD)
https://hiskio.com/courses/385?promo_code=13K49YE&p=blog1
基礎概念
https://hiskio.com/courses/349?promo_code=13LY5RE
另外,歡迎按讚加入我個人的粉絲專頁,裡面會定期分享各式各樣的文章,有的是翻譯文章,也有部分是原創文章,主要會聚焦於 CNCF 領域
https://www.facebook.com/technologynoteniu
如果有使用 Telegram 的也可以訂閱下列頻道來,裡面我會定期推播通知各類文章
https://t.me/technologynote
你的捐款將給予我文章成長的動力