从美图容器优化实践谈Kubernetes网络方案设计

本文通过介绍美图线上容器化的实践经验,包括线上遇到的实际问题,来探讨 Kubernetes 环境下的网络方案设计。值得正在转型 K8S 的架构师学习和借鉴。

李连荣,美图高级系统研发工程师,曾建立支持千万的长连接服务,从零开始在建立美图的容器化服务,并主导完成美图容器化的网络方案。在网络、存储方面有非常深厚的造诣。

目前,我们的 Kubernetes 集群选择使用 Calico 作为基础网络方案。


选择 Calico 网络方案的挑战

Calico 是一套基于路由(BGP)的 SDN,它通过路由转发的方式实现容器的跨主机通信。Calico 将每个节点虚拟为一个“路由器”并为之分配独立的虚拟网段,该路由器为当前节点上的容器提供路由服务。

更多 Calico 项目介绍可参阅 https://www.projectcalico.org/ 下面以具体网络为例介绍其中的设计与难点。

从美图容器优化实践谈Kubernetes网络方案设计

以上图为例,如果节点 192.168.1.2 分配的虚拟网段是 10.233.1.0/24,其上运行了一个容器 10.233.1.2,其路由信息如下:

10.233.1.2 0.0.0.0 255.255.255.255 UH 0 0 0 cali814214d5913

当物理机收到目标地址为10.233.1.2 的IP 数据包时会转发到网口 cali814214d5913,而 cali814214d5913 是通过 veth-pair 创建的网卡,它与本机上 IP 地址为 10.233.1.2 的容器互通,因此,IP 地址为 10.233.1.2 容器就可以收到相应的 IP 数据包。

当位于节点 192.168.1.3 上的容器 10.233.2.2 给 10.233.1.2 发送 IP 数据包时,需要知道 10.233.1.2 所在的物理节点的 IP,并添加以下路由规则:

10.233.1.0/16 192.168.1.2 eth0

Calico 通过 BGP 实现节点间相互学习路由规则。节点 192.168.1.2与节点192.168.1.3 建立 BGP 邻居,节点 192.168.1.3 可以通过 BGP 学习到上面的路由规则。当容器 10.233.2.2 发送 IP 数据包给 10.233.1.2 时,会根据上面的路由规则转发到节点 192.168.1.2,并由节点 192.168.12 转发给 10.233.1.2,进而实现容器的跨主机通信。

但是当两个容器所在的节点处于不同的子网时,如10.233.3.2,其所在的节点192.168.2.2与节点192.168.1.2处于不同的子网,此时无法在192.168.2.2上添加以下路由:

10.233.1.0/16 192.168.1.2 eth0

这是因为物理机 192.168.2.2与物理机 192.168.1.2 链路层不通。为了解决这个问题,Calico 选择了 IPIP。IPIP 是将虚拟网络的 IP 数据包封装到物理网络的 IP 数据包里传输。启用 IPIP 后,节点上会出现相应的虚拟网卡,通常是 tunl0,节点1 92.168.2.2 可以添加以下路由规则:

10.233.1.0/16 192.168.1.2 tunl0

与之前的路由的区别在网口换成了 tunl0。当 10.233.3.2 发送 IP 数据包给10.233.1.2 时,其所在的节点会将 IP 数据包转发到网口 tunl0,转发到 tunl0 的 IP 数据包会被 IPIP 驱动接管。IPIP 驱动会将每个 IP 数据包封装到物理网络的 IP 数据包内(目标地址是下一跳地址,即 192.168.1.2,Payload 是虚拟机发出的原始 IP 数据包)发送出去。

由于该 IP 数据包的目标地址是节点 192.168.1.2,因此,可以经过物理网关进行转发。运行在节点 192.168.1.2 上 IPIP 服务接收到该物理网络 IP 数据包后将其 Payload 取出,再根据节点 192.168.1.2 上路由规则转发给相应的容器,进而实现了容器的跨子网通信。

Calico 网络方案存在的问题

