本文用到的環(huán)境如下:
host: centos7
docker: 通過yum install -y docker安裝,版本號為1.10.3
docker鏡像:
# Version: 0.0.1 FROM ubuntu:latest MAINTAINER paul liu "pollux.liu@msn.com" RUN apt-get update RUN apt-get install -y net-tools RUN apt-get install -y iputils-ping CMD /bin/bash
場景圖:

我的host主機接有無線路由器,通過ADSL撥號上網(wǎng),網(wǎng)卡eth0固定IP為192.168.0.200,網(wǎng)關為路由器的IP 192.168.0.1。
在host上安裝docker,并運行容器。
docker0的作用
通過以下命令安裝docker,
yum install -y docker
啟用docker,
systemctl start docker
然后在host主機運行ifconfig或ip a命令,可以看到除去host原有的網(wǎng)卡eth0和回環(huán)lo外,多了個docker0。

docker0 IP為172.17.0.1,所在的網(wǎng)段默認為B類私網(wǎng)地址172.17.0.0/16。可以將docker0看做是host主機的一塊虛擬網(wǎng)卡。這樣host主機就等同于配置了雙網(wǎng)卡,兩塊網(wǎng)卡之間可以通信,但前提是啟用ip_forward。
這是docker0的第一個身份。
運行兩個容器docker1,docker2,然后在host主機上運行brctl show查看,

這里可以看出docker0的第二個身份,一個虛擬交換機。每運行一個容器,就會產(chǎn)生一對veth,其中一端連接到docker0上,另一端連接到容器的eth0上。這樣,所有連接到docker0的容器組成了一個局域網(wǎng)。如下圖:

在host主機上運行ifconfig,也會發(fā)現(xiàn)多了兩個veth這樣的網(wǎng)絡接口。
在host主機上運行ip addr show veth6d9a691,可以查看到該veth具有mac地址,這也正說明了docker0的虛擬交換機的身份,交換機是通過mac地址通信的,連接到交換機的設備必須具有mac地址。

由于docker0自身也具有mac地址,這個與純二層交換機是不同的,并且綁定了IP 172.17.0.1,容器默認把docker0作為了網(wǎng)關。也就是docker0還兼具路由的功能,因此可以把docker0看做是一個三層交換機,可以做二層數(shù)據(jù)包轉(zhuǎn)發(fā),也可以做三層路由轉(zhuǎn)發(fā)。
在容器中運行route -n查看路由如下:

在host主機上運行route -n查看路由如下:

在host中,訪問本網(wǎng)段192.168.0.0是通過eth0轉(zhuǎn)發(fā)數(shù)據(jù)包的,訪問172.17.0.0網(wǎng)段是通過docker0轉(zhuǎn)發(fā)數(shù)據(jù)包的,而對于其他如公網(wǎng)是通過eth0將數(shù)據(jù)包轉(zhuǎn)發(fā)給網(wǎng)關192.168.0.1,再由該網(wǎng)關進行數(shù)據(jù)包轉(zhuǎn)發(fā)的,比如上網(wǎng)。
容器是如何連接到外部網(wǎng)絡的
在容器中運行ping sohu.com或ping 192.168.0.200都可以ping通。
默認情況下,不需要再額外做任何配置,在一臺host主機上,通過docker0,各容器之間可以互通,并且可以通過host的eth0連接外網(wǎng)。
通俗的講,通過docker0組成了一個網(wǎng)段為172.17.0.0/16的以太網(wǎng),docker容器發(fā)起請求時,如果是相同網(wǎng)段則經(jīng)由docker0轉(zhuǎn)發(fā)到目標機器,如果是不同網(wǎng)段,則經(jīng)由docker0,轉(zhuǎn)發(fā)到host的另一塊網(wǎng)卡eth0上,由eth0負責下一步的數(shù)據(jù)包轉(zhuǎn)發(fā),比如公網(wǎng)地址。
下面進一步分析一下報文是怎么發(fā)送到外面的。
容器內(nèi)部發(fā)送一條公網(wǎng)請求報文,通過eth0,在veth被接收。此時報文已經(jīng)來到了主機上,通過查詢主機的路由表(route -n),如果發(fā)現(xiàn)報文應該通過主機的eth0,從默認網(wǎng)關發(fā)送出去,那么報文就被從docker0轉(zhuǎn)發(fā)給主機的eth0,但前提是首先啟用ip_forward功能,才能在host主機的docker0和eth0兩個網(wǎng)卡間傳遞數(shù)據(jù)包。
由于目標地址并不屬于host主機所在網(wǎng)段,那么會匹配機器上的 iptables中的nat表POSTROUTING鏈中的規(guī)則。
在host主機運行命令iptables -L -n -t nat --line-numbers,查看nat表,這里只看POSTROUTING鏈:

第一行中說明,對于源地址為172.17.0.0/16網(wǎng)段的數(shù)據(jù)包,發(fā)出去之前通過MQSQUERADE偽裝。linux內(nèi)核會修改數(shù)據(jù)包源地址為host主機eth0的地址(也就是192.168.0.200),然后把報文轉(zhuǎn)發(fā)出去。對于外部來說,報文是從主機eth0發(fā)送出去的。
局域網(wǎng)內(nèi)的機器由于都是私有IP,是無法直接訪問互聯(lián)網(wǎng)的(數(shù)據(jù)包可以發(fā)出去,但回不來。)如果要上網(wǎng),除了可以通過硬件路由器,也可以通過軟件路由,在iptables的nat表中的POSTROUTING鏈中添加SNAT規(guī)則。
測試一下,在host主機運行命令iptables -t nat -D POSTROUTING 1將第一條規(guī)則刪掉,那么在容器中就運行命令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,這篇文章已經(jīng)有過描述,可以參考:Docker前傳之linux iptables
外部網(wǎng)絡如何訪問容器提供的服務
新建一Dockerfile,用以運行nginx容器:
# Version: 0.0.1 FROM paulliu/ubuntu_ip RUN apt-get install -y nginx EXPOSE 80
在host主機運行構(gòu)建命令構(gòu)建鏡像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規(guī)則:

也就是在容器啟動時通過-p 80將host主機192.168.0.200:32773映射為容器172.17.0.4:80。
注意:docker容器每次啟動時獲取的IP地址未必是一樣的,而且-p 80是在host主機上隨機選擇一個端口號進行映射,每次啟動的端口號也未必是一樣的。但iptables中相關的規(guī)則是自動變更的。
在host主機運行curl localhost:32773或者在其他主機運行curl 192.168.0.200:32773結(jié)果如下:
