问题描述
测试集群三台master,每个master上面的kube-apiserver都频繁的重启。登录其中一台master,发现kube-apiserver的内存占用特别高,每次重启完后内存很快就飙到了20G左右,而且还有继续增长的趋势。因为默认kube-apiserver的静态pod是没有设置memeory limit的,最终api-server会吃光机器的所有内存,导致master机器运行异常。
查看容器内存占用的命令:
docker stats --no-stream |grep kube-apiserver
临时方案
出现问题后,没有找到问题根源,所以先修改了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文件。在kubernetes 1.18这个功能是默认打开的,1.18之前需要修改kube-apiserver的启动参数加上--profile true。测试集群kube-apiserver是1.17.3,所以需要修改启动参数,修改过程如下,profiling 改为true
vi /etc/kubernetes/manifests/kube-apiserver.yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
component: kube-apiserver
tier: control-plane
name: kube-apiserver
namespace: kube-system
spec:
containers:
- command:
- kube-apiserver
- --advertise-address=10.250.7.21
- --allow-privileged=true
- --anonymous-auth=True
- --apiserver-count=3
- --authorization-mode=Node,RBAC
- --bind-address=0.0.0.0
- --client-ca-file=/etc/kubernetes/ssl/ca.crt
- --enable-admission-plugins=NodeRestriction
- --enable-aggregator-routing=False
- --enable-bootstrap-token-auth=true
- --endpoint-reconciler-type=lease
- --etcd-cafile=/etc/ssl/etcd/ssl/ca.pem
- --etcd-certfile=/etc/ssl/etcd/ssl/node-t-paas-k8s-0-master-0.pem
- --etcd-keyfile=/etc/ssl/etcd/ssl/node-t-paas-k8s-0-master-0-key.pem
- --etcd-servers=https://10.250.7.21:2379,https://10.250.7.22:2379,https://10.250.7.23:2379
- --feature-gates=CSINodeInfo=true,VolumeSnapshotDataSource=true,ExpandCSIVolumes=true,RotateKubeletClientCertificate=true
- --insecure-port=0
- --kubelet-client-certificate=/etc/kubernetes/ssl/apiserver-kubelet-client.crt
- --kubelet-client-key=/etc/kubernetes/ssl/apiserver-kubelet-client.key
- --kubelet-preferred-address-types=InternalDNS,InternalIP,Hostname,ExternalDNS,ExternalIP
- --profiling=true
修改完保存,kube-apiserver是静态pod修改完yaml文件后,kubelete会自动重启kube-apiserver的容器。pprof工具是go tool里的工具,所以需要下载go语言包到宿主机。为了方便链接(不用指定证书),使用kubectl proxy 在宿主机127.0.0.1:8001启动一个代理。下面是详细操作记录:
wget https://studygolang.com/dl/golang/go1.15.6.linux-amd64.tar.gz
tar xzvf go1.15.6.linux-amd64.tar.gz
#启动proxy
kubectl proxy
Starting to serve on 127.0.0.1:8001
#新开一个terminal,执行如下指令,进入交互界面
./go tool pprof http://127.0.0.1:8001/debug/pprof/heap
#进入交互界面后,输入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页面响应都比之前快了许多。