[k8s]从docker容器网络到flannel

目录

 

docker有哪几种网络模型?

1 host:使用宿主机的网络栈.

2 none:不分配

3 bridge:

4.容器访问宿主机流程

5.宿主机访问容器

6.不同宿主机间的容器怎么访问

6.1 flannel

问题点:

6.2 VXLAN



 

docker有哪几种网络模型?

分为host/bridge/none

[root@tv2-callchain-tool-02 work]# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
4765e37b1355        bridge              bridge              local
126dff0a2bdf        host                host                local
097ef05196da        none                null                local

1 host:使用宿主机的网络栈.

 也就是不开启 Network Namespace.

像这样直接使用宿主机网络栈的方式,虽然可以为容器提供良好的网络性能,但也会不可避免地引入共享网络资源的问题,比如端口冲突。所以,在大多数情况下,我们都希望容器进程能使用自己 Network Namespace 里的网络栈,即:拥有属于自己的 IP 地址和端口.

但是问题就是,现在每个容器都相当于一台独立的主机,怎么通信呢?现实中,如果两台机器通信,一根网线即可;如果多台机器通信,那么需要把机器都连接到一个交换机上(二层).  

啥是交换机.基础好的可以直接跳过了.基础不好的简单普及一下.

只要是网络上跑的包,都是完整的,可以有下层没有上层,但是不会有上层没下层.因此你一个http请求,要封装tcp,ip,mac信息等等.

1层:物理层

     如果两台电脑想连在一起,可以使用一个网线给连接起来,但是要记得水晶头1-3,2-6的用法. 此时组成了一个lan

     如果多个机器想连在一起,可以用hub(集线器),这个东西没有脑子,就是一个广播小能手.

2层:数据链路层(mac层)

           使用物理层进行联调的问题在于

           1.使用广播,我发的东西,大家都能知道,太危险了.  (引入mac地址)

           2大家在一个网线上发东西,谁先发谁后发呢?   (多路访问,包含信道划分/轮流协议/随机接入协议)

           3.如果发错了咋增(crc检测)

           因此引入了mac(Medium Access Control)层来解决这个问题.

          

         有了mac地址,当a机器收到一个包,打开mac层,发现mac地址是自己,那么就接受,然后打开ip,也是自己,就看看tcp端口,是80,就给nginx,是81,就给tomcat等

         又引入另外一个问题,最开始咋知道目标mac地址呢?那就是ARP协议.局域网里,只知道ip,不知道mac的时候.就会发送广播,然后目标机器,就会返回它的mac地址

         因为有了mac层,hub广播的三个问题我们都解决了.那么是不是hub就完美了呢?

        No,还是广播的问题,现在啥啥都一个字节不差的广播给所有的口(hub的接口).导致量太大了,我们能不能聪明点.记住哪个口是啥mac地址.如果不是发给它的,我们就别发了.有的,这就是交换机.

交换机怎么知道每个口的mac地址呢?这就是我们父母经常说的话了,要学习.

 交换机怎么学习呢?

1.记住来的口的mac地址就行了.

2.过一会,所有口的mac地址都知道了.并且会记在一张表,称为转发表,表的内容大概就是 01口---mac地址xxx     02口-----mac地址aaa    03口----mac地址lafsdl.  注意表数据有过期时间的,不然mac地址变了咋整

3层:ip层

mac层只是在局域网中有效,如果你所在ip是198.162.1.4,而你访问的ip是10.100.1.3.你广播给所有的小伙伴,也没人应答,因此这个机器可能在隔壁县呢.  这个流程是这样的

a.当你访问的ip不在局域网中(咋判断两个ip在不在一个局域网?自己百度吧),那么你要发给你的网关.(这时候就是把mac地址改为网关的mac地址,源ip是自己,目的ip是10.100.1.3).

b. 网关收到后,发现mac地址是自己,那就打开包,发现目标ip不是自己.那么自己只是个搬运工.需要转发这个包给10.100.1.3,同样,网关需要查找下规则(route表).一个网关往往有多个网卡,一个网卡的ip地址是和你一个网段,然后另几个网卡有另外的ip和网段.  不过原理都是一样的.现在你访问到了网关了,那么网关根据路由表(route).判断怎么去访问. 网关通常就是一个路由器.路由器有两个网卡,一个是内网的192.168.xxx,一个是连接移动/电信/联通的.  而路由表是啥呢?其实就是一个规则.(可以百度下netfilter.这里东西太多,不写了),所以理论上,我们的一台pc机都可以作为一个路由器.前提是你有多个网卡,不然一个网卡的话,你想想,别人配置网关是你,到你这,你啥也干不了,还要把数据再转发给你的网关..

2 none:不分配

不分配网卡,也不使用别人的

3 bridge:

刚刚在1.1说了,如果想将每个容器通过网线连接起来,就需要建立一个虚拟交换机.就是网桥(工作在二层).主要功能是根据 MAC 地址学习来将数据包转发到网桥的不同端口(Port)上。上面说了怎么学习,是记录请求方的端口和mac地址

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

ok那下一个问题来了,我们怎么把容器连接到这个虚拟交换机上呢?我们可没有网线.

这时候vethpear出厂了.

Veth Pair 设备的特点是:它被创建出来后,总是以两张虚拟网卡(Veth Peer)的形式成对出现的。并且,从其中一个“网卡”发出的数据包,可以直接出现在与它对应的另一张“网卡”上,哪怕这两个“网卡”在不同的 Network Namespace 里。

这就使得 Veth Pair 常常被用作连接不同 Network Namespace 的“网线”。如下图.是容器间请求的流程

172.17.0.2容器中的route信息

route命令可以查看 linux路由信息.

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

容器route1.访问172.17.xx.xx,Gateway是0.0.0.0,说明要直连.不用把请求包交给网关.直接使用eth0发就行.就是在一个局域网里.需要寻址(arp),然后发送.

容器route2.如果不是172.17.xx.xx,那么说明不是同局域网的请求,那么需要把请求给网关(172.17.0.1也就是docker0网桥).

注意:一旦一张虚拟网卡被“插”在网桥上,它就会变成该网桥的“从设备”。从设备会被“剥夺”调用网络协议栈处理数据包的资格,从而“降级”成为网桥上的一个端口。而这个端口唯一的作用,就是接收流入的数据包,然后把这些数据包的“生杀大权”(比如转发或者丢弃),全部交给对应的网桥.因此vethpear网卡,在收到包之后也不会走协议栈,而是直接流到网桥里.网桥收到后,会扮演一个二层交换机的角色.

因此,容器1发送数据到自己的eth0,然后到对应的vethpear,然后流到网桥.然后网桥会查学习表(CAM),找到对应的端口(也就是vethpear网卡,已经退化成端口了),转发给这个端口.只有,数据就流到这个vethpear对应的容器里的网卡了.

以上,就是容器访问容器的例子

在宿主机上执行 可以跟踪数据包流程 /var/log/syslog


# 在宿主机上执行
$ iptables -t raw -A OUTPUT -p icmp -j TRACE
$ iptables -t raw -A PREROUTING -p icmp -j TRACE

被限制在 Network Namespace 里的容器进程,实际上是通过 Veth Pair 设备 + 宿主机网桥的方式,实现了跟同其他容器的数据交换。

注意:容器访问容器,不会请求网关,也就是172.17.0.1.还有,网桥既有交换机的功能,同时也有网关的功能.他有网关的ip地址.

4.容器访问宿主机流程

1.根据容器里的router,访问到docker0网关.注意这个是网关ip,请求到网关后,当然要进行处理了.就像你的机器请求到路由器.这里只不过容器和网关在一台机器上,而且交换机和网关都是一个网桥干的活.

2.到达网关后,网关收到数据(源mac,目标mac(就是docker0的mac),源ip:172.17.0.2,目标ip:10.168.0.3),这时候docker0的协议栈就会查询宿主机的router,会使用eth0网卡,请求10.168.0.3.(arp寻址,然后封装数据包(目标mac地址(xxx)))

 

5.宿主机访问容器

同理:

1.查看宿主机的router.发现应该走docker0这个网卡.

2.由于是局域网,因此要arp寻址.之后把数据转发给对应端口.ending

 

 

6.不同宿主机间的容器怎么访问

由于不同宿主机间的docker0网桥没有任何关系,因此很难访问到,解决方法就是通过软件,创建一个公共的覆盖很多宿主机的网桥.成为Overlay Network(覆盖网络)

 

