【博客683】k8s list请求优化以及合理使用list以维护集群稳定性

这篇具有很好参考价值的文章主要介绍了【博客683】k8s list请求优化以及合理使用list以维护集群稳定性。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

k8s list请求优化以及合理使用list以维护集群稳定性

apiserver/etcd List 示例分析

  • 1、LIST apis/cilium.io/v2/ciliumendpoints?limit=500&resourceVersion = 0

    这里同时传了两个参数,但 resourceVersion=0 会导致 apiserver 忽略 limit=500, 所以客户端拿到的是全量 ciliumendpoints 数据

    此时不会查etcd,因为有resourceVersion = 0,且resourceVersion = 0会忽略limit,因为limit一定要查etcd
    一种资源的全量数据可能是比较大的,需要考虑清楚是否真的需要全量数据

  • 2、LIST api/v1/pods?filedSelector=spec.nodeName%3Dnode1

    这个请求是获取 node1 上的所有 pods(%3D 是 = 的转义)。
    根据 nodename 做过滤,给人的感觉可能是数据量不太大,但其实背后要比看上去复杂:

    这种行为是要避免的,除非对数据准确性有极高要求,特意要绕过 apiserver 缓存。
    首先,这里没有指定 resourceVersion=0,导致 apiserver 跳过缓存,直接去etcd读数据;
    其次,etcd 只是 KV 存储,没有按 label/field 过滤功能(只处理 limit/continue),
    所以,apiserver 是从 etcd 拉全量数据,然后在内存做过滤,开销也是很大的,后文有代码分析。

  • 3、LISTapi/v1/pods?filedSelector=spec.nodeName%3Dnode1&resourceVersion = 0

    跟 2 的区别是加上了 resourceVersion=0,因此 apiserver 会从缓存读数据,性能会有量级的提升。
    但要注意,虽然实际上返回给客户端的可能只有几百 KB 到上百 MB(取决于 node 上 pod 的数量、pod 上 label 的多少等因素), 但 apiserver 需要处理的数据量可能是几个 GB。后面会有定量分析。

以上可以看到,不同的 LIST 操作产生的影响是不一样的,而客户端看到数据还有可能只 是 apiserver/etcd 处理数据的很小一部分。如果基础服务大规模启动或重启, 就极有可能把控制平面打爆。

判断是否必须从 etcd 读数据:shouldDelegateList()

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 被忽略(hasLimit 那一行代码),也就是说, 虽然指定了 limit=500,但这个请求会返回全量数据。

  • 问:还有其他情况需要去etcd拉取吗?

    答:apiserver cache未建立完成的时候

apiserver/etcd List 开销分析

List 请求可以分为两种:

  • 1、List 全量数据:开销主要花在数据传输;

  • 2、指定用 label 或字段(field)过滤,只需要匹配的数据。

    大部分情况下,apiserver 会用自己的缓存做过滤,这个很快,因此耗时主要花在数据传输;
    需要将请求转给 etcd 的情况,
    前面已经提到,etcd 只是 KV 存储,并不理解 label/field 信息,因此它无法处理过滤请求。实际的过程是:apiserver 从 etcd 拉全量数据,然后在内存做过滤,再返回给客户端。
    因此除了数据传输开销(网络带宽),这种情况下还会占用大量apiserver CPU 和内存

大规模部署时潜在的问题

用 k8s client-go 根据 nodename 过滤 pod:

podList, err := Client().CoreV1().Pods(“”).List(ctx(), ListOptions{FieldSelector: “spec.nodeName=node1”})

来实际看一下它背后的数据量。以一个 4000 node,10w pod 的集群为例,全量 pod 数据量:

  • etcd 中:紧凑的非结构化 KV 存储,在 1GB 量级;
  • apiserver 缓存中:已经是结构化的 golang objects,在 2GB 量级
  • apiserver 返回:client 一般选择默认的 json 格式接收, 也已经是结构化数据。全量 pod 的 json 也在 2GB 量级。

可以看到,某些请求看起来很简单,只是客户端一行代码的事情,但背后的数据量是惊人的。指定按 nodeName 过滤 pod 可能只返回了 500KB 数据,但 apiserver 却需要过滤 2GB 数据 —— 最坏的情况,etcd 也要跟着处理 1GB 数据 (以上参数配置确实命中了最坏情况,见下文代码分析)。

集群规模比较小的时候,这个问题可能看不出来(etcd 在 LIST 响应延迟超过某个阈值 后才开始打印 warning 日志);规模大了之后,如果这样的请求比较多,apiserver/etcd 肯定是扛不住的。

LIST example

为了避免客户端库(例如 client-go)自动帮我们设置一些参数,我们直接用 curl 来测试,指定证书就行了:

$ cat curl-k8s-apiserver.sh
curl -s --cert /etc/kubernetes/pki/admin.crt --key /etc/kubernetes/pki/admin.key --cacert /etc/kubernetes/pki/ca.crt $@
使用方式:

$ ./curl-k8s-apiserver.sh "https://localhost:6443/api/v1/pods?limit=2"
{
  "kind": "PodList",
  "metadata": {
    "resourceVersion": "2127852936",
    "continue": "eyJ2IjoibWV0YS5rOHMuaW8vdjEiLCJ...",
  },
  "items": [ {pod1 data }, {pod2 data}]
}

指定 limit=2:response 将返回分页信息(continue)

$ ./curl-k8s-apiserver.sh "https://localhost:6443/api/v1/pods?limit=2"
{
  "kind": "PodList",
  "metadata": {
    "resourceVersion": "2127852936",
    "continue": "eyJ2IjoibWV0YS5rOHMuaW8vdjEiLCJ...",
  },
  "items": [ {pod1 data }, {pod2 data}]
}

可以看到:

  • 确实返回了两个 pod 信息,在 items[] 字段中;
  • 另外在 metadata 中返回了一个 continue 字段,客户端下次带上这个参数,apiserver 将继续返回剩下的内容,直到 apiserver 不再返回 continue。

指定 limit=2&resourceVersion=0:limit=2 将被忽略,返回全量数据:

$ ./curl-k8s-apiserver.sh "https://localhost:6443/api/v1/pods?limit=2&resourceVersion=0"
{
  "kind": "PodList",
  "metadata": {
    "resourceVersion": "2127852936",
    "continue": "eyJ2IjoibWV0YS5rOHMuaW8vdjEiLCJ...",
  },
  "items": [ {pod1 data }, {pod2 data}, ...]
}

items[] 里面是全量 pod 信息。

指定 spec.nodeName=node1&resourceVersion=0 vs. spec.nodeName=node1

$ ./curl-k8s-apiserver.sh "https://localhost:6443/api/v1/namespaces/default/pods?fieldSelector=spec.nodeName%3Dnode1" | jq '.items[].spec.nodeName'
"node1"
"node1"
"node1"
...

$ ./curl-k8s-apiserver.sh "https://localhost:6443/api/v1/namespaces/default/pods?fieldSelector=spec.nodeName%3Dnode1&resourceVersion=0" | jq '.items[].spec.nodeName'
"node1"
"node1"
"node1"
...

速度差异很大,用 time 测量以上两种情况下的耗时,会发现对于大一些的集群,这两种请求的响应时间就会有明显差异。

对于 4K nodes, 100K pods 规模的集群,以下数据供参考:

  • 不带 resourceVersion=0(读 etcd 并在 apiserver 过滤): 耗时 10s
  • 带 resourceVersion=0(读 apiserver 缓存): 耗时 0.05s

部署和调优建议

1、List 请求默认设置 ResourceVersion=0

不设置这个参数将导致 apiserver 从 etcd 拉全量数据再过滤,导致很慢,规模大了 etcd 扛不住

