作者: 若禾

CNStack 是阿里云推出的一款开放的一站式企业级云原生技术中台。在异构的混合云基础设施上,对资源进行统一纳管和优化调度,以开放的、云原生的方式为平台及业务系统提供生产可用的产品及组件,帮助用户打造满足大规模、高性能、合规性和业务连续性等要求的分布式应用系统,提升企业数字化转型的整体效能。hybridnet 是 CNStack 使用的容器网络实现,同时也是阿里云推出的唯二开源通用 K8s 网络插件实现之一。

开源仓库地址:https://github.com/alibaba/hybridnet

解释下“通用”俩字,阿里云目前存在两种开源 K8s 网络插件实现:terway、hybridnet。与 hybridnet 网络插件不同,terway 网络插件是与阿里云基础设施紧密结合的,并不考虑在非阿里云公有云基础设施环境部署的情况,而 hybridnet 更像是类似 flannel、calico 的社区解决方案,支持在任意环境拉起和部署。本文会详细描述 hybridnet 网络插件的设计与实现。

背景

从诞生到现在,在阿里内部 hybridnet 一直承载的是“私有化交付基石”的角色,即为以 K8s 为交付媒介的 PaaS/SaaS 产品提供在客户线下机房的网络部署、运维能力,其性质看起来也就和 “为所有人提供 K8s 网络” 更加类似,我们对 hybridnet 的期望有几点:

  1. 架构简单、功能稳定。对于要交付到客户线下机房的产品,一旦出现问题,排查成本是非常高的,稳定性是重中之重。
  2. “On Anywhere” 部署。这确实是一开始的期望,听起来不可思议,但某种程度上确实实现了。
  3. 灵活。能够满足不同客户功能需求以及就广泛通用场景进行功能扩展。
  4. 运维简单。负责交付实施的同学能够快速上手。

当然,社区也存在众多优秀的网络插件实现,我们也对其进行了了解和调研,总结下最终没有采用的原因:

  1. 社区网络插件的网络模式是相对简单的,不是 overlay 就是 underlay,看下来没法既实现 “On Anywhere” 部署,又满足客户对于 underlay 网络场景的需求(性能或者网络打通)。
  2. 对于 underlay 网络场景需求,社区提供的主流解决方式一直是 “只提供通过 bgp 宣告路由” 的方式,这种方式对没有进行 bgp 组网的公司并不是很友好,我们仍需要没有 bgp 条件环境下的容器网络实现(也就是 vlan 方式),这方面社区并没有非常成熟的方案。
  3. 大部分社区网络插件的 ip 分配是 “为节点分配网段” 的模型,似乎并没有考虑处于 “传统运维” 过渡到 “云原生运维” 阶段用户可能存在的“需要容器固定 ip 被访问”的场景,也难以扩展实现。
  4. 部分社区网络插件的技术体系(ovs、ebpf)在运维、二次开发方面的门槛和成本比较高。

设计与实现

如何 “On Anywhere”

在设计之初,需要想明白的一个问题是:如何既实现 “On Anywhere” 部署又满足客户对于 underlay 网络场景的需求?首先我们要理解 overlay、underlay 在交付场景下的不同意义。

“overlay” 的网络方案作为在基础网络之上构建的抽象网络,本身其实就是屏蔽了底层基础设施依赖的,以 vxlan 容器网络为例,容器网络的 ip 流量被封装成宿主机的 udp 数据包,依赖的只是比较简单的 “底层网络环境放行 udp 流量” 能力,其实已经具备了 “On Anywhere” 部署的条件。

而在 “underlay” 容器网络方案中,容器网络的报文被 “原封不动” 地通过宿主机网卡转发到基础网络环境里,借助底层网络完成后续的通讯,底层网络环境需要具备打通以及放行所有容器网络流量的能力,这就在不同场景下带来了交付上的重重挑战,交付一个 underlay K8s 集群时,在 K8s 集群能正常工作之前,我们需要客户协助进行的工作包括但是不限于 “申请/规划网段”、“配置交换机端口”、“配置交换机路由”、“修改防火墙” 等等,整个过程由于门槛因素往往需要网络专家直接参与,极大增加了交付成本。

