kubernetes|kubernetes 网络组件 calico 运行原理分析

# Calico 使用 Internet 类似的方案,互联网比任何数据中心都大,因此 Calico 同样天然具有可扩展性 # Calico 将宿主机模拟为 Internet 中的路由器,使用BGP同步路由,使用iptables实现网络策略 ... # 因此它没有使用隧道或NAT来实现转发,而是巧妙的把所有的二、三层流量转成三层流量后通过Node上的路由配置完成转发# ---------------------------------------------------------------- 解释# 由于各物理机中为容器分配不同网段,因此完全可将这些物理机视为路由器并将容器的地址信息生成路由表【subnet?】 # Calico默认不使用overlay网络模型,而是用虚拟路由替代虚拟交换,因此是纯三层方案 ( 纯三层SDN实现,创建并管理一个三层平面网络 ) # 集群所有节点利用Linux内核实现模拟的路由器 VRouter 进行数据的转发,所有 VRouter 通过BGP协议将自身 Workload 的IP路由信息向整个网络传播 # 小规模部署时可直接互联,大规模时可通过额外的 BGP route reflector (反射) 实现,从而避免BGP的全互联 ... # 它自带的基于iptables的ACL管理组件非常灵活,能满足复杂的安全隔离需求 # 综上所述:Calico利用了Linux内核原生的路由及iptables防火墙功能 ...# BGP ... # 边界网关协议 Border Gateway Protocol 是互联网核心的去中心化的自治路由协议 ... # 它不使用传统IGP协议的度量指标,而是基于路径、网络策略、规则集来决定路由 ... # 该协议通过维护IP路由表或CIDR前缀表来实现自治系统 (AS) 间的互通,属于矢量路由协议 (在自治系统之间提供路由) # BGP系统的主要功能是和其他的BGP系统交换网络可达信息。网络可达信息包括列出的自治系统(AS)的信息 ... # BGP通过携带AS路径信息来标记途经的AS,带有本地AS的编号的路由将被丢弃,从而避免域间产生环路 # BGP在小规模集群中可直接互联,在大规模集群中可通过额外的 BGP route reflector 反射完成 # 当BGP处在全互联的模式时: 每个 BIRD 都要和其他 BIRD 建立TCP/179的连接,这样连接总数是 N^2 (不建议超过100台) # 当BGP处在路由反射模式时: 指定个别 BIRD 作为 BGP Route Reflector,每个BIRD只要与其建立连接即可学得全网路由# Calico的两种网络模式: #1.IPinIP将所有节点间的路由创建隧道再进行连接,因此该模式会在Node中创建名为 tunl0 的虚拟接口 (overlay) #2.BGP直接把节点作为虚拟路由路器 vRouter,因此不存在Tunnel# ---------------------------------------------------------------- 主要组件/概念etcd *# 负责网络元数据,基于其提供的watch机制实现Calico网络状态的准确、实时性 (可与kubernetes共用) Felix *# 运行在所有节点,负责更新节点路由、ACL等信息并管理容器 Endpoint (接入) 资源,并将运行状态写入etcd Calico/node (BIRD) *# 运行在所有节点,负责把Felix写入内核的路由信息分发到整个Calico网络来实现集群节点间POD的通信有效性,即 VRouter BGP Route Reflector (RR) # 即BGP路由反射器,仅大规模部署时使用 (通过其实现集中式的路由分发、学习) Orchestrator plugin *# 提供与各种云计算编排工具的紧密集成和同步支持,针对不同的平台、架构有着不同的Plugin ... Dikastes/Envoy# 可选的 sidecars,通过互相TLS验证来保护工作负载到工作负载的通信,并增加应用层控制策略 ... calicoctl *# 命令行管理工具,通过其实现高级的策略和网络 ... Endpoint# 接入到Calico网络中的网卡称为Endpoint (这里即POD) AS# 网络自治系统,通过BGP协议与其它的AS交换路由信息 (自治网络拥有独立交换机、路由器等,可独立运转) IBGP# AS内部的BGP_Speaker,与相同AS的ibgp、ebgp交换路由信息 EBGP# AS边界的BGP_Speaker,与相同AS的ibgp、以及不同AS的ebgp交换路由信息# 在kubernetes的架构中当Calico作为网络策略和网络组件时实际运行了两类容器 $ kubectl get po -n kube-system | grep calico calico-kube-controllers-5f6db94794-cmqs41/1Running174d# calico-kube-controllers calico-node-k92qh1/1Running174d# calico-node calico-node-lt7sn1/1Running174d# calico-node-qh5961/1Running174d# calico-node-wt8n91/1Running174d## ---------------------------------------------------------------- 工作原理 #etcd #/ # [Felix & Calico node]# Calico与weave在拓扑方面类似: 都是在所有主机启动虚拟机路由器从而将它们作为路由器运行,最终形成互联互通 # 它把集群中的每个物理节点视为路由器,然后把所有的容器认为是连在这个路由器上的网络终端,即Endpoint # 在这些虚拟路由器之间跑的是标准的BGP协议,它们自己学习并最终形成可转发的网络拓扑,因此基于Calico的方案其实是纯L3方案 ... # 综上所述,因此每个节点都会运行两个主要的程序: 1.Felix:# 监听etcd事件,如: 分配了POD、添加了IP,并确保容器 Endpoint (接入) ... 2.BGP Client: # 也称为 calico node,作为虚拟的路由器通过BGP进行路由的传播,使外界可直接访问容器IP而不需做任何类似NAT的操作# Example ... # 1.POD创建时Calico为POD生成veth pair设备并将其一端设为容器网络命名空间内的网卡并设置IP/mask等信息... # 2.同时veth pair的另一端直接暴露在宿主机cali前缀的设备上 (没有网桥) 并设置路由规则将这个POD地址直接暴露到宿主机的通信路由之上 # 3.于此同时Calico为每个节点分配不同的子网做POD分配的IP范围,这样即可根据子网CIDR为每个节点生成比较固定的路由规则 (存疑) # 4.根据容器要访问的目的IP所在的子网CIDR及该节点上的路由规则找到下一跳要到达的宿主机IP # 5.流量到达目标节点之后,再根据当前宿主机内的路由表直接到达容器的veth pair插在宿主机的一端,从而互通 # 这里最核心的下一跳的路由规则是由Calico的Felix进程维护的,而路由信息则是通过BIRD组件使用TCP/BGP协议来传播的 # 该过程全部在L3完成 () ...# ----------------------------------------------------------------# 任意选择集群中的一个节点作为实验节点,进入容器A并查看容器A的IP [root@POD-A ~]# ip address show 1: lo: mtu 65536 qdisc noqueue state UNKNOWN qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 3: eth0@if771: mtu 1440 qdisc noqueue state UP link/ether 66:fb:34:db:c9:b4 brd ff:ff:ff:ff:ff:ff inet 172.17.8.2/32 scope global eth0# 容器获取的是32位掩码的IP,表示将容器A作为一个单点的局域网 valid_lft forever preferred_lft forever# # 查看容器A的默认路由及本地ARP缓存 [root@POD-A ~]# ip route show# default via 169.254.1.1 dev eth0# 容器的默认路由指向的网关IP (其实该地址是IANA网络组织定义的保留IP) 169.254.1.1 dev eth0 scope link# 集群中所有容器网关都是此IP,但整个拓扑中没有此IP,因为它没有实际意义 ...# 容器只要能把数据包交给此IP,经其转发出去即可~!# 查看容器A的ARP缓存 [root@POD-A ~]# ip neigh 169.254.1.1 dev eth0 lladdr ee:ee:ee:ee:ee:ee REACHABLE# 此MAC是Calico写入的,用于利用Arp代理响应请求 # Calico利用网卡的代理ARP功能,当ARP请求的目标跨网段时网关收到此请求并用自身MAC返回请求者 (Proxy ARP) ...# 查看宿主机的网卡信息 [root@localhsot ~]# ip addr ... 771: calicba2f87f6bb@if4: mtu 1440 qdisc noqueue state UP group default link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 14# 此calico设备没有IP地址,因为不需要 inet6 fe80::ecee:eeff:feee:eeee/64 scope link valid_lft forever preferred_lft forever # 在宿主机查看网卡MAC时会发现这个veth设备的MAC即容器网关的MAC,所以容器A发的包第一跳就是此网卡,因此就到达了物理机A这个模拟的路由器 ... # Calico本质上是利用 Proxy ARP 撒了一个善意的谎言 ...# 查看宿主机的路由信息 [root@localhsot ~]# ip route ...# 在 Calico 方案中宿主查看路由表可直接看到POD的IP 172.17.8.2 dev calicba2f87f6bb scope link# 此节点中的容器IP (172.17.8.2) 将路由到此节点的calicba2f87f6bb中 172.17.x.x via xx.xx.xx.xx ....# 其他网段节点的路由信息 (Calico中容器IP是/32位掩码)...# 查看是否开启ARP代理 [root@localhsot ~]# cat /proc/sys/net/ipv4/conf/calicba2f87f6bb/proxy_arp 1# ---------------------------------------------------------------- 总结# Calico通过巧妙的方法将POD内所有的流量引导特殊的网关地址 169.254.1.1,从而引流到宿柱机中以 cali 前缀命名的网络接口上 # 最终将二三层流量全部转换成三层流量进行转发,通过Node开启代理ARP功能实现ARP应答使得ARP广播被抑制在主机上,抑制广播风暴,不会有ARP表膨胀的问题 # 利用veth的代理ARP功能让容器NetworkNamespace中出来的所有转发都变成三层路由转发,然后再利用主机中的路由表进行转发 # 综上所述:把容器网络名称空间的网卡与宿主机中关联的veth pair设备认作是默认网关,下一跳就是外面的物理机# 当容器创建后由当前所在主机的 Felix 创建这条直接路由 (direct routes) \ # 当 Felix 创建后 BIRD 会监控到路由的变化并将它们通过TCP/179传播到集群中其它的 BIRD,其它主机上便出现了下一跳为当前主机的路由 # BGP Client BIRD(BIRD Internet Routing Daemon) 是网络路由软件,支持很多路由协议:BGP、RIP、OSPF等,calico用它在节点间共享路由信息 # 集群中的Node变成了路由器 (vRouter),它们为运行在本机上的容器或虚机提供路由,Linux内核负责包的路由,通过BPG协议来控制路由分发,Flelix管理路由信息 # 处于节点上的Endpoint只能同本节点vRouter通信,vRouter间通过BGP协议来同步节点上的Endpoint信息 # 当容器创建后由当前所在主机的 Felix 创建这条直接路由 (direct routes),Endpoints组成的网络是单纯三层网络,报文流向完全由路由规则控制,无overlay的开销 # 直接路由创建后,BGP客户端BIRD会监控到路由变化,然后将它们宣告到其它主机的BIRD上,其它主机便出现了下一跳为当前主机的路由 # Calico通过操作主机iptables来达到ACL的功能,隔离不同容器或不同主机上两个容器X和Y的规则,会在X和Y所在的主机上都建立相应的iptables规则,双重保障 # Calico通过MAC/Arp-proxy、路由表、iptables合力完成静态路由的配置 # 对于如何管理路由、安全信息和容器动态漂移等工作,Calico还做了更多的事情。但究其运行的实质还是上面所描述的这些 # Calico也提供了自己的名为"calico-ipam"的IPAM插件,该插件旨在与Calico很好地配合使用

