在現(xiàn)實世界中,容器間通信的可靠性和安全性相當重要,即使容器分屬于不同網(wǎng)絡中的不同主機。這也是覆蓋網(wǎng)絡大展拳腳的地方,它允許創(chuàng)建扁平的、安全的二層網(wǎng)絡來連接多個主機,容器可以連接到覆蓋網(wǎng)絡并直接互相通信。
Docker 提供了原生覆蓋網(wǎng)絡的支持,易于配置且非常安全。其背后是基于 Libnetwork 以及相應的驅動來構建的。
Libnetwork 是 CNM 的典型實現(xiàn),從而可以通過插拔驅動的方式來實現(xiàn)不同的網(wǎng)絡技術和拓撲結構。
Docker 提供了一些諸如 Overlay 的原生驅動,同時第三方也可以提供驅動。
在 2015 年 3 月,Docker 公司收購了一個叫作 Socket Plane 的網(wǎng)絡初創(chuàng)企業(yè)。收購的原因有二,首先是因為這會給 Docker 帶來真正意義的網(wǎng)絡架構,其次是讓容器間聯(lián)網(wǎng)變得非常簡單,以至于開發(fā)人員都可以配置它。
Docker 公司在這兩點上都取得了巨大的成功。但是,簡潔的網(wǎng)絡命令實際由大量的組件構成。這部分內容是在進行生產(chǎn)環(huán)境部署和問題定位前必須要了解的。
要完成下面的示例,需要兩臺 Docker 主機,并通過一個路由器上兩個獨立的二層網(wǎng)絡連接在一起。如下圖所示,注意節(jié)點位于不同網(wǎng)絡之上。
可以選擇 Linux 容器主機或者 Windows 容器主機。Linux 內核版本不能低于 4.4(高版本更好),Windows 需要 Windows Server 2016 版本,并且應安裝最新的補丁。
首先需要將兩臺主機配置為包含兩個節(jié)點的 Swarm 集群。接下來會在 node1 節(jié)點上運行 docker swarm init 命令使其成為管理節(jié)點,然后在 node2 節(jié)點上運行 docker swarm join 命令來使其成為工作節(jié)點。
在 node1 節(jié)點上運行下面的命令。
$ docker swarm init \
--advertise-addr=172.31.1.5 \
--listen-addr=172.31.1.5:2377
Swarm initialized: current node (1ex3...o3px) is now a manager.
在 node2 上運行下面的命令。如果需要在 Windows 環(huán)境下生效,則需要修改 Windows 防火墻規(guī)則,打開 2377/tcp、7946/tcp 以及 7946/udp 等幾個端口。
$ docker swarm join \
--token SWMTKN-1-0hz2ec...2vye \
172.31.1.5:2377
This node joined a swarm as a worker.
現(xiàn)在就已經(jīng)創(chuàng)建好了包含管理節(jié)點 node1 和工作節(jié)點 node2 兩個節(jié)點的 Swarm 集群了。
現(xiàn)在創(chuàng)建一個名為 uber-net 的覆蓋網(wǎng)絡。
在 node1(管理節(jié)點)節(jié)點上運行下面的命令。若要這些命令在 Windows 上也能運行,需要在 Windows Docker 節(jié)點上添加 4789/udp 規(guī)則。
$ docker network create -d overlay uber-net
c740ydi1lm89khn5kd52skrd9
剛剛創(chuàng)建了一個嶄新的覆蓋網(wǎng)絡,能連接 Swarm 集群內的所有主機,并且該網(wǎng)絡還包括一個 TLS 加密的控制層!如果還想對數(shù)據(jù)層加密的話,只需在命令中增加 -o encrypted 參數(shù)。
可以通過 docker network ls 命令列出每個節(jié)點上的全部網(wǎng)絡。
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
ddac4ff813b7 bridge bridge local
389a7e7e8607 docker_gwbridge bridge local
a09f7e6b2ac6 host host local
ehw16ycy980s ingress overlay swarm
2b26c11d3469 none null local
c740ydi1lm89 uber-net overlay swarm
在 Windows Docker 主機上輸出內容如下。
NETWORK ID NAME DRIVER SCOPE
8iltzv6sbtgc ingress overlay swarm
6545b2a61b6f nat nat local
96d0d737c2ee none null local
nil5ouh44qco uber-net overlay swarm
列表的最下方就是剛剛創(chuàng)建的網(wǎng)絡 uber-net。其他的網(wǎng)絡是在安裝 Docker 以及初始化 Swarm 集群的時候創(chuàng)建的。
如果在 node2 節(jié)點上運行 docker network ls 命令,就會發(fā)現(xiàn)無法看到 uber-net 網(wǎng)絡。這是因為只有當運行中的容器連接到覆蓋網(wǎng)絡的時候,該網(wǎng)絡才變?yōu)榭捎脿顟B(tài)。這種延遲生效策略通過減少網(wǎng)絡梳理,提升了網(wǎng)絡的擴展性。
現(xiàn)在覆蓋網(wǎng)絡已經(jīng)就緒,接下來新建一個 Docker 服務并連接到該網(wǎng)絡。Docker 服務會包含兩個副本(容器),一個運行 node1 節(jié)點上,一個運行在 node2 節(jié)點上。這樣會自動將 node2 節(jié)點接入 uber-net 網(wǎng)絡。
在 node1 節(jié)點上運行下面的命令。
Linux 示例如下。
$ docker service create --name test \
--network uber-net \
--replicas 2 \
ubuntu sleep infinity
Windows 示例如下。
> docker service create --name test `
--network uber-net `
--replicas 2 `
microsoft\powershell:nanoserver Start-Sleep 3600
Windows 示例使用反引號的方式將單條命令分為多行,以提高命令的可讀性。PowerShell 中使用反引號來轉義換行字符。
該命令創(chuàng)建了名為 test 的新服務,連接到了 uber-net 這個覆蓋網(wǎng)絡,并且還基于指定的鏡像創(chuàng)建了兩個副本(容器)。在兩個示例中,均在容器中采用 sleep 命令來保持容器運行,并在休眠結束后退出該容器。
由于運行了兩個副本(容器),而 Swarm 包含兩個節(jié)點,因此每個節(jié)點上都會運行一個副本。
可以通過 docker service ps 命令來確認上面的操作。
$ docker service ps test
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE
77q...rkx test.1 ubuntu node1 Running Running
97v...pa5 test.2 ubuntu node2 Running Running
當 Swarm 在覆蓋網(wǎng)絡之上啟動容器時,會自動將容器運行所在節(jié)點加入到網(wǎng)絡當中。這意味著此時在 node2 節(jié)點上就可以看到 uber-net 網(wǎng)絡了。
目前已經(jīng)成功在兩個由物理網(wǎng)絡連接的節(jié)點上創(chuàng)建了新的覆蓋網(wǎng)絡。同時,還將兩個容器連接到了該網(wǎng)絡當中。
現(xiàn)在使用 ping 命令來測試覆蓋網(wǎng)絡。
如下圖所示,在兩個獨立的網(wǎng)絡中分別有一臺 Docker 主機,并且兩者都接入了同一個覆蓋網(wǎng)絡。目前在每個節(jié)點上都有一個容器接入了覆蓋網(wǎng)絡。測試一下兩個容器之間是否可以 ping 通。
為了執(zhí)行該測試,需要知道每個容器的 IP 地址(為了測試,暫時忽略相同覆蓋網(wǎng)絡上的容器可以通過名稱來互相 ping 通的事實)。
運行 docker network inspect 查看被分配給覆蓋網(wǎng)絡的 Subnet。
$ docker network inspect uber-net
[
{
"Name": "uber-net",
"Id": "c740ydi1lm89khn5kd52skrd9",
"Scope": "swarm",
"Driver": "overlay",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "10.0.0.0/24",
"Gateway": "10.0.0.1"
}
<Snip>
由以上輸出可見,uber-net 的子網(wǎng)是 10.0.0.0/24。注意,這與兩個節(jié)點的任意底層物理網(wǎng)絡 IP 均不相符(172.31.1.0/24 和 192.168.1.0/24)。
在 node1 和 node2 節(jié)點上運行下面兩條命令。這兩條命令可以獲取到容器 ID 和 IP 地址。在第二條命令中一定要使用讀者自己的環(huán)境中的容器 ID。
需要在兩臺節(jié)點上分別運行上述命令,獲取兩個容器的 ID 和 IP 地址。
下圖展示了配置現(xiàn)狀。在運行環(huán)境中,子網(wǎng)和 IP 地址信息可能不同。
由圖可知,一個二層覆蓋網(wǎng)絡橫跨兩臺主機,并且每個容器在覆蓋網(wǎng)絡中都有自己的 IP 地址。這意味著 node1 節(jié)點上的容器可以通過 node2 節(jié)點上容器的 IP 地址 10.0.0.4 來 ping 通,該 IP 地址屬于覆蓋網(wǎng)絡。盡管兩個節(jié)點分屬于不同的二層網(wǎng)絡,還是可以直接 ping 通。接下來驗證這一點。
登錄到 node1 的容器,并 ping 另一個的容器。
在 Linux Ubuntu 容器中執(zhí)行該操作的話,需要安裝 ping 工具包。如果讀者使用 Windows PowerShell 示例,ping 工具已默認安裝。
Linux 示例如下。
Windows 示例如下。
> docker container exec -it 1a4f29e5a4b6 pwsh.exe
Windows PowerShell
Copyright (C) 2016 Microsoft Corporation. All rights reserved.
PS C:\> ping 10.0.0.4
Pinging 10.0.0.4 with 32 bytes of data:
Reply from 10.0.0.4: bytes=32 time=1ms TTL=128
Reply from 10.0.0.4: bytes=32 time<1ms TTL=128
Reply from 10.0.0.4: bytes=32 time=2ms TTL=128
Reply from 10.0.0.4: bytes=32 time=2ms TTL=12
PS C:\>
由運行結果可知 node1 上的容器可以通過覆蓋網(wǎng)絡 ping 通 node2 之上的容器了。
還可以在容器內部跟蹤 ping 命令的路由信息。路由信息只有一條,證明容器間通信確實通過覆蓋網(wǎng)絡直連。
如果希望 Linux 示例中的 traceroute 可執(zhí)行,需要安裝 traceroute 包。
Linux 示例如下。
$ root@396c8b142a85:/# traceroute 10.0.0.4
traceroute to 10.0.0.4 (10.0.0.4), 30 hops max, 60 byte packets
1 test-svc.2.97v...a5.uber-net (10.0.0.4) 1.110ms 1.034ms 1.073ms
Windows 示例如下。
PS C:\> tracert 10.0.0.3
Tracing route to test.2.ttcpiv3p...7o4.uber-net [10.0.0.4]
over a maximum of 30 hops:
1 <1 ms <1 ms <1 ms test.2.ttcpiv3p...7o4.uber-net [10.0.0.4]
Trace complete.
到目前為止,已經(jīng)通過單條命令創(chuàng)建了覆蓋網(wǎng)絡,并向該網(wǎng)絡中接入了容器。這些容器分布在兩個不同的主機上,兩臺主機分屬于不同的二層網(wǎng)絡。在找出兩臺容器的 IP 之后,驗證了容器可以通過覆蓋網(wǎng)絡完成直連。
現(xiàn)在已經(jīng)知道如何創(chuàng)建并使用容器覆蓋網(wǎng)絡,接下來介紹一下這一切背后的技術原理。
首先必須知道,Docker 使用 VXLAN 隧道技術創(chuàng)建了虛擬二層覆蓋網(wǎng)絡。所以,在詳解之前,先快速了解一下 VXLAN。
在 VXLAN 的設計中,允許用戶基于已經(jīng)存在的三層網(wǎng)絡結構創(chuàng)建虛擬的二層網(wǎng)絡。在前面的示例中創(chuàng)建了一個子網(wǎng)掩碼為 10.0.0.0/24 的二層網(wǎng)絡,該網(wǎng)絡是基于一個三層 IP 網(wǎng)絡實現(xiàn)的,三層 IP 網(wǎng)絡由 172.31.1.0/24 和 192.168.1.0/24 這兩個二層網(wǎng)絡構成。具體如下圖所示。
VXLAN 的美妙之處在于它是一種封裝技術,能使現(xiàn)存的路由器和網(wǎng)絡架構看起來就像普通的 IP/UDP 包一樣,并且處理起來毫無問題。
為了創(chuàng)建二層覆蓋網(wǎng)絡,VXLAN 基于現(xiàn)有的三層 IP 網(wǎng)絡創(chuàng)建了隧道。小伙伴可能聽過基礎網(wǎng)絡(Underlay Network)這個術語,它用于指代三層之下的基礎部分。
VXLAN 隧道兩端都是 VXLAN 隧道終端(VXLAN Tunnel Endpoint, VTEP)。VTEP 完成了封裝和解壓的步驟,以及一些功能實現(xiàn)所必需的操作,如下圖所示。
在前面的示例中,讀者通過 IP 網(wǎng)絡將兩臺主機連接起來。每個主機運行了一個容器,之后又為容器連接創(chuàng)建了一個 VXLAN 覆蓋網(wǎng)絡。
為了實現(xiàn)上述場景,在每臺主機上都新建了一個 Sandbox(網(wǎng)絡命名空間)。正如前文所講,Sandbox 就像一個容器,但其中運行的不是應用,而是當前主機上獨立的網(wǎng)絡棧。
在 Sandbox 內部創(chuàng)建了一個名為 Br0 的虛擬交換機(又稱做虛擬網(wǎng)橋)。同時 Sandbox 內部還創(chuàng)建了一個 VTEP,其中一端接入到名為 Br0 的虛擬交換機當中,另一端接入主機網(wǎng)絡棧(VTEP)。
在主機網(wǎng)絡棧中的終端從主機所連接的基礎網(wǎng)絡中獲取到 IP 地址,并以 UDP Socket 的方式綁定到 4789 端口。不同主機上的兩個 VTEP 通過 VXLAN 隧道創(chuàng)建了一個覆蓋網(wǎng)絡,如下圖所示。
這是 VXLAN 上層網(wǎng)絡創(chuàng)建和使用所必需的。
接下來每個容器都會有自己的虛擬以太網(wǎng)(veth)適配器,并接入本地 Br0 虛擬交換機。目前拓撲結構如下圖所示,雖然是在主機所屬網(wǎng)絡互相獨立的情況下,但這樣能更容易看出兩個分別位于不同主機上的容器之間是如何通過 VXLAN 上層網(wǎng)絡進行通信的。
在本例中,將 node1 上的容器稱為 C1,node2 上的容器稱為 C2,如下圖所示。假設 C1 希望 ping 通 C2,類似前面章節(jié)中的示例。
C1 發(fā)起 ping 請求,目標 IP 為 C2 的地址 10.0.0.4。該請求的流量通過連接到 Br0 虛擬交換機 veth 接口發(fā)出。虛擬交換機并不知道將包發(fā)送到哪里,因為在虛擬交換機的 MAC 地址映射表(ARP 映射表)中并沒有與當前目的 IP 對應的 MAC 地址。
所以虛擬交換機會將該包發(fā)送到其上的全部端口。連接到 Br0 的 VTEP 接口知道如何轉發(fā)這個數(shù)據(jù)幀,所以會將自己的 MAC 地址返回。這就是一個代理 ARP 響應,并且虛擬交換機 Br0 根據(jù)返回結果學會了如何轉發(fā)該包。接下來虛擬交換機會更新自己的 ARP 映射表,將 10.0.0.4 映射到本地 VTEP 的 MAC 地址上。
現(xiàn)在 Br0 交換機已經(jīng)學會如何轉發(fā)目標為 C2 的流量,接下來所有發(fā)送到 C2 的包都會被直接轉發(fā)到 VTEP 接口。VTEP 接口知道 C2,是因為所有新啟動的容器都會將自己的網(wǎng)絡詳情采用網(wǎng)絡內置 Gossip 協(xié)議發(fā)送給相同 Swarm 集群內的其他節(jié)點。
交換機會將包轉發(fā)到 VTEP 接口,VTEP 完成數(shù)據(jù)幀的封裝,這樣就能在底層網(wǎng)絡傳輸。具體來說,封裝操作就是把 VXLAN Header 信息添加以太幀當中。
VXLAN Header 信息包含了 VXLAN 網(wǎng)絡 ID(VNID),其作用是記錄 VLAN 到 VXLAN 的映射關系。每個 VLAN 都對應一個 VNID,以便包可以在解析后被轉發(fā)到正確的 VLAN。
封裝的時候會將數(shù)據(jù)幀放到 UDP 包中,并設置 UDP 的目的 IP 字段為 node2 節(jié)點的 VTEP 的 IP 地址,同時設置 UDP Socket 端口為 4789。這種封裝方式保證了底層網(wǎng)絡即使不知道任何關于 VXLAN 的信息,也可以完成數(shù)據(jù)傳輸。
當包到達 node2 之后,內核發(fā)現(xiàn)目的端口為 UDP 端口 4789,同時還知道存在 VTEP 接口綁定到該 Socket。所以內核將包發(fā)給 VTEP,由 VTEP 讀取 VNID,解壓包信息,并根據(jù) VNID 發(fā)送到本地名為 Br0 的連接到 VLAN 的交換機。在該交換機上,包被發(fā)送給容器 C2。
以上大體介紹了 Docker 覆蓋網(wǎng)絡是如何利用 VXLAN 技術的。
最后一件需要注意的是,Docker 支持使用同樣的覆蓋網(wǎng)絡實現(xiàn)三層路由。例如,讀者可以創(chuàng)建包含兩個子網(wǎng)的覆蓋網(wǎng)絡,Docker 會負責子網(wǎng)間的路由。創(chuàng)建的命令如 docker network create --subnet=10.1.1.0/24 --subnet=11.1.1.0/24 -d overlay prod-net。該命令會在 Sandbox 中創(chuàng)建兩個虛擬交換機,默認支持路由。