随着交付经验的积累,我们发现,大部分需要私有化交付的产品是不依赖 underlay 网络能力的,即便是产品需要依赖 underlay 能力,这部分 pod 的数量在整个集群中的占比也通常比较小,大部分产品的管理组件 pod 并没有特殊的网络要求。也就是说,对于大部分产品,如果能够保证管理组件在任何环境中能正常拉起,那产品的使用是不成问题的,其他配置甚至可以在交付后的阶段增量进行,提升交付效率。

进而我们得出了结论:“我们需要一个 overlay 和 underlay 网络能够同时存在的集群,其中 underlay 网络能否正常不影响 overlay 网络,underlay 网络可以增量配置,并且集群内 overlay 网络能和 underlay 网络不依赖基础设施互通”,这似乎就是目前私有化场景 “On Anywhere” 部署的正确答案。

通过 CRD 配置管理集群网络

为了便于管理,我们需要一个方式统一描述 overlay、underlay 容器网络资源,考虑到 underlay 容器网络依赖是对底层基础设施敏感的,并且往往呈现出 “节点分组” 的现象(比如同一交换机下的节点能够使用相同网段),我们提出了 “网络域” 的概念。

网络域中包含了一组具有相同网络性质(比如,处于相同 VLAN 环境、属于同一网关、属于相同二层网络)的节点,作为创建 ip 的底层资源。一个网络域具有这样的性质:“一个带有特定地址的 Pod 如果能被部署到网络域内的某一节点,那么带有相同地址的 Pod 应该能被调度到同一网络域内的其他任意节点”。对应了 Network CR 对象。在集群中可以通过 kubectl get network 命令查看,比如:

[root@iZf8z4x1svumpy31kcrltaZ ~]# kubectl get network init -oyaml
apiVersion: networking.alibaba.com/v1
kind: Network
metadata:
  annotations:
    meta.helm.sh/release-name: hybridnet
    meta.helm.sh/release-namespace: kube-system
  creationTimestamp: "2023-02-10T05:52:11Z"
  generation: 1
  labels:
    app.kubernetes.io/managed-by: Helm
    webhook.hybridnet.io/ignore: "true"
  name: init
  resourceVersion: "40647"
  uid: cc889b11-9ede-4711-93b3-910f0e59335e
spec:
  netID: 4
  type: Overlay
status:
  dualStackStatistics:
    available: 65526
    total: 0
    used: 0
  ipv6Statistics:
    available: 65534
    total: 65535
    used: 1
  lastAllocatedIPv6Subnet: init-2
  lastAllocatedSubnet: init
  nodeList:
  - izf8z4x1svumpy31kcrltaz
  - izf8zatm3b8zaaks5gqk0tz
  statistics:
    available: 65526
    total: 65534
    used: 8
  subnetList:
  - init
  - init-2

Network 对象通过 nodeSelector 选择带有特定 label 的节点作为网络域中的节点,借助网络域的概念,我们实现了依赖特定网络资源 pod 的自动调度。

前面我们说到,overlay 网络是不依赖底层网络资源的,所以创建 overlay 的 Network 对象时,nodeSelector 一定只能为空,并且每个集群只能创建一个 overlay 的网络域,这网络域对应了集群内的所有节点

通过 “网络域” 概念解决了 “基于网络资源的可用节点范围对 pod 进行自动化调度” 问题之后,我们还需要引入一个 Subnet 对象描述 ip 地址分配信息。

Subnet 对象是依附于 Network 对象的,每个 Subnet 必须且只能属于一个 Network,同一个 Network 中可以有多个 Subnet。每个 Subnet 对象描述了该网络域内一个可用的容器网段的地址信息,比如:

[root@iZf8z4x1svumpy31kcrltaZ ~]# kubectl get subnet init -oyaml
apiVersion: networking.alibaba.com/v1
kind: Subnet
metadata:
  annotations:
    meta.helm.sh/release-name: hybridnet
    meta.helm.sh/release-namespace: kube-system
  creationTimestamp: "2023-02-10T05:52:11Z"
  generation: 1
  labels:
    app.kubernetes.io/managed-by: Helm
    webhook.hybridnet.io/ignore: "true"
  name: init
  resourceVersion: "40646"
  uid: ca73b837-b28f-4727-949d-d06f152202ed
spec:
  config:
    autoNatOutgoing: true
  network: init
  range:
    cidr: 172.24.0.0/16
    version: "4"
status:
  available: 65526
  lastAllocatedIP: 172.24.0.23
  total: 65534
  used: 8

每个 hybridnet 集群要正常工作,至少需要存在一个 Network 和 Subnet。可以说 Network 和 Subnet 对象就是 hybridnet 对用户提供的配置入口。对于每个 pod 的 ip,hybridnet 会自动创建一个 IPInstance 对象,该 CRD 是 namespace scope 的,与 pod 处于相同 namespace。IPInstance 对象主要的功能是用来持久化 hybridnet 地址分配信息,当然也可以用来提供给用户查询和审计,对于用户来讲,通常是只读的。

基于策略路由的 overlay/underlay 混合数据链路

策略路由

Linux 内核的路由能力其实非常强大,绝大部分场景下我们只用到了其中的一小部分,比如在一台 Linux 机器上运行 ip route 命令,我们会得到下面的输出:

[root@iZf8z4x1svumpy31kcrltaZ ~]# ip route
default via 172.16.255.253 dev eth0
169.254.0.0/16 dev eth0 scope link metric 1002
172.16.0.0/16 dev eth0 proto kernel scope link src 172.16.69.139

这就是我们所经常看见的 “路由表”,但这其实只是内核的其中一张名字叫做 “main” 的路由表,也是一般情况下,我们 “添加路由”、“删除路由” 的操作对象。

Linux 内核从 2.2 版本开始就支持了策略路由(Policy Route)能力,通过执行 ip rule 命令我们可以看到策略路由规则,默认情况下 Linux 操作系统中默认会存在 “local”、“main”、“default” 三张路由表,其中最前面的数字代表了优先级,数字越小,优先级越高,优先级从高到低第一张能够匹配流量的路由表会生效:

[root@iZf8z4x1svumpy31kcrltaZ ~]# ip rule
0:  from all lookup local
32766:  from all lookup main
32767:  from all lookup default

其中 “default” 一般为空,“local” 表中比较关键的是存在了带有 local 关键字的路由条目,对应了每个节点的本地 ip 地址,表示发送到本机的哪些流量应该不进行转发直接进入上层协议栈处理:

[root@iZf8z4x1svumpy31kcrltaZ ~]# ip route show table default
[root@iZf8z4x1svumpy31kcrltaZ ~]# ip route show table local
broadcast 127.0.0.0 dev lo proto kernel scope link src 127.0.0.1
local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1
local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1
broadcast 127.255.255.255 dev lo proto kernel scope link src 127.0.0.1
broadcast 172.16.0.0 dev eth0 proto kernel scope link src 172.16.69.139
local 172.16.69.139 dev eth0 proto kernel scope host src 172.16.69.139
broadcast 172.16.255.255 dev eth0 proto kernel scope link src 172.16.69.139

数据链路概述

在一个 hybridnet 集群内,流量的行为总体描述如下:

  1. 同一节点上的 pod 之间通信的流量只在节点内部转发
  2. overlay pod 与其他 pod (包括 underlay pod)互相访问的流量都是隧道流量
  3. underlay pod 访问非 overlay pod 的流量(包括节点、集群外 ip 地址)为非隧道流量
  4. 集群内节点访问 overlay pod 的流量为隧道流量(不经过 NAT)
  5. overlay pod 访问集群内节点以及集群外部 ip 的流量为 NAT 流量(SNAT 成对应节点 ip)

数据链路完全基于策略路由实现,这让 hybridnet 跟 kube-proxy、felix 的 iptables/ipvs 规则以及其他类似的 service/networkpolicy 实现能够良好地适配,其流量路径如图:

 title=

在一个只有 overlay 网段的环境(这里有两个网段,我们认为环境中只有 ipv4 的 172.24.0.0/16,ipv6 的策略路由要使用 ip -6 rule 查看,逻辑一样),可以看到节点策略路由规则如下:

[root@iZf8z4x1svumpy31kcrltaZ ~]# kubectl get subnet
NAME     VERSION   CIDR                                          START   END   GATEWAY   TOTAL   USED   AVAILABLE   NETID   NETWORK
init     4         172.24.0.0/16                                                         65534   8      65526               init
init-2   6         5408:4003:10bb:6a01:83b9:6360:c66d:0000/112                           65535   1      65534               init
[root@iZf8z4x1svumpy31kcrltaZ ~]#
[root@iZf8z4x1svumpy31kcrltaZ ~]#
[root@iZf8z4x1svumpy31kcrltaZ ~]#
[root@iZf8z4x1svumpy31kcrltaZ ~]#
[root@iZf8z4x1svumpy31kcrltaZ ~]# ip rule
0:  from all lookup local
1:  from all lookup 39999
2:  from all lookup 40000
3:  from all fwmark 0x20/0x20 lookup 40001
4:  from 172.24.0.0/16 fwmark 0x0/0x4040 lookup 10000
32766:  from all lookup main
32767:  from all lookup default
[root@iZf8z4x1svumpy31kcrltaZ ~]#

我们可以看到 hybridnet 在策略路由中添加了四条规则,分别对应了 39999、40000、40001、10000 的路由表,表中的路由条目分别如下:

[root@iZf8z4x1svumpy31kcrltaZ ~]# ip route show table 39999
172.24.0.1 dev hybr6b3adcd7e1e
172.24.0.4 dev hybr1b7a9ed1883
172.24.0.7 dev hybr6809d4b1e0e
172.24.0.9 dev hybr56df5339e20
172.24.0.11 dev hybr3c237dc2041
[root@iZf8z4x1svumpy31kcrltaZ ~]# ip route show table 40000
172.24.0.0/16 dev eth0.vxlan4
[root@iZf8z4x1svumpy31kcrltaZ ~]# ip route show table 40001
default dev eth0.vxlan4
[root@iZf8z4x1svumpy31kcrltaZ ~]# ip route show table 10000
[root@iZf8z4x1svumpy31kcrltaZ ~]#

用最少的策略路由规则数量,我们实现了 hybridnet 的数据链路:

  • 对于 39999 路由表中的每个条目,其目标网段为本节点上 pod 的 ip 地址、出口设备为 pod 对应的 veth 设备。这个路由表的会处理所有目标地址为本机上 Pod 的流量,将其直接发送给本机 pod 的 netns 处理。我们可以看到 39999 表的优先级是最高的
  • 对于 40000 路由表中的每个条目,其目标网段为集群内的 overlay 网段、出口设备为 vxlan 设备。这个路由表处理了所有目标地址为 overlay 容器网段的流量,保证所有发往 overlay pod 的流量会经过 vxlan 设备进行隧道封装(这里没有配置nexthop,表示下一跳 ip 就为数据包的目标 ip)
  • 40001 路由表中目前只有一条 default 路由,它的作用是让 “其他节点访问本节点上 overlay pod” 的反向流量经过 overlay 设备,达到 “集群内节点访问 overlay pod 的流量为隧道流量(不经过 NAT)” 的效果
  • 10000 路由表其实比较特殊,我们可以看到它对应的策略路由规则是 from 172.24.0.0/16 fwmark 0x0/0x4040 lookup 10000,以这条规则为例,它表示 “只有源 ip 在 172.24.0.0/16 网段中并且没有相关 mark 的流量才会进入 10000 表处理”。对于集群内每个容器网段都会有这样一条策略路由规则,对应路由表中的条目描述了 “从本节点上对应该容器网段 pod 中发出的流量” 应该如何处理,不同网络模式的网段会有不同的规则:
    • 对于 overlay 的网段(overlay 只有 vxlan [ 1] 模式),路由表内每个路由条目代表发送到某个 underlay 网段的流量。比如我们在集群内通过增量进行 underlay 网络配置 [ 2] ,添加了两个192.168.56.0/24和192.168.57.0/24的 Subnet(以及对应的 Network),一个为 vlan [ 3] 模式,一个为 bgp [4 ] 模式,10000 路由表会变成这样(因为192.168.56.1是 vlan 网段的网关,用 throw 类型的条目忽略):