Linux中NetworkManager对Calico接口的影响
# NetworkManager管理主机默认网络命名空间中接口的路由表的功能,这可能会干扰Calico正确处理网络路由的能力 # 为了确保Calico能够管理主机上的cali和tunl接口,如果主机上运行着NetworkManager则需按下面的方法配置NetworkManager # 创建一个配置文件 /etc/NetworkManager/conf.d/calico.conf 来制止这种干扰 cat > /etc/NetworkManager/conf.d/calico.conf <

关于加粗样式 annotations 与 calico 分配地址的方式
# 如果在calico的配置文件里配置: conf.Policy.PolicyType 的值为 k8s # 那么它会从POD中获取POD的labels和annotations,再根据配置文件的 conf.IPAM.Type 的值是否是 calico-ipam # 然后从annotations中分别取出KEY:cni.projectcalico.org/ipv4pools、cni.projectcalico.org/ipv6pools(若有)的值配置ipv4地址池和ipv6地址池 # 然后从annotations取出KEY:cni.projectcalico.org/ipAddrsNoIpam、cni.projectcalico.org/ipAddrs(如果有)的值配置ipAddrsNoIpamipAddrs参数 # 根据ipAddrsNoIpam和ipAddrs的值有下列4种情况: #1.若 ipAddrs 和 ipAddrsNoIpam 的值为空则直接通过 calico-ipam 去分配IP #2.若 ipAddrs 和 ipAddrsNoIpam 的值均不为空则抛出错误 (不能同时配置ipAddrs和ipAddrsNoIpam) #3.若 ipAddrsNoIpam 不为空则使用ipAddrsNoIpam设置的IP地址作为result(不调用calico-ipam) #4.若 ipAddrs 不为空则使用 ipAddrs 设置的IP地址调用calico-ipam进行分配# 默认情况下Calico对整个POD CIDR使用单个IP池,但是可将Pod CIDR划分为多个池! ... # 可使用命名空间将单独的IP池分配给特定的节点选择,或分配给集群中的团队、用户、应用 ...

