kube-apiserver 核心职责
提供Restful API;代理集群组件,如dashboard、流式日志、kubectl exec
会话;缓存全量的etcd 数据且无状态服务可水平扩展。
kube-List操作
绝大部分情况下,kube-apiserver 都会直接从本地缓存提供服务(因为它缓存了集群全量数据),某些特殊情况,apiserver 就只能将请求转发给 etcd,例如
1、客户端明确要求从 etcd 读数据,追求最高的数据准确性,客户端 LIST 参数设置不当也可能会走到这个逻辑;
2、apiserver 本地缓存还没建好
请求举例
1、LIST apis/cilium.io/v2/ciliumendpoints?limit=500&resourceVersion = 0
resourceVersion=0 表示会从apiserver缓存中获取数据,resourceVersion=""表示从etcd获取数据,但因为apiserver的cache不支持limit/continue,etcd是 KV 存储,虽没有按 label/field 过滤功能但能处理 limit/continue,所以 resourceVersion=0 会导致 limit=500被忽略,导致客户端拿到的是全量 ciliumendpoints 数据。
2、LIST api/v1/pods?filedSelector=spec.nodeName%3Dnode1
%3D 是 = 的转义,这个请求是根据 nodename 做过滤,获取 node1 上的所有 pods,感觉数据量不太大,但其实背后要比看上去复杂:
(1)没有指定 resourceVersion=0会导致 apiserver 跳过缓存,直接去 etcd 读数据;
(2)etcd 只是 KV 存储,没有按 label/field 过滤功能(只处理 limit/continue),因此apiserver 是从 etcd 拉全量数据,然后在内存做过滤,再返回给客户端,开销是很大的。
3、LIST api/v1/pods?filedSelector=spec.nodeName%3Dnode1&resourceVersion = 0
resourceVersion=0表示 apiserver 会从缓存读数据,性能会有量级的提升,但是apiserver需要在内存做过滤再返回给客户端, apiserver 需要处理的数据量可能会非常大。
如何判断是否必须从 etcd 读数据:shouldDelegateList()
//https://github.com/kubernetes/kubernetes/blob/v1.24.0/staging/src/k8s.io/apiserver/pkg/storage/cacher/cacher.go#L591
func shouldDelegateList(opts storage.ListOptions) bool {
resourceVersion := opts.ResourceVersion
pred := opts.Predicate
pagingEnabled := DefaultFeatureGate.Enabled(features.APIListChunking) // 默认是启用的
hasContinuation := pagingEnabled && len(pred.Continue) > 0 // Continue 是个 token
hasLimit := pagingEnabled && pred.Limit > 0 && resourceVersion != "0" // 只有在 resourceVersion != "0" 的情况下,hasLimit 才有可能为 true
// 1. 如果未指定 resourceVersion,从底层存储(etcd)拉去数据;
// 2. 如果有 continuation,也从底层存储拉数据;
// 3. 只有 resourceVersion != "0" 时,才会将 limit 传给底层存储(etcd),因为 watch cache 不支持 continuation
return resourceVersion == "" || hasContinuation || hasLimit || opts.ResourceVersionMatch == metav1.ResourceVersionMatchExact
}
客户端未设置 ListOption{} 中的 ResourceVersion 字段,会对应到这里的 resourceVersion == ""从而导致从 etcd 拉全量数据;
客户端设置了 limit=500&resourceVersion=0 不会导致下次 hasContinuation==true,因为resourceVersion=0 将导致 limit 被忽略仍会返回全量数据。
因此,未指定resourceVersion、resourceVersionMatch=exact(即同时resourceVersion=非零值)、有limit/continue都会直接从etcd读数据。
部署和调优建议
1、Get/List 请求设置 ResourceVersion=0
, client-go 的 ListWatch/Informer 接口默认已经设置了 ResourceVersion=0
;
2、优先使用 namespaced API;
3、Restart backoff,对于kubelet、cilium-agent、daemonsets等需要通过有效的 restart backoff 降低大面积重启时对kube-apiserver的压力;
4、频繁的list操作尤其是带筛选条件的list建议使用informer的 ListWatch 机制,将数据拉到本地,业务逻辑根据需要自己从 local cache 过滤。如果只是一次性的 list操作,并且有筛选条件, 那显然应该通过设置 label 或字段过滤器,让 apiserver 帮我们把数据过滤出来。同时不要忘记在请求中同时带上 resourceVersion=0。
resourceVersion的语义
1、对于Get resourceVersion的语义
任何版本指的是最新可用资源版本优先,但不要求强一致性。
2、对于List resourceVersion的语义
从 v1.19 版本开始,apiserver支持list在resourceVersion=<非零值>的同时携带 resourceVersionMatch参数,来确定如何解析 resourceVersion。
resourceVersionMatch=Exact,表示精确匹配resourceVersion,如果resourceVersion找不到则返回 HTTP 410 (Gone)的响应。
resourceVersionMatch=NotOlderThan,不表示老于指定版本的resourceVersion,最新可用资源版本优先。
3、对于Watch resourceVersion的语义