通过 Calico 的工作原理可以看出,Calico 存在以下问题:

  • 使用 IPIP 时,需要嵌套 IP 协议,多余的打包和拆包动作会带来的性能开销。
  • 使用 IPIP 时,嵌套的 IP 协议头导致实际有效的 MTU 长度变小,也会影响实际的带宽利用率。
  • 由于集群外的节点无法学习集群内的路由信息,故无法直接访问集群内的容器。

根据 Calico 的工作原理可知,Calico 为了解决容器的跨子网通信选择了 IPIP,也正是因为引入了 IPIP 才引发了一系列的性能问题,那么,为什么 Calico 会选择 IPIP 协议呢?

为了理解这个问题,我们先看一下传统的物理网络是如何解决跨子网通信的。仍以上图为例,物理机 192.168.2.3 访问物理机 192.168.1.2 的步骤如下:

  • 物理机 192.168.2.3 检测到目标 IP 与自己处于不同的子网,因此,通过默认路由规则发送给其所在网关 192.168.2.1。
  • 物理网关 192.168.2.1 通过路由协议可以知道网关 192.168.2.1 可以转发 IP 包给物理 192.168.1.2,因此,将相应的 IP 包转发给网关 192.168.1.1。
  • 物理网关 192.168.1.1 再将 IP 包转发给物理机 192.168.1.2。

我们再来看一下 Calico 网络。如果没有 IPIP,容器 10.233.4.2 访问容器 10.233.1.2 的步骤如下:

  • 宿主机 192.168.2.3 检测到目标 IP 与自己处于不同的子网,因此,会将目标地址为 10.233.1.2 的 IP 包转发给其所在网关 192.168.2.1。
  • 网关 192.168.2.1 没有匹配的路由规则,因此 drop 该 IP 包并会返回目标不可达。

如果引入 IPIP,容器 10.233.4.2 访问容器 10.233.1.2 的步骤如下:

  • 宿主机 192.168.2.3 匹配到路由规则 “10.233.1.0/16 192.168.1.2 tunl0”。
  • 宿主机 192.168.2.3 将该容器发出的 IP 包通过 tunl0 端口转发到物理机192.168.1.2。
  • IPIP 驱动将容器发出的 IP 包(目标地址 10.233.1.2)作为物理网络 IP 包(目标地址 192.168.1.2)的 Payload 发出。
  • 宿主机 192.168.2.3 按照物理网络的传输方式将物理网络 IP 包发送到宿主机 192.168.1.2。
  • 宿主机 192.168.1.2 上 IPIP 驱动接收到该 IP 包之后解包并将该 Payload 作为 IP 包转发给容器 10.233.1.2。

因此,在物理网关不能为虚拟网络提供路由服务的前提下,Calico 选择使用 IPIP 的方式来解决容器的跨子网通信。

如果通过某种方法,让物理网关能够学习到 Calico 虚拟网络的路由规则,那么物理网关就可以为虚拟网络提供路由服务,Calico 就可以在不引入 IPIP 的前提下实现容器的跨子网通信,但是,Calico 为什么没有选择这种方式呢?

这是因为 Calico 的主要应用场景是公有云,公有云具备以下特性:

  • 多数公有云厂商可以提供一个稳定的大二层环境,其内的主机可以工作在同一个子网段,也就不存在跨子网通信的问题
  • 不是所有的公有云厂商都可以提供 BGP 路由学习的接口,如果云厂商不提供 BGP 接口,Calico就无法同步虚拟网络路由规则给公有云

因此,在这个前提下,Calico 选择 IPIP 方式是非常合理的。

对于私有云场景:

  • 不是所有的私有云环境都支持大二层,对 Calico 跨子网通信的需求是非常强烈的。
  • 所有硬件(包括网关)都在可控范围内,只要物理网关支持 BGP 协议,Calico 就可以同步路由规则给物理网关。

因此,对于私有云场景,通过让 Calico 同步虚拟网络路由规则给物理网关的方式来解决跨子网通信会是更好的选择。