关于 etcd
# ref: https://docs.projectcalico.org/reference/etcd-rbac/calico-etcdv3-paths# etcd保存了calico的网络元数据,用于协调calico网络的多个节点# etcd每个目录保存的数据大致功能如下 1./calico/ipam# IP地址分配管理,保存了节点上分配的各个子网段及网段中IP地址的分配情况 2./calico/v1# profile和policy的配置信息,节点上运行的容器endpoint信息(IP地址、veth pair interface的名字等) 3./calico/bgp# 和BGP相关的信息,包括mesh是否开启,每个节点作为gateway通信的IP地址,AS number等

BIRD Internet Routing Daemon
# BIRD是布拉格查理大学数学与物理学院的一个学校项目,项目名是 BIRD Internet Routing Daemon 的缩写 # BIRD项目旨在开发功能齐全的动态IP路由守护进程,主要针对(但不限于)Linux、FreeBSD和其他类UNIX ... # 它支持很多的路由协议: BGP、RIP、OSPF等,Calico正是利用它在节点间学习路由信息,并最终形成全网互联 ... # 官网 https://bird.network.cz

关于部署
# 环境需求: https://docs.projectcalico.org/getting-started/kubernetes/requirements # 快速安装: https://docs.projectcalico.org/getting-started/kubernetes/quickstart # 官方答疑: https://docs.projectcalico.org/reference/faq # 指定节点IP作为calico使用的地址:https://docs.projectcalico.org/networking/ip-autodetection# 默认安装以下资源 #1.calico/node使用DaemonSet在每个主机上运行,关于其声明中的环境变量参数:https://docs.projectcalico.org/reference/node/configuration #2.使用DaemonSet在每台Node安装Calico CNI二进制文件和网络配置 #3.calico/kube-controllers作为Deployment资源实例运行 #4.calico-etcd-secrets,可选的,提供TLS ETCD #5.calico-configConfigMap,包含用于配置安装的参数: calico_backend(后端使用)、cni_network_config(CNI网络配置安装在每个节点)

