kube-apiserver内存溢出问题调查及go tool pprof工具的使用

发布时间 2023-07-26 16:29:51作者: fengjian1585

问题描述

测试集群三台master,每个master上面的kube-apiserver都频繁的重启。登录其中一台master,发现kube-apiserver的内存占用特别高,每次重启完后内存很快就飙到了20G左右,而且还有继续增长的趋势。因为默认kube-apiserver的静态pod是没有设置memeory limit的,最终api-server会吃光机器的所有内存,导致master机器运行异常。

查看容器内存占用的命令:

docker stats --no-stream |grep kube-apiserver

crio 使用 crictl
container_id=`crictl ps -a | grep etcd`
crictl  stats $container_id

临时方案

出现问题后,没有找到问题根源,所以先修改了kube-apiserver的静态pod yaml文件,位于/etc/kubernetes/manifests/kube-apiserver.yaml,添加resources.limits.memory 为32g(本机内存为64g)。这样现在kube-apiserver的内存,使它消耗内存到32g的时候就oom kill自动重启, 大内存方案可行,如果内存较小,很快就会重启。

问题分析

api-server内存持续升高,肯定是有资源在堆内存里申请了空间但是没有释放。首先考虑是否是因为建立了太多的链接导致的,使用如下指令查询kube-apiserver链接数:

netstat -nat | grep -i "6443" | wc -l

发现链接数在100多并不算多。

继续分析只能考虑导出kube-apiserver的heap文件来查看其详细的内存分布。这种方式需要使用go语言包的pprof工具,下面详细讲解go tool pprof 工具的使用以及kube-apiserver的配置。

 

go tool pprof 工具使用

kube-apiserver集成了pprof工具,可以通过链接/debug/prof/heap的url来获得heap文件。

pprof是什么

pprof 是 go 语言自带的基础库,可以分析程序的运行情况,并且提供可视化的功能。它包含两个相关的库:runtime/pprof 和 net/http/pprof , net/http/pprof 可以提供一个 HTTP 接口,获得性能数据,它的底层使用的 runtime/pprof

pprof如何使用

一般如果我们使用 net/http/pprof 并开启了端口监听,如8001端口,我们就可以通过浏览器访问该服务pprof的页面,例如 http://127.0.0.1:8080/debug/pprof/ 

我们常用的参数为

allocs 所有过去内存分配的采样
goroutine 所有当前goroutine的堆栈跟踪
heap 对活动对象的内存分配进行采样
profile cpu profile采样

刚才分析的是 heap 内存情况,同样可以根据需要分析前面说的所有采样。

#所有过去内存分配的采样
go tool pprof http://127.0.0.1:8001/debug/pprof/allocs
#对活动对象的内存分配进行采样(活动)
go tool pprof http://127.0.0.1:8001/debug/pprof/heap
# 下载 cpu profile,默认从当前开始收集 30s 的 cpu 使用情况,需要等待 30s
go tool pprof http://127.0.0.1:8001/debug/pprof/profile
# wait 120s
go tool pprof http://127.0.0.1:8001/debug/pprof/profile?seconds=120    
#导致同步原语阻塞的堆栈跟踪
go tool pprof http://127.0.0.1:8001/debug/pprof/block
#所有当前goroutine的堆栈跟踪
go tool pprof http://127.0.0.1:8001/debug/pprof/goroutine
#争用互斥锁持有者的堆栈跟踪
go tool pprof http://127.0.0.1:8001/debug/pprof/mutex
#当前程序的执行轨迹。

 

 

使用 pprof 分析 kubelet 性能

想要获取 kubelet 的 pprof 数据,首先需要使用kubectl proxy命令启动API server代理

# kubectl proxy --address='0.0.0.0'  --accept-hosts='^*$'
Starting to serve on [::]:8001

 

#新开一个terminal,执行如下指令,进入交互界面

#输入
./go tool pprof http://127.0.0.1:8001/debug/pprof/heap


# 输入top 20
#进入交互界面后,输入top 20查看内存使用前20的函数调用
Fetching profile over HTTP from http://127.0.0.1:8001/debug/pprof/heap
Saved profile in /root/pprof/pprof.kube-apiserver.alloc_objects.alloc_space.inuse_objects.inuse_space.004.pb.gz
File: kube-apiserver
Type: inuse_space
Time: Dec 10, 2020 at 2:46pm (CST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)top 20


#得到如下输出
Showing nodes accounting for 11123.22MB, 95.76% of 11616.18MB total
Dropped 890 nodes (cum <= 58.08MB)
Showing top 20 nodes out of 113
     flat  flat%   sum%        cum   cum%
