[Kubernetes]浅谈容器网络

Veth Pair

这部份内容主要介绍一个设备: Veth Pair .
做为一个容器,它能够声明直接使用宿主机的网络栈,即:不开启 Network Namespace .在这种状况下,这个容器启动后,直接监听的就是宿主机的 80 端口.
像这样直接使用宿主机网络栈的方式,虽然能够为容器提供良好的网络性能,但也会不可避免地引入共享网络资源的问题,好比端口冲突.因此,在大多数状况下,咱们都但愿容器进程能使用本身 Network Namespace 里的网络栈,即:拥有属于本身的 IP 地址和端口.
可是这个时候,就有另一个问题了:这个别隔离的容器进程,该如何跟其余 Network Namespace 里的容器进程进行交互呢?
最直接的办法,就是把它们用一根网线链接起来;而若是想要实现多台主机之间的通讯,那就须要用网线,把它们链接在一台交换机上.
在 Linux 中,可以起到虚拟交换机做用的网络设备,是网桥( Bridge ).它是一个工做在数据链路层( Data Link )的设备,主要功能是根据 MAC 地址学习来将数据包转发到网桥的不一样端口( Port )上.
为了实现这个目的, Docker 项目会默认在宿主机上建立一个名叫 docker0 的网桥,凡是链接在 docker0 网桥上的容器,均可以经过它来进行通讯.
可是,咱们又该如何把这些容器"链接"到 docker0 网桥上呢?此时,咱们就须要使用一种名叫 Veth Pair 的虚拟设备了.
Veth Pair 设备的特色是:它被建立出来后,老是以两张虚拟网卡( Veth Peer )的形式成对出现的.而且,从其中一个"网卡"发出的数据包,能够直接出如今与它对应的另外一张"网卡"上,哪怕这两个"网卡"在不一样的 Network Namespace 里面.这样,用来链接不一样 Network Namespace 的"网线"就成为了可能.
nginx

举个例子

好比,如今咱们启动了一个叫作 nginx-1 的容器:web

$ docker run –d --name nginx-1 nginx

而后咱们能够进入到这个容器中,查看一下它的网络设备:docker

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

能够看到,在这个容器中,有一张叫作 eth0 的网卡,它正是一个 Veth Pair 设备在容器里的这一端.经过 route 命令,能够查看到 nginx-1 容器的路由表,可以看到,这个 eth0 网卡是这个容器里的默认路由设备;全部对 172.17.0.0/16 网段的请求,也会被交给 eth0 来处理(第二条 172.17.0.0 路由规则)
而这个 Veth Pair 设备的另外一端,则在宿主机上.能够经过查看宿主机的网络设备看到它,以下所示:
bash

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

经过 ifconfig 命令的输出,咱们能够看到, nginx-1 容器对应的 Veth Pair 设备,在宿主机上是一张虚拟网卡,它的名字是: veth9c02e56 .而且,经过 brctl show 的输出,能够看到这张网卡被"插"在了 docker0 上.
若是此时,咱们在这台宿主机上启动另外一个 Docker 容器,好比 nginx-2 :
网络

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

会发现,有一个新的,名叫 vethb4963f3 的虚拟网卡,也被"插"在了 docker0 网桥上.此时,若是在 nginx-1 容器中 ping 一下 nginx-2 容器的 IP 地址( 172.17.0.3 ),就会发现,在同一宿主机上的两个容器,默认就是相互连通的.
以上整个流程,来一张示意图:(这样能够更清楚一些)
在这里插入图片描述
此时,应该就能够理解了,在默认状况下,被限制在 Network Namespace 里的容器进程,其实是经过 Veth Pair 设备 + 宿主机网桥的方式,实现了和其余容器的数据交换.
因此,若是遇到容器连不通"外网"时,第一反应应该是先试试 docker0 网桥能不能 ping 通,而后查看一下跟 docker0 和 Veth Pair 设备相关的 iptables 规则是否是有异常,通常就可以找到问题的答案.
以上介绍的是单机容器网络的实现原理和 docker0 网桥的做用.在这里的关键在于,容器若是想要和外界进行通讯,它发出的 IP 包就必须从它的 Network Namespace 里出来,来到宿主机上,而解决这个问题的方法就是:为容器建立一个一端在容器里充当默认网卡,另外一端在宿主机上的 Veth Pair 设备.
svg

思惟扩展

看到这里,估计你会有另一个疑问,那我若是想要"跨主机通讯"呢?
在 Docker 的默认配置下,一台宿主机上的 docker0 网桥,和其余宿主机上的 docker0 网桥,没有任何关联,它们互相之间就没办法连通.
可是若是咱们经过软件的方式,建立一个整个集群"公用"的网桥呢?而后把集群里全部的容器都链接到这个网桥上,是否是就能够相互通讯了?
一点儿毛病都没.
这样,咱们就能够画出以下的示意图:
在这里插入图片描述
经过示意图,可以看到,构建这种容器网络的核心在于:咱们须要在已有的宿主机网络上,再经过软件构建出一个覆盖在已有宿主机网络之上的,能够把全部容器连通在一块儿的虚拟网络.这种技术就是: Overlay Network (覆盖网络).
可是这只是理论上,落实到实际,仍是有些出入的.
后面我看时间和精力,若是可能,再写一篇关于"跨主机"通讯的讲解文章(感受有些难度,我不肯定可以讲清楚,可是尽可能)
oop

以上内容来自我学习<深刻剖析Kubernetes>专栏文章以后的一些看法,有偏颇之处,还望指出.
感谢您的阅读~
性能