- !!!基于 BGP 协议实现 Calico 的 IPIP 网络 - 知乎 (zhihu.com)
- K8s网络实战分析之Calico-ipip模式_碰碰猪的博客-CSDN博客_calico ipip
- calico设置IPIP模式_挖煤工人学IT的博客-CSDN博客_ipipmode
- K8s 网络之深入理解 CNI - 知乎 (zhihu.com)
- K8s 网络之 从 0 实现一个 CNI 网络插件 - 知乎 (zhihu.com)
- 基于 ebpf 和 vxlan 实现一个 k8s 网络插件(一) - 知乎 (zhihu.com)
- 基于 ebpf 和 vxlan 实现一个 k8s 网络插件(二) - 知乎 (zhihu.com)
- 什么是VXLAN - 华为 (huawei.com)
- 浅入浅出 iptables 原理:在内核里骚一把 netfilter~ - 知乎 (zhihu.com)
*本文是Envoy透明流量劫持和路由转发(Calico网络)学习笔记的后续笔记
CNI在K8s全景中的位置:
——k8s的CRI(container runtime interface)规范旨在定义 k8s 如何更好地操作容器技术,该规范大概分为三部分:CRI Client(kubelet),CRI Server(Containerd),OCI Runtime( runc)。
—— OCI 规范中有一步叫 “创建网络”:该规范让 CRI 调用这个插件(自由实现不同的 CNI 插件),并把容器的运行时信息,包括容器的命名空间,容器 ID 等信息传给插件,插件虽然实现的方法各式各样,但是最终目的只有一个,就是让集群中的各种 pod 之间能自由通信。
——首先 kubelet 会启动 CRI Runtime(container runtime interface),这个简单来讲就是在 kubelet 端启动一个 grpc 的客户端,然后需要有个对应的 CRI Server 端,这里一般实现就是 containerd,也就是说 containerd 会启动一个 grpc 的服务端,接收来自 kubelet 的 “创建 Pod” 的请求。在接到这个请求后,containerd 会调用“创建 Sandbox”的接口,所谓创建 sandbox 就是给 Pod 中的容器们提前启动一个具有稳定网络资源以及存储资源的隐藏 pause 容器(在 k8s 里,挂载存储功能由 CSI 实现,挂载网络由 CNI 实现)。但是 containerd 属于“高级运行时”,真正去拉起容器的地方在 OCI 中,也叫“低级运行时”,一般实现可能有 runc 或者 kata 等,最常用的可能就是 runc。{containerd 基本上只负责调用(高级运行时),真正实现这些功能的地方在 OCI 的 runc(或其他低级运行时)中;containerd 到 OCI 之间还会有一层 shim,containerd 到 runc 有 runc-shim,到 kata 有 kata-shim},然后由于为了给 sandbox 创建网络资源,会先去 /etc/cni/net.d 目录下加载网络配置文件,然后根据配置文件中的 type 字段,去 /opt/cni/bin 目录下找对应的二进制插件,随后把容器的运行时信息作为环境变量,再把配置文件作为标准输出,对这个二进制进行调用。二进制插件调用的过程主要就是要实现三个点:
- pod IP地址管理:该功能在calico或者flannel的实现中,都是通过自定义crd,然后插件通过api-server去操作这些crd实现的(当然本质上还是存在 etcd 中)
- 节点内 pod 间通信
- 不同节点的 pod 间通信
——a、实现完 IPAM,约等于我们实现 CNI 插件的第一步“实现 IP 地址管理”的功能,怎么设计 IPAM(ip address manager)呢?创建 etcd 的客户端(调用 etcd 官方提供的 etcd 包),必须得将 k8s 的 ca 根证书,以及 etcd 客户端需要使用的证书和私钥作为配置项传给 etcd 包,不然无法连接到 https 的 etcd 集群中。实现 IPAM 需要在 etcd 中创建一个 IP 地址池,设置这个 pool 的目的就是每当我们有一个节点加入到集群中,一旦该节点上的 kubelet 调用了咱们的插件,也就会触发在插件中使用到的 ipam 服务,ipam 服务在节点上初始化的时候,就会去从这个 pool 中拿出一个未使用的网段作为自己这台节点的网段。除了需要在 etcd 中设置一个 pool 用来保存网段之外,我还在 etcd 中为每个节点设置了一个用来记录“当前节点已使用的 ip”的 key。
——b、实现同一台节点上,互相隔离的两个 pod 之间通信,一般采用的办法就是 veth pair + bridge。(K8s 网络之 从 0 实现一个 CNI 网络插件 - 知乎 (zhihu.com))
——c、要实现让不同节点的设备互通方法有很多,不过总的归类来讲,大致有四种:
- SDN 网络
- 走主机的静态路由,这种方式相对简单
- overlay 网络,也叫隧道网络
- 动态路由网络
类似第二种方式,在 flannel 里叫 gw-host,第三种方式可以使用 vxlan 或者其他隧道网络技术,第四种是 calico 主要采用的方式,使用 BGP 协议在多台主机上做动态路由设置。主机路由的原理,其实也非常简单:
- 从 etcd 中获取到除了本节点以外的所有节点的 pod 所在网段地址
- 从 etcd 中获取除了本节点以外的所有节点的 ip 地址
- 将这些网段地址设置在本机的路由表中,其他节点网段地址为 dst,其他节点的 ip 为 gw,或者叫下一跳(via)
- 设置主机网卡 iptables 为允许做 ip forward
——实现 cni 插件,主要是通过 veth-pair & bridge 实现的同节点之间通信,然后以 etcd 为依赖实现了一个 ipam 用来管理 ip 地址们,做到了简单的 host-gw 这种动态路由的方式,从而实现了不同节点之间的通信。新的 "ebpf & vxlan" 模式(参考 cilium 的原理实现一个基于 ebpf 和 vxlan 的 cni 网络插件),做到同节点上依赖 ebpf 通信,不同节点不同网段依赖 ebpf & vxlan 来实现网络通信。
Calico 是 tigera 公司开源的一个 k8s 网络的一整套解决方案。
1、Calico 的主要网络模式有两种,一种是 tunnel 模式,一种是路由模式。其中 tunnel 模式又同时支持 vxlan 和 ipip。(calico 现在也已经支持了 ebpf 模式)
2、知识点
- BGP和IPIP不是二选一的关系,类似via 192.168.31.218等其它host的路由信息需要通过BGP(bird在每台主机上开启BGP协议的客户端进程,bird创建出口 iface 是 tunl0 设备的规则;Feilx监听etcd)来获取。
- 实验室ipip Mode: Always模式。另有ipip Mode:cross-subnet模式,如果只对跨越子网边界的流量进行封装,需要使用跨子网的IP-in-IP方式,假设两个node 都在同一个网段,就会不封装,直接走网络bgp模式;如果不在同一个网段,就会使用IP-in-IP模式。——If your network fabric performs source/destination address checks(二所检测源地址) and drops traffic when those addresses are not recognized, it may be necessary to enable IP-in-IP encapsulation of the inter-workload traffic.
- 在大型网络规模中,如果仅仅使用BGP client形成mesh全网互联的方案就会导致规模限制,50 台node以上calico建议使用RR(Router Reflector)也就是路由反射器。
- cali的mac地址并不会在实际的链路层报文中使用,因此calico将所有cali网络设备的mac地址都设置为全e
- 创建一条黑洞路由(route 10.244.2.0/24 blackhole)的作用, 和 ip route add blackhole 效果一样,走这条路由规则的数据包都会被丢弃(其实可以通过抓包 lo 网卡看到),Calico 中需要黑洞是为了当同一台节点中的 pod 互相访问的时候,不让数据流出到其他节点
- “/etc/calico/confd/config/bird.cfg”是 Calico 运行 BIRD 最主要的配置文件,这个文件在容器里:
——Calico 自己 fork 了一份儿 BIRD 的代码,给改成了一个只支持 IPIP 的轻量级 BIRD。整体流程上,其实就是做了大概如下几件事儿:
- 从 IPAM 中获取一个还没有使用的 ip 地址作为本 pod 的 ip
- 创建一对儿 veth pair,一边放在 pod 的 netns 中,一边留在 host 上
- 给 pod 的 netns 设置交换路由
- 并把 pod 留在 host 的那半拉 veth 开启 proxy_arp 以及 forwarding
- 然后创建 ipip tunnel 设备
- 给这个 ipip tunnel 设备一个 ip 地址
- 开启 ipip tunnel 设备的 forwarding
- 创建 BIRD 配置文件。注意这里因为每个节点的 ip 之类的配置信息都不一样,所以这里需要通过 template 的方式动态生成模板的字符串
- 启动从 calico bird 那编译好的 bird 程序,注意这个程序你得想办法让它以独立子进程的方式执行哦
- 把 pod ip 返回给外边,也就是返回给 kubelet
- flannel是利用的静态路由表配置或者 vxlan 实现的网络通信。calico是通过 BGP 协议实现了动态路由。
3、不同网络模式是怎么获取信息(每个host是如何获取其他host的路由信息),创建iptables规则:
- host-gw 模式是每次启动一个 pod 时都去 etcd 中拉一下其他节点的 nodeip 以及 podip 的对应信息;
- ebpf 是通过 watch etcd,当有 pod 被创建时 etcd 自动同步给每个节点;
- BGP 协议就有点类似于 watch etcd 的那种方式,本质上都是在每个节点上启动一条进程,这条进程会监听本机的路由表以及网络设备变化,同时还能将这些变化发送给在同一个 “自治系统” 中的其他 BGP 客户端。
- Feilx 是 Calico 中和BIRD程序同步运行的另外一条进程,主要作用就是监听 etcd,当节点上要创建 pod 时,ipam 会分配 ip,然后这个 ip 会写入到 etcd,此时这个 Feilx 就能通过监听 etcd 来感知到被分配了哪些 ip,然后把这些 ip 写入到本机的路由表中,此时 BIRD 进程就能监听到路由表发生了变化,然后把新的路由规则根据配置给宣告出去。
- Feilx 进程除了创建路由表的条目之外,还会创建很多 iptables 规则,通过这些 iptables 规则的一些策略来让 Calico 的网络变得更健壮。
4、BIRD 是一个实现了多种动态路由协议(比如 BGP、OSPF 等),可以运行在 Linux/Unix 操作系统的程序。决定 “哪些本机的路由可以发给其他节点”,“本机可以接收哪些路由信息”,“本机可以和哪些节点形成自治系统”
- BIRD 在内存中维护了一个自己的路由表,这张路由表和 Linux 中的路由表(FIB 表)不一样。BIRD 的这张表中的内容包含 BIRD 自己从 Linux 的 FIB 路由表中收集到的本机的路由信息,以及从其他 BIRD 客户端接收到的其他节点的路由信息。BIRD 会根据一些策略算法,把其他节点发过来的路由信息,写入到 Linux 本机的路由表,也就是 FIB 表中,以此来让当前节点感知到其他节点上的 ip 变化。
- Linux路由表被bird配置了新的路由规则了,就像 calico 这样,会有一些出口 iface 是 tunl0 设备的规则。
- calico 自己实现了一个 bird,这个 bird 可以在每台主机上开启 BGP 协议的客户端进程,用来做路由宣告。
- 在运行 BGP 的客户端进程之前,需要指定一个 AS 号,一共有 2^32 次方个,这些自治网络的 AS 号需要由一个叫 IANA 的国际组织来颁发的。从 64512 到 65534 中间的一千多个 AS 号是可以给自己玩着用的。其中 64512 这个号码就是 calico 默认的 BGP 网络的 AS 号。
5、calico 会通过 deamonset 在每台节点上都运行一个 “calico-node”。
- 使用 pstree 查看这个容器进程可以发现首先启动了 pause 进程,然后就是通过 “bird6” 以及 “bird” 命令启动了 BIRD 进程,同时还运行了 felix 进程;
- calico-node容器是一个hostNetwork类型的网络,也就是说它容器里操作网络就相当于操作宿主机的网络。这也是容器中的 BIRD 和 Feilx 进程能修改 host 路由信息的缘故。