对基于 Calico-bgp 模式的网络解决方案模拟部署
# -------------- Host_0 ---------------|| -------------- Host_1 ---------------| #||| #[network Namespace_0]||[network Namespace_1]| #[10.20.1.2/24]| -------- |[10.20.1.3/24]| #\192.168.1.32 || 192.168.1.16/| #[veth0] --[ens192] --||--[ens192] --[veth0]| #-------------------------------------------------------------------------------------------------| # [Host_0]# ip route show[Host_1]# ip route show # <10.20.1.2 dev veth0 scope link><10.20.1.3 dev veth0 scope link> # <10.20.1.3 via 192.168.1.16 dev ens192><10.20.1.2 via 192.168.1.32 dev ens192> # ............# --------------------------------------------- Host_0ip link add veth0 type veth peer name eth0# 创建 veth 设备并为其两端命名为 veth0 与 eth0 ip netns add ns0# 创建网络命名空间: ns0 ip link set eth0 netns ns0# 将宿主机的 eth0 添加到ns0虚拟网络环境 ... ip netns exec ns0 ip a add 10.20.1.2/24 dev eth0# 进入容器的网络名称空间,设置容器的IP ip netns exec ns0 ip link set eth0 up# 启用在容器一端的 veth 设备 ip netns exec ns0 ip route add 169.254.1.1 dev eth0 scope link# ... ip netns exec ns0 ip route add default via 169.254.1.1 dev eth0# ns0网络空间的所有数据都转发到一个虚拟的IP:169.254.1.1发送ARP请求 ip link set veth0 up# 启用宿主机一端的 veth 设备 ip route add 10.20.1.2 dev veth0 scope link# 宿主机中设备本机容器IP的路由信息 ip route add 10.20.1.3 via 192.168.1.16 dev ens192# 宿主机中设置其他节点的容器IP的路由信息 (下一跳指向了Host_1) echo 1 > /proc/sys/net/ipv4/conf/veth0/proxy_arp# 开启宿主机的ARP代理功能 ...# --------------------------------------------- Host_1ip link add veth0 type veth peer name eth0# ip netns add ns1# ip link set eth0 netns ns1# 注: ip netns exec ns1 ip a add 10.20.1.3/24 dev eth0# 容器的IP是一个可直接进行路由的地址 (以实现裸机性能) ip netns exec ns1 ip link set eth0 up# 而在flannel的vxlan模式下,需将此地址通过其大二层进行路由 ... ip netns exec ns1 ip route add 169.254.1.1 dev eth0 scope link# ip netns exec ns1 ip route add default via 169.254.1.1 dev eth0# ip link set veth0 up# ip route add 10.20.1.3 dev veth0 scope link# ip route add 10.20.1.2 via 192.168.1.32 dev ens192# echo 1 > /proc/sys/net/ipv4/conf/veth0/proxy_arp## --------------------------------------------- # 在宿主机 Host_0 进行两端容器地址网络连通性测试 $ ip netns exec ns1 ping 10.20.1.3# PING 10.20.1.3 (10.20.1.3) 56(84) bytes of data. 64 bytes from 10.20.1.3: icmp_seq=1 ttl=62 time=0.303 ms 64 bytes from 10.20.1.3: icmp_seq=2 ttl=62 time=0.334 ms ...# --------------------------------------------- 总结 # 具体的转发过程如下:# 1.Host_0 的 veth 端收到ARP请求时通过开启网卡的代理ARP功能直接把自己的MAC返回给ns0的容器 (这是重点) # 2.ns0的容器发送的数据表中目的地址为对端ns1中的容器IP # 3.因为使用了169.254.1.1这样的保留IP,宿主机判断为基于L3的路由转发,根据其路由表 10.20.1.3 via 192.168.1.16 dev ens192 发往 Host_1 # 4.如果配置了BGP,这里就会看到 proto 协议为 BIRD ... # 5.当Host_1收到来自容器10.20.1.3的数据包时,根据其路由表 10.20.1.3 dev veth0 scope link,转发到对应的 veth0 端从而到达 ns1 # 6.回程相同 ...

