eth和veth
❶ 四、Docker網路揭秘
Docker 之所以功能這么強大,其實就是充分利用了Linux Kernel的特性:NameSpace、CGroups、UnionFileSystem。通過這些特性實現了資源隔離、限制與分層等。本文這次就來揭曉Docker中的容器是如何做到網路互通的。
兩台機器如果要實現通信,其實就是通過底層的網卡進行數據傳輸,每個網卡都有一個唯一的MAC地址,網卡又會綁定一個ip地址,只要兩台機器的網路可以互通,那麼這兩台機器就可以進行通信。
想要實現通信,就得有兩個同一網段的網卡,兩個網卡必須是可以 ping 通的。
Docker在安裝成功後,會在宿主機創建一個docker0網卡,這個網卡就是負責容器與宿主機之間通信的橋梁。
通過Docker創建一個容器之後,會在宿主機再創建一個網卡,也就是上面的 veth3543ea3@if7 ,容器內也會創建一個網卡。
一般成對的網卡,網卡組件名稱後面的數字是連續的,比如宿主機的 @if7 和容器內的 @if8 ,正是這成對的網卡,才實現了容器與宿主機之間的通信。這其實是利用 Linux Kernel 的特性 NameSpace 實現的網卡隔離,不同NameSpace下的網卡是獨立的,就像Java程序中的 package 一樣。
從上面的例子中看到容器與宿主機之間的通信好像並不是通過docker0網卡實現的?
其實這只是單容器的狀態,可能看不出docker0的作用。用圖來表示一下單容器的網卡通信情況。
通過docker生成的 eth0 和 veth 兩個網卡實現同一網段內的通信,而這個網卡又是橋接在docker0網卡上的。
再看下有多個容器的情況。
兩個容器之間可以互相通信的原因就是因為docker0的存在,因為它們的網卡都是橋接在docker0上,所以也就有了和另一個容器通信的橋。
我們來驗證一下是不是這樣!
這種網路連接方法我們稱為Bridge,這也是docker中默認的網路模式。可以通過命令查看docker中的網路模式:
通過 docker network ls 命令查看到,docker提供了3種網路模式,brige模式我們已經知道了,那 host 和 none 又是什麼意思呢?不妨來驗證一下:
這種模式只會創建一個本地的環路網卡,無法與其他容器或宿主機進行通信。
在創建自己的network之前先來解釋一下為什麼要創建新的network。
我們用一個例子來演示一下不同容器之間的通信。
容器之間通過 ip 是可以正常訪問的,但是有沒有這種情況:如果一個容器出問題了,我們重啟之後它的ip變了,那是不是其他用到這個容器的項目配置是不是都得改。
有沒有可能直接通過容器名稱來訪問呢?來驗證一下:
發現並不能 ping 通,但是可以使用別的手段來達到這個目的。
通過上面這種方式就可以做到以容器名來 ping 通其他容器,其實它就跟windows系統中在 hosts 文件里加了個映射是一樣的。
可以看到創建自定義的 network 自動幫我們實現了這個功能,而且使用自定義的 network 也方便管理,不同業務類型的容器可以指定不同的 network。
不同的 network ip網段也不一樣,這樣也可以增加單機中可以創建的容器的數量。
在創建一個容器的時候,一般都需要講容器需要暴露的埠映射到物理主機的相同埠或其他埠,因為在外網環境下是不方便直接連接到容器的,所以需要通過映射埠的方式,讓外網訪問宿主機的映射埠來訪問容器。
如果想建立多個容器,勢必需要埠映射,以滿足不同容器使用相同埠的情況。
以上這些內容都是在單容器進行操作,容器之間通信也只是通過 docker0 實現的橋接模式。
如果要實現多機之間的docker通信,其實還是通過網卡,只不過需要其他的技術來實現了。
本章節就不在演示,到後面的章節再來分析!
❷ Docker網路
Docker網路
使用docker0網橋,docker0的默認網段是172.17.0.0,網關地址為172.17.0.1,通過bridge模式啟動的容器,進入容器日內部並使用ip route show指令可以看到其使用的網關就是docker0的網關地址。
在宿主機上通過brctl show docker0可以看到docker0橋接的網卡與啟動的容器的veth一致。
容器之間的通訊:
從a容器ping b容器
1、數據包從a容器對應的veth流到docker0
2、docker0廣播arp尋找b容器對應的地址
3、b容器的veth收到廣播並回復docker0自己就是目標
4、a容器與b容器建立連接並實現通信
容器與外網通訊
1、veth數據流到docker0網橋
2、docker0網橋流量流向eth0
3、eth0流量流向對應的目標
只要是eth0可以訪問到的,容器就可以訪問到
直接使用宿主機的網路,無法指定出入的流量以及暴露的埠,默認暴露出去的地址是0.0.0.0:xxxx,可以直接訪問(TODO())。
通過docker指令創建自定義的網橋,由於默認的bridge模式無法為container指定ip,所以需要通過自定義網橋的方式來實現,且自定義網橋可以限制容器之間的相互訪問,跨網橋無法互相訪問。
使用指定網橋的時候只要通過network指定網橋名稱即可,且使用自定義的網橋可以指定容器的IP。並且由於是固定IP,就可以通過iptable來做各種的規則。
這個模式就是指定一個已有的容器,共享該容器的IP和埠。除了網路方面兩個容器共享,其他的如文件系統,進程等還是隔離開的。
這個模式下,dokcer不為容器進行任何網路配置。需要我們自己為容器添加網卡,配置IP。
Docker是通過iptable的方式實現流量的控制的
通過添加iptable規則實現
該指令添加了一條nat規則,把所有來自 172.17.0.0/16 網段且即將流出本主機的數據包的源 IP 地址都修改為 10.0.0.100(建議通過添加自定義網橋的方式來做)。
直接通過-p的方式暴露一個埠出去,默認使用的是0.0.0.0,所有網卡均可訪問,也可以在參數中指定特定的網卡,例如:-p 192.168.1.1:80:80來指定只能通過該網卡來訪問。
查看nat表
iptables -L -n -t nat --line-numbers
查看路由規則
route -n
❸ 單host下Docker的默認網路配置
本文用到的環境如下:
host: centos7
docker: 通過 yum install -y docker 安裝,版本號為1.10.3
docker鏡像:
# Version: 0.0.1 FROM ubuntu:latest MAINTAINER paul liu "[email protected]" RUN apt-get update RUN apt-get install -y net-tools RUN apt-get install -y iputils-ping CMD /bin/bash
場景圖:
我的host主機接有無線路由器,通過ADSL撥號上網,網卡eth0固定IP為192.168.0.200,網關為路由器的IP 192.168.0.1。
在host上安裝docker,並運行容器。
通過以下命令安裝docker,
yum install -y docker
啟用docker,
systemctl start docker
然後在host主機運行 ifconfig 或 ip a 命令,可以看到除去host原有的網卡eth0和回環lo外,多了個docker0。
docker0 IP為172.17.0.1,所在的網段默認為B類私網地址172.17.0.0/16。可以將docker0看做是host主機的一塊虛擬網卡。這樣host主機就等同於配置了雙網卡,兩塊網卡之間可以通信,但前提是啟用ip_forward。
這是docker0的第一個身份。
運行兩個容器docker1,docker2,然後在host主機上運行 brctl show 查看,
這里可以看出docker0的第二個身份,一個虛擬交換機。每運行一個容器,就會產生一對veth,其中一端連接到docker0上,另一端連接到容器的eth0上。這樣,所有連接到docker0的容器組成了一個區域網。如下圖:
在host主機上運行 ifconfig ,也會發現多了兩個veth這樣的網路介面。
在host主機上運行 ip addr show veth6d9a691 ,可以查看到該veth具有mac地址,這也正說明了docker0的虛擬交換機的身份,交換機是通過mac地址通信的,連接到交換機的設備必須具有mac地址。
由於docker0自身也具有mac地址,這個與純二層交換機是不同的,並且綁定了IP 172.17.0.1,容器默認把docker0作為了網關。也就是docker0還兼具路由的功能,因此可以把docker0看做是一個三層交換機,可以做二層數據包轉發,也可以做三層路由轉發。
在容器中運行 route -n 查看路由如下:
在host主機上運行 route -n 查看路由如下:
在host中,訪問本網段192.168.0.0是通過eth0轉發數據包的,訪問172.17.0.0網段是通過docker0轉發數據包的,而對於其他如公網是通過eth0將數據包轉發給網關192.168.0.1,再由該網關進行數據包轉發的,比如上網。
在容器中運行 ping sohu.com 或 ping 192.168.0.200 都可以ping通。
默認情況下,不需要再額外做任何配置,在一台host主機上,通過docker0,各容器之間可以互通,並且可以通過host的eth0連接外網。
通俗的講,通過docker0組成了一個網段為172.17.0.0/16的乙太網,docker容器發起請求時,如果是相同網段則經由docker0轉發到目標機器,如果是不同網段,則經由docker0,轉發到host的另一塊網卡eth0上,由eth0負責下一步的數據包轉發,比如公網地址。
下面進一步分析一下報文是怎麼發送到外面的。
容器內部發送一條公網請求報文,通過eth0,在veth被接收。此時報文已經來到了主機上,通過查詢主機的路由表( route -n ),如果發現報文應該通過主機的eth0,從默認網關發送出去,那麼報文就被從docker0轉發給主機的eth0,但前提是首先啟用ip_forward功能,才能在host主機的docker0和eth0兩個網卡間傳遞數據包。
由於目標地址並不屬於host主機所在網段,那麼會匹配機器上的 iptables中的nat表POSTROUTING鏈中的規則。
在host主機運行命令 iptables -L -n -t nat --line-numbers ,查看nat表,這里只看POSTROUTING鏈:
第一行中說明,對於源地址為172.17.0.0/16網段的數據包,發出去之前通過MQSQUERADE偽裝。linux內核會修改數據包源地址為host主機eth0的地址(也就是192.168.0.200),然後把報文轉發出去。對於外部來說,報文是從主機eth0發送出去的。
區域網內的機器由於都是私有IP,是無法直接訪問互聯網的(數據包可以發出去,但回不來。)如果要上網,除了可以通過硬體路由器,也可以通過軟體路由,在iptables的nat表中的POSTROUTING鏈中添加SNAT規則。
測試一下,在host主機運行命令 iptables -t nat -D POSTROUTING 1 將第一條規則刪掉,那麼在容器中就運行命令 ping sohu.com 就ping不通了。但仍然可以ping通host主機。
在host主機運行命令以下命令恢復:
iptables -t nat -I POSTROUTING -s 172.17.0.0/16 -o eth0 -j SNAT --to-source 192.168.0.200
或者
iptables -t nat -I POSTROUTING -s 172.17.0.0/16 -j MASQUERADE
關於SNAT和MASQUERADE,這篇文章已經有過描述,可以參考: Docker前傳之linux iptables
新建一Dockerfile,用以運行nginx容器:
# Version: 0.0.1 FROM paulliu/ubuntu_ip RUN apt-get install -y nginx EXPOSE 80
在host主機運行構建命令構建鏡像 docker build -t paulliu/nginx .
在host主機運行容器啟動命令 docker run -d -p 80 --name nginx1 paulliu/nginx nginx -g "daemon off;"
在host主機查看容器的埠映射 docker port nginx1 80
在host主機運行命令 iptables -nat -L -n 可以看到在PREROUTING鏈中多了以下DNAT規則:
也就是在容器啟動時通過 -p 80 將host主機192.168.0.200:32773映射為容器172.17.0.4:80。
注意:docker容器每次啟動時獲取的IP地址未必是一樣的,而且 -p 80 是在host主機上隨機選擇一個埠號進行映射,每次啟動的埠號也未必是一樣的。但iptables中相關的規則是自動變更的。
在host主機運行 curl localhost:32773 或者在其他主機運行 curl 192.168.0.200:32773 結果如下: