k8s 网络方案 --Flannel的"套路"

导言

对 docker 略有所知的人应该了解,docker ip 是仅存在于宿主机的 nampaces 中,在真实的物理网络中是不配有拥有地位的, 即不存在对应的路由和 mac 地址的,因此就无法进行数据包的转发和路由, 这就造成了分属于不同宿主机上的 docker 无法通信的难题

聪明如你可能想到利用 nat 端口映射的方式解决;在服务器数量不多的简单场景下,我们确实可以这样做,一顿操作,将 docker 中的应用端口映射到宿主机的端口,便可实现与外界的端口可达了;但是,如果你面对的是成百上千运行着 docker 服务器,那将是巨大的工作量,几顿操作猛如虎,过程其实很辛苦,相信习惯了蛮力的你到时候也会萌生 "偷懒" 的想法

CNI

为了解决上述问题,k8s 提出了适配自身架构的网络方案标准CNI:

  • Pod 之间无需设置 NAT 即可实现相互的网络通讯
  • 每个 Pod 都会看到自己和其他 Pod 一样可看到自身的 IP 地址
  • 运行 Kubernetes 集群的节点,可支持物理机或者虚拟机,或任何支持运行 Kubernetes 的环境,这些节点也可无需设置 NAT,便可实现同其他 Pod 节点的相互通讯。

官方表述有点啰嗦了,说人话就是 Docker 之间要直面彼此,不耍套路,排除用 NAT 这种 "类代理" 的方式,双用自己真实的 ip 沟通;换言之,k8s 要实现的是扁平的网络,容器不需要借助宿主机的 ip,只需自身 ip 也能实现相互通信。但是,本着只提需求,绝不参与的原则,k8s 并没有自己去实现底层网络方案,而是借由第三方开源方案去实现。

目前业界普遍使用的方案有 Flannel、Calico、Weave 和 Canal,在实现原理以及流行度上,最有代表的当属 flannel 和 calico

  • Flannel
    通过二层 overlay 实现,让 docker 数据包在 overlay 隧道中传输

  • calico
    将每台宿主机都当成一个路由器,构建一个 BGP 网络,docker 数据通过三层路由转发

以下将会尝试分析 Flannel 架构原理

Flannel 是 CoreOs 公司 为 K8s 设计的通过 Overlay Network 方式实现的架构,所谓的 overlay, 其实就是 tcp 的嵌套,在 tcp 协议中再封装一层 tcp/udp 协议,下图便是 vxlan overlay 包格式

image.png

依据封装协议的不同,flannel 还可以分为多种 backend

  • udp
    udp 模式的解封装是在用户态进行的,比较耗费性能
  • host-gw
    只适用于同一二层网络,无法跨越三层
  • vxlan
    vxlan 解封装在内核态进行,性能高,能跨域三层

Flannel vxlan 模式的组件主要包括 ectd、Flanneld 等

组件

ETCD

细心的你可能会发现 docker 分配到的网段是 172.17.0.0/16, 再启一台宿主机,你发现 docker 分配到的还是同样的网段,那 docker ip 岂不会存在重复的可能?这时就需要一个中心节点来保证 docker ip 的全局唯一性,etcd 担当的正是这样一个角色,它存储着所有节点分配到的网段,当然,它其实只是 Flannel 元信息的搬运工,真正负责划分网段,注册上报 subnet vtep 等信息的是 Flanneld

Flanneld

flanned 作为 ectcd 的 agent 运行在宿主机节点上,主要有以下功能:

  • 从 etcd 中获取 network 的配置信息
  • 划分 subnet,并在 etcd 中进行注册
  • 将子网信息记录到宿主机的/run/flannel/subnet.env
root@ip-172-25-34-198:~#  cat /run/flannel/subnet.envFLANNEL_NETWORK=10.244.0.0/16
FLANNEL_SUBNET=192.168.2.1/24
FLANNEL_MTU=8951
FLANNEL_IPMASQ=true
ubuntu@ip-172-25-33-13:~$ cat /run/flannel/subnet.envFLANNEL_NETWORK=10.244.0.0/16
FLANNEL_SUBNET=192.168.1.1/24
FLANNEL_MTU=8951
FLANNEL_IPMASQ=true

如上所示,两个节点分配到了不同的 subnet, 这些信息会上报到 ectcd, 并被注入到 docker 启动参数中,从而保证了 docker ip 的集群唯一性

vxlan 封装流程

ip 已经唯一了,那么跨节点的 docker 究竟是如何通信的?预知底层原理,还需举个栗子

便于理解, 基于 vxlan 模式分析下 跨节点 docker 192.168.1.4 —> 192.168.2.2 间的数据转发流程

image.png

1. 数据一开始从 ip 为 192.168.1.4 的 docker 出发,根据路由表,目的地址为 192.168.2.2 的数据包匹配到了默认路由, 于是报文从容器的 eth0 网络接口发送出去

1. 数据一开始从 ip 为 192.168.1.4 的 docker 出发,根据路由表,目的地址为 192.168.2.2 的数据包匹配到了默认路由, 于是报文从容器的 eth0 网络接口发送出去

root@ip-172-25-33-13:/etc/cni/net.d# docker exec bd69 ip addr1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever3: eth0@if8: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 8951 qdisc noqueue state UP
    link/ether 96:53:14:4a:4e:bd brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.4/24 scope global eth0
       valid_lft forever preferred_lft forever
root@ip-172-25-33-13:/etc/cni/net.d# docker exec bd69 ip routedefault via 192.168.1.1 dev eth010.244.0.0/16 via 192.168.1.1 dev eth0192.168.1.0/24 dev eth0  proto kernel  scope link  src 192.168.1.4

2. 从 docker 出来的数据会发送到哪里? 理论上,docker 和宿主机位于两个不同的网络 namespace 中,网络是不通的,但是,vethpair 的出现,让情况不一样了,vethpari 是连接两个 namespaces 的”网线”,串联起 docker eth0 接口和宿主机的 cni0 接口,于是数据包会被再次转发到宿主机的 cni0

3. 宿主机上的路由表,有一条目的地址为 192.168.2.0/24 的路由,于是颠沛流离的目标数据包又再次被转发到 flannel.1 端口

root@ip-172-25-33-13:/etc/cni/net.d# ip routedefault via 172.25.32.1 dev eth0172.17.0.0/16 dev docker0  proto kernel  scope link  src 172.17.0.1 linkdown172.25.32.0/20 dev eth0  proto kernel  scope link  src 172.25.33.13192.168.0.0/24 via 192.168.0.0 dev flannel.1 onlink192.168.1.0/24 dev cni0  proto kernel  scope link  src 192.168.1.1192.168.2.0/24 via 192.168.2.0 dev flannel.1 onlink

4.flannel.1 是一个虚拟 VTEP(Vxlan tunnel endpoint) 设备,负责 vxlan 协议报文的封解包,一旦数据包发送到 flannel.1,就会进入 vxlan 协议的套路

当数据报文来到 flannel.1 时,需要根据 vxlan 协议封装数据包,此时的 dst ip 为 192.168.2.2,flannel.1 需要知道 192.168.2.2 对应的 mac 地址, 不同于传统的二层寻址,flannel.1 不会发送 arp 请求去获得 192.168.2.2 的 mac 地址,而是由 Linux kernel 将一个“L3 Miss”事件请求发送的用户空间的 flanned 程序。Flanned 程序收到内核的请求事件之后,从 etcd 查找能够匹配目标地址的子网的 flannel.1 设备的 mac 地址,即发往的 pod 所在 host 中 flannel.1 设备的 mac 地址, 这样,就形成了如下完整的 vxlan 内层数据封包格式

image.png

5. 按照 TCP 协议栈从上向下的封装工序,要形成完整的能够传输的数据帧,还需要目标 ip 和目标 mac 地址,那么这两者如何找寻呢?
对于目标 ip,flannel.1 根据对端 vtep 的 mac 地址,从 fdb(forwarding database) 中查找 mac 地址 26:1c:b0:2b:17:31 对应的 ip 地址,如下所示,该 mac 地址对应的 ip 为 172.25.34.198

root@ip-172-25-33-13:~# bridge fdb show dev flannel.1
22:5b:16:5a:1b:fc dst 172.25.42.118 self permanent26:1c:b0:2b:17:31 dst 172.25.34.198 self permanent

再通过 arp 协议即可找到 172.25.34.198 对应的 mac 地址,于是,一个完整的数据帧便呈现如下:

image.png

最终,目标 ip 为 172.25.34.198 目标端口为 8472 的数据帧便从宿主机的 eth0 端口发送出去

6. 数据帧通过二层网络顺利转发到达对端 172.25.34.198 宿主机的 8472 端口,此端口正是 Flanneld 的监听端口,Flanneld 解封 vxlan 数据包, 发现数据包目标 ip 为 192.168.2.2, 匹配系统明细路由,将数据包转发给了 cni0 端口

root@ip-172-25-34-198:~# ip route
default via 172.25.32.1 dev eth0172.17.0.0/16 dev docker0  proto kernel  scope link  src 172.17.0.1 linkdown
172.25.32.0/20 dev eth0  proto kernel  scope link  src 172.25.34.198
192.168.0.0/24 via 192.168.0.0 dev flannel.1 onlink
192.168.1.0/24 via 192.168.1.0 dev flannel.1 onlink
192.168.2.0/24 dev cni0  proto kernel  scope link  src 192.168.2.1

7. 前文提及,cni0 端口是与 docker namespace 直连的,于是通过 vethpair 将数据转发到了 docker192.168.2.1 中

历经千辛万苦,尝尽无数套路,数据包最终实现了跨主机 docke 间的通信

总结

Flanneld 和 ETCD 共同构筑了覆盖所有集群节点的 overlay 网络,使得 docker 之间能够在这层 "隧道" 中使用自己的 ip 相互通信。


猜你喜欢

转载自blog.51cto.com/3379770/2638236