clientgo的Indexer三部曲之二:性能测试

2023年 8月 30日 49.5k 0

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):github.com/zq2599/blog…

本篇概览

  • 本文是《client-go的Indexer》系列的第二篇,在前文咱们通过实例掌握了client-go的Indexer的基本功能,本篇咱们尝试对下面这两种接口进行压力测试,用数据验证Indexer的性能优势,看看是否如理论分析那样真的存在
  • 第一个接口:basic/get_obj_by_obj_key,这个接口会用到Store.GetByKey方法,从本地缓存中取得pod对象返回,如下图红色箭头所示
  • 在这里插入图片描述

  • 第二个接口:remote/get_obj_by_obj_key_remote_query,此接口会调用client-go库的api,向api-server发起http请求,查找指定pod的信息返回
    在这里插入图片描述
  • 部署情况说明

    • 今天的压测所涉及的服务和应用,它们的部署情况如下图所示,共两台Ubuntu电脑,电脑1用于执行压测,上面部署了k6(或者部署docker,用docker来运行k6),电脑2部署了kubernetes,同时也运行着名为client-go-indexer-tutorials的应用,该应用就是咱们编写的代码:实现了今天要压测的两个接口
      在这里插入图片描述

    源码下载

    • 接下来要进入的是编码环节,如果您不想写代码,也可以从GitHub上直接下载,地址和链接信息如下表所示(github.com/zq2599/blog…
    名称 链接 备注
    项目主页 github.com/zq2599/blog… 该项目在GitHub上的主页
    git仓库地址(https) github.com/zq2599/blog… 该项目源码的仓库地址,https协议
    git仓库地址(ssh) git@github.com:zq2599/blog_demos.git 该项目源码的仓库地址,ssh协议
    • 这个git项目中有多个文件夹,本篇的源码在tutorials/client-go-indexer-tutorials文件夹下,如下图红框所示:
      在这里插入图片描述

    编码(第一个接口:basic/get_obj_by_obj_key)

    • 第一个接口的源码其实在前文其实已写好,就不重新写了,这里看一下关键代码,如下所示,其实也就一行(调用INDEXER.GetByKey)
    // GetObjByObjKey b. 根据对象的key返回(演示Store.Get方法)
    func GetObjByObjKey(c *gin.Context) {
    	rawObj, exists, err := INDEXER.GetByKey(ObjKey(c))
    
    	if err != nil {
    		c.String(500, fmt.Sprintf("b. get pod failed, %v", err))
    	} else if !exists {
    		c.String(500, fmt.Sprintf("b. get empty pod, %v", err))
    	} else {
    		if v, ok := rawObj.(*v1.Pod); ok {
    			c.JSON(200, v)
    		} else {
    			c.String(500, "b. convert interface to pod failed")
    		}
    	}
    }
    
    • 在初始化gin的时候绑定path和handler即可
    	// 用于提供基本功能的路由组
    	basicGroup := r.Group("/basic")
    
    	// a. 查询指定语言的所有对象的key(演示2. IndexKeys方法)
    	basicGroup.GET("get_obj_keys_by_language_name", basic.GetObjKeysByLanguageName)
    
    	// b. 返回对象的key,返回对应的对象(演示Store.GetByKey方法)
    	basicGroup.GET("get_obj_by_obj_key", basic.GetObjByObjKey)
    

    编码(第二个接口:remote/get_obj_by_obj_key_remote_query)

    • 要使用client-go库,首先要准备好ClientSet对象,这个在前文也准备好了,放在全局变量中随时可以用,来回顾初始化的代码
    var ClientSet *kubernetes.Clientset
    var once sync.Once
    
    func initIndexer() {
    	log.Println("开始初始化Indexer")
    
    	var kubeconfig *string
    
    	// 试图取到当前账号的家目录
    	if home := homedir.HomeDir(); home != "" {
    		// 如果能取到,就把家目录下的.kube/config作为默认配置文件
    		kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
    	} else {
    		// 如果取不到,就没有默认配置文件,必须通过kubeconfig参数来指定
    		kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
    	}
    
    	// 加载配置文件
    	config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
    	if err != nil {
    		panic(err.Error())
    	}
    
    	// 用clientset类来执行后续的查询操作
    	ClientSet, err = kubernetes.NewForConfig(config)
    	if err != nil {
    		panic(err.Error())
    	}
    	...
    
    • 再来看如何用ClientSet向api-server发起请求,这也是熟悉的api,在《client-go实战》系列中屡屡用到
    // GetObjByObjKey 远程请求,根据指定key查询pod对象
    func GetObjByObjKey(c *gin.Context) {
    	rawObj, err := basic.ClientSet.
    		CoreV1().
    		Pods(basic.NAMESPACE).
    		Get(c, c.DefaultQuery("pod_name", ""), metav1.GetOptions{})
    
    	if err != nil {
    		c.String(500, fmt.Sprintf("g. get pod failed, %v", err))
    	} else {
    		c.JSON(200, rawObj)
    	}
    }
    
    • 最后是绑定path和方法
    	remoteGroup := r.Group("/remote")
    	// g. 使用clientset远程查询
    	remoteGroup.GET("get_obj_by_obj_key_remote_query", remote.GetObjByObjKey)
    
    • 请将程序运行起来,稍后压测会用到
    • 至此,用于性能对比测试的两个接口代码都已经写好,接下来开始准备性能测试

    用k6压测第二个接口(远程访问api-server的方式)

    • 这里用到k6作为压测工具,您也可以选择自己熟悉的工具来用,选择k6是因为足够简单省事儿,如果您已经装好了docker,执行压测只要一行命令就行了
    • 首先编写第二个接口的压测脚本,也就是压测client-go远程访问api-server查询对象的性能,新建文件remote.js,内容如下,可见非常简单,就是发请求检查返回码和返回body
    import http from 'k6/http';
    import { check } from 'k6';
    
    export default function () {
      const res = http.get(`http://${__ENV.MY_HOSTNAME}/remote/get_obj_by_obj_key_remote_query?pod_name=${__ENV.POD_NAME}`);
      check(res, {
        'is status 200': (res) => res.status === 200,
        'body size is > 0': (r) => r.body.length > 0,
      });
    }
    
    • 如果您的电脑上部署了docker,那么执行以下命令即可完成压力测试,命令中的参数稍后会详细说明
    docker run 
    --rm 
    -i 
    loadimpact/k6 
    run 
    --duration 60s 
    --vus 10 
    -e MY_HOSTNAME=192.168.50.76:18080 
    -e POD_NAME=nginx-deployment-696cc4bc86-2rqcg 
    - < remote.js
    
    • 这里解释清楚上述命令用到的每个参数,
    docker run     	// docker运行容器
    --rm 				// 等当前控制台结束时删除该容器(相当于一次性任务)
    -i 				// 保持STDIN打开
    loadimpact/k6 		// 镜像名
    run 				// 容器中执行的命令,即启动k6的命令
    --duration 60s 	// k6的参数:压测时长60秒
    --vus 10 			// k6的参数:并发数为10
    -e MY_HOSTNAME=192.168.50.76:18080 				// remote.js脚本中用到的参数,压测服务的IP和端口
    -e POD_NAME=nginx-deployment-696cc4bc86-2rqcg 		// remote.js脚本中用到的参数,pod名称
    - < remote.js										// k6压测脚本名称
    
    • 压测结束,详细数据如下,没有报错,整体QPS为5,虽然我的电脑很烂,但是这么低的QPS还是让人看了直摇头...
         ✓ is status 200
         ✓ body size is > 0
    
         checks.........................: 100.00% ✓ 638      ✗ 0
         data_received..................: 1.5 MB  25 kB/s
         data_sent......................: 53 kB   857 B/s
         http_req_blocked...............: avg=119.67µs min=927ns   med=2.63µs   max=4.2ms    p(90)=4.96µs   p(95)=7.16µs
         http_req_connecting............: avg=115.35µs min=0s      med=0s       max=4.13ms   p(90)=0s       p(95)=0s
         http_req_duration..............: avg=1.9s     min=15.62ms med=1.99s    max=2.11s    p(90)=2.01s    p(95)=2.03s
           { expected_response:true }...: avg=1.9s     min=15.62ms med=1.99s    max=2.11s    p(90)=2.01s    p(95)=2.03s
         http_req_failed................: 0.00%   ✓ 0        ✗ 319
         http_req_receiving.............: avg=244.4µs  min=23.86µs med=115.38µs max=8.72ms   p(90)=343.48µs p(95)=674.31µs
         http_req_sending...............: avg=18.34µs  min=4.59µs  med=12.75µs  max=209.44µs p(90)=25.43µs  p(95)=35.07µs
         http_req_tls_handshaking.......: avg=0s       min=0s      med=0s       max=0s       p(90)=0s       p(95)=0s
         http_req_waiting...............: avg=1.9s     min=15.51ms med=1.99s    max=2.11s    p(90)=2.01s    p(95)=2.03s
         http_reqs......................: 319     5.159646/s
         iteration_duration.............: avg=1.9s     min=19.58ms med=1.99s    max=2.11s    p(90)=2.01s    p(95)=2.03s
         iterations.....................: 319     5.159646/s
         vus............................: 5       min=5      max=10
         vus_max........................: 10      min=10     max=10
    

    用k6压测第一个接口(获取本地缓存的数据)

    • 远程访问api-server的方式,QPS只有5,接下来该测试第一个接口了,看走本地缓存在性能上是否能比远程访问更强,这要是翻车了该咋办,博客都写不下去了...,Indexer成了全村的希望
      在这里插入图片描述
    • 先编写k6脚本,名为indexer.js
    import http from 'k6/http';
    import { check } from 'k6';
    
    export default function () {
      const res = http.get(`http://${__ENV.MY_HOSTNAME}/basic/get_obj_by_obj_key?obj_key=${__ENV.OBJ_KEY}`);
      check(res, {
        'is status 200': (res) => res.status === 200,
        'body size is > 0': (r) => r.body.length > 0,
      });
    }
    
    • 完整的压测命令如下
    docker run 
    --rm 
    -i 
    loadimpact/k6 
    run 
    --duration 60s 
    --vus 10 
    -e MY_HOSTNAME=192.168.50.76:18080 
    -e OBJ_KEY=indexer-tutorials/nginx-deployment-696cc4bc86-2rqcg 
    - < indexer.js
    
    • 压测结束,详细数据如下,没有报错,整体QPS为993,嘿嘿,稳了,没翻车...
         ✓ is status 200
         ✓ body size is > 0
    
         checks.........................: 100.00% ✓ 119236     ✗ 0
         data_received..................: 288 MB  4.8 MB/s
         data_sent......................: 10 MB   168 kB/s
         http_req_blocked...............: avg=4.67µs   min=343ns  med=3.13µs  max=4.81ms   p(90)=5.55µs   p(95)=6.56µs
         http_req_connecting............: avg=633ns    min=0s     med=0s      max=4.4ms    p(90)=0s       p(95)=0s
         http_req_duration..............: avg=9.87ms   min=3.15ms med=9.03ms  max=238.05ms p(90)=13.56ms  p(95)=15.55ms
           { expected_response:true }...: avg=9.87ms   min=3.15ms med=9.03ms  max=238.05ms p(90)=13.56ms  p(95)=15.55ms
         http_req_failed................: 0.00%   ✓ 0          ✗ 59618
         http_req_receiving.............: avg=321.15µs min=8.16µs med=70.93µs max=62.14ms  p(90)=478.85µs p(95)=2.07ms
         http_req_sending...............: avg=19.72µs  min=2.29µs med=14.48µs max=2.82ms   p(90)=25.42µs  p(95)=37.31µs
         http_req_tls_handshaking.......: avg=0s       min=0s     med=0s      max=0s       p(90)=0s       p(95)=0s
         http_req_waiting...............: avg=9.53ms   min=3.07ms med=8.69ms  max=237.72ms p(90)=13.13ms  p(95)=15.04ms
         http_reqs......................: 59618   993.440775/s
         iteration_duration.............: avg=10.05ms  min=3.24ms med=9.21ms  max=238.67ms p(90)=13.75ms  p(95)=15.73ms
         iterations.....................: 59618   993.440775/s
         vus............................: 10      min=10       max=10
         vus_max........................: 10      min=10       max=10
    
    • 至此,压测完成,同样是获取pod的信息,Indexer由于不涉及网络请求,在性能优势上表现的很明显,当然了Indexer也不是万能了,前文编码中,它的局限性也体现出来了
  • 要和api-server保持长连接,以获取数据最新的变化
  • 本地内存中长期存放资源数据,相比之下client-go的一次请求响应就搞定了
  • 灵活性,下图是client-go远程请求api-server的代码,可以按需求随时改变条件,namespace、label-selector等等自由变化,而Indexer则一开始就要把范围确定好,然后只能本地获取这些资源的内容
    在这里插入图片描述
    • 综上所述,两种方式各有优劣,就算混合着用也没问题,一切还是为业务服务吧
    • 至此,性能篇就完成了,接下来就是《client-go的Indexer三部曲》的终篇:源码篇,相信经历了前两篇的实战,您对Indexer已经有了很深入的了解,所以接下来阅读源码也就是件很轻松愉快的事情了

    参考

    • k6官方资料:k6.io/docs

    欢迎关注掘金:程序员欣宸

    学习路上,你不孤单,欣宸原创一路相伴...

    相关文章

    KubeSphere 部署向量数据库 Milvus 实战指南
    探索 Kubernetes 持久化存储之 Longhorn 初窥门径
    征服 Docker 镜像访问限制!KubeSphere v3.4.1 成功部署全攻略
    那些年在 Terraform 上吃到的糖和踩过的坑
    无需 Kubernetes 测试 Kubernetes 网络实现
    Kubernetes v1.31 中的移除和主要变更

    发布评论