6.1 flannel

Flannel 项目是 CoreOS 公司主推的容器网络方案

flannel支持三种实现:

1.VXLAN;

2.host-gw;

3.UDP。

 

1.UDP模式

最早支持,性能最长,已经废弃

首先要说下,flannel运行时会生成一个TUN设备,那么啥是tun设备呢?

TUN设备

TUNLinux内核的一种虚拟三层网络设备,纯软件实现,通过此设备可以处理来自网络层的数据。

可以看到tun设备和eth0的区别:   eth0一端连着协议栈,一端连着物理网络;但是 tun设备一端连着协议栈,一端连着该设备对应的一个用户进程(即flannel进程)

tun的工作:在操作系统内核和用户应用程序之间传递 IP 包

以 flannel0 设备为例:像上面提到的情况,当操作系统将一个 IP 包发送给 flannel0 设备之后,flannel0 就会把这个 IP 包,交给创建这个设备的应用程序,也就是 Flannel 进程。这是一个从内核态(Linux 操作系统)向用户态(Flannel 进程)的流动方向.

反之,如果 Flannel 进程向 flannel0 设备发送了一个 IP 包,那么这个 IP 包就会出现在宿主机网络栈中,然后根据宿主机的路由表进行下一步处理。这是一个从用户态向内核态的流动方向。

 

例子:node1中的100.96.1.2请求 node2中的100.96.2.3

宿主机 Node 1 上有一个容器 container-1,它的 IP 地址是 100.96.1.2,对应的 docker0 网桥的地址是:100.96.1.1/24。

宿主机 Node 2 上有一个容器 container-2,它的 IP 地址是 100.96.2.3,对应的 docker0 网桥的地址是:100.96.2.1/24。

 

前提工作,flannel启动后,会生成一条iptables规则.如下图

1.当请求100.96.2.3时,会命中下面的规则.此时会进入到flannel0设备,刚刚说了,这个设备会接受数据包,给对应的flannel进程.

# 在Node 1上
$ ip route
default via 10.168.0.1 dev eth0
100.96.0.0/16 dev flannel0  proto kernel  scope link  src 100.96.1.0
100.96.1.0/24 dev docker0  proto kernel  scope link  src 100.96.1.1
10.168.0.0/24 dev eth0  proto kernel  scope link  src 10.168.0.2

2.flannel进程会查询要访问的这个ip位于哪个宿主机上.这个咋查呢?肯定是记录在某个数据库了.没错,是记录在了etcd中.

2.1 查询有哪些子网

$ etcdctl ls /coreos.com/network/subnets
/coreos.com/network/subnets/100.96.1.0-24
/coreos.com/network/subnets/100.96.2.0-24
/coreos.com/network/subnets/100.96.3.0-24

2.2根据这个子网.查询宿主机ip

$ etcdctl get /coreos.com/network/subnets/100.96.2.0-24
{"PublicIP":"10.168.0.3"}

3.找到宿主机ip后,我再把ip包封装到udp包里.

源ipnode1的ip
目的ipnode2的ip(刚刚从etcd查出来)
源port8285
目的port8285

每台宿主机上的 flanneld进程,都监听着一个 8285 端口.

===================传输给node2====================>

4.node2机器收到udp的数据包

  • 看下mac地址是自己的
  • 打开ip包,发现ip地址也是自己
  • 再打开传输层的包,发现是udp.再看下端口,是8285.那么就去交给flannel进程处理

5.flannel进程读包的处理如下

  • 解析出ip包.注意这个ip包是啥样的?
源ip100.96.1.2
目的ip100.96.2.3
  • 将ip包发往tun设备.
  • tun设备会根据路由表,进一步处理这个ip包的流向,如下图,会命中第二条规则.

# 在Node 2上
$ ip route
default via 10.168.0.1 dev eth0
100.96.0.0/16 dev flannel0  proto kernel  scope link  src 100.96.2.0
100.96.2.0/24 dev docker0  proto kernel  scope link  src 100.96.2.1
10.168.0.0/24 dev eth0  proto kernel  scope link  src 10.168.0.3

  • 包发送给网关(100.96.2.0)后,网关会把数据转发给100.96.2.3.

