以前筆者寫了有些關於dokcer的各類相關技術的文章,惟獨Docker網絡這一塊沒有具體的來分享。後期筆者會陸續更新Docker集羣以及Docker高級實踐的文章,因此在此以前必需要和你們一塊兒來解讀一下Docker網絡原理。前端
就比如中國武術同樣:學招數,會的只是一時的方法;練內功,纔是受益終生長久之計。認真看下去你會有收穫的,咱們一塊兒來把docker的內功修練好。node
在深刻Docker內部的網絡原理以前,咱們先從一個用戶的角度來直觀感覺一下Docker的網絡架構和基本操做是怎麼樣的。linux
Docker在1.9版本中(如今都1.17了)引入了一整套docker network子命令和跨主機網絡支持。這容許用戶能夠根據他們應用的拓撲結構建立虛擬網絡並將容器接入其所對應的網絡。git
其實,早在Docker1.7版本中,網絡部分代碼就已經被抽離並單獨成爲了Docker的網絡庫,即libnetwork。在此以後,容器的網絡模式也被抽像變成了統一接口的驅動。github
爲了標準化網絡的驅動開發步驟和支持多種網絡驅動,Docker公司在libnetwork中使用了CNM(Container Network Model)。CNM定義了構建容器虛擬化網絡的模型。同時還提供了能夠用於開發多種網絡驅動的標準化接口和組件。web
libnetwork和Docker daemon及各個網絡驅動的關係能夠經過下面的圖進行形象的表示。docker
如上圖所示,Docker daemon經過調用libnetwork對外提供的API完成網絡的建立和管理等功能。libnetwrok中則使用了CNM來完成網絡功能的提供。而CNM中主要有沙盒(sandbox)、端點(endpoint)、網絡(network)這3種組件。shell
libnetwork中內置的5種驅動則爲libnetwork提供了不一樣類型的網絡服務。接下來分別對CNM中的3個核心組件和libnetwork5種內置驅動進行介紹。ubuntu
一、沙盒(sandbox)
一個沙盒也包含了一個容器網絡棧的信息。沙盒能夠對容器的接口、路由和DNS設置等進行管理。沙盒的實現能夠是Linux netwrok namespace、FreeBSD jail或者相似的機制。一個沙盒能夠有多個端點和多個網絡。後端
二、端點(endpoint)
一個端點能夠加入一個沙盒和一個網絡。端點的實現能夠是veth pair、Open vSwitch內部端口或者類似的設備。一個端點只能夠屬於一個網絡而且只屬於一個沙盒。
三、網絡(network)
一個網絡是一組能夠直接互相聯通的端點。網絡的實現能夠是Linux bridge、VLAN等。一個網絡能夠包含多個端點
libnetwork共有5種內置驅動:bridge驅動、host驅動、overlay驅動、remote驅動、null驅動。
一、bridge驅動
此驅動爲Docker的默認設置驅動,使用這個驅動的時候,libnetwork將建立出來的Docker容器鏈接到Docker網橋上。做爲最常規的模式,bridge模式已經能夠知足Docker容器最基本的使用需求了。然而其與外界通訊使用NAT,增長了通訊的複雜性,在複雜場景下使用會有諸多限制。
二、host驅動
使用這種驅動的時候,libnetwork將不爲Docker容器建立網絡協議棧,即不會建立獨立的network namespace。Docker容器中的進程處於宿主機的網絡環境中,至關於Docker容器和宿主機共同用一個network namespace,使用宿主機的網卡、IP和端口等信息。
可是,容器其餘方面,如文件系統、進程列表等仍是和宿主機隔離的。host模式很好地解決了容器與外界通訊的地址轉換問題,能夠直接使用宿主機的IP進行通訊,不存在虛擬化網絡帶來的額外性能負擔。可是host驅動也下降了容器與容器之間、容器與宿主機之間網絡層面的隔離性,引發網絡資源的競爭與衝突。
所以能夠認爲host驅動適用於對於容器集羣規模不大的場景。
三、overlay驅動
此驅動採用IETE標準的VXLAN方式,而且是VXLAN中被廣泛認爲最適合大規模的雲計算虛擬化環境的SDN controller模式。在使用過程當中,須要一個額外的配置存儲服務,例如Consul、etcd和zookeeper。還須要在啓動Docker daemon的時候額外添加參數來指定所使用的配置存儲服務地址。
四、remote驅動
這個驅動實際上並未作真正的網絡服務實現,而是調用了用戶自行實現的網絡驅動插件,使libnetwork實現了驅動的可插件化,更好地知足了用戶的多種需求。用戶只須要根據libnetwork提供的協議標準,實現其所要求的各個接口並向Docker daemon進行註冊。
五、null驅動
使用這種驅動的時候,Docker容器擁有本身的network namespace,可是並不爲Docker容器進行任何網絡配置。也就是說,這個Docker容器除了network namespace自帶的loopback網卡名,沒有其餘任何網卡、IP、路由等信息,須要用戶爲Docker容器添加網卡、配置IP等。
這種模式若是不進行特定的配置是沒法正常使用的,可是優勢也很是明顯,它給了用戶最大的自由度來自定義容器的網絡環境。
咱們初步瞭解了libnetwork中各個組件和驅動後,爲了能深刻的理解libnetwork中的CNM模型和熟悉docker network子命令的使用,咱們來經過libnetwork官方github上的示例進行驗證一下,以下圖所示:
在上圖示例中,使用Docker 默認的bridge驅動進行演示。在此例中,會在Docker上組成一個網絡拓撲的應用:
一、經過如下命令分別建立名爲backend、frontend兩個網絡:
# docker network create backend # docker network create frontend
二、使用docker network ls 能夠查看這臺主機上的全部Docker網絡:
# docker network ls NETWORK ID NAME DRIVER SCOPE 879f8d1788ba backend bridge local 6c16e2b4122d bridge bridge local d150ba23bdc0 frontend bridge local 1090f32081e8 host host local 7bf28f042f6c none null local
除了剛纔建立的backend和frontend以外,還有3個網絡。這3個網絡是Docker daemon默認建立的,分別使用了3種不一樣的驅動,而這3種驅動則對應了Docker原來的3種網絡模式。須要注意的是,3種內置的默認網絡是沒法使用docker network rm進行刪除的,不信大家試一下。
三、接下來建立3個容器,並使用下面的命令將名爲c1和c2的容器加入到backend網絡中,將名爲c3的容器加入到frontend網絡中:
# docker run -itd --name c1 --net backend busybox # docker run -itd --name c2 --net backend busybox # docker run -itd --name c3 --net frontend busybox
而後,分別進入c1和c3容器使用ping命令測試其與c2的連通性,由於c1和c2都在backend網絡中,因此二者能夠連通。可是,由於c3和c2不在一個網絡中,因此兩個容器之間不能連通:
#進入c1容器ping c2通、ping c3不通。其它兩個容器就不進入演示了,你們本身能夠試一下: # docker exec -it c1 sh # ping c2 PING c2 (172.19.0.3): 56 data bytes 64 bytes from 172.19.0.3: seq=0 ttl=64 time=0.065 ms 64 bytes from 172.19.0.3: seq=1 ttl=64 time=0.050 ms # ping c3 ping: bad address 'c3'
而且,能夠進入c2容器中使用命令ifconfig來查看此容器中的網卡及配置狀況。能夠看到,容器中只有一塊以太網卡,其名稱爲eth0,而且配置了和網橋backend同在一個IP段的IP地址,這個網卡就是CNM模型中的端點:
四、最後,使用以下命令將c2容器加入到frontend網絡中:
# docker network connect frontend c2
再次,在c2容器中使用命令ifconfig來查看此容器中的網卡及配置狀況。發現多了一塊名爲eth1的以太網卡,而且其IP和網橋frontend同在一個IP段。測試c2與c3的連通性後,能夠發現二者已經連通。
能夠看出,docker network connect命令會在所鏈接的容器中建立新的網卡,以完成其與所指定網絡的鏈接。
前面咱們演示了bridge驅動下的CNM使用方式,下面來分析一下bridge驅動的實現機制又是怎樣的。
# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface ... 172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0 ...
此條路由表示全部目的IP地址爲172.17.0.0/16的數據包從docker0網卡轉發。
而後使用docker run命令建立一個執行shell(/bin/bash)的Docker容器,假設容器名稱爲con1。
在con1容器中能夠看到它有兩個網卡lo和eth0。lo設備沒必要多說,是容器的迴環網卡;eth0即爲容器與外界通訊的網卡,eth0的ip 爲 172.17.0.2/16,和宿主機上的網橋docker0在同一個網段。
查看con1的路由表,能夠發現con1的默認網關正是宿主機的docker0網卡,經過測試, con1能夠順利訪問外網和宿主機網絡,所以代表con1的eth0網卡與宿主機的docker0網卡是相互連通的。
這時在來查看(ifconfig)宿主機的網絡設備,會發現有一塊以「veth」開頭的網卡,如veth60b16bd,咱們能夠大膽猜想這塊網卡確定是veth設備了,而veth pair老是成對出現的。veth pair一般用來鏈接兩個network namespace,那麼另外一個應該是Docker容器con1中的eth0了。以前已經判斷con1容器的eth0和宿主機的docker0是相連的,那麼veth60b16bd也應該是與docker0相連的,不難想到,docker0就不僅是一個簡單的網卡設備了,而是一個網橋。
真實狀況正是如此,下圖即爲Docker默認網絡模式(bridge模式)下的網絡環境拓撲圖,建立了docker0網橋,並以eth pair鏈接各容器的網絡,容器中的數據經過docker0網橋轉發到eth0網卡上。
這裏的網橋概念等同於交換機,爲連在其上的設備轉發數據幀。網橋上的veth網卡設備至關於交換機上的端口,能夠將多個容器或虛擬機鏈接在上面,這些端口工做在二層,因此是不須要配置IP信息的。圖中docker0網橋就爲連在其上的容器轉發數據幀,使得同一臺宿主機上的Docker容器之間能夠相互通訊。
你們應該注意到docker0既然是二層設備,它上面怎麼設置了IP呢?docker0是普通的linux網橋,它是能夠在上面配置IP的,能夠認爲其內部有一個能夠用於配置IP信息的網卡接口(如同每個Open vSwitch網橋都有一個同名的內部接口同樣)。在Docker的橋接網絡模式中,docker0的IP地址做爲連於之上的容器的默認網關地址存在。
在Linux中,可使用brctl命令查看和管理網橋(須要安裝bridge-utils軟件包),好比查看本機上的Linux網橋以及其上的端口:
# brctl show bridge name bridge id STP enabled interfaces docker0 8000.02420b69b449 no veth1b11267
更多關於brctl命令的功能和用法,你們經過man brctl或brctl --help查閱。
docker0網橋是在Docker daemon啓動時自動建立的,其IP默認爲172.17.0.1/16,以後建立的Docker容器都會在docker0子網的範圍內選取一個未佔用的IP使用,並鏈接到docker0網橋上。
除了使用docker0網橋外,還可使用本身建立的網橋,好比建立一個名爲br0的網橋,配置IP:
# brctl addbr br0 # ifconfig br0 18.18.0.1
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
參數說明:
-s :源地址172.17.0.0/16
-o:指定數據報文流出接口爲docker0
-j :動做爲MASQUERADE(地址假裝)
上面這條規則關係着Docker容器和外界的通訊,含義是:將源地址爲172.17.0.0/16的數據包(即Docker容器發出的數據),當不是從docker0網卡發出時作SNAT。這樣一來,從Docker容器訪問外網的流量,在外部看來就是從宿主機上發出的,外部感受不到Docker容器的存在。
那麼,外界想到訪問Docker容器的服務時該怎麼辦呢?咱們啓動一個簡單的web服務容器,觀察iptables規則有何變化。
一、首先啓動一個 tomcat容器,將其8080端口映射到宿主機上的8080端口上:
#docker run -itd --name tomcat01 -p 8080:8080 tomcat:latest
二、而後查看iptabels規則,省略部分無用信息:
#iptables-save *nat -A POSTROUTING -s 172.17.0.4/32 -d 172.17.0.4/32 -p tcp -m tcp --dport 8080 -j MASQUERADE ... *filter -A DOCKER -d 172.17.0.4/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 8080 -j ACCEPT
能夠看到,在nat、filter的Docker鏈中分別增長了一條規則,這兩條規則將訪問宿主機8080端口的流量轉發到了172.17.0.4的8080端口上(真正提供服務的Docker容器IP和端口),因此外界訪問Docker容器是經過iptables作DNAT(目的地址轉換)實現的。
此外,Docker的forward規則默認容許全部的外部IP訪問容器,能夠經過在filter的DOCKER鏈上添加規則來對外部的IP訪問作出限制,好比只容許源IP192.168.0.0/16的數據包訪問容器,須要添加以下規則:
iptables -I DOCKER -i docker0 ! -s 192.168.0.0/16 -j DROP
不只僅是與外界間通訊,Docker容器之間互個通訊也受到iptables規則限制。同一臺宿主機上的Docker容器默認都連在docker0網橋上,它們屬於一個子網,這是知足相互通訊的第一步。同時,Docker daemon會在filter的FORWARD鏈中增長一條ACCEPT的規則(--icc=true):
-A FORWARD -i docker0 -o docker0 -j ACCEPT
這是知足相互通訊的第二步。當Docker datemon啓動參數--icc(icc參數表示是否容許容器間相互通訊)設置爲false時,以上規則會被設置爲DROP,Docker容器間的相互通訊就被禁止,這種狀況下,想讓兩個容器通訊就須要在docker run時使用 --link選項。
在Docker容器和外界通訊的過程當中,還涉及了數據包在多個網卡間的轉發(如從docker0網卡轉發到宿主機ens160網卡),這須要內核將ip-forward功能打開,即將ip_forward系統參數設1。Docker daemon啓動的時候默認會將其設爲1(--ip-forward=true),也能夠經過命令手動設置:
# echo 1 > /proc/sys/net/ipv4/ip_forward # cat /proc/sys/net/ipv4/ip_forward 1
# docker exec -it tomcat01 bash root@3d95d30c69d3:/usr/local/tomcat# mount ... /dev/mapper/centos-root on /etc/resolv.conf type xfs (rw,relatime,attr2,inode64,noquota) /dev/mapper/centos-root on /etc/hostname type xfs (rw,relatime,attr2,inode64,noquota) /dev/mapper/centos-root on /etc/hosts type xfs (rw,relatime,attr2,inode64,noquota) ...
這樣能解決主機名的問題,同時也能讓DNS及時更新(改變resolv.conf)。因爲這些文件的維護方法隨着Docker版本演進而不斷變化,所以儘可能不修改這些文件,而是經過Docker提供的參數進行相關設置,配置方式以下:
注意:對以上3個文件的修改不會被docker commit保存,也就是不會保存在鏡像中,重啓容器也會致使修改失效。另外,在不穩定的網絡環境下使用須要特別注意DNS的設置。
至此,Docker的網絡先介紹到這裏,內容有點多,看到這裏的朋友確實頗有耐心,可是我相信應該對docker網絡也有了全新的理解。下次筆者在和你們一塊兒來討論下docker高級網絡的一些功能。
喜歡個人文章,請點擊最上方右角處的《關注》支持一下!