Docker(十四)-Docker四種網路模式
Docker 安裝時會自動在 host 上建立三個網路,我們可用 docker network ls
命令檢視:
- none模式,使用--net=none指定,該模式關閉了容器的網路功能。
- host模式,使用--net=host指定,容器將不會虛擬出自己的網絡卡,配置自己的IP等,而是使用宿主機的IP和埠。
- bridge模式,使用--net=bridge指定,預設設定 ,此模式會為每一個容器分配、設定IP等,並將容器連線到一個docker0虛擬網橋,通過docker0網橋以及Iptables nat表配置與宿主機通訊。
- container模式,使用--net=container:NAME_or_ID指定,建立的容器不會建立自己的網絡卡,配置自己的IP,而是和一個指定的容器共享IP、埠範圍。
none 網路
none沒有網路。掛在這個網路下的容器除了 lo,沒有其他任何網絡卡。容器建立時,可以通過 --network=none
指定使用 none 網路。
host 網路
連線到 host 網路的容器共享 Docker host 的網路棧,容器的網路配置與 host 完全一樣。可以通過 --network=host
指定使用 host 網路。
直接使用 Docker host 的網路最大的好處就是效能,如果容器對網路傳輸效率有較高要求,則可以選擇 host 網路。當然不便之處就是犧牲一些靈活性,比如要考慮埠衝突問題,Docker host 上已經使用的埠就不能再用了。
Docker host 的另一個用途是讓容器可以直接配置 host 網路。比如某些跨 host 的網路解決方案,其本身也是以容器方式執行的,這些方案需要對網路進行配置,比如管理 iptables。
bridge 網路
Docker 安裝時會建立一個 命名為 docker0
的 linux bridge。如果不指定--network
,建立的容器預設都會掛到 docker0
上
brctl show #檢視bridge網路 yum install bridge-utils
docker network inspect bridge #檢視bridge 網路的詳細資訊
當前 docker0 上沒有任何其他網路裝置,我們建立一個容器看看有什麼變化。
一個新的網路介面 veth28c57df
被掛到了 docker0
上,veth28c57df
就是新建立容器的虛擬網絡卡。
下面看一下容器的網路配置。
容器有一個網絡卡 [email protected]
。大家可能會問了,為什麼不是veth28c57df
呢?
實際上 [email protected]
和 veth28c57df
是一對 veth pair。veth pair 是一種成對出現的特殊網路裝置,可以把它們想象成由一根虛擬網線連線起來的一對網絡卡,網絡卡的一頭([email protected]
)在容器中,另一頭(veth28c57df
)掛在網橋 docker0
上,其效果就是將 [email protected]
也掛在了 docker0
上。
我們還看到 [email protected]
已經配置了 IP 172.17.0.2
,為什麼是這個網段呢?讓我們通過 docker network inspect bridge
看一下 bridge 網路的配置資訊:
原來 bridge 網路配置的 subnet 就是 172.17.0.0/16,並且閘道器是 172.17.0.1。這個閘道器在哪兒呢?大概你已經猜出來了,就是 docker0。
當前容器網路拓撲結構如圖所示:
容器建立時,docker 會自動從 172.17.0.0/16 中分配一個 IP,這裡 16 位的掩碼保證有足夠多的 IP 可以供容器使用。
除了 none, host, bridge 這三個自動建立的網路,使用者也可以根據業務需要建立 user-defined 網路。
user-defined 網路
Docker 提供三種 user-defined 網路驅動:bridge, overlay 和 macvlan。overlay 和 macvlan 用於建立跨主機的網路。
我們可通過 bridge 驅動建立類似前面預設的 bridge 網路,例如:
檢視一下當前 host 的網路結構變化:
新增了一個網橋 br-eaed97dc9a77
,這裡 eaed97dc9a77
正好新建 bridge 網路 my_net
的短 id。執行 docker network inspect
檢視一下 my_net
的配置資訊:
這裡 172.18.0.0/16 是 Docker 自動分配的 IP 網段。
我們可以自己指定 IP 網段嗎?
答案是:可以。
只需在建立網段時指定 --subnet
和 --gateway
引數:
這裡我們建立了新的 bridge 網路 my_net2
,網段為 172.22.16.0/24,閘道器為 172.22.16.1。與前面一樣,閘道器在 my_net2
對應的網橋 br-5d863e9f78b6
上:
容器要使用新的網路,需要在啟動時通過 --network
指定:
容器分配到的 IP 為 172.22.16.2。
到目前為止,容器的 IP 都是 docker 自動從 subnet 中分配,我們能否指定一個靜態 IP 呢?
答案是:可以,通過--ip
指定。
注:只有使用 --subnet
建立的網路才能指定靜態 IP。
my_net
建立時沒有指定 --subnet
,如果指定靜態 IP 報錯如下:
好了,我們來看看當前 docker host 的網路拓撲結構。
通過前面小節的實踐,當前 docker host 的網路拓撲結構如下圖所示,今天我們將討論這幾個容器之間的連通性。
兩個 busybox 容器都掛在 my_net2 上,應該能夠互通,我們驗證一下:
可見同一網路中的容器、閘道器之間都是可以通訊的。
my_net2
與預設 bridge 網路能通訊嗎?
從拓撲圖可知,兩個網路屬於不同的網橋,應該不能通訊,我們通過實驗驗證一下,讓 busybox 容器 ping httpd 容器:
確實 ping 不通,符合預期。
“等等!不同的網路如果加上路由應該就可以通訊了吧?”我已經聽到有讀者在建議了。
這是一個非常非常好的想法。
確實,如果 host 上對每個網路的都有一條路由,同時作業系統上打開了 ip forwarding,host 就成了一個路由器,掛接在不同網橋上的網路就能夠相互通訊。下面我們來看看 docker host 滿不滿足這些條件呢?
ip r
檢視 host 上的路由表:
# ip r
......
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
172.22.16.0/24 dev br-5d863e9f78b6 proto kernel scope link src 172.22.16.1
......
172.17.0.0/16 和 172.22.16.0/24 兩個網路的路由都定義好了。再看看 ip forwarding:
# sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1
ip forwarding 也已經啟用了。
條件都滿足,為什麼不能通行呢?
我們還得看看 iptables:
# iptables-save
......
-A DOCKER-ISOLATION -i br-5d863e9f78b6 -o docker0 -j DROP
-A DOCKER-ISOLATION -i docker0 -o br-5d863e9f78b6 -j DROP
......
原因就在這裡了:iptables DROP 掉了網橋 docker0 與 br-5d863e9f78b6 之間雙向的流量。
從規則的命名 DOCKER-ISOLATION
可知 docker 在設計上就是要隔離不同的 netwrok。
那麼接下來的問題是:怎樣才能讓 busybox 與 httpd 通訊呢?
答案是:為 httpd 容器新增一塊 net_my2 的網絡卡。這個可以通過docker network connect
命令實現。
我們在 httpd 容器中檢視一下網路配置:
容器中增加了一個網絡卡 eth1,分配了 my_net2 的 IP 172.22.16.3。現在 busybox 應該能夠訪問 httpd 了,驗證一下:
busybox 能夠 ping 到 httpd,並且可以訪問 httpd 的 web 服務。當前網路結構如圖所示:
容器之間可通過 IP,Docker DNS Server 或 joined 容器三種方式通訊
IP 通訊
從上一節的例子可以得出這樣一個結論:兩個容器要能通訊,必須要有屬於同一個網路的網絡卡。
滿足這個條件後,容器就可以通過 IP 互動了。具體做法是在容器建立時通過 --network
指定相應的網路,或者通過 docker network connect
將現有容器加入到指定網路。可參考上一節 httpd 和 busybox 的例子,這裡不再贅述。
Docker DNS Server
通過 IP 訪問容器雖然滿足了通訊的需求,但還是不夠靈活。因為我們在部署應用之前可能無法確定 IP,部署之後再指定要訪問的 IP 會比較麻煩。對於這個問題,可以通過 docker 自帶的 DNS 服務解決。
從 Docker 1.10 版本開始,docker daemon 實現了一個內嵌的 DNS server,使容器可以直接通過“容器名”通訊。方法很簡單,只要在啟動時用 --name
為容器命名就可以了。
下面啟動兩個容器 bbox1 和 bbox2:
docker run -it --network=my_net2 --name=bbox1 busybox
docker run -it --network=my_net2 --name=bbox2 busybox
然後,bbox2 就可以直接 ping 到 bbox1 了:
使用 docker DNS 有個限制:只能在 user-defined 網路中使用。也就是說,預設的 bridge 網路是無法使用 DNS 的。下面驗證一下:
建立 bbox3 和 bbox4,均連線到 bridge 網路。
docker run -it --name=bbox3 busybox
docker run -it --name=bbox4 busybox
bbox4 無法 ping 到 bbox3。
joined 容器
joined 容器是另一種實現容器間通訊的方式。
joined 容器非常特別,它可以使兩個或多個容器共享一個網路棧,共享網絡卡和配置資訊,joined 容器之間可以通過 127.0.0.1 直接通訊。請看下面的例子:
先建立一個 httpd 容器,名字為 web1。
docker run -d -it --name=web1 httpd 然後建立 busybox 容器並通過 --network=container:web1
指定 jointed 容器為 web1:
請注意 busybox 容器中的網路配置資訊,下面我們檢視一下 web1 的網路:
看!busybox 和 web1 的網絡卡 mac 地址與 IP 完全一樣,它們共享了相同的網路棧。busybox 可以直接用 127.0.0.1 訪問 web1 的 http 服務。
joined 容器非常適合以下場景:
-
不同容器中的程式希望通過 loopback 高效快速地通訊,比如 web server 與 app server。
-
希望監控其他容器的網路流量,比如執行在獨立容器中的網路監控程式。