关于 flannel、calico、cililum 工作模式的说明

发布时间 2023-07-14 10:42:58作者: 2gbxzhdaz

https://docs.projectcalico.org/networking/determine-best-networking

https://kubernetes.io/docs/concepts/cluster-administration/networking/#the-kubernetes-network-model

flannel、calico、cililum


Encapsulation
# 封装是指在附加层中加入网络数据包以提供其他上下文和信息的过程
# overlay 网络中,封装被用于从虚拟网络转换到底层地址空间,从而能路由到不同位置,当到达后将其解封装并继续路由到其最终地址
# 消费者 POD 通常并不直接调服务的 ClusterIP,而是先调用服务名
# 因为 ClusterIP 会变(如针对 TEST/UAT/PROD 等不同环境的发布 ClusterIP 会不同),而服务名通常不变
# NodePort 是将内部服务对外暴露的基础 => LoadBalancer 底层通常依赖于 NodePort

Overlay (Network)
# 覆盖网络可使底层网络在不了解 POD IP 的情况下实现集群节点间的 POD 通信
# 不同节点的 POD 之间的数据包通过 VXLAN/UDP 封装,将原始报文封装到目的是对端节点 IP 的外层数据包中,并隐藏内部数据包的 POD IP
# 这可以由 Linux 内核高效完成,但仍有很小开销,若运行网络密集型工作负载,可能希望避免这种开销
# 相比之下,不使用覆盖的操作提供了最高性能的网络(因为离开 POD 的数据包直接由物理线路进行传输)
# 覆盖网络是建立在现有网络之上的虚拟的逻辑网络,常用于在现有网络基础之上提供有用的网络抽象,并分离和保护不同的逻辑网络

# ? 网络由 CNI (container network interface) 插件实现(非 K8S 本身)
# ? 不同节点间的 POD 网络,可以采用路由方案实现,也可以采用覆盖网络方案 ...
# ? 路由方案依赖于底层的网络设备,但没有额外的性能开销,覆盖网络方案不依赖于底层网络,但有额外封包解包的开销
# ------------------------------------------------------------------------------------------ Flannel

# flannel 属于 Overlay 网络,主要是 L2(当后端基于 Vxlan 时)
# 它的 Vxlan 模式与 calico 的 ipip 模式性能相当或略差,因为 IPIP 的包头比 Vxlan 协议的包头小
# 它的 host-gw 模式性能优于 overlay 甚至接近宿主机直接通信,该模式下的性能与 calico 的 bgp 模式相当
# 它的 UDP 模式性能最差(因为其封装过程在用户态实现,而 Vxlan 与 IPIP 封装在内核态完成)

# 优势: 部署简单,性能一般
# 劣势: 没办法实现固定 IP 的容器漂移,没法做子网隔离,对上层设计依赖程度高,没有 IPAM, IP 地址浪费,对 Docker 启动方法有绑定

# ------------------------------------------------------------------------------------------ Calico

# Calico 主要是 L3(当使用 calico-bgp 模式时)
# 是纯三层网络方案,通过 Etcd 维护网络信息,将宿主视作互联网的路由器并基于 BGP 协议同步路由、基于 iptables 实现网络策略
# 它并未使用隧道或 NAT 实现转发(不需要 Overlay)而是巧妙的将所有 L2 和 L3 流量转为 L3 流量后通过宿主的路由配置实现转发
# 网络策略是其最受追捧的功能之一,并且还支持固定 IP 的配置

# 还能与服务网格 Istio 集成,以便在服务网格层和网络基础架构层中解释和实施集群内工作负载的策略
# 这意味着用户可以配置强大的规则,描述 POD 应如何发送和接受流量,提高安全性并控制网络环境

# ------------------------------------------------------------------------------------------ Cilium

# Cilium 工作在 L3 ...
# 集群所有节点都有个 daemonSet 叫 cilium-agent => 管理系统内核的 BPF(cilium 代替了 kube-proxy)
# 是基于 eBPF 和 XDP 的高性能网络方案,eBPF/XDP 处理数据包的速度可同 DPDK 媲美,零拷贝直接内核态处理
# 还支持 L3,L4,L7 的网络策略,主要特点就是 BPF(性能比 iptables 强数倍)
# 实际上 tcpdump 的实现也是基于 BPF,相关论文: http://www.tcpdump.org/papers/bpf-usenix93.pdf

