如何打通 K8s 虚拟网络(flannel vxlan 网络)和 K8s 2层网络(macvlan网络)

背景

谈论问题,先谈背景。

K8s 在虚拟网络下存在很多问题。比如说:

  1. Pod IP 无法被外部访问,只能在 K8s 集群内访问。直接从实例上无法做到与物理机实例互联互通。
  2. Java 等依赖外部注册中心的服务,如果不放到同一个 K8s 集群里,服务之间无法互访。
  3. 复杂场景下,比如:多机房、集群灾备等,需要多 K8s 集群。原生 K8s 集群下,每个集群网络都是虚拟的,多个虚拟网络要互通。如果是 flannel 的话,可以借助同一个 etcd 来实现多 K8s 集群的 Pod 互通,但是 service ip 可就不行了,依然是个问题。
  4. 以应用中心为视角来看,从上层架构来说,底层 instance 具备可访问行,不论是容器实例,物理机,还是虚拟机实例,都应该具备可访问属性。而虚拟网络下的容器实例,违背了这一点。

所以说,K8s 虚拟网络模型,需要变迁到 K8s 2层容器网络模型。改造为2层网络的方式主要处理几个点:

  1. 从 CNI 中,选择容器网络2层化方案。
  2. 从 CNI 中,选择合适的 IPAM 方案。

K8s 官方的 CNI 中,能比较好的支持容器2层网络方案的,比如:bridge + veth pair、macvlan、ipvlan 等。而 IPAM ,可以说最基本的就是 host-local 方案了。

简单比较一下 bridge 模型和 macvlan 模式

bridge 就是虚拟交换机:网桥。从一些资料来看,如果单纯的说 bridge,其本身性能还是很高的,但是 bridge 下挂 veth pair,整套连起来运作,veth pair 会拖累 bridge 的性能表现。这样一来,就远没有 macvlan 性能高了。我们参考几组数据:

1、macvlan bridge 模式,对 CPU 负载的消耗,bridge 模式占用 CPU 资源最高。

2、对比 macvlan、bridge、OVS 及 Native Host 的性能

注意:CHC 表是的是,同主机上 Container->Host->Container 模式。CHHC 表是的是跨主机:Container->Host->Host->Container 模式。

可以看到,macvlan 模式下,不论是同主机间的容器互访,还是跨主机间的容器互访,其单位时间内的请求/响应量级都很高,几乎接近主机网络(Native Host)。

再来看一下吞吐量(容器互访,横坐标为TCP)

更详细的评测数据,可以参考:performance of container networking.pdf

总的来说,从某个角度看,macvlan 就是要替换 bridge + veth 模式的,就好比,macvtap 就是要替换 bridge + tap 模式的,macvlan 和 macvtap 只是用在场景不同而已。而我们团队,出于性能考虑,目前开发环境、线上环境,CNI 插件都是用 macvlan 做的2层容器网络,IPAM(IP Address Management) 模式,使用的是简单的 host-local(本机分配) 模式(后期要调整)。

是的。这里提到,“我们团队”。其实,在整个公司中,做 K8s 团队的不止我们一个。其他团队的实施,还主要以 flannel 虚拟网络为主。目前来说,2个团队现在已经整合为一个部门的团队,所以,K8s 基础设施也要统一,恰好,其中重要的一环,就是将现在虚拟网络的 K8s,也改造为 L2 层网络的 K8s 。目的主要有3个:

  1. 底层设施层面,统一 K8s 集群。不要虚拟和2层都有,增加维护成本。
  2. 在上层部署系统层面,由一套统一的部署系统,分别对接底层的 K8s。这就要求底层的 K8s 是统一的。
  3. 更上面的各种抽象模型统一,比如,对接服务到应用中心,而应用中心下的 instance 模型,要求 instance 实例都在 2层网络下,这样一来,围绕应用中心展开的:配置中心、发现中心、监控告警,都对接 2 层 instance 实例,就好开展了。

好了,问题来了:

  1. 虽然 K8s 集群做成 L2 K8s 不是很复杂的事儿,但是改造完得迁移服务。
  2. 迁移的过程,不是强制将原集群的实例,部署到新集群里,这个过程,是用户部署的动作来触发,应该是平滑的,对其业务无感知的。强制从1个集群迁移到另外一个集群,风险太高。
  3. 要保证业务平滑,必须会遇到一个现实问题:部分服务在虚拟网络 K8s 中,部分在2层网络 K8s 中。虚拟网络 K8s 集群里的实例访问 K8s L2层网络下的实例还好,不会有问题,但是,反过来,可就访问不通了(原生 K8s 虚拟网络的所有实例的可访问性,是只有集群内部生效的)。