9226.15MB 79.43% 79.43%  9226.15MB 79.43%  bytes.makeSlice
1122.61MB  9.66% 89.09%  1243.65MB 10.71%  k8s.io/kubernetes/vendor/k8s.io/api/core/v1.(*ConfigMap).Unmarshal
 139.55MB  1.20% 90.29%   153.05MB  1.32%  k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/apis/meta/v1.(*ObjectMeta).Unmarshal
 120.53MB  1.04% 91.33%   165.03MB  1.42%  encoding/json.(*decodeState).objectInterface
 117.29MB  1.01% 92.34%   117.29MB  1.01%  reflect.unsafe_NewArray
 108.03MB  0.93% 93.27%   108.03MB  0.93%  reflect.mapassign
  66.51MB  0.57% 93.84%    66.51MB  0.57%  k8s.io/kubernetes/vendor/github.com/json-iterator/go.(*Iterator).ReadString
  62.51MB  0.54% 94.38%    62.51MB  0.54%  k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/registry/generic.ObjectMetaFieldsSet
  61.51MB  0.53% 94.91%    61.51MB  0.53%  k8s.io/kubernetes/vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource.objectMetaFieldsSet
  43.01MB  0.37% 95.28%   137.03MB  1.18%  k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/runtime.structToUnstructured
     18MB  0.15% 95.43%   183.61MB  1.58%  k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/storage/cacher.(*watchCache).Replace
  13.50MB  0.12% 95.55%   137.03MB  1.18%  k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/runtime.toUnstructured
      9MB 0.077% 95.63%   237.54MB  2.04%  k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured.unstructuredJSONScheme.decodeToUnstructured
   7.50MB 0.065% 95.69%   144.53MB  1.24%  k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/runtime.(*unstructuredConverter).ToUnstructured
      6MB 0.052% 95.74%   123.29MB  1.06%  reflect.MakeSlice
   1.50MB 0.013% 95.76%  8944.02MB 77.00%  encoding/json.mapEncoder.encode

 

通过heap文件输出,可以看到占用内存最多的是makeSlice函数占用了9G多的内存。它的调用者是v1.(*ConfigMap).Unmarshal,configmap是kubernetes的一种资源,Unmarshal是json和struct做转换的函数。所以怀疑与configmap资源有关。

在宿主机上使用

kubectl get configmap -A 发现命令卡死没有输出,但是在特定的某一个namespace下进行kubectl get configmap是有返回的。所以怀疑其中的某一个namespace下有大量的configmap,kubectl get configma卡死从而导致kubectl get configmap -A卡死。

下面就尝试在每一个namespace下执行kubectl get configmap。当执行到kubectl get configmap -n kubesphere-controls-system,时发现命令卡住,在等待了3分钟后有数据返回。使用wc统计了数量发现kubesphere-controls-system下有17万多的configmap,大多是kubeconfig-xxxx样子的。后面的xxx是用户名。使用kubectl get user同样发现了有17万多的用户。应该是之前同步的ldap的用户,每创建一个用户,就会在kubesphere-controls-system下新建一个configmaps。查看etcd db文件,发现达到了1.5g左右,其他正常的集群db文件一般在50M甚至更低。

分析到这里,问题基本明朗了。可能是kubesphere 2.x版本时同步了ldap的数据到kubesphere中,kubesphere每创建一个user就会新建个对应的configmap里面存着key和cert。17万configmap,导致api-server去list configmap时无法一次获取到,就会一直创建slice(这里应该是golang语言包里的一个bug https://studygolang.com/artic...,从而使api-server耗尽内存

解决方案

最终的解决方案就是删除这些ldap用户,kubesphere升级到3.0后每一次登录都直接到ldap验证,且是在host集群上执行的。被纳管集群不需要存贮这些数据。在使用kubectl delete user发现用户无法删除,因为etcd数据量太大了,kube-apiserver与etcd之间的调用性能下降很严重。kube-apiserver已经无法正常的提供服务了。所以考虑使用etcdctl直接链接etcd来删除数据

#删除 user
ETCDCTL_API=3 etcdctl --endpoints https://10.250.7.21:2379 --cacert $ETCD_TRUSTED_CA_FILE --cert $ETCD_CERT_FILE --key $ETCD_KEY_FILE del /registry/iam.kubesphere.io/users/ --prefix
#删除对应的 configmap
ETCDCTL_API=3 etcdctl --endpoints https://10.250.7.21:2379 --cacert $ETCD_TRUSTED_CA_FILE --cert $ETCD_CERT_FILE --key $ETCD_KEY_FILE del /registry/configmaps/kubesphere-controls-system/kubeconfig- --prefix

 

删除完数据后,使用docker restart 重启kube-apiserver,观测了一会,发现内存一直保持在1g左右。kubectl操作以及sit集群的web页面响应都比之前快了许多。

 

参考:

https://segmentfault.com/a/1190000039649589
https://blog.csdn.net/buppt/article/details/127505818