因此,除非对数据准确性要求极高,必须从 etcd 拉数据,否则应该在 LIST 请求时设置 ResourceVersion=0 参数, 让 apiserver 用缓存提供服务。

如果你使用的是 client-go 的 ListWatch/informer 接口, 那它默认已经设置了 ResourceVersion=0。

2、优先使用 namespaced API

如果要 LIST 的资源在单个或少数几个 namespace,考虑使用 namespaced API:

Namespaced API: /api/v1/namespaces//pods?query=xxx

Un-namespaced API: /api/v1/pods?query=xxx

3、Restart backoff

对于 per-node 部署的基础服务,例如 kubelet、cilium-agent、daemonsets,需要 通过有效的 restart backoff 降低大面积重启时对控制平面的压力。

例如,同时挂掉后,每分钟重启的 agent 数量不超过集群规模的 10%(可配置,或可自动计算)。

4、优先通过 label/field selector 在服务端做过滤

如果需要缓存某些资源并监听变动,那需要使用 ListWatch 机制,将数据拉到本地,业务逻辑根据需要自己从 local cache 过滤。这是 client-go 的 ListWatch/informer 机制。

但如果只是一次性的 LIST 操作,并且有筛选条件,例如前面提到的根据 nodename 过滤 pod 的例子, 那显然应该通过设置 label 或字段过滤器,让 apiserver 帮我们把数据过滤出来。LIST 10w pods 需要几十秒(大部分时间花在数据传输上,同时也占用 apiserver 大量 CPU/BW/IO), 而如果只需要本机上的 pod,那设置 nodeName=node1 之后,LIST 可能只需要 0.05s 就能返回结果。
另外非常重要的一点时,不要忘记在请求中同时带上resourceVersion=0。

  • Label selector
    在 apiserver 内存过滤。

  • Field selector
    在 apiserver 内存过滤。

  • Namespace selector
    etcd 中 namespace 是前缀的一部分,因此能指定 namespace 过滤资源,速度比不是前缀的 selector 快很多。

5、配套基础设施(监控、告警等)

以上分析可以看成,client 的单个请求可能只返回几百 KB 的数据,但 apiserver(更糟糕的情况,etcd)需要处理上 GB 的数据。因此,应该极力避免基础服务的大规模重启,为此需要在监控、告警上做的尽量完善。

  • 使用独立 ServiceAccount
    每个基础服务(例如 kubelet、cilium-agent 等),以及对 apiserver 有大量 LIST 操作的各种 operator, 都使用各自独立的 SA, 这样便于 apiserver 区分请求来源,对监控、排障和服务端限流都非常有用。

  • Liveness 监控告警
    基础服务必须覆盖到 liveness 监控。

6、Get 请求:GetOptions{}

基本原理与 ListOption{} 一样,不设置 ResourceVersion=0 会导致 apiserver 去 etcd 拿数据,应该尽量避免。文章来源地址https://www.toymoban.com/news/detail-609632.html