所以,一个问题摆在眼前:K8s 虚拟网络,要和 K8s L2 层网络打通。这样才能保证新部署的服务,部署到 L2 层 K8s 集群里之后,它还可以访问到原来那个 K8s 虚拟网络下的实例。这一点对 Java 这种要通过注册中心,走RPC调用的服务,尤为重要。

注意:本文说的打通,其立场,是 2 个集群打通,而不是一个 L2 层网卡的 K8s 集群,要和多个 虚拟网络 K8s 集群打通。

K8s 虚拟网络(flannel)和 L2 层网络(macvlan)

L2 macvlan 网络

要做 2 个不同网络集群的打通,macvlan 网络最简单,简单理解为一个网桥就好,实际上,我们的 macvlan 就是用的 bridge 模式。

下面是 K8s 基于 macvlan 网络的一个简单架构图:

马蜂窝二层K8s.png

这个网络模式有几个核心的点:

  1. 容器之间,是可以通过 macvlan 互访的,不需要借助外部交换机。所以,macvlan 模式,用的是 bridge 模式,而不是 vepa 模式,vepa 模式的话,需要交换机开启“发夹”模式,对于我们的场景来说,没有必要在交换机上做这个事情,另外,交换机上还没有统一管理规则的需求。
  2. macvlan 的父接口,是 bond0,bond0 是做了网卡聚合的。可以提高带宽。
  3. 宿主机网络上,有一个 macv-host 网卡。这个网卡其实是 macvlan 的一个子接口,其目的,是为了实现宿主机与容器互访,这一点非常重要。
  4. K8s 集群的交换机是独享,且做了高可用的。为了应对以后容器数量增长提前做的准备。
  5. 我们是自建机房。如果是云服务器的话,macvlan 模式可能不一定用的了。因为 macvlan 依赖网卡特性以及网卡的混杂模式,这些特点,云服务器不一定支持。云服务器的话,可以考虑云服务厂商提供的现成的,且与 VPC 良好集合的方案。

flannel 网络

flannel 网络,相对于 macvlan 网络来说,要复杂一些。

flannel 网络,是 CoreOS 团队为 K8s 打造的一款用于解决 POD 跨主机互访的网络实现。它是一个 overlay 网络,是构建在 L2 层网络之上的虚拟网络。每一个 Pod 实例,都有一个虚拟 IP 地址。

下面一是 flannel 的网络架构图:

Cluster Host

这个网络模式有几个核心的点:

  1. 在主机上,flannel 的守护进程 flanneld 在启动之后,会创建一个 flannel.1 的网卡(如果是 vxlan backend 的话)。它的作用,就是为了容器跨主机访问做封包、解包、创建整个 POD 网络互访需要的路由规则等内容的。要点:解决容器跨节点通信。
  2. 在主机上,还有一个 cni0 网卡。它的作用,和 docker 本身网络下的 docker0 作用是一样的,它们都是网桥 bridge。目的是解决容器在同一个主机上通信的。另外,cni0 这个网卡,并不是 flannel 启动的,确切的说,它是 K8s flannel 的这个 CNI 插件(在第一次创建 Pod 的时候)创建的。
  3. flannel 网络是一个虚拟网络,容器的流量要出访,必须经过 flannel 做封包处理,这也是 overlay 网络的特点。flannel vxlan 是做的 UDP 封包。

如果在 L2 层 K8s 网络里引入 flannel 网络,必须理清几个点

打通 2 个网络的方案中,是可以考虑,在 L2 层 K8s 中,引入虚拟网络这种方案的。如果是这种方案的话,flannel 网络下 K8s 用的组件,是不一定都得用在 L2 层 K8s 网络里的。因此,要理清几个点:

①:访问虚拟网络 POD 实例,就一定需要知道目标实例在哪个物理机节点。这个是谁来维护的(flanneld 守护进程)。

②:只是知道谁来维护 POD 网络信息还不行,必须得具备访问虚拟网络的能力,这就依赖 flannel 的 网卡:flannel.1 。这个网卡,也是 flanneld 守护进程创建的。

