1. 程式人生 > >Docker 網絡之理解 bridge 驅動

Docker 網絡之理解 bridge 驅動

In tcp 方式 name mas 概念 幫助 res 滿足

筆者在前文《Docker 網絡之進階篇》中介紹了 CNM(Container Network Model),並演示了 bridge 驅動下的 CNM 使用方式。為了深入理解 CNM 及最常用的 bridge 驅動,本文將探索 bridge 驅動的實現機制。
說明:本文的演示環境為 ubuntu 16.04。

dokcer0 網橋

在 Ubuntn 上安裝 docker 後,宿主機上默認被創建了一個名為 docker0 的網卡,其 IP 為 172.17.0.1/16:

技術分享圖片

有了這個網卡,宿主機還會在內核的路由表中添加一條到達相應網絡的靜態路由記錄:

技術分享圖片

這條路由信息表示所有目的 IP 為 172.17.0.0/16 的數據包都會從 docker0 網卡發出。接下來我們創建一個名為 mycon 的容器,並觀察其網絡配置:

技術分享圖片

在 mycon 容器內可以看到兩塊網卡:lo 和 eth0。其中 lo 是容器的回環網卡,eth0 是容器與外界通信的網卡,eth0 的 IP 信息為 172.17.0.2/16,和宿主機上的網卡 bridge0 在同一網段中。查看 mycon 的路由信息:

技術分享圖片

mycon 容器的默認網關正是宿主機的 docker0 網卡。通過 ping 命令測試與外網的連通性,此時容器 mycon 是可以連通外網的,這就說明 mycon 的 eth0 網卡與宿主機的 docker0 網卡是連通的。

下面我們來查看宿主機的網絡設備:

技術分享圖片

發現多了一個以 "veth" 開頭的網卡,這是一個 veth 設備。而 veth 設備總是成對出現的,那麽與 veth7537a16 配對的就應該是 mycon 容器中的 eth0 了。既然 mycon 容器中的 eth0 是與 docker0 連通的,那麽 veth7537a16 也應該是與 docker0 連通的。因此 docker0 並不是一個簡單的網卡設備,而是一個網橋

!下圖展示了 docker bridge 網絡模式的拓撲圖:

技術分享圖片

事實上,docker 創建了 docker0 網橋,並以 veth pair 連接各個容器的網絡,容器中的數據通過 eth0 發送到 docker0 網橋上,並由 docker0 網橋完成轉發。這裏網橋的概念等同於交換機,為連在其上的設備轉發數據幀。網橋上的 veth 網卡設備相當於交換機上的端口,可以將多個容器連接在它們上面,這些端口工作在二層,所以是不需要配置 IP 信息的。上圖中的 docker0 網橋就為連在其上的容器轉發數據幀,使得同一臺宿主機上的 docker 容器之間可以相互通信。既然 docker0 是二層設備,那麽它為什麽還需要 IP 呢?其實,docker0 是一個普通的 linux 網橋,是可以為它配置 IP 的,我們可以認為它的內部有一個可以用於配置 IP 的網卡。Docker0 的 IP 地址作為所連接的容器的默認網關地址

docker0 網橋是在 docker daemon 啟動時自動創建的,其默認 IP 為 172.17.0.1/16,之後通過 bridge 驅動創建的容器都會在 docker0 的子網範圍內選取一個未占用的 IP 使用,並連接到 docker0 網橋上。Docker daemon 提供了如下參數可以幫助用戶自定義 docker0 的設置。

  • --bip=CIDR:設置 docker0 的 IP 地址和子網範圍,使用 CIDR 格式,如 192.168.1.0/24。這個參數僅僅是配置 docker0 的,對用戶自定義的網橋無效。
  • --fixed-cidr=CIDR:限制 docker 容器可以獲取的 IP 地址範圍。Docker 容器默認可以獲取的 IP 範圍為 docker 網橋的整個子網範圍,此參數可以將其縮小到某個子網範圍內,所以這個參數必須在 docker 網橋的子網範圍內。
  • --mtu=BYTES:指定 docker0 網橋的最大傳輸單元(MTU)。

除了使用 docker0 網橋外,用戶還可以使用自定義的網橋,然後通過 --bridge=BRIDGE 參數傳遞給 docker daemon。比如我們可以創建一個自定義網橋 br0:

$ sudo ip link add name br0 type bridge
$ sudo ifconfig br0 188.18.0.1

技術分享圖片

然後在啟動 docker daemon 時設置參數 --bridge=br0 即可。

iptables 規則

在安裝 docker 時,會默認在宿主機中添加一些 iptables 規則,用於 docker 容器之間已經容器與外界的通信。我們可以通過 iptables-save 命令查看到 nat 表上 POSTROUTING 鏈上的有這麽一條規則:

-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE

這條規則關系著 docker 容器與外界的通信,其含義是將不是從網卡 docker0 發出的且源地址為 172.17.0.0/16 的數據包(容器中發出的數據包)做 SNAT。這樣一來,從 docker 容器中訪問外網的流量,在外部看來就是從宿主機上發出的,外部感覺不到 docker 容器的存在。

當外界想要訪問 docker 容器運行的服務時該怎麽辦呢?接下來我們將啟動一個簡單的 web 服務器:

$ docker run -d -p 3000:3000 --name=myweb ljfpower/nodedemo

然後觀察 iptables 規則的變化:

$ sudo iptables-save
…
*nat
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 3000 -j DNAT --to-destination 172.17.0.3:3000*filter
-A DOCKER -d 172.17.0.3/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 3000 -j ACCEPT
…

可以看到,在 nat 表和 filter 表中的 DOCKER 鏈中分別增加來一條規則,這兩條規則將訪問宿主機 3000 端口的請求轉發到 172.17.0.3 的 3000 端口上(提供服務的 docker 容器的 IP 和端口),所以外界訪問 docker 容器是通過 iptables 做 DNAT 實現的。
Docker 默認的 forward 規則允許所有的外部 IP 訪問容器,我們可以通過在 filter 的 DOCKER 鏈上添加規則來對外部的 IP 訪問做出限制,比如只允許源 IP 為 192.168.21.212(筆者是在局域網內演示的)的數據包訪問容器,添加的規則如下:

$ sudo iptables -I DOCKER -i docker0 ! -s 192.168.21.212 -j DROP

不僅僅是與外界通信,docker 容器之間相互通信也受到 iptables 規則的限制。同一臺宿主機上的 docker 容器默認都連在 docker0 網橋上,它們屬於同一個子網,這是滿足通信的第一步。同時,docker daemon 會在 filter 表的 FORWARD 鏈中增加一條 ACCEPT 的規則(--icc=true):

-A FORWARD -i docker0 -o docker0 -j ACCEPT

這是滿足容器間相互通信的第二步。當 docker daemon 的啟動參數 -icc(icc 參數表示是否允許容器間相互通信) 設置為 false 時,上面的規則被設置為 DROP,容器間的相互通信就被禁止了,這時如果想讓兩個容器通信就需要在 docker run 命令中使用 --link 選項。

在 docker 容器和外界的通信過程中,還涉及了數據包在多個網卡之間的轉發(比如從 docker0 網卡到宿主機 eth0 網卡),這需要內核將 ip forward 功能打開,就是把內核參數 ip_forward 設置為 1。Docker daemon 在啟動的時候會執行這個操作,我們可以通過下面的命令進行檢查:

$ cat /proc/sys/net/ipv4/ip_forward

技術分享圖片

返回的結果為 1,表示內核的 ip forward 功能已經打開。

容器的 DNS 和主機名(hostname)

使用同一個 docker 鏡像可以創建很多個 docker 容器,但是這些容器的 hostname 並不相同,也就是說 hostname 並沒有被寫入到鏡像中。實際上容器中的 /etc 目錄下有 3 個文件是在容器啟動後被虛擬文件覆蓋掉的,分別是 /etc/hostname、/etc/hosts 和 /etc/resolv.conf,通過在容器中運行 mount 命令可以看到它們:

# mount/dev/mapper/ubuntu--vg-root on /etc/resolv.conf type ext4 (rw,relatime,errors=remount-ro,data=ordered)
/dev/mapper/ubuntu--vg-root on /etc/hostname type ext4 (rw,relatime,errors=remount-ro,data=ordered)
/dev/mapper/ubuntu--vg-root on /etc/hosts type ext4 (rw,relatime,errors=remount-ro,data=ordered)
…

這種方式既能解決主機名的問題,同時也能讓 DNS 及時更新(改變 resolv.conf)。由於這些文件的維護方法會隨著 docker 版本的升級而不斷變化,所以盡量不要修改這些文件,而是通過 docker 提供的相關參數進行設置,其參數配置方式如下。

  • -h HOSTNAME 或者 --hostname=HOSTNAME:設置容器的 hostname,此名稱會寫入到 /etc/hostname 和 /etc/hosts 文件中,也可以在容器的 bash 提示符中看到。
  • --dns=IP_ADDRESS…:為容器配置 DNS,會被寫入到 /etc/resolv.conf 文件中。

這兩個參數都是針對容器的需要在創建容器時進行設置。比如下面的 demo:

$ docker run -it --name mycon -h lion --dns=8.8.8.8 ubuntu:14.04

技術分享圖片

總結

本文主要通過演示 docker0 網橋相關的功能來探索 docker 網絡中的 bridge 驅動的實現機制。從本文中不難看出,linux 系統中,docker 的 bridge 驅動是依賴於系統的 ip forward 以及 iptables 等核心功能的。因此在學習 docker 的過程中,適當的補充 linux 相關的知識也是十分必要的!

參考:
《docker 容器與容器雲》

Docker 網絡之理解 bridge 驅動