1. 程式人生 > >Docker 1.9的新網路特性,以及Overlay詳解

Docker 1.9的新網路特性,以及Overlay詳解

作者簡介:林帆,ThoughtWorks公司軟體工程師及DevOps諮詢師,具有豐富的持續交付和伺服器運維自動化實踐經驗,專注於DevOps和容器技術領域。在InfoQ、CSDN網站和《程式設計師》雜誌上發表有多篇相關領域文章,著有《CoreOS實踐之路》一書。

Docker在1.9版本中引入了一整套的自定義網路命令和跨主機網路支援。這是libnetwork專案從Docker的主倉庫抽離之後的一次重大變化。不論你是否已經注意到了,Docker的網路新特性即將對使用者的習慣產生十分明顯的改變。
‌‌

libnetwork和Docker網路

libnetwork專案從lincontainer和Docker程式碼的分離早在Docker 1.7版本就已經完成了(從Docker 1.6版本的網路程式碼中抽離)。在此之後,容器的網路介面就成為了一個個可替換的外掛模組。由於這次變化進行的十分平順,作為Docker的使用者幾乎不會感覺到其中的差異,然而這個改變為接下來的一系列擴充套件埋下了很好的伏筆。

概括來說,libnetwork所做的最核心事情是定義了一組標準的容器網路模型(Container Network Model,簡稱CNM),只要符合這個模型的網路介面就能被用於容器之間通訊,而通訊的過程和細節可以完全由網路介面來實現。

Docker的容器網路模型最初是由思科公司員工Erik提出的設想,比較有趣的是Erik本人並不是Docker和libnetwork程式碼的直接貢獻者。最初Erik只是為了擴充套件Docker網路方面的能力,設計了一個Docker網橋的擴充套件原型,並將這個思路反饋給了Docker社群。然而他的大膽設想得到了Docker團隊的認同,並在與Docker的其他合作伙伴廣泛討論之後,逐漸形成了libnetwork的雛形。

在這個網路模型中定義了三個的術語:Sandbox、Endpoint和Network。

這裡寫圖片描述

如上圖所示,它們分別是容器通訊中『容器網路環境』、『容器虛擬網絡卡』和『主機虛擬網絡卡/網橋』的抽象。

Sandbox:對應一個容器中的網路環境,包括相應的網絡卡配置、路由表、DNS配置等。CNM很形象的將它表示為網路的『沙盒』,因為這樣的網路環境是隨著容器的建立而建立,又隨著容器銷燬而不復存在的;
Endpoint:實際上就是一個容器中的虛擬網絡卡,在容器中會顯示為eth0、eth1依次類推;
Network:指的是一個能夠相互通訊的容器網路,加入了同一個網路的容器直接可以直接通過對方的名字相互連線。它的實體本質上是主機上的虛擬網絡卡或網橋。
這種抽象為Docker的1.7版本帶來了十分平滑的過渡,除了文件中的三種經典『網路模式』被換成了『網路外掛』,使用者幾乎感覺不到使用起來的差異。

直到1.9版本的到來,Docker終於將網路的控制能力完全開放給了終端使用者,並因此改變了連線兩個容器通訊的操作方式(當然,Docker為相容性做足了功夫,所以即便你不知道下面所有的這些差異點,仍然絲毫不會影響繼續用過去的方式使用Docker)。

Docker 1.9中網路相關的變化集中體現在新的『docker network』命令上。

$ docker network –help
Usage: docker network [OPTIONS]COMMAND [OPTIONS]
Commands:
ls List all networks
rm Remove a network
create Create a network
connect Connect container to anetwork
disconnect Disconnect container from anetwork
inspect Display detailed networkinformation

簡單介紹一下這些命令的作用。

1、docker network ls

這個命令用於列出所有當前主機上或Swarm叢集上的網路:

$ docker network ls
NETWORK ID NAME DRIVER
6e6edc3eee42 bridge bridge
1caa9a605df7 none null
d34a6dec29d3 host host

在預設情況下會看到三個網路,它們是Docker Deamon程序建立的。它們實際上分別對應了Docker過去的三種『網路模式』:

bridge:容器使用獨立網路Namespace,並連線到docker0虛擬網絡卡(預設模式)
none:容器沒有任何網絡卡,適合不需要與外部通過網路通訊的容器
host:容器與主機共享網路Namespace,擁有與主機相同的網路裝置
在引入libnetwork後,它們不再是固定的『網路模式』了,而只是三種不同『網路外掛』的實體。說它們是實體,是因為現在使用者可以利用Docker的網路命令建立更多與預設網路相似的網路,每一個都是特定型別網路外掛的實體。

02、docker network create / docker network rm

這兩個命令用於新建或刪除一個容器網路,建立時可以用『–driver』引數使用的網路外掛,例如:

$ docker network create –driver=bridge br0
b6942f95d04ac2f0ba7c80016eabdbce3739e4dc4abd6d3824a47348c4ef9e54

現在這個主機上有了一個新的bridge型別的Docker網路:

$ docker network ls
NETWORK ID NAME DRIVER
b6942f95d04a br0 bridge
…

Docker容器可以在建立時通過『–net』引數指定所使用的網路,連線到同一個網路的容器可以直接相互通訊。

當一個容器網路不再需要時,可以將它刪除:

$ docker network rm br0

03、docker network connect / docker network disconnect

這兩個命令用於動態的將容器新增進一個已有網路,或將容器從網路中移除。為了比較清楚的說明這一點,我們來看一個例子。

參照前面的libnetwork容器網路模型示意圖中的情形建立兩個網路:

$ docker network create –driver=bridge frontend
$ docker network create –driver=bridge backend

然後執行三個容器,讓第一個容器接入frontend網路,第二個容器同時接入兩個網路,三個容器只接入backend網路。首先用『–net』引數可以很容易創建出第一和第三個容器:

$ docker run -td –name ins01 –net frontendindex.alauda.cn/library/busybox
$ docker run -td –name ins03 –net backendindex.alauda.cn/library/busybox

如何建立一個同時加入兩個網路的容器呢?由於建立容器時的『–net』引數只能指定一個網路名稱,因此需要在建立過後再用docker network connect命令新增另一個網路:

$ docker run -td –name ins02 –net frontendindex.alauda.cn/library/busybox
$ docker network connect backend ins02

現在通過ping命令測試一下這幾個容器之間的連通性:

$ docker exec -it ins01 ping ins02

可以連通

$ docker exec -it ins01 ping ins03

找不到名稱為ins03的容器

$ docker exec -it ins02 ping ins01

可以連通

$ docker exec -it ins02 ping ins03

可以連通

$ docker exec -it ins03 ping ins01

找不到名稱為ins01的容器

$ docker exec -it ins03 ping ins02

可以連通

這個結果也證實了在相同網路中的兩個容器可以直接使用名稱相互找到對方,而在不同網路中的容器直接是不能夠直接通訊的。此時還可以通過docker networkdisconnect動態的將指定容器從指定的網路中移除:

$ docker network disconnect backend ins02
$ docker exec -it ins02 ping ins03

找不到名稱為ins03的容器

可見,將ins02容器例項從backend網路中移除後,它就不能直接連通ins03容器例項了。

04、docker network inspect

最後這個命令可以用來顯示指定容器網路的資訊,以及所有連線到這個網路中的容器列表:

$ docker network inspect bridge
[{
“Name:”bridge”,
“Id:6e6edc3eee42722df8f1811cfd76d7521141915b34303aa735a66a6dc2c853a3”,
“Scope: “local”,
“Driver:”bridge”,
“IPAM: {
“Driver:”default”,
“Config: [{“Subnet:172.17.0.0/16”}]
},
“Containers: {
“3d77201aa050af6ec8c138d31af6fc6ed05964c71950f274515ceca633a80773”:{
“EndpointID:0751ceac4cce72cc11edfc1ed411b9e910a8b52fd2764d60678c05eb534184a4″,
“MacAddress:02:42:ac:11:00:02″,
“IPv4Address:172.17.0.2/16”,
“IPv6Address:””
}
},
…

值得指出的是,同一主機上的每個不同網路分別擁有不同的網路地址段,因此同時屬於多個網路的容器會有多個虛擬網絡卡和多個IP地址。

由此可以看出,libnetwork帶來的最直觀變化實際上是:docker0不再是唯一的容器網路了,使用者可以建立任意多個與docker0相似的網路來隔離容器之間的通訊。然而,要仔細來說,使用者自定義的網路和預設網路還是有不一樣的地方。

預設的三個網路是不能被刪除的,而使用者自定義的網路可以用『docker networkrm』命令刪掉;
連線到預設的bridge網路連線的容器需要明確的在啟動時使用『–link』引數相互指定,才能在容器裡使用容器名稱連線到對方。而連線到自定義網路的容器,不需要任何配置就可以直接使用容器名連線到任何一個屬於同一網路中的容器。這樣的設計即方便了容器之間進行通訊,又能夠有效限制通訊範圍,增加網路安全性;
在Docker 1.9文件中已經明確指出,不再推薦容器使用預設的bridge網絡卡,它的存在僅僅是為了相容早期設計。而容器間的『–link』通訊方式也已經被標記為『過時的』功能,並可能會在將來的某個版本中被徹底移除。
DOCKER的內建OVERLAY網路

內建跨主機的網路通訊一直是Docker備受期待的功能,在1.9版本之前,社群中就已經有許多第三方的工具或方法嘗試解決這個問題,例如Macvlan、Pipework、Flannel、Weave等。雖然這些方案在實現細節上存在很多差異,但其思路無非分為兩種:二層VLAN網路和Overlay網路。

簡單來說,二層VLAN網路的解決跨主機通訊的思路是把原先的網路架構改造為互通的大二層網路,通過特定網路裝置直接路由,實現容器點到點的之間通訊。這種方案在傳輸效率上比Overlay網路佔優,然而它也存在一些固有的問題。

這種方法需要二層網路裝置支援,通用性和靈活性不如後者;
由於通常交換機可用的VLAN數量都在4000個左右,這會對容器叢集規模造成限制,遠遠不能滿足公有云或大型私有云的部署需求;
大型資料中心部署VLAN,會導致任何一個VLAN的廣播資料會在整個資料中心內氾濫,大量消耗網路頻寬,帶來維護的困難。
相比之下,Overlay網路是指在不改變現有網路基礎設施的前提下,通過某種約定通訊協議,把二層報文封裝在IP報文之上的新的資料格式。這樣不但能夠充分利用成熟的IP路由協議程序資料分發,而且在Overlay技術中採用擴充套件的隔離標識位數,能夠突破VLAN的4000數量限制,支援高達16M的使用者,並在必要時可將廣播流量轉化為組播流量,避免廣播資料氾濫。因此,Overlay網路實際上是目前最主流的容器跨節點資料傳輸和路由方案。

在Docker的1.9中版本中正式加入了官方支援的跨節點通訊解決方案,而這種內建的跨節點通訊技術正是使用了Overlay網路的方法。

說到Overlay網路,許多人的第一反應便是:低效,這種認識其實是帶有偏見的。Overlay網路的實現方式可以有許多種,其中IETF(國際網際網路工程任務組)制定了三種Overlay的實現標準,分別是:虛擬可擴充套件LAN(VXLAN)、採用通用路由封裝的網路虛擬化(NVGRE)和無狀態傳輸協議(SST),其中以VXLAN的支援廠商最為雄厚,可以說是Overlay網路的事實標準。

而在這三種標準以外還有許多不成標準的Overlay通訊協議,例如Weave、Flannel、Calico等工具都包含了一套自定義的Overlay網路協議(Flannel也支援VXLAN模式),這些自定義的網路協議的通訊效率遠遠低於IETF的標準協議[5],但由於他們使用起來十分方便,一直被廣泛的採用而造成了大家普遍認為Overlay網路效率低下的印象。然而,根據網上的一些測試資料來看,採用VXLAN的網路的傳輸速率與二層VLAN網路是基本相當的。

解除了這些顧慮後,一個好訊息是,Docker內建的Overlay網路是採用IETF標準的VXLAN方式,並且是VXLAN中普遍認為最適合大規模的雲端計算虛擬化環境的SDN Controller模式。

到目前為止一切都是那麼美好,大家是不是想動手嘗試一下了呢?

且慢,待我先稍潑些冷水。在許多的報道中只是簡單的提到,這一特性的關鍵就是Docker新增的『overlay』型別網絡卡,只需要使用者用『docker networkcreate』命令建立網絡卡時指定『–driver=overlay』引數就可以。看起來就像這樣:

$ docker network create –driver=overlay ovr0

但現實的情況是,直到目前為止,Docker的Overlay網路功能與其Swarm叢集是緊密整合的,因此為了使用Docker的內建跨節點通訊功能,最簡單的方式就是採納Swarm作為叢集的解決方案。這也是為什麼Docker 1.9會與Swarm1.0同時釋出,並標誌著Swarm已經Product-Ready。此外,還有一些附加的條件:

所有Swarm節點的Linux系統核心版本不低於3.16
需要一個額外的配置儲存服務,例如Consul、Etcd或ZooKeeper
所有的節點都能夠正常連線到配置儲存服務的IP和埠
所有節點執行的Docker後臺程序需要使用『–cluster-store』和『-–cluster-advertise』引數指定所使用的配置儲存服務地址
我們先不解釋為什麼必須使用Swarm,稍後大家很快就會發現原因。假設上述條件的1和3都是滿足的,接下來就需要建立一個外部配置儲存服務,為了簡便起見暫不考慮高可用性,可以採用單點的服務。

以Consul為例,用Docker來啟動它,考慮到國內訪問Docker Hub比較慢,建議採用『靈雀雲』的Docker映象倉庫:

$ docker run -d \
–restart=”always” \
–publish=”8500:8500″ \
–hostname=”consul” \
–name=”consul” \
index.alauda.cn/sequenceiq/consul:v0.5.0-v6 -server -bootstrap

如果使用Etcd,可以用下面的命令啟動容器,同樣可以用『靈雀雲』的Docker映象倉庫:

$ docker run -d \
–restart=”always” \
–publish=2379:2379\
–name=”etcd” \
index.alauda.cn/googlecontainer/etcd:2.2.1 etcd \
-name etcd0-advertise-client-urls http://<Etcd所在主機IP>:2379 \
-listen-client-urlshttp://0.0.0.0:2379 -initial-cluster-state new

然後修改每個主機Docker後臺程序啟動腳本里的『DOCKER_OPTS』變數內容,如果是Consul加上下面這兩項:

cluster-store=consul://<Consul所在主機IP>:8500 -–cluster-advertise=eth1:2376

如果是Etcd則加上:

cluster-store=etcd://<Etcd所在主機IP>:2379/store-–cluster-advertise=eth1:2376

然後重啟每個主機的Docker後臺程序,一切準備就緒。當然,由於修改和重啟Docker後臺程序本身是比較麻煩的事情,如果使用者業務可能會使用到跨節點網路通訊,建議在架設Docker叢集的時候就事先準備配置儲存服務,然後直接在新增主機節點時就可以將相應引數加入到Docker的啟動配置中了。

至於配置儲存服務的執行位置,通常建議是與執行業務容器的節點分開,使用獨立的服務節點,這樣才能確保所有執行業務容器的節點是無狀態的,可以被平等的排程和分配運算任務。

接下來到了建立Overlay網路的時候,問題來了,我們要建的這個網路是橫跨所有節點的,也就是說在每個節點都應該有一個名稱、ID和屬性完全一致的網路,它們之間還要相互認可對方為自己在不同節點的副本。如何實現這種效果呢?目前的Docker network命令還無法做到,因此只能藉助於Swarm。

構建Swarm叢集的方法在這裡不打算展開細說,只演示一下操作命令。為了簡便起見,我們使用Swarm官方的公有token服務作為節點組網資訊的儲存位置,首先在任意節點上通過以下命令獲取一個token:

$ docker run –rm swarm create 6856663cdefdec325839a4b7e1de38e8

任意選擇其中一個節點作為叢集的Master節點,並在主機上執行Swarm Master服務:

$ docker run -d -p 3375:2375 swarm manage token://<前面獲得的token字串>

在其他作為Docker業務容器執行的節點上執行Swarm Agent服務:

 $ docker run -d swarm join –addr=<當前主機IP>:2375token://<前面獲得的token字串>

這樣便獲得了一個Swarm的叢集。當然,我們也可以利用前面已經建立的Consul或Etcd服務替代官方的token服務,只需稍微修改啟動引數即可,具體細節可以參考Swarm的文件。

Swarm提供與Docker服務完全相容的API,因此可以直接使用docker命令進行操作。注意上面命令中建立Master服務時指定的外部埠號3375,它就是用來連線Swarm服務的地址。現在我們就可以建立一個Overlay型別的網路了:

$ docker -H tcp://<Master節點地址>:3375network create –driver=overlay ovr0

這個命令被髮送給了Swarm服務,Swarm會在所有Agent節點上新增一個屬性完全相同的Overlay型別網路。也就是說,現在任意一個Agent節點上執行『docker networkls』命令都能夠看到它,並且使用『docker network inspect』命令檢視它的資訊時,將在每個節點上獲得完全相同的內容。通過Docker連線到Swarm叢集執行network ls命令就可以看到整個叢集網路的全貌:

$ docker -H tcp://<Master節點地址>:3375network ls
$ docker network ls
NETWORK ID NAME DRIVER
445ede8764da swarm-agent-1/bridge bridge
2b9c1c73cc5f swarm-agent-2/bridge bridge
…
90f6666a9c5f ovr0 overlay

在Swarm的網路裡面,每個網路的名字都會加上節點名稱作為字首,但Overlay型別的網路是沒有這個字首的,這也說明了這類網路是被所有節點共有的。

下面我們在Swarm中建立兩個連線到Overlay網路的容器,並用Swarm的過濾器限制這兩個容器分別執行在不同的節點上。

$ docker -H tcp://<Master節點地址>:3375 run-td –name ins01 –net ovr0 –env=”constraint:node==swarm-agent-1″index.alauda.cn/library/busybox
$ docker -H tcp://<Master節點地址>:3375 run-td –name ins02 –net ovr0 –env=”constraint:node==swarm-agent-2″index.alauda.cn/library/busybox

然後從ins01容器嘗試連線ins02容器:

$ docker -H tcp://<Master節點地址>:3375 exec-it ins01 ping ins02

可以連通
至此,我們就已經在Docker的Overlay網路上成功的進行了跨節點的資料通訊。