③:通常 flannel 网络里,还有一个 cni0 网卡,它要解决的点是同一个主机上容器互通的。而 L2 层 K8s 集群访问虚拟网络 K8s 集群,是要跨节点访问的,所以,L2 层网络的 K8s 下,cni0 网卡并不需要。

因此,从表现上来看,如果我们的方案是在 L2 层 K8s 里起虚拟网络 flannel 的话,只用 flannel 网络里的 flanneld 进程,而不用 flannel 网络下的 CNI 插件。

实施方案

关于打通 2 个现有不同网络模式的 K8s 集群,做到实例互通,其实最大的点在于,对哪个集群做改造。L2 层网络的 K8s 是基于 macvlan 来做的,这种模式的 K8s 下,容器 IP 就和虚拟机 IP 一样,可以认为 L2 层网络的 K8s 下的容器,就和现有的物理机、虚拟机一样。因此,这个问题的本质就成了:如何让 L2 层网络的 K8s 可以访问到虚拟网络的 K8s 实例。

方案有3个:

方案1:通过 VPN 的方式

VPN,在 L2 层网络的每一个节点,通过 VPN 的方式接入虚拟网络中。所以,需要虚拟网络 K8s 的某个节点开启 VPN Server,2 层网络的每一个 K8s 节点,都通过 VPN Client 接入到虚拟网络中。这种方式,有2个弊端:

  1. 原来的虚拟网络集群的某个节点,得起一个 Server,这个 Server 所在节点,要承载 2个 集群的所有互访流量。这个 Server 所在主机的网络带宽可能会成为瓶颈,另外,Server 一旦挂了,2个集群的互通,全部中断。
  2. L2 层网络 K8s 的所有节点,都得启动 VPN Client。这也是个维护成本。

方案2:找几个节点,做路由转发,相当于路由器的角色。

这个方案的主要核心在于,在虚拟网络 K8s 中,挑选几个节点,作为路由器角色,暂且称之为中转节点。L2 层网络K8s 访问虚拟网络 K8s 的流量,都经过这几个中转节点。中转节点,必须要在虚拟网络 K8s 中。访问模式为:2层容器->中转节点->虚拟网络容器。这个方案,如果可以实施的话,还是会存在中转节点是网络瓶颈的问题(但是比方案1好多了)。

方案3:将 L2 层网络的 K8s 集群的节点,全部加入到虚拟网络 K8s 的网络中。

这个方案的本质是,在 L2 层网络下的 K8s 节点上,部署 flannel 的组件。从网络层的角度看,L2 K8s 集群的实例,即属于 macvlan 2层网络,又属于 flannel 虚拟网络。但是这里有几个细节是要注意的:

  1. L2 K8s 集群部署的 flannel 其实是需要在每个节点都部署的。
  2. L2 K8s 节点上的 flannel,其职责,只是为了启动 flannel.1 这个虚拟网卡,这个网络的职责,就是承担 L2 K8s 节点实例,访问虚拟网络的一个桥梁。
  3. flannel 组件,必须,且不能干预到 L2 层 K8s 体系内的 2 个角色:Docker 、CNI。之所以提这个点,是因为,flannel 的部署往往和容器的 IP 分配耦合在一起。尤其是 flannel 官方提供的部署工具中,还有操作 docker 的脚本。这个是不需要执行的。

上面的3个方案,最终的落地方案是:方案3 。原因是操作简单,没有任何节点成为整个方案的瓶颈。

最终方案中,要解决及不准备解决的问题

  1. 虚拟网络 K8s 下的容器,访问 L2 层 K8s 下的容器和物理机。( 要解决,且天然就没有屏障)
  2. L2 物理机访问虚拟网络容器。(要解决)
  3. L2 容器访问虚拟网络容器。(要解决,注意,这里没和第 2 点放到一起,是因为这个问题解起来不一样)
  4. L2 容器和物理机访问虚拟网络 service vip 。(不解决)

关于在 L2 层 K8s 中,【不打算】支持访问 flannel 虚拟网络里的 service ip 的理由如下:

①:目前,需要网络里的服务,不需要靠 K8s service ip 来调用。比如:Java 体系内的 Spring Cloud 实例互访就不需要 service 。