[root@iZf8z4x1svumpy31kcrltaZ ~]# ip route show table 10000
192.168.56.0/24 dev eth0.vxlan4
throw 192.168.56.1
192.168.57.0/24 dev eth0.vxlan4
    • 对于 vlan 模式的网段,路由表的内容是固定的,一条目标地址为本网段 CIDR 的直接路由,一条下一跳为网段网关 ip (对应 Subnet 对象的spec.range.gateway 字段)的默认路由,目标是让同网段的流量直接走物理网卡发送出去,跨网段的流量走外部网关路由进行转发,在对应 vlan 网络域中的节点上查看路由规则:
[root@iZf8z4x1svumpy31kcrltaZ ~]# ip rule
0:  from all lookup local
1:  from all lookup 39999
2:  from all lookup 40000
3:  from all fwmark 0x20/0x20 lookup 40001
4:  from 172.24.0.0/16 fwmark 0x0/0x4040 lookup 10000
5:  from 192.168.56.0/24 fwmark 0x0/0x4040 lookup 10001
32766:  from all lookup main
32767:  from all lookup default
[root@iZf8z4x1svumpy31kcrltaZ ~]#
[root@iZf8z4x1svumpy31kcrltaZ ~]#
[root@iZf8z4x1svumpy31kcrltaZ ~]#
[root@iZf8z4x1svumpy31kcrltaZ ~]#
[root@iZf8z4x1svumpy31kcrltaZ ~]# ip route show table 10001
default via 192.168.56.1 dev eth0
192.168.56.0/24 dev eth0 scope link
[root@iZf8z4x1svumpy31kcrltaZ ~]#
    • 对于 bgp 模式的网段,路由表的内容是固定的,一条下一跳为 BGP 网关 ip (对应 Network 对象的spec.config.bgpPeers[0].address字段,这个地址需要是当前节点路由可达的)的默认路由,目标是让所有流量走外部网关路由进行转发,在对应 bgp 网络域中的节点上查看路由规则:
[root@iZf8zatm3b8zaaks5gqk0tZ ~]# ip rule
0:  from all lookup local
1:  from all lookup 39999
2:  from all lookup 40000
3:  from all fwmark 0x20/0x20 lookup 40001
4:  from 172.24.0.0/16 fwmark 0x0/0x4040 lookup 10000
5:  from 192.168.57.0/24 fwmark 0x0/0x4040 lookup 10001
32766:  from all lookup main
32767:  from all lookup default
[root@iZf8zatm3b8zaaks5gqk0tZ ~]# ip route show table 10001
default via 172.16.255.253 dev eth0
[root@iZf8zatm3b8zaaks5gqk0tZ ~]#

链路实现细节

如何让宿主机成为路由器

要使用策略路由构建容器网络,我们需要宿主机来 “路由” 节点上所有 pod 的流量,也就是成为本节点所有 pod 的路由器。这一点其实和 calico 是一样的,我们沿用了相同的设计。

