极客时间 张磊 深入剖析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。即:在已经存在的宿主机网络上,再通过软件构建一个在已有宿主机之上的、可以把所有容器连通在一起的虚拟网络。