Docker容器与容器云 - 网络基础学习总结

很多人可能在项目中已经使用docker很长时间,但是却很少有人知道docker的网络是如何实现的。我应该就算是很多人中的一个。

开始前有一点需要注意的是:如果你现在正使用的是docker for mac,建议你还是在mac上安装vagrant,然后使用vagrant开启一台linux虚拟机,然后在这台虚拟机上安装docker for linux。这样我们的学习环境也更加的贴近生产环境。

docker0网桥

当在一台未经特殊网络配置的ubuntu机器上安装完docker之后,在宿主机上通过使用ifconfig命令可以看到多了一块名为docker0的网卡:

vagrant@vagrant-ubuntu-trusty-64:~$ ifconfig
docker0   Link encap:Ethernet  HWaddr 02:42:b6:12:b7:33
          inet addr:172.17.0.1  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::42:b6ff:fe12:b733/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:45 errors:0 dropped:0 overruns:0 frame:0
          TX packets:18 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:3044 (3.0 KB)  TX bytes:1460 (1.4 KB)

eth0      Link encap:Ethernet  HWaddr 08:00:27:36:92:90
          inet addr:10.0.2.15  Bcast:10.0.2.255  Mask:255.255.255.0
          inet6 addr: fe80::a00:27ff:fe36:9290/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:503098 errors:0 dropped:0 overruns:0 frame:0
          TX packets:236748 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:397962985 (397.9 MB)  TX bytes:14862746 (14.8 MB)

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:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

可以看到此docker0网卡的IP为172.17.0.1/16。有了这样一块网卡,宿主机也会在内核路由表上添加一条到达相应网络的静态路由,可以通过route -n查看。

vagrant@vagrant-ubuntu-trusty-64:~$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.0.2.2        0.0.0.0         UG    0      0        0 eth0
10.0.2.0        0.0.0.0         255.255.255.0   U     0      0        0 eth0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0

当一宿主机要发送数据时,它会查自己的内核路由表,找到一条最精确匹配目标主机IP地址的路由来转发数据。

我们知道,同一网段内发送数据是不需要经过网关的,而不同网段之间发送数据必需经过网关。

我们的宿主机有三张网卡:

lo(127.0.0.1),本机回环网卡;

eth0(10.0.2.15)在10.0.2.0/24网段;

docker0(172.17.0.1)在172.17.0.0/16网段;

假如:

  • Destination IP是10.0.2.13,则会匹配到第二条路由。即所有目的IP为10.0.2.0/24网络的数据包从eth0网卡发出。Gateway为0.0.0.0表示不经过网关,这个很好理解,因为同一网段内发送数据不需要经过网关。

  • Destination IP是172.17.0.3,则会匹配到第三条路由。即所有目的IP为172.17.0.0/16网络的数据包从docker0网卡发出。

  • Destination IP是其它,比如32.10.2.0,则会匹配到第一条路由:默认路由。即数据包从eth0发出,而发往外网的数据包需要经过网关转发,所发配置了Gateway为10.0.2.2

现在使用docker run创建一个docker容器。

sudo docker run -it --name myubuntu ubuntu /bin/bash

然后在容器中执行ifconfig,如果你发现你的容器中没有ifconfig命令,可以执行以下命令安装net-tools:

root@bde36ed1f506:/# apt-get update

root@bde36ed1f506:/# apt-get install net-tools

// 顺带也把iputils-ping装了
root@bde36ed1f506:/# apt-get install iputils-ping

然后再执行ifconfig

