探索 Docker bridge 的正確姿勢,親測有效!
上一回合,小白折騰了 Docker 架構,鐵要趁熱我開始學習 Docker 容器網絡。此刻的心情,是激動的,也是不安的。激動是因為終于要面對 Docker 的第一座大山 : 網絡 ,不安是因為網絡問題一直以來都是小白的軟肋,那些年我們一起學過的網絡知識,如今……
硬著頭皮翻開《 Docker 進階與實戰 》開始 Docker 網絡初探,里面講述了 Docker 容器網絡的好幾種模式。其中 “ bridge” 模式 成功的吸引了我的注意。bridge 模式俗稱橋接模式,關于它的定義小白早就忘了,但不難理解的是 bridge 的作用, bridge 可以連接不同的東西。
早期的二層網絡中,bridge 可以連接不同的 LAN 網,如下圖所示。當主機 1 發出一個數據包時,LAN 1 的其他主機和網橋 br0 都會收到該數據包。網橋再將數據包從入口端復制到其他端口上(本例中就是另外一個端口)。因此,LAN 2 上的主機也會接收到主機 A 發出的數據包,從而實現不同 LAN 網上所有主機的通信。

隨著網絡技術的發展,傳統 bridge 衍生出適用不同應用場景的模式,其中最典型要屬 Linux bridge 模式,它是 Linux Kernel 網絡模塊的一個重要組成部分,用以保障不同虛擬機之間的通信,或是虛擬機與宿主機之間的通信,如下圖所示 :

依葫蘆畫瓢,Docker bridge 十有八九是用來連接不同容器,或是連接容器與宿主機的。
帶著疑問,我快速瀏覽了這個章節,結果是大驚從早到晚失色,書中的介紹比我預想的復雜很多,Docker bridge 模式不僅使用了 veth pair 技術,還使用了 網絡命名空間技術 ,更令我吃驚的是,Docker bridge 模式下竟然采用了 NAT 方式。Docker bridge 和 Linux bridge 二者,初看如出一轍,再看又相去甚遠,還真是傻傻分不清楚。沒想到我的容器網絡學習計劃,剛起步便遭遇了滑鐵盧。
沒有搞清楚 Docker bridge 與 Linux bridge 的區別前,這書簡直沒法看了。依小白的經驗,云里霧里的時候摸清楚基本概念最有效,先從 Linux bridge 模式的基本工作原理入手,再從 Docker bridge 模式下的 “黑科技” (veth pair、網絡命名空間技術、NAT)入手 ,或許能找出點頭緒。壓抑住內心的憤懣,我翻開了《 深入理解 LINUX 網絡技術的內幕 》,找尋這些關鍵字的足跡。
?Linux bridge 模式
Linux bridge 模式下,Linux Kernel 會創建出一個 虛擬網橋 ,用以實現 主機網絡 接口 與 虛擬網絡接口 間的通信。從功能上來看,Linux bridge 像一臺虛擬交換機,所有橋接設置的虛擬機分別連接到這個交換機的一個接口上,接口之間可以相互訪問且互不干擾,這種連接方式對物理主機而言也是如此。

在橋接的作用下,虛擬網橋會把主機網絡接口接收到的網絡流量轉發給虛擬網絡接口,于是后者能夠接收到路由器發出的 DHCP(動態主機設定協議,用于獲取局域網 IP)信息及路由更新。這樣的工作流程,同樣適用于不同虛擬網絡接口間的通信。具體的實現方式如下所示:
虛擬機與宿主機通信: 用戶可以手動為虛擬機配置IP 地址、子網掩碼,該 IP 需要和宿主機 IP 處于同一網段,這樣虛擬機才能和宿主機進行通信。
虛擬機與外界通信: 如果虛擬機需要聯網,還需為它手動配置網關,該網關也要和宿主機網關保持一致。
除此之外,還有一種較為簡單的方法,那就是虛擬機通過 DHCP 自動獲取 IP,實現與宿主機或宿主機以外的世界通信,小白親測有效。
Docker bridge 模式
大致清楚 Linux bridge 模式后,再來看 Docker bridge 模式,小白也有了信心。再次翻開《 Docker 進階與實戰》,仔細閱讀后小白了解到在該 bridge 模式下,Docker Daemon 會創建出一個名為 docker0 的 虛擬網橋 ,用來連接 宿主機 與 容器 ,或者連接 不同的容器 ,書中的介紹與小白之前的假設也不謀而合。
Docker 利用 veth pair [注釋1]技術,在宿主機上創建了兩個虛擬網絡接口 veth0 和 veth1(veth pair 技術的特性可以保證無論哪一個 veth 接收到網絡報文,都會無條件地傳輸給另一方)。