===============发给容器了==========>

6.容器收到包后,会处理.......处理之后,会发送返回数据包给容器1

7.类似的.目的地址变成了100.96.1.2,同样也要经过docker0网关--->flannelId设备---->flannelid进程,查询etcd找此ip对应的宿主机ip,包装为udp包------>发送给主机node1---->node1的flannelid进程接受后,解析出ip包------>发给node1的flannelId设备(tun)----->node1的网关(docker0)--->node1的containerId.

 

问题点:

1.flanneld 的处理过程会导致ip包从内核态-->用户态--->内核态.频繁的切换和数据的拷贝

2.udp的封装和解封也是在用户态完成的.效率低

 

6.2 VXLAN

VXLAN,即 Virtual Extensible LAN(虚拟可扩展局域网),是 Linux 内核本身就支持的一种网络虚似化技术.所以说,VXLAN 可以完全在内核态实现上述封装和解封装的工作,从而通过与前面相似的“隧道”机制,构建出覆盖网络(Overlay Network).

VXLAN 的覆盖网络的设计思想是:在现有的三层网络之上,“覆盖”一层虚拟的,由内核 VXLAN 模块负责维护的二层网络.而为了能够在二层网络上打通“隧道”,VXLAN 会在宿主机上设置一个特殊的网络设备作为“隧道”的两端。这个设备就叫作 VTEP,即:VXLAN Tunnel End Point(虚拟隧道端点).

此时的flannelId既有ip地址也有mac地址.

 

例子:

node1的10.1.15.2---->node2的10.1.16.3

1.container-1向container-2发送数据,由于不属于同一个网段,因此走docker0网关.docker0网关查询route表,发下需要走flannelId这个设备.因此将数据包发往flannelID

2.flannelId收到数据后,不会像UDP那样封装ip包,然后发送给node2,而是想办法直接将数据发送到node2的VTEP.

2.1怎么获取node2 VTEP ip呢? 这就是flannelId进程干的事情了.node2启动的时候,node1的flannelid进程会写入一条规则.

其中访问10.1.16.0/24网段的时候,走flannel.1设备,请求的网关地址是10.1.16.0.这个不就是VTEP地址么

$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
...
10.1.16.0       10.1.16.0       255.255.255.0   UG    0      0        0 flannel.1

2.2 那怎么获取目标VTEP的mac地址呢?ARP,这里第一步是去找arp表.

在node2启动的时候,node1的flannelId进程又把node2 VTEP的mac地址写入了本地的arp表

# 在Node 1上
$ ip neigh show dev flannel.1
10.1.16.0 lladdr 5e:f8:4f:00:e3:37 PERMANENT

可以看到,最新版本的 Flannel 并不依赖 L3 MISS 事件和 ARP 学习,而会在每台节点启动时把它的 VTEP 设备对应的 ARP 记录,直接下放到其他每台宿主机上

2.3 有了目标ip,目标mac地址.封包

源mac地址node1的容器的地址
源ipnode1中容器的地址
目标mac地址node2启动后,node1会写入一条arp规则.对应的是node2的VTEP地址
目标ip地址

node2启动后,node1会写入一条规则,其中的网关就是node2的VTEP Ip

但是,现在还不行,因为这个mac地址,宿主机2它不知道啊,这是啥玩意,如果这样发出去,宿主机2连这个包都不会接受.

所以接下来,Linux 内核还需要再把“内部数据帧”进一步封装成为宿主机网络里的一个普通的数据帧,好让它“载着”“内部数据帧”,通过宿主机的 eth0 网卡进行传输。

封装成udp.

1.需要知道node2宿主机的ip

这个信息谁维护呢?还是flannelId这个进程.他会记录 VTEP的mac地址对应的宿主机Ip的映射关系.  

2.封装包

这些信息都怎么来的

1.目的主机ip地址  这个是flannelid维护的.是通过VTEP的mac地址来配置的宿主机ip

2.目的主机mac  知道目的主机ip后可以 ARP获取

3.UDPHeader就不说了,端口是写死的

4.udp包装的就是后面那个 内部包.   

  4.1目的VTEP mac地址,这个咋获取的?通过router规则

  4.2目的容器的ip地址,这个就是我们要访问的ip.好说

