kube-proxy源码阅读
通过阅读kube-proxy的源码可以将proxy的主要逻辑总结为下图所示:
首先顺着代码阅读到ProxyServer->Run()函数,这里是kube-proxy启动的主逻辑,启动了两个server,分别是:
...
var errCh chan error
if s.BindAddressHardFail {
errCh = make(chan error)
}
// Start up a healthz server if requested
serveHealthz(s.HealthzServer, errCh)
// Start up a metrics server if requested
serveMetrics(s.MetricsBindAddress, s.ProxyMode, s.EnableProfiling, errCh)
...
每个服务都是通过定时任务保证服务存活:
func serveHealthz(hz healthcheck.ProxierHealthUpdater, errCh chan error) {
if hz == nil {
return
}
fn := func() {
err := hz.Run()
if err != nil {
klog.ErrorS(err, "Healthz server failed")
if errCh != nil {
errCh <- fmt.Errorf("healthz server failed: %v", err)
// if in hardfail mode, never retry again
blockCh := make(chan error)
<-blockCh
}
} else {
klog.ErrorS(nil, "Healthz server returned without error")
}
}
go wait.Until(fn, 5*time.Second, wait.NeverStop)
}
然后通过informer机制的list/watch感知services(被观察者)和endpoints(被观察者)的变化,通过观察者模式实现消息同步,从而触发观察者(Proxier)的syncProxyRules方法更新网络规则:
...
serviceConfig := config.NewServiceConfig(informerFactory.Core().V1().Services(), s.ConfigSyncPeriod)
serviceConfig.RegisterEventHandler(s.Proxier) // 观察者模式
go serviceConfig.Run(wait.NeverStop)
endpointSliceConfig := config.NewEndpointSliceConfig(informerFactory.Discovery().V1().EndpointSlices(), s.ConfigSyncPeriod)
endpointSliceConfig.RegisterEventHandler(s.Proxier) // 观察者模式
go endpointSliceConfig.Run(wait.NeverStop)
...
Proxier提供的方法列表,先大概从名称上来了解一下方法用途:
func (proxier *Proxier) precomputeProbabilities(numberOfPrecomputed int) {/*...*/}
func (proxier *Proxier) probability(n int) string{/*...*/}
func (proxier *Proxier) Sync(){/*...*/}
func (proxier *Proxier) SyncLoop(){/*...*/}
func (proxier *Proxier) setInitialized(value bool){/*...*/}
func (proxier *Proxier) isInitialized() bool{/*...*/}
func (proxier *Proxier) OnServiceAdd(service *v1.Service){/*...*/}
func (proxier *Proxier) OnServiceUpdate(oldService, service *v1.Service){/*...*/}
func (proxier *Proxier) OnServiceDelete(service *v1.Service){/*...*/}
func (proxier *Proxier) OnServiceSynced(){/*...*/}
func (proxier *Proxier) OnEndpointsAdd(endpoints *v1.Endpoints){/*...*/}
func (proxier *Proxier) OnEndpointsUpdate(oldEndpoints, endpoints *v1.Endpoints){/*...*/}
func (proxier *Proxier) OnEndpointsDelete(endpoints *v1.Endpoints) {/*...*/}
func (proxier *Proxier) OnEndpointsSynced() {/*...*/}
func (proxier *Proxier) deleteEndpointConnections(connectionMap []proxy.ServiceEndpoint){/*...*/}
func (proxier *Proxier) appendServiceCommentLocked(args []string, svcName string){/*...*/}
func (proxier *Proxier) syncProxyRules(){/*...*/}
其中Proxier的实现方式有两种,分别是:iptables和 IPVS
IPVS:
IPVS 全名 IP Virtual Server
实现 L4 的 load balancing
主要在 cluster 前的实体主机上运行
直接请求 base on service 的 TCP / UDP 到真实的 server
使真实 server 的 service 显示为有一个 IP 的虚拟 service
实现为 Netfilter 框架上的 module
Based on kernel 中的 hash tables
Kernel source code: net/netfilter/ipvs
ipvsadm 是管理 IPVS 的工具
iptables:
大致了解 IPVS ,那 IPVS 和 iptables 有什么差別呢?
其实他们都是使用 Netfilter ,来让封包传送的机制,但作面却是不一样的。
在 INPUT chain 这边,不论是 IPVS 或是 iptables 都会到 userspace 来进行封包解析,来看看封包要往哪里走。然而,就是在判断封包要往哪里走,这里的方法两者使用的方式是不一样的。
iptables 规则设定是:n 张 table,每张 table 内有 m 个 chain ,每个 chain 中有 多个rule。
虽然封包不会跑过所有的 Table 和 chain,但 rules 通常会是很多的。虽然封包只要被拆解一次,但封包在比对每个 rule 时,都要再看要用进來的这个封包和目前轮到的 rule 进行行比对。
这也是为什么我们在下 rule 的时候很重视 rule 的顺序性。(所以 rule 也会因人的设定好不好,效能也会有差异)。
IPVS 在判断上面就简单很多,他是用 hash table(hash表),在时间复杂度上,通常是是 O(1),即便遇到最差的情況(worst case),也只是 O(n)。
在kube-proxy中这两种Proxier的实现的最大区别也主要是规则的更新,也就是syncProxyRules这个方法,下面梳理两种实现的主要逻辑:
iptables:
- 更新proxier.endpointsMap,proxier.servieMap
- 创建自定义链将当前内核中 filter 表和 nat 表中的全部规则导入到内存中
- 为每个 service 创建规则
- 为 clusterIP 设置访问规则
- 为 externalIP 设置访问规则
- 为 ingress 设置访问规则
- 为 nodePort 设置访问规则
- 为 endpoint 生成规则链
- 写入 DNAT 规则
- 删除不再使用的服务自定义链
- 使用 iptables-restore 同步规则
IPVS:
- 通过 iptables-save 获取现有的 Filter 和 NAT 表存在的链数据
- 创建自定义链与规则
- 创建 Dummy 接口和 ipset 默认列表
- 为每个服务生成 ipvs 规则
- 对 serviceMap 内的每个服务进行遍历处理,对不同的服务类型(clusterip/nodePort/externalIPs/load-balancer)进行不同的处理(ipset 列表/vip/ipvs 后端服务器)
- 根据 endpoint 列表,更新 KUBE-LOOP-BACK 的 ipset 列表
- 若为 clusterIP 类型更新对应的 ipset 列表 KUBE-CLUSTER-IP
- 若为 externalIPs 类型更新对应的 ipset 列表 KUBE-EXTERNAL-IP
- 若为 load-balancer 类型更新对应的 ipset 列表 KUBE-LOAD-BALANCER、KUBE-LOAD-BALANCER-LOCAL、KUBE-LOAD-BALANCER-FW、KUBE-LOAD-BALANCER-SOURCE-CIDR、KUBE-LOAD-BALANCER-SOURCE-IP
- 若为 NodePort 类型更新对应的 ipset 列表 KUBE-NODE-PORT-TCP、KUBE-NODE-PORT-LOCAL-TCP、KUBE-NODE-PORT-LOCAL-SCTP-HASH、KUBE-NODE-PORT-LOCAL-UDP、KUBE-NODE-PORT-SCTP-HASH、KUBE-NODE-PORT-UDP
- 同步 ipset 记录
- 刷新 iptables 规则
此外,由于 linux 内核原生的 ipvs 模式只支持 DNAT,不支持 SNAT,所以,在以下几种场景中 ipvs 仍需要依赖 iptables 规则:文章来源:https://www.toymoban.com/news/detail-501549.html
- 1、kube-proxy 启动时指定 –-masquerade-all=true 参数,即集群中所有经过 kube-proxy 的包都做一次 SNAT;
- 2、kube-proxy 启动时指定 --cluster-cidr= 参数;
- 3、对于 Load Balancer 类型的 service,用于配置白名单;
- 4、对于 NodePort 类型的 service,用于配置 MASQUERADE;
- 5、对于 externalIPs 类型的 service;
但对于 ipvs 模式的 kube-proxy,无论有多少 pod/service,iptables 的规则数都是固定的。文章来源地址https://www.toymoban.com/news/detail-501549.html
到了这里,关于kube-proxy源码阅读的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!