root@bde36ed1f506:/# ifconfig
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:11218 errors:0 dropped:0 overruns:0 frame:0
          TX packets:11128 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:24859036 (24.8 MB)  TX bytes:606400 (606.4 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:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

在myubuntu容器中有两块网卡:eth0和lo。eth0为容器与外界通信的网卡。而且eth0(172.17.0.2)与宿主机中的docker0(172.17.0.1)在同一个网段。

现在我们查看myubuntu的路由表:

root@bde36ed1f506:/# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.17.0.1      0.0.0.0         UG    0      0        0 eth0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0

可以发现myubuntu的默认网关正是宿主机的docker0网卡。我们刚才install了那么多package说明容器是可以访问到外网的,说明容器的eth0与宿主机的docker0网卡是互通的。

现在我们回到宿主机的console,执行ifconfig

vagrant@vagrant-ubuntu-trusty-64:~$ ifconfig

...
vethee89fa0 Link encap:Ethernet  HWaddr 36:32:c3:bf:5e:78
          inet6 addr: fe80::3432:c3ff:febf:5e78/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:11330 errors:0 dropped:0 overruns:0 frame:0
          TX packets:11422 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:619186 (619.1 KB)  TX bytes:26177038 (26.1 MB)

会发现有一块以"veth"开头的网卡,如上:vethee89fa0。我们可以猜测这块网卡是veth设备,而veth pair总是成对出现的,很多人可能不知道veth设备是什么。

veth pair是用于不同network namespace间进行通信的方式,veth pair将一个network namespace数据发往另一个network namespace的veth。

如下:

Docker容器与容器云 - 网络基础学习总结_第1张图片

如果多个network namespace需要进行通信,则需要借助bridge:

Docker容器与容器云 - 网络基础学习总结_第2张图片

想知道更多>>

现在我们知道, veth pair通常用来连接两个network namespace,那么另一个应该就是docker容器中的eth0了。之前我们已经知道myubuntu容器的eth0和宿主机的docker0是相连的,那么vethee89fa0也应该是与docker0相连的。那么,现在来看docker0就不只是一个简单的网卡设备了,而是一个网桥。

真实情况正是如此,下图即为docker默认网络模式(bridge模式)下的网络环境拓扑图:

Docker容器与容器云 - 网络基础学习总结_第3张图片

我们在宿主机上安将docker时会添加一个docker0的网卡,当我们使用docker run创建一个容器,并且没有指定网络模式的时候,会使用默认网络模式,创建出一个docker0网桥,并以veth pair连接各容器的网络,容器中的数据通过docker0网桥转发到宿主机的eth0网卡上。

在linux中,可以使用brctl命令查看和管理网桥(需要安装bridge-utils软件包),如查看本机上的linux网桥以及其上的端口:

vagrant@vagrant-ubuntu-trusty-64:~$ sudo brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242b612b733 no vethee89fa0

如果我再使用docker run再创建一个docker容器后再查看linux网桥以及其上的端口:

vagrant@vagrant-ubuntu-trusty-64:~$ sudo brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.0242b612b733 no vethee89fa0
veth0c3e53a

会发现又多了一双veth pair连接到了docker0网桥。

现在我们再来看这个网桥,我们会觉得它就像是一个交换机,为连在其上的设备转发数据帧。网桥上的veth网卡设备相当于交换机上的端口,可以将多个容器或虚拟机连接在其上,这些端口工作在二层,所以是不需要配置IP信息的。图中docker0网桥变为连在其上的容器转发数据帧,使得同一台宿主机上的docker容器之间可以相互通信。也许你应该已经注意到docker0既然是二层设备,其上怎么也配置了IP呢? docker0是普通的Linux网桥,它是可以在上面配置IP的,可以认为其内部有一个可以用于配置IP信息的网卡接口。那么为什么要为它配置IP呢?在Docker的桥接网络模式中,docker0的IP地址作为连接于之上的容器的默认网关存在

iptables规则

Docker安装完成后,默认会在宿主机上增加一些iptables规则,以用于docker容器和容器之间以及和外界有通信,可以使用iptables-save命令查看规则。

vagrant@vagrant-ubuntu-trusty-64:~$ sudo iptables-save
# Generated by iptables-save v1.4.21 on Sun Jun 18 08:24:03 2017
*nat
:PREROUTING ACCEPT [4:1276]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [480:28821]
:POSTROUTING ACCEPT [480:28821]
:DOCKER - [0:0]
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A POSTROUTING -s 172.17.0.3/32 -d 172.17.0.3/32 -p tcp -m tcp --dport 3306 -j MASQUERADE
-A DOCKER -i docker0 -j RETURN
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 3306 -j DNAT --to-destination 172.17.0.3:3306
COMMIT
# Completed on Sun Jun 18 08:24:03 2017
# Generated by iptables-save v1.4.21 on Sun Jun 18 08:24:03 2017
*filter
:INPUT ACCEPT [2374:4027249]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [1986:129221]
:DOCKER - [0:0]
:DOCKER-ISOLATION - [0:0]
-A FORWARD -j DOCKER-ISOLATION
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A DOCKER -d 172.17.0.3/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 3306 -j ACCEPT
-A DOCKER-ISOLATION -j RETURN
COMMIT
# Completed on Sun Jun 18 08:24:03 2017

可以看到nat表上的POSTROUTING链有这么一条规则:

-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE

这条规则关系着docker容器和外界的通信,含义是将源地址为172.17.0.0/16的数据包(即Docker容器发出的数据),当不是从docker0网卡发出时做SNAT(源地址转换,将IP包的数据地址替换为相应网卡的地址)。这样一来,从Docker容器访问外网的流量,在外部看来就是从宿主机上发出的,外部感觉不到Docker容器的存在。

在iptables中可以灵活的做各种网络地址换(NAT),我想很多人可能不是太了解NAT(Network address translation)。

网络地址转换主要有两种: SNAT(source NAT)和DNAT(destination NAT)。

SNAT,即源地址目标转换。比如,多个PC机使用ADSL路由器共享上网。每个PC机都配置了内网IP,PC机访问外部网络的时候,路由器将数据包的报头中的源地址替换成路由器的IP,当外部网络的服务器,比如,网站web服务器接到访问请求的时候,他的日志记录下来的是路由器的IP地址,而不是PC机的内网IP。

这是因为,这个服务器收到的数据包的报头里边的源地址已经被换了,所以叫做SNAT,基于源地址的地址转换。

DNAT,即目标地址转换。典型的应用是,有个web服务器放在内网,前端有个防火墙配置公网IP。互联网上的访问者使用公网IP来访问这个网站。当访问的时候,客户端发出一个数据包,这个数据包的报头里边,目标地址写的是防火墙的公网IP。防火墙会把这个数据包的报头改写一次,将目标地址改写成web服务器的内网IP,然后再把这个数据包发送到内网的web服务器上。

MASQUERADE,地址伪装,在iptables中有着和SNAT相近的效果,但也有一些区别。

使用SNAT的时候,即可以NAT成一地址也可以NAT成多个地址,但是,对于SNAT,不管是几个地址,必须明确的指定要SNAT的IP。假如当前系统用的是ADSL动态拨号方式,那么每次拨号,出口IP都会改变,这个时候如果按照现在的方式来配置iptables就会出现问题。因为每次拨号后,服务器地址都会变化,而iptables规则内的ip是不会随着自动变化的。每次地址变化后都必须手工修改一次iptables,把规则里边的固定IP改成新的IP,这样很不方便。

MASQUERADE就是针对这种场景而设计的,他的作用是,从服务器的网卡上,自动获取当前IP地址来做NAT。

比如下边的命令:

iptables -t nat -A POSTROUTING -s 10.8.0.0/255.255.255.0 -o eth0 -j MASQUERADE

如此配置的话,不用指定SNAT的目标IP了。不管现在eth0的出口获得了怎样的动态IP,MASQUERADE会自动读取eth0现在的IP地址然后做SNAT。这样就很好的实现了动态SNAT地址转换。

你可能感兴趣的:(Docker容器与容器云 - 网络基础学习总结)