kubernetes

https://kubernetes.io/docs/concepts/cluster-administration/networking/

https://kubernetes.io/zh-cn/docs/concepts/services-networking/service/


# K8S 的网络由容器网络接口 CNI 调用相关网络插件实现,CNI 网络插件的核心功能如下
#   => POD 内的容器间通讯(通过localhost交互)
#   => POD 之间处于相同网络平面,使用全局唯一的地址直接通讯
#   => POD 与 Service 之间的通讯
#   => Service 与外部网络通讯
#   => 集群的节点与任意 POD 之间的通讯(除非存在人为的网络策略)
#   => 通过网络策略实现隔离(限制 POD 之间通信)

# ------------------------------------------------------------------------------------------ POD 

# POD 拥有 IP,不同 POD 之间使用处于同一网络平面的地址直接通信
# POD 内的每个容器都使用相同的网络命名空间: netns(容器的 IP、port 等网络配置均与 POD 相同)
# 该机制类似 docker 的 container 网络模式,新的容器再不创建自己的接口和 IP,而是直接用 POD 中特殊的 Pause 容器 (infra) 的网络

# ------------------------------------------------------------------------------------------ Tips

# Node1 的 POD1 与 Node2 的 POD2 通信流程描述:
# POD1 的网卡 eth0 将数据发给挂接到 root 网络命名空间的 veth0 从而被虚拟网桥接收,查看其转发表后发现没有 POD2 的 MAC
# 此时会把包转发到默认路由(root 命名空间的 eth0 上,即已到达宿主网卡)通过宿主的 eth0 发到外部网络中
# 经寻址转发到 Node2 之后首先被其 root 命名空间的 eth0 接收并检查发现其目标是 POD2,从而交给虚拟网桥路由到 veth1(最终到达 POD2 的 eth0)
# ref: https://www.cnblogs.com/jojoword/p/11214256.html

Service
# POD IP 是不持久的,当集群节点故障、POD 规模缩减或重启时新 POD 的 IP 可能会变,因此衍生出 Service 解决此类问题
# Service 关联若干 POD 并拥有虚拟 IP(它是由 kube-proxy 通过控制 iptables/ipvs 规则实现的,是保存在所有集群节点中的路由规则)
# 访问 Service 关联的 POD 内的应用时只需访问其虚拟 IP 即可(它是持久的)
# Service 关联的 POD 规模改变、故障重启、节点重启时对使用此虚 IP 的用户是无感知的 ...
# 当数据包到达 Service 的虚 IP 之后会被 K8S 为其自动创建的负载均衡器路由到其后端真正的 POD 中
# Service 本质上一种由 kube-proxy 实现的动态关联后端的 LoadBalancer 机制

Cluster IP (Service)
# kube-proxy 对 APIserver 执行 Watch(观察集群中所有 Service 资源的状态变更)
# 因此当 Service 的虚拟 IP 及相关联的 POD 发生改变时 kube-proxy 将实时更新 ...
# Service 的 ClusterIP 是不能 Ping 通的(它是由 kube-proxy 实时控制的 iptables/lvs 规则路由到相关的 endpoint 中的)
# 当结合 ClusterIP:Port 时才能访问到对应服务

# 若需让集群外部流量进入集群内部的网络,可在网络栈的不同层面实现:
# 1. NodePort
# 2. Service LoadBalancer 
# 3. Ingress(建立在 service 之上且工作于 L7)
# 4. ......

# ------------------------------------------------------------------------------------------ brctl show & ip nerns list

# 宿主节点所有的网桥设备及接入相关设备的veth接口信息
brctl show
# bridge name     bridge id               STP enabled     interfaces
# docker0         8000.024263517750       no
# cni0            8000.b659c28ce04f       no              veth1b123783
#                                                         veth23h213b1
#                                                         veth487d23f8
#                                                         veth32a13b32
#                                                         veth4c219a12

# 宿主节点的所有网络命名空间
ip netns list
# cni-506a694d09fb-18dc-f9fe-064b-dc2675579527 (id: 0)
# cni-6d9742fb3c2d-8dc8-b3c2-efe1-8c86c027cc5e (id: 1)
# cni-61e6f613064b-c027-1e6f-3f63-f16e4041aeae (id: 2)
# cni-afe97599efe1-7cc5-11e6-11e6-37a30747c156 (id: 3)
# cni-a4718dc83f63-976b-5795-22da-c79c8b104e90 (id: 4)

# ------------------------------------------------------------------------------------------ DNS (A/AAAA、SRV)

# 容器中默认的解析设置
# nameserver 10.32.0.10
# search <namespace>.svc.cluster.local  svc.cluster.local  cluster.local
# options ndots:5

Service (A/AAAA)
# my-svc.my-namespace.svc.cluster-domain.example
# 集群内所有服务(包括 CodeDNS 自身)均被赋予 DNS 名称,默认情况下在 POD 的 DNS 搜索列表中会包含其所处命名空间和集群的默认域
# 当执行 DNS 查询时若不指定命名空间则被限制在其所在的命名空间范围内,因此,若需访问其他命名空间中的服务,就需要在查询时明确指定命名空间
# 普通服务(非无头服务)会被以 my-svc.my-namespace.svc.cluster-domain.example 的形式分配 A/AAAA 记录,将解析为对应的 ClusterIP
# 无头(Headless)服务与普通服务的域名格式完全一致,区别在于其返回的是对应服务所关联的 POD 集合的 IP 列表,而不是 ClusterIP
# 因此客户端需要能使用这组 IP,或使用标准的轮询策略从这组 IP 中选择 ...

POD (A/AAAA)
# pod-ip-address.my-namespace.pod.cluster-domain.example

# POD created by a deployment exposed as a service (A/AAAA):
# pod-ip-address.deployment-name.my-namespace.svc.cluster-domain.example

# 针对命名端口的 SRV 记录 ...
# 集群会为命名的端口创建 SRV 记录,这些端口是普通服务或无头服务的一部分
# 命名端口的 SRV 记录格式: _my-port-name._my-port-protocol.my-svc.my-namespace.svc.cluster-domain.example
# 针对对普通服务,该记录被解析成端口号和域名: my-svc.my-namespace.svc.cluster-domain.example
# 针对对无头服务,该记录被解析成多个端口号及其对应域名: auto-generated-name.my-svc.my-namespace.svc.cluster-domain.example

Tips


# 将集群内的服务暴露给互联网上的用户使用有两个问题:
#  1. Service => Internet
#  2. Internet => Service

# ------------------------------------------------------------------------------------------ 

当 POD IP 在集群外不可路由时
# 若 POD 地址在集群外不可路由,则当 POD 尝试与集群外的 IP 建立网络连接时 K8S 使用 SNAT(源网络地址转换)来更改源 IP
# 将源 IP(POD地址)改为(映射)其所在宿主节的地址,并且建立连接的任何返回数据包都将自动映射回 POD IP
# 任何集群外的请求都不能直接访问 POD,因为更"广泛"的网络不知道如何将数据包路由到 POD 的 IP
# 针对相反方向的连接,从集群外访问 POD 时只能通过 Service 或 Ingress 实现 ...

当 POD IP 在集群外部可路由时
# 若 POD 地址可以在集群外路由,则 POD 可以在没有 SNAT 的情况下接到外部世界
# 并且外部也可以直接访问 POD 而无需借助 Service 或 Ingress

# ------------------------------------------------------------------------------------------

# Linux 在网络栈中引入网络命名空间,将独立的网络协议栈隔离到不同的空间中,彼此间无法通信,docker 利用该特性实现了不容器间的网络隔离
# Veth 设备对(虚拟网络接口对)实现了在不同网络命名空间的通信
# 网桥是二层网络设备,可以将 Linux 支持的不同的端口(接口)连起来,并提供类似交换机的多对多通信
# Netfilter 负责在内核中执行各种挂接的规则(过滤、修改、丢弃等),它运行在内核空间
# Iptables 则是在用户空间运行的进程,负责协助维护内核中 Netfilter 的各种规则表(二者相配合以实现整个网络协议栈中灵活的数据包处理机制)
# Linux 拥有完整的路由功能,当协议栈的 IP 层处理数据包的发送或转发时会借助路由表来决定发往哪里 ...
# kubernetes 对集群内部的网络进行了重新抽象,从而实现整个集群网络的扁平化,它的网络模型可以完全抽离物理节点去理解
# Service 是 K8S 为为屏蔽后端实例(POD)的动态变化和对多实例的负载均衡而引入的资源,它常与 deployment 关联(定义了服务的访问入口)