进入 pod 可以看到,pod netns 内部(main 表)只存在一条 default 路由条目,并且该路由条目的下一条 ip 为一个固定的不存在的 ip 地址(hybridnet 使用的是和 calico 相同的 169.254.1.1、fe80::ecee:eeff:feee:eeee);在 pod netns 外部,host 侧会在 pod 对应的 veth 设备上配置 proxy_arp/proxy_ndp,保证所有从 pod 中发出的、“下一跳地址为上述不存在地址”的流量都能被 host netns 处理。

虚拟大二层的 vxlan 网络

对于 calico、flannel 而言,“节点绑定网段” 是容器网络 ip 分配的基本模型。在 hybridnet 中,由于存在 “固定ip” 的诉求,如果节点固定某一个或者某几个容器网段的话,固定了 ip 的 pod 也会被迫与节点生命周期绑定,极大地影响了 pod 的可用性。我们希望每个 overlay 网段都是整个集群节点范围内可用的,固定了 ip 的 pod 可以漂移到集群的其他节点上,同时也具备更加简单灵活的 ip 扩容能力(不需要考虑节点上容器网段大小)。

在 overlay 的方案上(只有 vxlan 模式),我们参考了 flannel 的实现。大同小异,对于如何使用 linux 内核的 vxlan 虚拟设备进行组网,需要考虑两个问题:

  1. 我们需要 vxlan 设备能够像 “外接了交换机的物理网卡” 一样工作,也就是说,数据包在由 vxlan 设备发出时,vxlan 设备需要能够处理该数据包的目标 mac 地址,这部分配置是需要我们去组织 vxlan 设备 fdb 表的,它决定了 “哪个 mac 要发往哪个 VTEP ip”
  2. 当 vxlan 设备变成一张可用网卡后,我们需要配置这张网卡相关的路由表,并且在跟 vxlan 设备相关的条目中需要给出一个可用的(能够被邻居解析的)下一跳 ip,让流量能够被正确从 vxlan 设备发出

在 flannel 历史上,存在三个版本的数据链路实现(可以在 flannel 代码注释 [ 5] 里看到):

  1. 第一个版本,flannel 会在用户态监听 vxlan 设备的 L2/L3 miss 事件,这主要指两个阶段:
    1. 当容器数据包在完成路由查询后要从 vxlan 设备发出之前,需要根据路由表的下一跳 ip 进行邻居解析,以填充容器数据包的目标 mac 地址。因为此时没有对应的邻居缓存,这会发出一个 L2 miss 事件,flannel 用户态进程在监听到 L2 miss 事件之后会通过预先在 etcd 中记录的信息返回结果,提供对应该下一跳 ip 的正确 mac 地址

      1. 当容器数据包被填充完毕后,会进入 vxlan 设备内部的发送逻辑。此时 vxlan 设备需要知道对应容器数据包 mac 地址的 VTEP 网关 ip 是什么,以构造对应的 udp 数据包,这个时候会产生一个 L3 miss 事件,flannel 用户态进程在监听到之后会通过预先在 etcd 中记录的信息返回结果,提供对应容器网段 ip 所在节点上的宿主机 ip 地址
  2. 第二个版本,flannel 移除了 L3 miss 事件的用户态处理。因为宿主机被加入集群之后 ip 基本不会再发生变化,所以 flannel 会在发现节点的时候,直接更新每个节点上的 vxlan fdb 表项,将宿主机的 ip 地址写入。
  3. 第三个版本也就是现在的版本,为了提升 flannel 的可靠性(如果数据链路依赖用户态过程,在 flannel 更新或者暂时 Crash 的时候,节点上的容器网络通信会断开,也就是数据链路连通性和管理组件生命周期是紧耦合的),flannel 移除了 L2 miss 事件的用户态处理,采用“发现节点之后直接为节点分配网段”的方式,为每个节点添加一条静态邻居缓存,并且为每个其他节点上的网段添加一条固定的路由规则

通过学习 flannel 的历史,不难发现,对于 “vxlan 设备上邻居解析过程”(L2 miss 事件)的处理,是能否去掉 “节点绑定网段” 限制的关键。基于这一点,hybridnet 做出了和 flannel 不同的选择,我们在保持数据链路连通性与组件生命周期松耦合的同时,实现了 “容器网段跨节点” 的 ip 分配模型。