Theory
Note that when using Calico in on-prem deployments you can also advertise service IP addresses allowing services to be conveniently accessed without going via a node port or load balancer ......# Calico是L3网络,其中每个Pod都获得/32的IP,且没有Pod的广播域 ...# 正是因为集群内各宿主机中的容器所属网段或容器的IP不同,因此可将这些宿主机配成路由器并按容器的网段或IP配置路由表# 实际上Calico项目提供的BGP网络解决方案与Flannel的 host-gw 模式几乎一样 ... # 所有数据包都通过路由的形式找到对应的主机和容器,然后通过BGP协议将所有路由同步到所有的机器或数据中心,从而完成整个网络互联 # 就是说Calico也是基于路由表实现容器数据包转发,但不同于Flannel使用flanneld进程来维护路由信息的做法 # 综上所述: 它在宿主机创建若干veth pair,一端在主机上,另一端在容器内,然后在容器和宿主机中分别设置路由来形成网络互联# -----------------------------------------Calico的优势 #1.没有封包和解包过程,完全基于两端宿主机的路由表进行转发 (无隧道封装的开销) #2.相比于通过overlay构成的大二层网络,用IBGP构成的扁平二层网络扩展模式更符合传统IP网络的分布式结构 #3.不会对物理层网络的二层参数如 MTU 引入新的要求 #4.能够配合使用 Network Policy 来实现 Pod 与 Pod 之间的访问控制Calico的劣势 #1.不易持多租户,而overlay中vxlan模式的VID实现了多租户的功能 (仅公司自用场景可行) #2.路由条目与POD数量相同,非常容易超过路由器、三层交换、甚至Node的处理能力,从而限制整个网络的扩张 #3.不容易与其他基于主机路由的网络应用集成 #4.要求宿主机处于同一个2层网络下,也就是连在一台交换机上 (否则需要中间的路由器进行BGP相关的配置)# -----------------------------------------Why all cali* interfaces have the MAC address ee:ee:ee:ee:ee:ee? # 在某些设置中内核无法生成持久性MAC,因此Calico就自己分配了这个MAC # 由于Calico使用点对点路由接口,因此流量不会到达数据链路层,所以对于所有 cali* 接口而言它们的MAC地址都是可以相同的Why can’t I see the 169.254.1.1 address mentioned above on my host? # Calico为了尽力避免干扰宿主机的任何其他配置,所以不会在每个POD接口的主机侧添加网关地址,而是利用了 proxy_arp ... # 这使得主机的行为就像网关一样,因此就不再需要为宿主端的接口分配实际的IP即可响应 169.254.1.1 的 ARP :-)# -----------------------------------------# BGP协议 # https://en.wikipedia.org/wiki/Border_Gateway_Protocol # https://docs.projectcalico.org/networking/bgp

    推荐阅读