====================udp发送==================>

3.node2收到udp的请求

3.1查看mac地址,是自己,那么自己收下这个包

3.2查看ip地址,发现是自己.ok

3.3 在拆,发现VXLAN header,这个时候,就会把数据包给 flannel.1设备

3.4 flannel设备则会继续拆包.取出原始ip 包.

3.5取出之后,就是发送给docker0--->容器--->返回数据给node1的容器

 

VXLAN 模式组建的覆盖网络,其实就是一个由不同宿主机上的 VTEP 设备,也就是 flannel.1 设备组成的虚拟二层网络。对于 VTEP 设备来说,它发出的“内部数据帧”就仿佛是一直在这个虚拟的二层网络上流动。这,也正是覆盖网络的含义。

 

7

flannel的两种模式都是用户的容器连接在 docker0 网桥上。而网络插件则在宿主机上创建了一个特殊的设备(UDP 模式创建的是 TUN 设备,VXLAN 模式创建的则是 VTEP 设备),docker0 与这个设备之间,通过 IP 转发(路由表)进行协作.

网络插件真正要做的事情,则是通过某种方法,把不同宿主机上的特殊设备连通,从而达到容器跨主机通信的目的

k8s网络插件要做的也是这个事情,不过抽象出一个cni接口,然后使用cni网桥替代docker0网桥.其他的没有区别

需要注意的是,CNI 网桥只是接管所有 CNI 插件负责的、即 Kubernetes 创建的容器(Pod)。而此时,如果你用 docker run 单独启动一个容器,那么 Docker 项目还是会把这个容器连接到 docker0 网桥上。所以这个容器的 IP 地址,一定是属于 docker0 网桥的 172.17.0.0/16 网段

 

在本篇文章中,我为你详细讲解了 Kubernetes 中 CNI 网络的实现原理。根据这个原理,你其实就很容易理解所谓的“Kubernetes 网络模型”了:所有容器都可以直接使用 IP 地址与其他容器通信,而无需使用 NAT。所有宿主机都可以直接使用 IP 地址与所有容器通信,而无需使用 NAT。反之亦然。容器自己“看到”的自己的 IP 地址,和别人(宿主机或者容器)看到的地址是完全一样的。

 

参考:https://time.geekbang.org/column/article/67351  大神的课真是牛批

课后思考题也不错思考题:为什么 Kubernetes 项目不自己实现容器网络,而是要通过 CNI 做一个如此简单的假设呢?

解答:没有亲历 Kubernetes 网络标准化的这个阶段,以下内容都是基于猜测,大家见笑了。
最开始我觉得这就是为了提供更多的便利选择,有了 CNI,那么只要符合规则,什么插件都可以用,用户的自由度更高,这是 Google 和 Kubernetes 开放性的体现。但转念一想,如果 Kubernetes 一开始就有官方的解决方案,恐怕也不会有什么不妥,感觉要理解的更深,得追溯到 Kubernetes 创建之初的外部环境和 Google 的开源策略了。Github 上最早的 Kubernetes 版本是 0.4,其中的网络部分,最开始官方的实现方式就是 GCE 执行 salt 脚本创建 bridge,其他环境的推荐的方案是 Flannel 和 OVS。
所以我猜测:
首先给 Kubernetes 发展的时间是不多的(Docker 已经大红大紫了,再不赶紧就一统天下了),给开发团队的时间只够专心实现编排这种最核心的功能,网络功能恰好盟友 CoreOS 的 Flannel 可以拿过来用,所以也可以认为 Flannel 就是最初 Kubernetes 的官方网络插件。Kubernetes 发展起来之后,Flannel 在有些情况下就不够用了,15 年左右社区里 Calico 和 Weave 冒了出来,基本解决了网络问题,Kubernetes 就更不需要自己花精力来做这件事了,所以推出了 CNI,来做网络插件的标准化。我觉得假如社区里网络一直没有好的解决方案的话,Kubernetes 肯定还是会亲自上阵的。
其次,Google 开源项目毕竟也不是做慈善,什么都做的面面俱到,那要消耗更多的成本,当然是越多的外部资源为我所用越好了。感觉推出核心功能,吸引开发者过来做贡献的搞法,也算是巨头们开源的一种套路吧。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值