1. 程式人生 > >從Docker零基礎到懂一點實踐教程(七)

從Docker零基礎到懂一點實踐教程(七)

Docker容器的網路連線

Docker容器的網路基礎

docker0

通過ifconfig命令我們可以檢視到一個名為“docker0”的虛擬網橋,Docker就是通過這個網路裝置為容器提供各種網路服務的。

schen@scvmu01:~/dockerfile/df_test7$ ifconfig docker0
docker0   Link encap:Ethernet  HWaddr 02:42:50:85:11:08  
          inet addr:172.17.0.1  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::
42:50ff:fe85:1108/64 Scope:Link UP BROADCAST MULTICAST MTU:1500 Metric:1 RX packets:45565 errors:0 dropped:0 overruns:0 frame:0 TX packets:57804 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:2637647 (2.6 MB) TX bytes:125311929 (125.3
MB) schen@scvmu01:~/dockerfile/df_test7$

在OSI七層模型中網橋是一個單純的資料鏈路層裝置,但是Linux的虛擬網橋卻有一些不同的特點:
1. 可以設定IP地址
2. 相當於擁有一個隱藏的虛擬網絡卡

在Linux中虛擬網橋是通用網路裝置抽象的一種,我們可以為其分配IP地址,當虛擬網橋擁有IP地址後,Linux就可以通過路由表在網路層定位這個裝置,這就相當於擁有了一個隱藏的虛擬網絡卡,而這個虛擬網絡卡的名字就是虛擬網橋的名字。

在一個容器啟動時,Docker會自動建立網路連線的兩端,一端是在容器中的網路裝置“eth0”,另一端是在執行docker守護程序的主機上,以“veth”開頭的網路介面,用來實現“docker0”網橋與容器之間的網路通訊。

schen@scvmu01:~$ sudo brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.02420a2cd5c8       no
schen@scvmu01:~$ 
schen@scvmu01:~$ docker run -it --name nw_test1 ubuntu /bin/bash
root@78890c5328d8:/# schen@scvmu01:~$ 
schen@scvmu01:~$ 
schen@scvmu01:~$ sudo brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.02420a2cd5c8       no              veth15614bf
schen@scvmu01:~$ 

我們看到Docker在網橋“docker0”上添加了一個名為“veth15614bf”的網路介面,而使用ifconfig命令也同樣可以看到:

schen@scvmu01:~$ ifconfig
docker0   Link encap:Ethernet  HWaddr 02:42:0a:2c:d5:c8  
          inet addr:172.17.0.1  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::42:aff:fe2c:d5c8/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:16 errors:0 dropped:0 overruns:0 frame:0
          TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:1072 (1.0 KB)  TX bytes:648 (648.0 B)

enp0s3    Link encap:Ethernet  HWaddr 08:00:27:91:ae:e1  
          inet addr:192.168.199.202  Bcast:192.168.199.255  Mask:255.255.255.0
          inet6 addr: fe80::a00:27ff:fe91:aee1/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:1027 errors:0 dropped:0 overruns:0 frame:0
          TX packets:666 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:178876 (178.8 KB)  TX bytes:101849 (101.8 KB)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:284 errors:0 dropped:0 overruns:0 frame:0
          TX packets:284 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1 
          RX bytes:23376 (23.3 KB)  TX bytes:23376 (23.3 KB)

veth15614bf Link encap:Ethernet  HWaddr 46:82:38:62:be:be  
          inet6 addr: fe80::4482:38ff:fe62:bebe/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:8 errors:0 dropped:0 overruns:0 frame:0
          TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:648 (648.0 B)  TX bytes:648 (648.0 B)

schen@scvmu01:~$ 

備註:檢視網橋所需的工具brctl可以通過sudo apt-get install bridge-utils命令獲取。

自定義虛擬網橋

當“docker0”提供的IP地址範圍不能滿足我們的需求時,我們可以建立一個自定義的虛擬網橋並讓Docker使用它,具體步驟是:
1. 新增一個虛擬網橋 sudo brctl addbr br0
2. 配置這個虛擬網橋 sudo ifconfig br0 192.168.100.1 netmask 255.255.255.0
3. 更改Docker守護程序的啟動配置 DOCKER_OPTS="-b=br0"

schen@scvmu01:~$ sudo brctl addbr br0
schen@scvmu01:~$ 
schen@scvmu01:~$ sudo ifconfig br0 192.168.200.1 netmask 255.255.255.0
schen@scvmu01:~$ 
schen@scvmu01:~$ ifconfig br0
br0       Link encap:Ethernet  HWaddr 2e:1b:de:5d:1c:32  
          inet addr:192.168.200.1  Bcast:192.168.200.255  Mask:255.255.255.0
          inet6 addr: fe80::2c1b:deff:fe5d:1c32/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:7 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 B)  TX bytes:578 (578.0 B)

schen@scvmu01:~$ 
schen@scvmu01:~$ sudo vi /etc/default/docker 
schen@scvmu01:~$ 
schen@scvmu01:~$ grep "^DOCKER_OPTS" /etc/default/docker 
DOCKER_OPTS="-b=br0"
schen@scvmu01:~$ 
schen@scvmu01:~$ sudo service docker restart
schen@scvmu01:~$ 
schen@scvmu01:~$ ps -ef | grep dockerd
root      3381     1  6 22:25 ?        00:00:02 dockerd -H fd:// -b=br0
schen     3506  2889  0 22:25 pts/2    00:00:00 grep --color=auto dockerd
schen@scvmu01:~$ 
schen@scvmu01:~$ docker run -it --name nw_test2 ubuntu /bin/bash
root@c4b71a368aed:/# schen@scvmu01:~$ 
schen@scvmu01:~$ 
schen@scvmu01:~$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
c4b71a368aed        ubuntu              "/bin/bash"         19 seconds ago      Up 16 seconds                           nw_test2
schen@scvmu01:~$ 
schen@scvmu01:~$ sudo brctl show
bridge name     bridge id               STP enabled     interfaces
br0             8000.727c26f1e078       no              vethdeb7922
docker0         8000.02420a2cd5c8       no
schen@scvmu01:~$ 
schen@scvmu01:~$ ifconfig vethdeb7922
vethdeb7922 Link encap:Ethernet  HWaddr 72:7c:26:f1:e0:78  
          inet6 addr: fe80::707c:26ff:fef1:e078/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:8 errors:0 dropped:0 overruns:0 frame:0
          TX packets:24 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:648 (648.0 B)  TX bytes:3431 (3.4 KB)

schen@scvmu01:~$ 
schen@scvmu01:~$ docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' nw_test2
192.168.200.2
schen@scvmu01:~$ 

我們看到這個容器已經連線到了我們自定義的網橋“br0”,它的IP地址屬於我們指定的網段。

Docker容器的互聯

允許所有容器間互聯

之前我們講過,同一主機上的容器是通過虛擬網橋進行連線的,因此預設狀態下,它們之間是可以互相訪問的。與此同時,Docker也提供了一個關於容器互聯的選項--icc,在預設情況下它的值為“true”,表示允許容器之間的互相連線。

為了演示,我們使用如下Dockerfile進行構建,在這一節中的所有實驗,我們使用的都是同一個映象:

schen@scvmu01:~/dockerfile/cct_test$ cat Dockerfile 
# Container connection test
FROM ubuntu:16.04
RUN apt-get update
RUN apt-get install -y iputils-ping
RUN apt-get install -y nginx
RUN apt-get install -y curl
RUN apt-get install -y libnet-ifconfig-wrapper-perl
EXPOSE 80
CMD /bin/bash
schen@scvmu01:~/dockerfile/cct_test$ 
schen@scvmu01:~/dockerfile/cct_test$ docker build -t shichen/cct .
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM ubuntu:16.04
 ---> bd3d4369aebc
Step 2 : RUN apt-get update
 ---> Using cache
 ---> 1f8637646fb7
Step 3 : RUN apt-get install -y iputils-ping
 ---> Using cache
 ---> be6a0581ce76
Step 4 : RUN apt-get install -y nginx
 ---> Using cache
 ---> eabc0b3001b4
Step 5 : RUN apt-get install -y curl
 ---> Using cache
 ---> 5ce546325260
Step 6 : RUN apt-get install -y libnet-ifconfig-wrapper-perl
 ---> Using cache
 ---> b0b563031e07
Step 7 : EXPOSE 80
 ---> Using cache
 ---> 281098766be7
Step 8 : CMD /bin/bash
 ---> Using cache
 ---> 7f5295283364
Successfully built 7f5295283364
schen@scvmu01:~/dockerfile/cct_test$ 
schen@scvmu01:~/dockerfile/cct_test$ docker images shichen/cct
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
shichen/cct         latest              7f5295283364        2 minutes ago       280.7 MB
schen@scvmu01:~/dockerfile/cct_test$ 

我們來啟動一個容器,並在其中執行Nginx,再啟動另一個容器,通過pingcurl命令進行驗證:

[email protected]:~/dockerfile/cct_test$ docker run -it -p 80 --name cct1 shichen/cct
[email protected]:/# ifconfig eth0
eth0      Link encap:Ethernet  HWaddr 02:42:ac:11:00:03  
          inet addr:172.17.0.3  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::42:acff:fe11:3/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:6 errors:0 dropped:0 overruns:0 frame:0
          TX packets:6 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:508 (508.0 B)  TX bytes:508 (508.0 B)

[email protected]:/# [email protected]:/# nginx
[email protected]:/# [email protected]:~/dockerfile/cct_test$ 
[email protected]:~/dockerfile/cct_test$ 
[email protected]:~/dockerfile/cct_test$ docker run -it --name cct2 shichen/cct
[email protected]:/# ping 172.17.0.3
PING 172.17.0.3 (172.17.0.3) 56(84) bytes of data.
64 bytes from 172.17.0.3: icmp_seq=1 ttl=64 time=1.60 ms
64 bytes from 172.17.0.3: icmp_seq=2 ttl=64 time=1.05 ms
64 bytes from 172.17.0.3: icmp_seq=3 ttl=64 time=0.579 ms
64 bytes from 172.17.0.3: icmp_seq=4 ttl=64 time=0.361 ms
^C
--- 172.17.0.3 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3006ms
rtt min/avg/max/mdev = 0.361/0.901/1.607/0.479 ms
[email protected]:/#          
[email protected]:/# curl http://172.17.0.3:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
[email protected]:/# 

我們知道,每次容器啟動時Docker都會為其重新分配IP地址,因此通過IP地址連線既不方便也不保險,好在Docker為我們提供了--link選項,用於在啟動容器時為其他容器新增別名,這樣我們就可以通過別名來訪問對應的容器了:

[email protected]:~/dockerfile/cct_test$ docker run -it --name cct3 --link=cct1:web_server shichen/cct
[email protected]4c0985296c2a:/# ping web_server
PING web_server (172.17.0.3) 56(84) bytes of data.
64 bytes from web_server (172.17.0.3): icmp_seq=1 ttl=64 time=0.877 ms
64 bytes from web_server (172.17.0.3): icmp_seq=2 ttl=64 time=1.35 ms
64 bytes from web_server (172.17.0.3): icmp_seq=3 ttl=64 time=0.726 ms
64 bytes from web_server (172.17.0.3): icmp_seq=4 ttl=64 time=1.44 ms
^C
--- web_server ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3008ms
rtt min/avg/max/mdev = 0.726/1.102/1.449/0.308 ms
[email protected]4c0985296c2a:/# 
[email protected]4c0985296c2a:/# env | grep -i web_server
WEB_SERVER_PORT_80_TCP=tcp://172.17.0.3:80
WEB_SERVER_PORT=tcp://172.17.0.3:80
WEB_SERVER_PORT_80_TCP_PROTO=tcp
WEB_SERVER_PORT_80_TCP_ADDR=172.17.0.3
WEB_SERVER_NAME=/cct3/web_server
WEB_SERVER_PORT_80_TCP_PORT=80
[email protected]4c0985296c2a:/# 
[email protected]4c0985296c2a:/# grep -i web_server /etc/hosts
172.17.0.3      web_server c6c4aaf0b503 cct1
[email protected]4c0985296c2a:/# 

不難發現,為了實現這個機制,Docker對容器做出了適當的修改。那麼,當我們重啟Docker時,所有容器的IP地址都會改變,不過不用擔心,Docker會自動為我們做出對應的修改,以使得我們可以繼續正確地使用別名:

[email protected]:~/dockerfile/cct_test$ sudo service docker restart
[email protected]:~/dockerfile/cct_test$ 
[email protected]:~/dockerfile/cct_test$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
[email protected]:~/dockerfile/cct_test$ 
[email protected]:~/dockerfile/cct_test$ docker restart cct1 cct2 cct3
cct1
cct2
cct3
[email protected]:~/dockerfile/cct_test$ 
[email protected]:~/dockerfile/cct_test$ docker attach cct3
[email protected]4c0985296c2a:/# 
[email protected]4c0985296c2a:/# ping web_server
PING web_server (172.17.0.2) 56(84) bytes of data.
64 bytes from web_server (172.17.0.2): icmp_seq=1 ttl=64 time=2.59 ms
64 bytes from web_server (172.17.0.2): icmp_seq=2 ttl=64 time=0.465 ms
64 bytes from web_server (172.17.0.2): icmp_seq=3 ttl=64 time=0.331 ms
64 bytes from web_server (172.17.0.2): icmp_seq=4 ttl=64 time=0.109 ms
^C
--- web_server ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3004ms
rtt min/avg/max/mdev = 0.109/0.873/2.590/0.999 ms
[email protected]4c0985296c2a:/# 
[email protected]4c0985296c2a:/# env | grep -i web_server
WEB_SERVER_PORT_80_TCP=tcp://172.17.0.2:80
WEB_SERVER_PORT=tcp://172.17.0.2:80
WEB_SERVER_PORT_80_TCP_PROTO=tcp
WEB_SERVER_PORT_80_TCP_ADDR=172.17.0.2
WEB_SERVER_NAME=/cct3/web_server
WEB_SERVER_PORT_80_TCP_PORT=80
[email protected]4c0985296c2a:/# 
[email protected]4c0985296c2a:/# grep -i web_server /etc/hosts
172.17.0.2      web_server dc8b267d6fa7 cct1
[email protected]4c0985296c2a:/# 

拒絕所有容器間互聯

要拒絕所有容器之間的互聯,我們只需要將Docker守護程序的--icc引數設定為“false”即可。

允許特定容器間的連線

要允許特定容器間的連線,需要滿足以下三個條件:
1. Docker守護程序--icc選項設定為“false”
2. Docker守護程序--iptables選項設定為“true”
3. 在容器執行時通過--link選項指定別名

--iptables=true時,Docker會配置Linux的iptables,這個選項預設是開啟的。配合--icc=false,Docker會為我們阻斷所有容器之間的互聯,而僅允許那些通過--link選項配置的連線:

schen@scvmu01:~/dockerfile/cct_test$ grep ^DOCKER_OPTS /etc/default/docker 
DOCKER_OPTS="--icc=false --iptables=true"
schen@scvmu01:~/dockerfile/cct_test$ 
schen@scvmu01:~/dockerfile/cct_test$ sudo service docker restart
schen@scvmu01:~/dockerfile/cct_test$ 
schen@scvmu01:~/dockerfile/cct_test$ ps -ef | grep dockerd
root     12002     1  2 22:41 ?        00:00:08 dockerd -H fd:// --icc=false --iptables=true
schen    12196  2546  0 22:46 pts/2    00:00:00 grep --color=auto dockerd
schen@scvmu01:~/dockerfile/cct_test$ 

我們重啟之前的三個容器,可以發現通過--link配置過的“cct3”能夠正常訪問“cct1”提供的網頁服務,而“cct2”卻不能:

[email protected]:~/dockerfile/cct_test$ docker restart cct1 cct2 cct3
cct1
cct2
cct3
[email protected]:~/dockerfile/cct_test$ 
[email protected]:~/dockerfile/cct_test$ docker attach cct1
[email protected]:/# 
[email protected]:/# ifconfig eth0
eth0      Link encap:Ethernet  HWaddr 02:42:ac:11:00:02  
          inet addr:172.17.0.2  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::42:acff:fe11:2/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:24 errors:0 dropped:0 overruns:0 frame:0
          TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:1944 (1.9 KB)  TX bytes:648 (648.0 B)

[email protected]:/# nginx
[email protected]:/# 
[email protected]:/# [email protected]:~/dockerfile/cct_test$ 
[email protected]:~/dockerfile/cct_test$ 
[email protected]:~/dockerfile/cct_test$ docker attach cct3
[email protected]:/# 
[email protected]:/# curl web_server
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
[email protected]:/# 
[email protected]:/# [email protected]:~/dockerfile/cct_test$ 
[email protected]:~/dockerfile/cct_test$ 
[email protected]:~/dockerfile/cct_test$ docker attach cct2
[email protected]:/# 
[email protected]:/# curl 172.17.0.2
curl: (7) Failed to connect to 172.17.0.2 port 80: Connection timed out
[email protected]:/# 

其實如果多加嘗試,我們還會發現,即便是在“cct3”中,也是無法通過ping命令訪問“cct1”的。我們可以通過檢視Linux的iptables設定來尋找答案:

schen@scvmu01:~/dockerfile/cct_test$ docker attach cct3
root@4c0985296c2a:/# 
root@4c0985296c2a:/# ping web_server
PING web_server (172.17.0.2) 56(84) bytes of data.
^C
--- web_server ping statistics ---
131 packets transmitted, 0 received, 100% packet loss, time 130433ms

root@4c0985296c2a:/# 
root@4c0985296c2a:/# schen@scvmu01:~/dockerfile/cct_test$ 
schen@scvmu01:~/dockerfile/cct_test$ 
schen@scvmu01:~/dockerfile/cct_test$ sudo iptables -L -n
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination         
DOCKER-ISOLATION  all  --  0.0.0.0/0            0.0.0.0/0           
DOCKER     all  --  0.0.0.0/0            0.0.0.0/0           
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0           
DROP       all  --  0.0.0.0/0            0.0.0.0/0           

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         

Chain DOCKER (1 references)
target     prot opt source               destination         
ACCEPT     tcp  --  0.0.0.0/0            172.17.0.2           tcp dpt:80
ACCEPT     tcp  --  172.17.0.4           172.17.0.2           tcp dpt:80
ACCEPT     tcp  --  172.17.0.2           172.17.0.4           tcp spt:80

Chain DOCKER-ISOLATION (1 references)
target     prot opt source               destination         
RETURN     all  --  0.0.0.0/0            0.0.0.0/0           
schen@scvmu01:~/dockerfile/cct_test$ 

不難發現,在“DOCKER”鏈中Linux只允許了“cct3”與“cct1”之間的80埠通訊,這就解釋了我們剛剛遇到的情況。

Docker容器與外部網路的連線

首先介紹兩個概念,一個叫做“ip-forward”,另一個叫做“iptables”,這兩個因素決定著Docker與外部網路的連線。

ip-forward