一个Linux容器能够看到的网络栈,实际上是被隔离在它自己的Network Namespace当中的。

所谓网络栈包括了:网卡、回环设备、路由表和iptables规则。对于一个进程来说,这些要素其实就构成了它发起和相应网络请求的基本环境。

当然作为一个容器,也可以声明直接使用宿主机的网络栈,即不开启Network Namespace,比如:

$ docker run –d –net=host --name nginx-host nginx

不过大多数情况下我们都希望容器有自己的网络,即独立的IP地址和端口。

对于容器之间的通信以及解决方案,把容器看作一个主机,容器所在的宿主机看作一个局域网,这样便容易理解了。

不同Network Namespace之间的容器进程的通信

每个容器都有独立的IP地址,所以这里可以把每个容器都看作一台主机,都拥有一套独立的网络栈。

主机之间想要通信,那么就需要将主机之间用网线连接起来。在Linux中,能够起到虚拟交换机作用的网络设别,是网桥(Bridge)。这是一个数据链路层的设备,主要功能是根据MAC地址学习来将数据包转发到网桥的不同端口上。

docker项目会默认在宿主机上创建一个名叫docker0的网桥,凡是连接在docker0网桥上的容器,就可以通过它来进行通信

那么容器之间的网络通信就可以利用这个docker0网桥来实现,接下来的问题就是

如何将容器链接到docker0网桥上?

使用Veth Pair虚拟设备。

Veth Pair设备的特点就是:总是成对创建两张虚拟网卡,并且一个网卡发出的数据包,可以直接出现在与它对应的另一张网卡上,哪怕这两个网卡在不同的Network Namespace中

使得Veth Pair常常被作为连接不同network namespace的网线。

启动一个docke容器然后进入查看网络设备

$ docker run –d --name nginx-1 nginx
# 在宿主机上
$ docker exec -it nginx-1 /bin/bash
# 在容器里
root@2b3c181aecf1:/# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.2  netmask 255.255.0.0  broadcast 0.0.0.0
        inet6 fe80::42:acff:fe11:2  prefixlen 64  scopeid 0x20<link>
        ether 02:42:ac:11:00:02  txqueuelen 0  (Ethernet)
        RX packets 364  bytes 8137175 (7.7 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 281  bytes 21161 (20.6 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
        
lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
        
$ route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         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

容器里的这个etho网卡正是Veth Pair设备在这个容器里的一端。而Veth Pair的另一端则在宿主机上,通过ifconfig命令可以看到以veth开头的虚拟网卡,并通过brctl show可以看到网卡插在了docker0上。

# 在宿主机上
$ ifconfig
...
docker0   Link encap:Ethernet  HWaddr 02:42:d8:e4:df:c1  
          inet addr:172.17.0.1  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::42:d8ff:fee4:dfc1/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:309 errors:0 dropped:0 overruns:0 frame:0
          TX packets:372 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0 
          RX bytes:18944 (18.9 KB)  TX bytes:8137789 (8.1 MB)
veth9c02e56 Link encap:Ethernet  HWaddr 52:81:0b:24:3d:da  
          inet6 addr: fe80::5081:bff:fe24:3dda/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:288 errors:0 dropped:0 overruns:0 frame:0
          TX packets:371 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0 
          RX bytes:21608 (21.6 KB)  TX bytes:8137719 (8.1 MB)
          
$ brctl show
bridge name bridge id  STP enabled interfaces
docker0  8000.0242d8e4dfc1 no  veth9c02e56

两个容器之间的网络通信如下图所示,docker0在这里充当一个交换机的作用

用一句话总结一下就是,Network Namespace隔离的容器进程,通过Veth Pair设备和宿主机网桥的方式实现了网络通信

宿主机与容器通信

当然宿主机与容器进行网络通信的时候,网络数据也是通过docker0网桥转发到对应的Veth Pair设备的。

容器与外部主机通信

容器发出的请求首先通过docker0网桥出现在宿主机上,然后根据宿主机内的路由表通过宿主机的eth0将请求转发到外部主机上。当然前提是宿主机与这个外部主机是互联的。

跨主机容器间的通信

一台主机上的容器与另一台主机上的容器进行通信。默认情况,两台主机上的docker0网桥是没有关联的,它们之间也无法互相连通,所以容器之间也无法通信。

如果我们通过软件的方式,创建一个整个集群“公用”的网桥,然后把集群里的所有容器都连接到这个网桥上,不就可以相互通信了吗

这种在已有的宿主机网络上,通过软件构建覆盖在已有宿主机网络之上、把所以容器联通在一起的虚拟网络技术叫做覆盖网络(Overlay Network)。


tags: 容器网络