本文主要描述使用容器服务过程中关于访问控制面组件、优化客户端访问模式相关建议和最佳实践。
作为 Kubernetes 开发者,您的组件可能:
通常情况下通过定期调用 list API 来查询对象状态,但该方法每次必须加载所有对象到 kube-apiserver 内存中进行序列化并传输。这类请求会占用控制面大量 CPU 和内存,使控制面(Control Plane)过载或请求超时,即使定期查询的 Kubernetes 对象状态未发生变化,也会在 kube-apiserver 上造成高负载。
强烈建议避免全量 list,替换为 list-watch 模式,即先通过调用 list API 加载所有对象后,后续使用 watch API 获取对象状态的增量变更。list-watch 模式与全量 list 相比,可有效缩短 kube-apiserver 处理请求的时间、减少请求流量,如果对象状态未发生更改,则不会产生额外的负载。
如果您使用 Golang 语言,请查看 SharedInformer 和 SharedInformerFactory,了解实现 list-watch 模式的 Golang 包。
无论是全量 list 还是 list-watch 模式,建议在 list 请求中设置 ResourceVersion 参数(ResourceVersion=0
),从 kube-apiserver cache 中读取数据,减少 kube-apiserver 与 etcd 数据库的交互次数。
说明
ns := "kube-system" selector := "app.kubernetes.io/name=xxx" labelSelector, err := labels.Parse(selector) //client-go clienset 客户端 pods1, kerr := kubeCli.CoreV1().Pods(ns).List(ctx, metav1.ListOptions{ LabelSelector: selector, ResourceVersion: "0", }) //controller-runtime 客户端 pods2 := &corev1.PodList{} rerr := runtimeCli.List(ctx, pods2, &client.ListOptions{ LabelSelector: labelSelector, Namespace: ns, Raw: &metav1.ListOptions{ ResourceVersion: "0", }, })
Kubernetes 在内部使用 watch API 发送关于对象更新的通知。即便 watch 调用需要的资源比定期 list 调用要少得多,但在大规模集群中的 watch 调用也会占用大量集群资源,影响集群性能。最大的影响来自于创建从多个位置观察频繁更改对象的 watch 调用,例如对运行在多个节点上的组件对应的 Pod 数据进行 watch 调用。集群上安装的第三方代码或扩展程序,可能会在后台创建此类 watch 调用。
我们推荐以下最佳实践:
ctrl.Options.NewCache = cache.BuilderWithOptions(cache.Options{ SelectorsByObject: cache.SelectorsByObject{ &corev1.Pod{}: { Field: fields.SelectorFromSet(fields.Set{"spec.nodeName": "node01"}), } } } )
lw := cache.NewListWatchFromClient(c.CoreV1().RESTClient(), "pods", metav1.NamespaceAll, fields.OneTermEqualSelector("spec.nodeName", string(nodeName))) informer := cache.NewSharedInformer(lw, &apiv1.Pod{}, resyncPeriod)
在 Kubernetes 1.22 之前版本,如果您在创建 Pod 时未指定 ServiceAccount(服务账号),Kubernetes 会自动执行以下操作:
在大规模集群中,上述操作意味着数千个不必要的 watch 调用,可能会在 kube-apiserver 上产生大量负载。
因此如果 Pod 中运行的逻辑不需要访问 Kubernetes API,建议您停用 ServiceAccount 的 API 访问凭证的自动挂载,以避免创建相关的 Secret 和 watch 调用。详细说明,请参见 Opt out of API credential automounting。
Kubernetes API 序列化协议建议使用 Protobuf(Protocol Buffers),相比于 JSON 更节省内存和传输流量。更多信息,请参见 Alternate representations of resources。
无论您使用 controller-runtime 库还是直接使用 client-go 库,都可以在 KubeConfig 中设置编码格式,代码样例如下:
kubeConfig.ContentType = "application/vnd.kubernetes.protobuf" kubeConfig.AcceptContentTypes = "application/vnd.kubernetes.protobuf,application/json" //controller-runtime mgr, err := ctrl.NewManager(kubeConfig, ctrl.Options{}) //client-go kubeClient, err := clientset.NewForConfig(kubeconfig)