聊聊Docker 1.9的新網絡特性
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』命令上。
{bash} $ 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-urls http://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網絡上成功的進行了跨節點的數據通信。
想簡單點?用靈雀云吧
不知大家發現了么有,不論是過去的Pipework、Flannel、Weave方式,還是Docker 1.9內置的Overlay網絡,盡管所有的這些方案都宣傳自己足夠的簡單易用,構建跨節點通信的容器依然是一件不得已而為之的事情。
之所以這樣說,是因為在現實應用場景中往往由于服務直接錯綜復雜的連接,需要相互通信的容器數量遠遠的超過的單個主機節點所能夠承受的容量,這才使得我們不得不在現有的基礎實施上自行維護一套服務機制,將容器間的通信擴展到多個節點上。但維護這些跨節點通信基礎設施本身是不為企業帶來實質的業務價值的!
在當下,云基礎設施迅速發展的大環境已經為許多企業創造了彎道超車的機遇:通過采用云平臺,企業省去了自己購置和組建大規模網絡和計算資源以及管理龐大運維團隊的投入,只需專注于業務本身的創意和設計就能收獲巨大的利潤。與此同時,容器作為新一代的服務部署和調度工具被越來越廣泛的采用,而解決容器通信問題本不應該成為企業需要關注的要點,現在卻成為擴大服務規模時橫在眼前無法忽視的阻礙。
靈雀云作為容器云服務平臺中的佼佼者,為容器的使用者屏蔽了容器運行節點的細節,使得用戶完全感覺不到跨節點通信所帶來的差異。那么如何在靈雀云中運行兩個容器,并使之相互通信呢?
首先安裝靈雀的alauda命令行工具,使用『alauda login』命令登陸。
'''
$ alauda login
Username: fanlin
Password: 靈雀密碼
[alauda] Successfully logged in as fanlin.
[alauda] OK
'''
然后運行兩個容器,前者運行Nginx服務,后者用于測試與前者的連接。
'''
$ alauda service run –publish=80/http web index.alauda.cn/library/nginx:1.9.9
[alauda] Creating and starting service “web”
[alauda] OK
$ alauda service run client index.alauda.cn/library/busybox:latest
[alauda] Creating and starting service “client”
[alauda] OK
'''
嘗試從client容器實例訪問web容器實例提供的HTTP服務,注意靈雀云自動生成的地址格式。
'''
$ alauda service exec client wget -O- http://web-fanlin.myalauda.cn
Password for fanlin@exec.alauda.cn :
…
<html>
<head>
<title>Welcome to nginx!</title>
<style>
…
'''
可以看到返回了Nginx默認首頁的內容,證明這兩個容器之間可以連通。除了建立單個的任務,還可以使用『alauda compose』命令快速建立多個容器組成的服務集合。
那么著兩個容器分別是運行在哪個主機上的?管他呢,靈雀云已經將這些底層細節統統藏起來了,所以只管往云上繼續創建更多的服務吧,再也不用擔心主機資源耗盡,面對跨節點怎么通信的問題啦。
作者簡介:林帆,ThoughtWorks公司軟件工程師及DevOps咨詢師,具有豐富的持續交付和服務器運維自動化實踐經驗,專注于DevOps和容器技術領域。在InfoQ、CSDN網站和《程序員》雜志上發表有多篇相關領域文章,著有《CoreOS實踐之路》一書。
來自: http://dockone.io/article/965