本文是关于Kubernetes service解析的第四章
- 深入理解Kubernetes service - 你真的理解service吗?
- 深入理解Kubernetes service - EndpointSlices做了什么?
- 深入理解Kubernetes service - kube-proxy架构分析
- 深入理解Kubernetes service - 如何扩展现有的kube-proxy架构?
所有关于Kubernetes service 部分代码上传至仓库 github.com/cylonchau/kube-haproxy
Overview
在前两部分中,学习了一些 service,于kube-proxy在设计架构,但存在扩展问题将引入了一些问题:
- 为什么需要了解这部分内容呢?
- 与传统架构有什么区别呢?
- 于eBPF 的 cilium又有什么区别呢?
- 既然eBPF可以做到,那为什么要这部分内容呢?
接下来的内容将围绕这四个问题展开来讲,而不是代码的讲解,代码可以看置顶
IPVS与iptables在kubernetes中应用时的问题
对于在使用了kubernetes用户以及了解 kube-proxy 架构后,知道当集群规模过大时,service必将增多,而一个service未必是一条iptables/ipvs规则,对于kubernetes这种分布式架构来说,集群规模越大,集群状态就越不可控,尤其时kube-proxy。
为什么单指kube-proxy呢?想想可以知道,pod的故障 或 node 的故障对于kubernetes集群来说却不是致命的,因为 这些资源集群中存在 避免方案,例如Pod的驱逐。而kube-proxy或iptables/IPVS问题将导致服务的不可控 『抖动』例如规则生成的快慢和Pod就绪的快慢不一致,部分节点不存在 service 此时服务必然抖动。
再例如 iptables/IPVS 排查的难度对于普通运维工程师或开发工程师的技术水平有很高的要求,网上随处可见分析该类问题的帖子:
对于上述问题,相信遇到了很难定位处理,虽然现在已fixed,并有eBPF技术的加入减少了此类问题的发生,但是eBPF实际同理于IPVS 都是需要对Linux内核有一定了解后才可以,这也就是为什么需要了解这部分
如果需要自定义proxier为什么会解决这个问题
这里就是放大到kubernetes意外的传统架构中,当直接部署于Linux系统上使用nginx等传统LB时就很少有人提到这些问题了,而这些问题存在一个关键字「Container」;而引发这个问题的则是 service。去除 service 的功能,传统架构于Kubernetes架构部署的应用则是相同的,只是区分了名称空间。
抓入关键的核心之后就做接下来的事情了,我称之为「shed kube-proxy, fetch service」;即把service提取到集群外部的LB之上,例如F5, nginx等。
这里会存在一个疑问:「这个不是ingress吗?」,这个问题会在下一章讲到 proxier与ingress有什么区别?
软件的设计
既然拿到了核心问题就该定义软件工作模式,这里将软件架构设计为三种:
- only fetch:任然需要 kube-proxy 组件,通过定义 contoller 将流量引入,即不过service,这种场景不会破坏现有的集群架构,从而去除service的功能,如果需要service功能配置外部service即可
- SK (similar kube-proxy):通过效仿kube-proxy + ipvs架构,将LB于proxier部署在每个worker节点上,让浏览都走本地
- replacement kube-proxy:完全取代kube-proxy 这于cilium类似了,但不同的是,proxier 可以于
kube-controller-manager
;kube-scheduler
作为控制平面为集群提供service
功能,而无需为所有worker节点都部署一个kube-proxy
或cilium
这种架构
最后一个问题
此时可以引入最后一个问题了:「既然eBPF可以做到,那为什么要这部分内容呢?」。
答:其一简单,每个运维人员无需额外知识都可以对 service 问题进行排错,简便了运维复杂度。另外这一部分其实是对于完整企业生态来讲,统一的流量转发平台是所必须的,有了这个就不需要单独的 service 功能了
实践:基于haproxy的proxier
在扩展proxier需要对 kube-proxy 有一定的了解,并且,kube-proxy 在可扩展性来说做的也是相当不错的,我们只需要实现一个 proxier.go 就可以基本上完成了对 kube-proxy ;而 proxier.go 的核心方法只需要三个函数即可(==这里是根据iptables/ipvs的设计进行的,也可以整合为一个方法==)
除了这三个函数外,其他的函数全都是 kube-proxy 已经实现好的通用的,这里直接使用或者按照其他内置proxier的方法即可
满足条件
- haproxy工作与proxier相同的节点,可以是集群内也可以是集群外,整个集群只需要一个
- 实现方法:syncProxyRules(), syncService(), syncEndpoint()
查看当前的service
|
|
查看service 配置
|
|
通过 proxier 生成了对应的 backend 与 frontend,这样就可以通过 haproxy 作为一个外部LB来跨过 service 与 IPVS/IPTables,通过这种情况下,我们可以将集群拉出一个平面至传统架构上,而又不影响集群的功能
在这种场景下需要注意的是:
- OF模式下,我们需要 kube-proxy 组件,而使用 kube-proxy 组件
- 所有模式下,haproxy worker和kubernetes nodes需处于一个网络平面
- 非OF模式下需要自行修改
kube-apiserver
源代码(主要是使kubernetes service分配机制)
Proxier与Ingress的区别
肯定有人会问,kubernetes提供了Ingress功能不是和这个一样吗?
答:对比一个LB是Proxier还是Ingress最好的区别就是“舍去kube-proxy”可以工作正常吗?
而kubernetes官方也提供说明,Ingress的后端是service,service的后端则是IPVS/IPTables,而IPVS的后端才是Pod;相对于Proxier LB,他的后端则直接是Pod,跨越了Service。
而相对的本文的学习思路,haproxy官方提供了对应的解决方案 [4] ;而由此,可以灵活的为Kubernetes提供更多的LB方案
Reference
[4] External haproxy