在 hybridnet 中,vxlan 网络的配置具有以下特点:

  1. 出口设备为 vxlan 设备的路由都是没有指定 “下一跳” 的,也就是当数据包从 vxlan 设备上发出时,邻居解析的目标 ip 与数据包的目标 ip 一致
  2. 每个节点上,hybridnet 会为本节点上的所有 pod ip 配置 vxlan 设备上的proxy类型邻居缓存条目,这意味着宿主机会自动处理从 vxlan 设备上收到的邻居请求,并且将 vxlan 设备的 mac 地址作为结果响应,这样,所有的 pod 之间通过 vxlan 网络通信实际上使用的是 pod 所在节点的 vxlan 设备 mac 地址
  3. hybridnet 会在 vxlan 设备中为每个节点的 VTEP ip 配置 00:00:00:00:00:00 dev dst self permanent的 fdb 条目,这种目标 mac 全零的条目的 VTEP ip 形成了一个组,当数据包的目标 mac 地址没有匹配到任何 fdb 条目时,内核会将数据包封装成 udp 隧道包并且分别拷贝一份发向该组中所有 VTEP ip
  4. hybridnet 会在 vxlan 设备中为每个节点的 VTEP ip 和 VTEP mac(节点 vxlan 设备的 mac 地址)配置 dev dst self permanent的 fdb 条目,当数据包的目标 mac 地址匹配到 VTEP mac 时,内核会将数据包封装成 udp 隧道包发向对应 VTEP ip
  5. hybridnet 会有用户态进程监听 vxlan 设备的 L2 miss 事件,当出现这种事件时,hybridnet 会查询 IPInstance 对象以及节点信息(通过 k8s informer 的本地缓存,通常比较快),如果查询到了,会直接返回对应邻居解析的结果,跳过内核态邻居解析的广播报文发送过程

总体来讲,因为 pod 通过 vxlan 网络通信的 mac 地址都是其所在节点的 vxlan 设备 mac 地址,所以 vxlan 设备的 fdb 表规模是可控的,通过 “每个节点的 VTEP ip 和 VTEP mac(节点 vxlan 设备的 mac 地址)fdb 表配置”,所有单播的报文都能够被正常处理。对于可能发送广播报文的邻居解析请求,我们使用 “手动维护广播组” 和 “用户态监听 L2 miss 代理邻居解析” 的方式,前者会保证组件异常时不影响网络通信,后者会在组件正常工作时降低甚至消除因为广播发生的流量开销。

更多关于如何配置 vxlan 网络可以查看 wiki 中 “vxlan 网络” 部分。

路由实现的 vlan 网络

对于 vlan 模式的网络,hybridnet 主要逻辑为:

  1. 为netID不为 0 的网段,创建 vlan 虚拟设备;如果netID为 0,直接使用宿主机网卡本身进行通信
  2. 在容器网络流量出口设备(如上面所说,可能是 vlan 虚拟设备也可能不是)上为节点上所有 vlan 模式的 pod ip 创建proxy类型邻居缓存

结合 “数据链路概述” 中描述的 vlan 网段路由规则,大致梳理规则之后我们会发现,此时理论上网络已经能通了,外部流量看起来会和二层桥接的流量一致,容器会使用宿主机网卡的 mac 地址进行通信。但是其实仍然存在问题,问题主要在于内核发送 arp 请求的 sender ip 选择过程。

在我们的设计中,当 vlan 的 pod 要对外进行通信时,pod 对于同网段或者网关地址的 arp 请求永远是由宿主机发出的,又因为 underlay 网络的 ip 地址比较宝贵,我们不希望浪费地址在节点上,所以并没有为节点分配任何容器网段的地址(overlay 网络也是一样);这就造成了,宿主机在替 pod 发送 arp 请求时只能将自己的 ip 填充到请求的 sender ip 字段。对于某些交换机,这类流量是非法的,因为看起来是 “跨网段的邻居解析请求”。