性能提升方案

其实 Calico 的文档中提到了一种同步路由规则给物理网关的方式,具体可以参考如下链接:

https://docs.projectcalico.org/v2.6/usage/external-connectivity

本文参考这种设计,将虚拟网络路由规则同步给物理网关,虚拟网络与物理网络建立 BGP 邻居的方式有两种:

  • 方案一:各节点分别物理网关建立 BGP 邻居
  • 方案二:中心化组件与物理网关建立 BGP 邻居

下面针对两种方案进行分析。

方案一

如下图所示,方案一需要每个 SDN 节点分别与其所在的物理网关建立 BGP 邻居。由于每个 SDN 节点作为本机容器的网关,如果运行在该物理机上 Calico 服务能够与物理网关建立 BGP,并将自己的路由规则同步给物理网关,那么,物理网关就可以学习虚拟网络的路由规则。

从美图容器优化实践谈Kubernetes网络方案设计

运行在节点上的 SDN 服务可以通过编码或者脚本实现自动建立 BGP 邻居的逻辑,但是,这也需要物理网关的支持。如果物理网关不支持自动建立 BGP 邻居,仅仅是在 SDN 端实现是没有意义的。

传统的 BGP 路由器需要逐个配置 BGP 邻居,这对容器化集群来说是不可接受到。因为容器化集群的规模一般都比较大,在集群使用过程中也会经常的调整节点,如扩容、缩容、机器故障等。如果每次调整节点都需要运维手动完成 BGP 邻居的配置,运维成本是巨大的,而且容易因误操作而影响集群稳定性,因此,需要网关支持自动建立 BGP 邻居。

要实现自动建立 BGP 邻居需要使用支持 Dynamic Neighbors 功能的路由器。传统的路由器在配置 BGP 邻居时需要指定明确的 IP 地址,而支持 Dynamic Neighbors 的路由器可以指定一个 IP 网段,该路由器可以自动接受指定网段内BGP设备发起的建立邻居的请求。

集群部署前先配置好路由器的 Dynamic Neighbors,调整集群节点时运行在节点上的 SDN 服务主动与之建立 BGP 邻居,并将其所在节点的路由信息同步给物理网关。例如,运行在物理机 192.168.1.2 上的 SDN 服务自动与物理网关 192.168.1.1 建立 BGP 邻居(eBGP)并将以下路由规则同步给物理网关:

10.233.1.0/24 192.168.1.2 eth0

物理网关 192.168.1.1 学习到以上规则之后会通过 BGP 或者其它路由同步协议同步给物理网关 192.168.2.1,通过这种方式,整个物理网络都可以学习上以上规则。集群的容器要发送数据到 10.233.1.0/24 网段时,直接通过其宿主机将数据包发送给物理网关,物理网关即可根据其学到的虚拟网络路由信息转发到目标主机,最后,再由目标主机转发给相应的容器。

方案二

方案二需要引入中心化组件,本文称之为 BGP Speaker,其网络结构如下:

从美图容器优化实践谈Kubernetes网络方案设计

BGP Speaker 运行在 SDN 集群内,它负责收集 SDN 集群内的路由信息并通过BGP 同步给物理网关。由于该模块可以收集 SDN 集群内的全部路由信息,故不再需要每个 SDN 节点单独与物理网关建立 BGP 邻居,也就不再依赖物理网关的的 Dynamic Neighbors 功能。

如下图所示,BGP Speaker 主要分为两部分:observer 和 publisher。

  • observer:负责收集 SDN 集群的路由信息。
  • publisher:将 observer 收集到的路由信息同步给物理网关。
从美图容器优化实践谈Kubernetes网络方案设计

Calico 将 SDN 集群相关的信息(包括配置信息、每个节点的虚拟网段等)保存在 etcd 里,而 etcd 的支持 watch,因此,observer 可以通过watch方式实时获取 Calico 集群的节点信息。observer 主要关注每个 SDN 节点划分的虚拟网段信息,并根据获取到的虚拟网段信息生成对应的路由规则,如节点 192.168.1.2 划分的虚拟网段是 10.233.1.0/24,observer 会生成以下路由规则:

10.233.1.0/24 192.168.1.2 eth0

publisher 实现了 BGP 协议,它与物理网关建立 BGP 邻居(eBGP),主要工作是将 observer 收集到的路由信息同步给物理网关。由于 publisher 需要将不同节点的路由信息同步给物理网关,因此,publisher 对物理网关来讲,实际上是个 BGP Route Reflector。

gobgp 提供了开源的 BGP 协议库,支持完整的 BGP 协议,可以作为 publisher的基础库。另外,BGP 是双向的,publisher 不仅可以将自己持有的路由信息同步给物理路由器,物理路由器也会同步路由信息给 publisher,由于 SDN 不需要了解物理网络的路由信息,因此,publisher 可以过滤掉这些路由信息。

由于 BGP Speaker 是中心化部署的,因此 BGP Spaker 的高可用程度会直接影响 SDN 集群的稳定性。BGP Speaker 直接从 SDN 集群的 etcd 存储中获取集群信息,其自身不需要保存任何数据(包括生成的路由信息),故 BGP Speaker是无状态的,因此,只需要在 SDN 集群内部署多套 BGP Speaker 即可实现高可用。

多套 BGP Speaker 之间相互不感知,各自独立运行。部署 BGP Speaker时,还可以根据物理网络的拓扑结构将 BGP Speaker 部署在不同的机架或者机房,保证某个机架或机房出现故障时 SDN 集群仍可正常工作。

安全隐患

以上两种方案都可以实现物理网络与虚拟网络的互通,但两种方案都是虚拟网络通过 BGP 协议自动的同步路由规则给物理网关,如果虚拟网络产生了错误的路由规则或者产生的路由规则与物理网络冲突,就会影响集群所处物理网络的运行状态;如果同一物理网络内部署了多套 SDN 集群且这多套 SDN 的网段有冲突时,也会造成虚拟网络的路由规则紊乱,影响 SDN 的稳定性。

因此,还需要采取一些措施来保证 SDN 之间以及 SDN 与物理网络之间不冲突。

确保 SDN 与物理网络不冲突

现在的物理网关大多都支持路由过滤,也就是说当网关通过 BGP 学习到新的路由规则时,可以根据一定的规则进行过滤,只有满足要求的路由规则,才会同步到自己的路由表。部署容器化集群时,应先规划好 SDN 的网络地址,要保证 SDN 的网络地址与物理网络不冲突。

配置物理网关时,可以通过设置过滤规则来保证 SDN 不能修改其所属网段以外的任何任何路由规则(例如,限制 SDN 只能更新目标地址处于 10.233.0.0/16 网段内的路由规则),通过这种方式,可以确保 SDN 不会影响物理网络稳定性。

确保 SDN 之间不冲突

物理网络与虚拟网络互通后,部署在同一个物理网络内的多个 SDN 集群的路由信息都会同步到物理网关,如果不同 SDN 的网络地址有重叠,就会引发冲突,导致 SDN 网络无法稳定工作,因此,在部署 SDN 集群时,还需要为不同的 SDN 集群分配不同的网络地址(这些地址都与物理网络不冲突)。配置网关时,限制每个 SDN 只能同步处于自己网络地址内的路由规则。

总结

Calico 存在性能问题的根本原因是物理网络与虚拟网络不互通,本文设计了两种方案实现物理网络与虚拟网络的互通,解决了 Calico 引入 IPIP 和 NAT 带来的性能问题,也带来了一定的安全隐患,但是,可以通过额外的保障措施排除安全隐患,保证物理网络与 SDN 网络的稳定运行。

本文转自kubernetes中文社区-从美图容器优化实践谈Kubernetes网络方案设计

上一篇:ASP.net:未能加载文件或程序集


下一篇:使用xtrabackup全部备份与恢复数据库的真实案例