导读:前文 Kubernetes 中的 ClusterIP、NodePort、LoadBalancer、Ingress 服务拜访形式比拟中总结了服务接入拜访的次要形式,以及它们之间隐含关系。有了这些概念根底后,K8s 利用开发和服务部署就容易很多了,但 Under the hood 服务拜访到底是如何实现的呢?这篇内容就 Kubernetes 的网络模型和典型的容器网络实现,特地是阿里云本人的容器网络插件(Terway)的计划做了一个较具体的总结。
Pod 之间 Container-to-Container networking
Linux networking namespace 为过程通信提供了一个逻辑网络栈,包含 network devices、routes、firewall rules。Network namespace(NS)治理理论是为其中的所有过程提供了一个独立的逻辑网络 Stack。
缺省状况下,Linux 将每个过程挂载在 Root NS 下,这些过程通过 eth0 通往里面的世界。
在 Pod 世界里所有其中的容器共享一个 NS,这些容器都有雷同的 IP 和 Port 空间,通过 localhost 拜访也是互通的。Shared storage 也是能够拜访的,通过 SharedVolume 挂载到容器中。如下一个 NS per pod 图例:
同 Node 中 Pod-to-Pod networking
先看同一个 Node 下 Pod 之间的 networking 如何实现?答案是通过 Virtual Ethernet Device (or veth pair)
的两块 Virtual interfaces,每块 veth 挂载在一个 NS 上,来实现跨 NS 的连贯。比方,一块挂在 Root NS(host)上,另一块挂在 Pod NS 上,好比一根网线把两个在不同网络空间的 traffic 连接起来了,如图:
有了 veth pair
这条 网线,Pods 网络能够连通到 Root NS 了,但在 Root NS 上如何实现对来自不同 Pod 的 packet 通信呢?答案是通过Linux Ethernet Bridge
,一个虚构的 Layer2 网络设备来实现不同 network segments 之间的 Ethernet packet switching。不得不提这个 old-school 协定:ARP,实现了 MAC 地址到 IP 地址的发现协定。Bridge 播送 ethframe 到所有连贯的设施(除发送者外),收到 ARP 回复后将 packet forward 到对应 veth 设施上。如图:
跨 Node 之间 Pod-to-Pod networking
进入这部分之前,先提及 K8s 在其(Pod)networking 设计上的 3 个 fundamental requirements,任何 networking 局部的实现都必须遵循这三个需要。
- 在不应用 NAT 下,所有 Pods 都能和其它任何 Pods 通信
- 在不应用 NAT 下,所有 Nodes 都能和所有 Pods 通信
- Pod 所看到本人的 IP 和其它 Pods 看到它的 IP 肯定是雷同的
简要来看,K8s 网络模型要求 Pod IP 在整个网络中都能通达。具体实现计划有三方面:
- Layer2(Switching)Solution
- Layer3(Routing)Solution,如,Calico, Terway
- Overlay Solution,如 Flannel
这部分下文介绍,目前且认为 Pod IP 的网络通达性是确保的。
在 Pod 取得 IP 之前,kubelet 为每个 Node 调配一个 CIDR 地址段(Classless inter-domain routing),每个 Pod 在其中获取惟一 IP,CIDR 地址块的大小对应于每个 Node 的最大 Pod 数量(默认 110 个)。在 Pod IP 和跨 Node 网络层部署胜利后,从源 Pod1 到目标 Pod4 的通信如图:
Pod-to-Service networking
K8s Service 治理服务的 Pods 状态,在 Pod 有变动下治理对应 IP 的变动,并治理对外提供服务的 Virtual IP 到 Pod IPs 路由拜访,实现内部对服务 Virtual IP 的拜访路由到 Pod IP,以此屏蔽内部对服务后端的实现状态。所以在服务创立时,会对应生成一个 Virtual IP(也即是 Cluster IP),任何对该 Virtual IP 的拜访将打散路由到服务所属的 Pods 上。
K8s 的服务是如何实现对 Virtual IP 的拜访负载平衡呢?答案是 netfilter 和 iptables。netfilters 是 Linux built-in networking framework,为 Linux 提供网络包过滤、NAT 和 Port translation 等丰盛的自定义 handler 实现。iptables 是运行在 Linux user-space 的规定管理系统,为 netfilter 框架提供丰盛的包转发规定治理。
在 K8s 实现中 kube-proxy(node deamon)通过 watch apiserver 来取得服务配置的变动,比方,服务的 Virtual IP 变动、Pod IP 变动(ie, pod up/down)。iptables 规定随之变动并将申请路由到服务对应的 Pod 上,Pod IP 选取是随机的,这样看 iptables 起到了 Pod 负载平衡作用。在拜访申请 Return path 上,iptables 会做一次 SNAT 以替换 IP header 的 Pod IP 为服务 Virtual IP,这样使得 Client 看起来申请仅在服务 Virtual IP 上进行通信。
从 K8S v1.11 中 IPVS(IP Virtual Server)被引入成为第二种集群内负载平衡形式。IPVS 同样也是构建基于 netfilter 之上,在创立服务定义时可指定应用 iptables 或 IPVS。IPVS 是特定适宜于服务负载平衡的解决方案,提供了十分丰盛的平衡算法利用场景。
应用 DNS
每个服务会设置一个 DNS 域名,kubelets
为每个容器进行配置 --cluster-dns=<dns-service-ip>
,用以解析服务所对应 DNS 域名到对应的 Cluster IP 或 Pod IP。1.12 后 CoreDNS 成为缺省 DNS 形式。服务反对 3 种类型 DNS records(A record、CNAME、SRV records)。其中罕用的是 A Records,比方,在cluster.local
的 DNS name 下,A record 格局如pod-ip-address.my-namespace.pod.cluster.local
,其中 Pod hostname 和 subdomain 字段能够设置为规范的 FQDN 格局,比方,custom-host.custom-subdomain.my-namespace.svc.cluster.local
CNI
容器网络模型在实现上是由 K8s 的节点 Pod 资源管控(kubelet)和听从 Container Networking Interface(CNI)规范的插件独特合作实现的。CNI 插件程序在其中充当了 ” 胶水 ” 作用:各种容器网络实现能在统一的操作接口下由 kubelet 对立管控调度。另外,多个容器网络也能共存于一个集群内,为不同 Pod 的网络需要提供服务,都是在 kubelet 的对立管控下实现。
Overlay networking: Flannel
Flannel 是 CoreOS 为 K8s networking 开发的解决方案,也是阿里云 ACK 产品反对的容器网络解决方案。Flannel 的设计原理很简洁,在 host 网络之上创立另一个扁平网络(所谓的 overlay),在其上地址空间中给每个 pod 容器设置一个 IP 地址,并用此实现路由和通信。
主机内容器网络在 docker bridge docker0
上实现通信,不再赘述。主机间通信应用内核路由表和 IP-over-UDP 封装进行实现。容器 IP 包流经 docker bridge 会转发到 flannel0
网卡(TUN)设施上,进而流入到 flanneld
过程中。flanneld
会对 packet 指标 IP 地址所属的网段信息查问其对应的 下一跳
主机 IP,容器子网 CIDR 和所属主机 IP 的映射 (key-value) 保留在 etcd 中,flanneld
查问失去 packet 指标 IP 所属的主机 IP 地址后,会将 IP packet 封装到一个 UDP payload 中并设置 UDP packet 指标地址为所失去的指标主机 IP,最初在 host 网络中发送出 UDP packet。达到指标主机后,UDP packet 会流经 flanneld
并在这里解封出 IP packet,再发送至 flannel0
、docker0
最初达到指标容器 IP 地址上。下图示意流程:
值得一提是,容器 CIDR 和下一跳主机 IP 的映射条目容量没有非凡限度。在阿里云 ACK 产品上该条目容量须要在 VPC/vSwitch 管制面中进行散发,思考到整体性能因素,在数量上做了肯定数量限度(缺省 48 个)。但在自建主机网络部署中,该数量限度就不会显著了,因为主机下一跳主机网络在一个大二层立体上。
Flannel 新版本 backend 不倡议采纳 UDP 封装形式,因为 traffic 存在 3 次用户空间与内核空间的数据拷贝,(如下图)性能上存在比拟大的损耗。新版本举荐用 VxLan 和云服务商版本的 backends 进行优化。
L3 networking: Calico
Calico 是 L3 Routing 上十分风行容器网络架构计划。次要组件是 Felix,BIRD 和 BGP Route Reflector。Felix 和 BIRD 均是运行在 Node 上的 deamon 程序。架构简要:
Felix 实现网卡的治理和配置,包含 Routes programming 和 ACLs。实现路由信息对 Linux kernel FIB 的操作和 ACLs 的治理操作。因为 Felix 性能完整性和运行独立性十分好,其性能作为 Off-the-shelf 被集成到阿里云 Terway 网络插件中,实现其网络策略性能。
BIRD(BGP client)实现内核路由 FIB 条目向集群网络侧散发,使其路由条目对所有网络节点中可见,并实现 BGP 路由协定性能。每一个 BGP client 会连贯到网络中其它 BGP client,这对规模较大的部署会是显著的瓶颈(due to the N^2 increase nature)。鉴于该限度引入了 BGP Route Reflector 组件,实现 BGP clients 路由信息在汇聚层上再进行散发(propagation)。在集群网站中 Reflector 组件能够部署多个,齐全能于部署规模大小来决定。Reflector 组件仅仅执行路由信令和条目标散发,其中不波及任何数据面流量。路由汇聚层散发:
L3 networking:Terway
Terway 是阿里云自研 CNI 插件,提供了阿里云 VPC 互通和不便对接阿里云产品的基础设施,没有 overlay 网络带来的性能损耗,同时提供了简略易用的 backend 性能。
Terway 性能上可分为三局部:1. CNI 插件,一个独立的 binary 运行程序;2. Backend Server(也称为 daemon),程序以独立 daemonSet 形式运行在每个 Node 上;3. Network Policy,齐全集成了 Calico Felix 实现。
CNI 插件 binary 是通过 daemonSet 部署中 initContainer 装置到所有节点上,实现了 ADD
、DEL
、CHECK
三个接口供 kubelet 调用。这里以一个 Pod 在创立过程中的网络 setup 步骤来阐明:
- 当一个 Pod 被调度到节点上时,kubelet 监听到 Pod 创立在本人节点上,通过 runtime(docker…)创立 sandbox 容器来买通所需 namespace。
- kubelet 调用插件 binary 的
cmdAdd
接口,插件程序对接口参数设置查看后,向 backendServer 发动AllocIP
调用。 -
backendServer 程序的
networkService
依据 Pod 的网络类型进行相应的 Pod IP 申请,反对三种网络类型ENIMultiIP
、VPCENI
、VPCIP
:ENIMultiIP
是 eni 网卡带多 IP 类型,由networkService
中的ResourceManager
在本人的 IP 地址池中进行 IP 地址调配VPCENI
是为 Pod 创立和挂载一个 eni,由networkService
中的allocateENI
向阿里云 Openapi 发动对所属 ecs 实例的 eni 创立、挂载,并取得对应 eni IP 地址VPCIP
是为 Pod 在 VPC 网络立体上调配一个 IP 地址,这是在插件程序中通过调用ipam
接口从 vpc 管控面获取的 IP 地址
-
在 backendServer 返回
AllocIP
调用(IP 地址)后果后,插件调用不同网络类型下的NetnsDriver
`Setup` 接口实现来实现从容器网卡通往主机网卡的链路设置,其中:-
ENIMultiIP
和VPCIP
均是采纳vethDriver
的链路模式,步骤包含:- Create veth pair
- Add IP addr for container interface
- Add routes
- Host side namespace config
- Add host routes and rules
-
VPCENI
稍有不同是为每个 Pod 在 VPC 立体上绑定一个 eni,其中包含两次NetnsDriver
接口调用:vethDriver
rawNicDriver
(次要实现 VPC 平面网络路由设置,包含缺省路由和网关等配置)
-
综上图示:
为什么须要反对上述三种网络类型?基本上是由阿里云 vpc 网络基础设施所决定,同时笼罩阿里云支流利用对 vpc 网络资源的应用场景需要。另一方面是对标 Amazon AWS 的容器网络解决方案,在基于 VPC 和 ENI 的网络设施上能反对等同性能。
ENI 多 IP、VPC ENI 和 VPC IP 的次要区别在于前两者下的 Pod 网段和 VPC 网段是雷同的,而 VPC IP 的网段和节点的宿主机网段不同。这样使得在 ENI 网络环境下的 IP 路由齐全在 VPC 的 L2 网络立体上进行,而 VPC IP 网络须要在 VPC 路由表中进行配置 Pod 网段的下一跳主机,和 Flannel 的路由模式相似。能够看出,ENI 网络能带来更灵便的路由抉择和更好的路由性能。如下两个截图反映其不同路由特点:
VPC ENI 网络:
VPC IP 网络:
ENI 多 IP(1 个主 IP/ 多个辅助 IP)网络下有 2 种路由模式:veth 策略路由
和ipvlan
。两者本质区别在于应用不同的路由模式,前者应用 veth pair
的策略路由,后者应用 ipvlan
网络路由。策略路由须要在节点上配置策略路由条目来保障辅助 IP 的流量通过它所属的弹性网卡。ipvlan
实现了一个网卡虚构出多个子网卡和不同的 IP 地址,eni 将其辅助 IP 绑定到这些虚构进去的子网卡上造成一个与 vpc 立体买通的 L3 网络。这种模式使 ENI 多 IP 的网络结构比较简单,性能绝对 veth 策略路由
网络也更好。两种网络模式切换通过配置即可实现(缺省是vethpair
):
值得一提的是 Terway 还实现了 ENI 多 IP 地址资源池的治理和分配机制。networkService
中的 eniIPFactory
为每个 eni 网卡创立一个 goroutine,该 eni 网卡上的 eniIP 的调配开释都在这个 goroutine 中实现。在创立一个 eniIP 时扫描曾经存在的 eni 网卡,如该 eni 还存在闲暇的 eniIP,该 goroutine 会通过 ipResultChan
返回给 eniIPFactory
一个调配的 IP。如果所有的 eni 网卡的 eniIP 都调配结束,会先创立一个新的 eni 网卡和对应的 goroutine,首次创立 eni 网卡时无需做 IP 调配,间接返回 eni 网卡主 IP 即可。eniIP 开释是逆向的,在 eni 网卡的最初一个 eniIP 开释时,整个 eni 网卡资源会开释掉。
另外,有一个 startCheckIdleTicker
goroutine 会定期扫描地址池的MaxPoolSize
和MinPoolSize
水位,在低于和高出水位阀值时会对地址池 eniIP 资源进行进行创立和开释,使得地址池 IP 资源处于一个可控水位范畴中。为了保障资源状态一致性,有一个 startGarbageCollectionLoop
goroutine 会定期扫描 IP 地址是否在用或过期状态,如检测到会进行资源 GC 操作。最初,Pod 资源状态数据都长久化在本地的一个boltDB
文件中 /var/lib/cni/terway/pod.db
,即便 Pod 曾经在 apiServer 中删除,GetPod
会从本地 boltDB
中读取正本数据。在 Pod 曾经删除但正本还存在 DB 的状况下,GC goroutine 检 测到会执行清理。截图简述:
总结下,从这些 backend 性能能够看到 Terway 为阿里云 vpc 网络资源设施连通性做了很好的封装,让基于阿里云 Kubernetes 的利用开发和部署带来更加简便和高效的长处。