容器與宿主機通信: 在橋接模式下,Docker Daemon 將 veth0 附加到 docker0 網橋上,保證宿主機的報文有能力發往 veth0。再將 veth1 添加到 Docker 容器所屬的網絡命名空間[注釋2],保證宿主機的網絡報文若發往 veth0 可以立即被 veth1 收到。
容器與外界通信: 容器如果需要聯網,則需要采用 NAT [注釋2] 方式。準確的說,是 NATP (網絡地址端口轉換) 方式。NATP 包含兩種轉換方式:SNAT 和 DNAT 。
- 目的 NAT (Destination NAT,DNAT): 修改數據包的目的地址。
當宿主機以外的世界需要訪問容器時,數據包的流向如下圖所示:

由于容器的 IP 與端口對外都是不可見的,所以數據包的目的地址為 宿主機 的 ip 和 端口 ,為 192.168.1.10:24 。
數據包經過路由器發給宿主機 eth0,再經 eth0 轉發給 docker0 網橋。由于存在 DNAT 規則,會將數據包的目的地址轉換為 容器 的 ip 和 端口 ,為 172.17.0.n:24 。
宿主機上的 docker0 網橋識別到容器 ip 和端口,于是將數據包發送附加到 docker0 網橋上的 veth0 接口,veth0 接口再將數據包發送給容器內部的 veth1 接口,容器接收數據包并作出響應。

- 源 NAT (Source NAT,SNAT): 修改數據包的源地址。
當容器需要訪問宿主機以外的世界時,數據包的流向為下圖所示:

此時數據包的源地址為 容器 的 ip 和 端口 ,為 172.17.0.n:24,容器內部的 veth1 接口將數據包發往 veth0 接口,到達 docker0 網橋。
宿主機上的 docker0 網橋發現數據包的目的地址為外界的 IP 和端口,便會將數據包轉發給 eth0 ,并從 eth0 發出去。由于存在 SNAT 規則,會將數據包的源地址轉換為 宿主機 的 ip 和 端口 ,為 192.168.1.10:24 。
由于路由器可以識別到宿主機的 ip 地址,所以再將數據包轉發給外界,外界接受數據包并作出響應。這時候,在外界看來,這個數據包就是從 192.168.1.10:24 上發出來的,Docker 容器對外是不可見的。

小結
小白的容器網絡學習只是剛剛開了頭,竟也能折騰出這么多玩兒意,有 veth pair,有網絡命名空間, 還有 NAT 。雖說 docker bridge 模式僅僅是容器網絡的冰山一角,后面的學習之路仍然且行且艱辛,但小白也掌握了一些學習經驗,那就是面對錯綜復雜的網絡模式,首先需要識其筋骨勝肉,抓住本質含義。小白就是憑著對 bridge 的理解,才展開了一系列大膽的假設,帶著問題最終在書中得到求證。Disappointing, but not fatal。
[注釋1] veth pair是用于不同network namespace間進行通信的方式,veth pair 將一個 network namespace 數據發往另一個 network namespace 的 veth。
[注釋2] 網絡命名空間是用于隔離網絡資源(/proc/net、IP 地址、網卡、路由等)。由于一個物理的網絡設備最多存放在一個網絡命名空間中,所以通過 veth pair 在不同的網絡命名空間中創建通道,才能達到通信的目的。
[注釋3] NAT 為網絡地址轉換(Network Address Translation)的縮寫,是一種在 ip 數據包通過路由器或防火墻時重寫來源 ip 地址或目的 ip 地址的技術。
來自:http://blog.daocloud.io/docker-bridge/