通过调查相关内核逻辑我们发现,在这种情况,内核决定 arp 请求 sender ip 时的逻辑主要为 “优先选择网卡上第一个跟 target ip 在同一网段,并且 scope 为link或者global的地址”,同时又因为需要避免 scope 为global的地址可能被内核子系统 “源地址选择” 过程选中,所以我们需要的是一个同网段中,scope 为link的 ip 地址。

为了解决这个问题,我们引入了 “enhanced address” 配置。大致思路是,对于节点所在 vlan 网络域中的所有网段,当出口设备没有容器网段对应的 ip 地址时,随机选择本节点上一个 pod ip 地址配置到出口设备上,这个地址的配置有以下特点:

  1. 开启了noprefixroute的 flag
  2. 地址的 scope 是link的
  3. 删除了 local 路由表中对应该地址自动生成的 local 路由条目

通过这样的配置,我们成功地让宿主机上的所有 “enhanced address” 只为 arp 解析过程服务,不影响其他过程。

更多关于如何配置 vlan 网络可以查看 wiki 中 “vlan 网络” 部分。

支持固定 ip 的 bgp 网络

通过前面的规则描述我们可以发现,在 hybridnet 中对于 bgp 模式的数据链路设计非常简单:“始终经过外部网关交换机”。比较复杂的部分在于 bgp 协议的管理。详细设计可以参考 wiki 中 “bgp 网络” 部分。

CNStack 如何使用 hybridnet

CNStack 通过 hybridnet 实现了 “On Anywhere” 部署。

在创建集群时,CNStack 集群会初始化 overlay 网络资源的创建,并且始终使用 overlay 作为默认网络类型,绝大部分集群内的 pod 会通过 overlay 网络拉起,包括 CNStack 平台的管理 pod,这样就保证了 CNStack 的白屏平台管理逻辑可以在任意网络环境的正确工作。

同时,CNStack 中集成了对 hybridnet 网络配置的白屏管理能力,用户可以在 CNStack 平台完成交付后,在白屏控制台上自助添加 underlay 网络配置,并且通过在发布 pod 时带上固定 annotation 的方式使用 underlay 网络资源(因为集群默认网络类型是 overlay 的,使用 underlay 网络需要特殊指定)。

通过这样的方式,传统意义上在交付 underlay K8s 集群之前需要完成的网络规划、配置工作变成了与交付过程解藕的、用户可选自助配置的逻辑,并且 underlay 的配置并不影响产品的可用性和稳定性,极大地提升了交付效率。

相关链接:

[1] vxlan

https://github.com/alibaba/hybridnet/wiki/VXLAN-%E7%BD%91%E7%BB%9C

[2] 增量进行 underlay 网络配置

https://github.com/alibaba/hybridnet/wiki/%E7%BD%91%E7%BB%9C%E9%85%8D%E7%BD%AE

[3] vlan

https://github.com/alibaba/hybridnet/wiki/VLAN-%E7%BD%91%E7%BB%9C

[4] bgp

https://github.com/alibaba/hybridnet/wiki/BGP-%E7%BD%91%E7%BB%9C

[5] 注释

https://github.com/flannel-io/flannel/blob/master/pkg/backend/vxlan/vxlan.go#L19

参考链接:

hybridnet github 仓库

https://github.com/alibaba/hybridnet

hybridnet 开源 wiki

https://github.com/alibaba/hybridnet/wiki

与容器服务 ACK 发行版的深度对话第二弹:如何借助 hybridnet 构建混合云统一网络平面

https://mp.weixin.qq.com/s/O095yS5xPtawkh55rvitTg

阿里云 CNStack 产品

https://www.aliyun.com/product/aliware/cnstack

CNStack 社区版

https://github.com/alibaba/CNStackCommunityEdition

阿里云 ACK 发型版

https://github.com/AliyunContainerService/ackdistro


阿里云云原生
1k 声望302 粉丝