到了这里,关于【博客683】k8s list请求优化以及合理使用list以维护集群稳定性的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Kubernetes(K8S)快速搭建typecho个人博客

    Kubernetes(K8S)快速搭建typecho个人博客 K8S集群环境,搭建教程参考腾讯云Lighthouse组建跨地域Kubernetes集群 K8S集群面板,搭建教程参考Kubernetes集群管理面板的安装及使用 - 青阳のblog-一个计算机爱好者的个人博客 (hipyt.cn) 如果没有集群或者服务器不够可以通过传送门新购。 腾讯

    2024年02月04日
    浏览(40)
  • Kubeadm - K8S1.20 - 高可用集群部署(博客)

    1.系统设置 注意事项: master节点cpu核心数要求大于2 ●最新的版本不一定好,但相对于旧版本,核心功能稳定,但新增功能、接口相对不稳 ●学会一个版本的 高可用部署,其他版本操作都差不多 ●宿主机尽量升级到CentOS 7.9 ●内核kernel升级到 4.19+ 这种稳定的内核 ●部署k8

    2024年02月05日
    浏览(33)
  • k8s 操作命令(合集List)

    一、K8S最常用命令如下: 1、获取pod信息:kubectl get pod 2、查看指定pod的日志信息:kubectl logs -f --tail(最后多少行) 500 podName(pod名) 3、查看pod的描述信息:kubectl describe pod podName 4、查看节点信息:kubectl get nodes 5、查看pod的详细信息,以yaml或者json格式展示:kubectl get pods -o yaml、

    2024年02月12日
    浏览(25)
  • 【博客682】k8s apiserver bookmarks机制以更高效检测变更

    List-Watch 是kubernetes中server和client通信的最核心的机制, 比如说api-server监听etcd, kubelet监听api-server, scheduler监听api-server等等,其实其他模块监听api-server相当于监听etcd,因为在k8s的设计中,只有api-server能跟etcd通信,其他模块需要etcd的数据就只好监听api-server了。 etcd默认保留

    2024年02月15日
    浏览(31)
  • docker在k8s容器中的作用,以及docker的底层原理,以及k8s的常用命令

        Docker的设计思想就是创建软件程序可移植性的轻量级容器,让其可以在任何安装了Docker的机器上,不用关心底层操作系统,就可以运行开发程序,就像集装箱一样使用。 Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从Apache2.0协议开源。 Docker 可以让开发者打包他们

    2024年04月27日
    浏览(42)
  • 云原生之深入解析K8S的请求和限制

    在 Kubernetes 中使用容器时,了解涉及的资源是什么以及为何需要它们很重要。有些进程比其它进程需要更多的 CPU 或内存,这很关键,永远不应该让进程饥饿,知道了这一点,那么应该正确配置容器和 Pod,以便充分利用两者。 使用 Kubernetes 时,限制和请求是重要的设置。Ku

    2024年02月13日
    浏览(33)
  • K8s技术全景:架构、应用与优化

    本文深入探讨了Kubernetes(K8s)的关键方面,包括其架构、容器编排、网络与存储管理、安全与合规、高可用性、灾难恢复以及监控与日志系统。 关注【TechLeadCloud】,分享互联网架构、云服务技术的全维度知识。作者拥有10+年互联网服务架构、AI产品研发经验、团队管理经验

    2024年04月08日
    浏览(93)
  • Gitlab+Jenkins+Docker+Harbor+K8s集群搭建CICD平台(持续集成部署Hexo博客Demo)

    目录 涉及内容: 一、CICD服务器环境搭建 1、docker 环境安装 (1)、拉取镜像,启动并设置开机自启 (2)、配置docker加速器 2、安装并配置GitLab (1)、创建共享卷目录 (2)、创建 gitlab 容器 (3)、关闭容器修改配置文件 (4)、修改完配置文件之后。直接启动容器 (5)、相关

    2024年03月15日
    浏览(50)
  • 完全清理k8s以及网络插件

    在卸载K8s组件前,先在所有节点执行kubeadm reset命令,清空K8s集群设置 使用一开始创建pod时同样的yaml文件 检查所有节点上的网络,看看是否存在Tunl0 删除Tunl0 直接清空所有镜像即可,如果有需要保留的镜像,则选择清空

    2024年02月11日
    浏览(32)
  • 6 张配图通俗易懂说透 K8S 请求和限制

    在 Kubernetes 中使用容器时,了解涉及的资源是什么以及为何需要它们很重要。有些进程比其他进程需要更多的 CPU 或内存。这很关键,永远不应该让进程挨饿。知道了这一点,我们应该正确配置容器和 Pod,以便充分利用两者。 使用 Kubernetes 时,限制和请求是重要的设置。本文

    2024年02月09日
    浏览(21)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包