极客时间 张磊 深入剖析Kubernetes 课程笔记


浅谈容器网络

一个Linux容器的网络栈被隔离在自己的Network Namespace中,Network Namespace包括了:网卡(Network Interface),回环设备(Lookback Device),路由表(Routing Table)和iptables规则。

# 声明直接使用宿主机的网络栈
docker run -d -net=host --name nginx-host nginx

大多数情况下,都希望容器使用自己的网络栈:即拥有自己的IP地址和端口。

同一宿主机上的容器之间如何通信

那么宿主机上不同network namespace下的容器之间如何通信?Linux 网桥(Bridge)

网桥是Linux内核中的一个模块,作用类似于虚拟交换机,它工作在链路层,主要功能是将数据包根据MAC地址转发到不同的端口。

利用Veth Path作为“网线”将不同Network Namespace下的容器连接到网桥上。

启动一个容器查看其中的网络设备:


# 在宿主机上
$ 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

可以看到容器nginx-1内部有一个接口eth0,这个接口是Veth Pair设备的一端,作为容器内的一个虚拟接口,Veth Pair的另一端连接在外部的网桥docker0上。

同时,容器内部的默认下一跳地址为172.17.0.1,接口为eth0,我们在宿主机执行ifconfig可以看到,这个下一跳的IP地址就是docker0网桥的IP地址。

# 在宿主机上
$ 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

同时,可以看到创建的Veth Pair设备在宿主机上名称为veth9c02e56,通过brctl命令,可以看到该设备被连接到了docker0网桥上。

这时,我们再创建nginx-2容器:

$ docker run –d --name nginx-2 nginx
$ brctl show
bridge name bridge id  STP enabled interfaces
docker0  8000.0242d8e4dfc1 no  veth9c02e56
       vethb4963f3

可以看到docker0网桥上被插入了另一个Veth Pair设备vethb4963f3。同时,在nginx-1中PING 172.17.0.3(nginx-2的IP地址),是可以通的。

原理
当在nginx-1中PING 172.17.0.3时,匹配到了nginx-1路由表中的第二条规则,路由规则的第二条中,所有发往网络172.17.0.0/16的数据包的网关为0.0.0.0,这条规则为直连规则,即:凡是匹配到这条规则的IP包,应该经过本机的eth0接口,通过二层网络直接发往目的主机

而为了通过二层网络进行转发,需要目的地址172.17.0.3的MAC地址,所以通过eth0接口发送一个 ARP 请求,来获得172.17.0.3的MAC地址。

ARP请求通过eth0接口发送给宿主机上的docker0网桥,然后被转发到所有连接在docker0网桥上的接口,容器nginx-2收到ARP请求之后返回一个包含自己MAC地址的ARP响应,该响应通过docker0网桥回复给nginx-1

nginx-1自己的网络协议栈收到MAC地址后,将该地址写入链路层数据包头,然后将数据包通过eth0发送出去。

值得注意的是,所有被插到网桥上的接口都被降级为网桥的一个端口,也就是说,这些接口都失去了拒绝接收链路层数据包的权利,只要网桥将数据包转发给该接口,那么它必须接收。

docker0在接收到来自nginx-1的数据包后,继续扮演二层交换机的作用,将数据转发给nginx-2

总之,被限制在Network Namespace中的容器进程,通过Veth Pair设备+宿主机网桥的方式,实现容器之间的数据交换。

类似,从宿主机访问容器内部,也是通过docker0网桥将数据转发到容器内。

两个不同宿主机上的容器之间如何通信

通过软件的方式,构建一个跨主机的“公用”网桥,然后把各个容器都连接到这个“公用”网桥上。

这种技术被称为 Overlay Network。即:在已经存在的宿主机网络上,再通过软件构建一个在已有宿主机之上的、可以把所有容器连通在一起的虚拟网络。


12-30 09:48