②:L2 层网络 K8s 访问虚拟网络 service ip,相当于动作比较大,需要在 L2 层 K8s 部署 kube-proxy ,且这个 kube-proxy 要指向到另外一个虚拟网络集群的 kueb-apiserver 上。仅为支持这样一个弱需求,就要增加了整体架构复杂度。且,如果在 L2 层 K8s 里引入其他集群的 kube-proxy,它会新生成很多的 iptables 规则,有碍于现有 L2 层 K8s 的整体性能。

③:在一个纯 L2 层的 K8s 网络里,支持 service ip ,并不是说起了 kube-proxy 就完了,这和 flannel 不一样。后边会专门开心的文章,将如何在 L2 层 K8s 对 service ip 的访问支持。

最终方案架构

K8s 虚拟网络(flannel)与2层网络(macvlan)容器网络打通 (1).png

说明:

  1. 左图为基于 flannel 的虚拟网络集群,右图为基于 macvlan 的 2层网络集群。
  2. flannel 的网络相对经典,主机内容器互通(比如 集群1中,容器1 和容器2 互通),唯一的依赖就是网桥:bridge。
  3. macvlan 网络类似,主机内容器互通(比如 集群2中,容器1 和容器2 互通),唯一的依赖,就是 bond0(网卡聚合),这里要注意,bond0 已经成了 macvlan 下的虚拟交换机,类似于网桥,和 bridge 最大的区别有2个,一个 macvlan 相比 bridge 来说,交换性能更高,另外就是,主机若要访问主机上的容器,必须借助基于 bond0 生成一个宿主机的新网卡(子接口):macv-host。
  4. 主机内,不同网络的流转,借助主机内的路由表和 iptable。
  5. 集群2(2层网络)里的物理机,若要访问集群1(虚拟网络)里的容器。需要在2层网络集群的节点上,搭建 flannel。这个 flannel 版本、配置,和集群1里的 flannel 一样,并且使用同一套 etcd 集群,如上图。所有 flannel 基于 集群1 里的 etcd,共享网络发现能力和数据。所以,flannel 的职责非常清晰,只有3个:网络发现+同步路由表+overlay包处理。这种方式,解决了 L2物理机访问虚拟网络容器 这个点。
  6. 集群2(2层网络)里的容器,若要访问集群1(虚拟网络)里的容器,除了上面的工作之外,还需要一个事儿,那就是让集群2里的容器,知道如何访问集群1的网络。因此,需要将集群2的 K8s macvlan CNI 做一个配置,将访问虚拟网络的路由表,配置进去,且这个对虚拟网络的路由表依赖的网关,设置为当前物理主机。换句话说,当前主机,要充当路由器的角色。

2层K8s里的容器访问虚拟网络的流量的数据流为:

2层网络 K8s 容器->流转到当前物理机->主机路由表->主机flannel->出2层K8s的节点->到虚拟K8s的节点->节点路由表->节点bridge网桥->虚拟网络容器内部。

补充

1、贴一下 L2 层 K8s 节点最终的 CNI 配置

这个是 L2 K8s CNI 必须要做的一个配置,其目的是,让 L2 K8s 里启动的容器里,包括一个路由表,告诉其如果访问的网络是虚拟网络的 IP 端口的话,流量不要走原来的网关,而是走到当前节点的物理机上。

另外,L2 K8s 节点还要做几个事儿:

  1. Linux 上 必须开启 ip_forward。
  2. iptables 中也要适当做放行,也就是说,需要在 FORWARD 链添加 ACCEPT。

上面的2个点如果不做处理的话,发往虚拟网络下容器的流量,到了宿主机之后,也会被丢弃。

2、一些参考

https://www.lijiaocn.com/%E6%8A%80%E5%B7%A7/2017/03/31/linux-net-devices.html
https://blog.huiyiqun.me/2016/11/24/virtualization-with-libvirt-kvm-and-macvtap.html
https://events.static.linuxfound.org/sites/events/files/slides/LinuxConJapan2014_makita_0.pdf
https://cs.nju.edu.cn/tianchen/lunwen/2017/sgws-Zhao.pdf
https://tigerprints.clemson.edu/cgi/viewcontent.cgi?article=1034&context=computing_pubs
https://www.cnblogs.com/echo1937/p/7249037.html
https://blog.51cto.com/dog250/1652063
https://www.praqma.com/stories/debugging-kubernetes-networking
https://msazure.club/flannel-networking-demystify

Donate comment here