本文发布于Cylon的收藏册,转载请著名原文链接~


Endpoint

Endpoints 就是 service 中后端的server,通常来说 endpoint 与 service是关联的,例如下面的一个endpoints 资源。

apiVersion: v1
kind: Endpoints
metadata:
  name: nginx
subsets:
  - addresses:
      - ip: 172.17.0.2
      - ip: 172.17.0.3
    ports:
      - port: 80
        name: "111" # 多个端口需要用name
      - port: 88
        name: "222"

而 Endpoints 资源是由控制平面的 Endpoints controller 进行管理的,主要用于将外部server引入至集群内时使用的,例如Kube-apiserver 在集群外的地址,以及external service所需要创建的。

我们看到 Endpoints controller 代码中,在对 该 informer 监听的包含 service 与 Pod,位于 NewEndpointController()

// NewEndpointController returns a new *EndpointController.
func NewEndpointController(podInformer coreinformers.PodInformer, serviceInformer coreinformers.ServiceInformer,
	endpointsInformer coreinformers.EndpointsInformer, client clientset.Interface, endpointUpdatesBatchPeriod time.Duration) *EndpointController {
	broadcaster := record.NewBroadcaster()
	broadcaster.StartStructuredLogging(0)
	broadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: client.CoreV1().Events("")})
	recorder := broadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "endpoint-controller"})

	if client != nil && client.CoreV1().RESTClient().GetRateLimiter() != nil {
		ratelimiter.RegisterMetricAndTrackRateLimiterUsage("endpoint_controller", client.CoreV1().RESTClient().GetRateLimiter())
	}
	e := &EndpointController{
		client:           client,
		queue:            workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "endpoint"),
		workerLoopPeriod: time.Second,
	}

	serviceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc: e.onServiceUpdate,
		UpdateFunc: func(old, cur interface{}) {
			e.onServiceUpdate(cur)
		},
		DeleteFunc: e.onServiceDelete,
	})
	e.serviceLister = serviceInformer.Lister()
	e.servicesSynced = serviceInformer.Informer().HasSynced

	podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc:    e.addPod,
		UpdateFunc: e.updatePod,
		DeleteFunc: e.deletePod,
	})
	e.podLister = podInformer.Lister()
	e.podsSynced = podInformer.Informer().HasSynced

	endpointsInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		DeleteFunc: e.onEndpointsDelete,
	})
	e.endpointsLister = endpointsInformer.Lister()
	e.endpointsSynced = endpointsInformer.Informer().HasSynced

    ....
    
}

EndpointSlices [1]

EndpointSlices 是提供为集群内用于替换 Endpoints 资源的一种灵活并具有扩展性的一种资源,由控制平面的 EndpointSlices Controller 来创建和管理的,默认情况下 EndpointSlices Controller 创建和管理的EndpointSlices 资源将不大于100个Endpoints;可以通过 kube-controller-manager 的参数 --max-endpoints-per-slice 设置,该参数最大为1000 [2]

通常情况下无需自行创建该资源,因为在创建 service 资源时 通常是通过 label 来匹配到对应的 backend server

下面是一个完整的 EndpointSlices 资源清单

addressType: IPv4
apiVersion: discovery.k8s.io/v1beta1 #注意版本 1.21后是 v1
endpoints:
- addresses:
  - 192.168.1.241
  conditions:
    ready: true
  targetRef:
    kind: Pod
    name: netbox-ff6dd9445-kxr4s
    namespace: default
    resourceVersion: "1994535"
  topology:
    kubernetes.io/hostname: master-machine
- addresses:
  - 192.168.1.242
  conditions:
    ready: true
  targetRef:
    kind: Pod
    name: netbox-ff6dd9445-566tj
    namespace: default
  topology:
    kubernetes.io/hostname: master-machine
kind: EndpointSlice
metadata:
  annotations:
    endpoints.kubernetes.io/last-change-trigger-time: "2023-02-24T22:40:20+08:00"
  labels:
    endpointslice.kubernetes.io/managed-by: endpointslice-controller.k8s.io
    kubernetes.io/service-name: netbox
  name: netbox-l489z
  namespace: default
ports:
- name: ""
  port: 80
  protocol: TCP

在代码中 EndpointSlices 资源是这么呈现的,可以看到主要的就是包含一组 Endpoints 资源

type EndpointSlice struct {
	metav1.TypeMeta
	metav1.ObjectMeta
	AddressType AddressType
	Endpoints []Endpoint
	Ports []EndpointPort
}

EndpointSlices 在 kube-proxy中的应用

Google 工程师 Rob Scott 在2020年一文 [3] 中提到了 EndpointSlices 的作用,从kubernetes 1.19 开始EndpointSlices 默认被开启,而开启后的kube-proxy将使用 EndpointSlices 读取集群内的service的 Endpoints,而这个最大的变化就是『拓扑感知路由』(Topology Aware Routing)

Rob Scott 在文中提到 EndpointSlice API 是为了提升 Endpoints API 的限制,例如,etcd的存储大小,以及pod规模变动时最大产生的超过22TB数据的问题

而这些问题可以通过文中变化图来说明,开启功能后会将所有匹配到的 Endpoint,划分为多个EndpointSlices,而在大规模集群环境场景下,每次的变更只需要修改其中一个 EndpointSlices 即可,这将带给 kube-proxy 提供超Endpoint模式 10倍的性能

端点切片

图:Kubernetes EndpointSlices
Source:https://kubernetes.io/blog/2020/09/02/scaling-kubernetes-networking-with-endpointslices

Notes:该文中没有提到的一点是:”EndpointSlices资源解决的是集群内的service节点问题,如你使用了endpoint类资源,那么不会触发到EndpointSlices的资源,这部分在 kube-proxy 源码中可以很清晰的看到

下面的 kube-proxy 日志可以看到获取 server是通过 Endpoints 还是 EndpointSlices

endpointslicecache.go:322] Setting endpoints for "default/netbox" to [192.168.1.241:80 192.168.1.242:80]
10008 proxier.go:1057] Syncing ipvs Proxier rules
10008 iptables.go:343] running iptables-save [-t filter]
10008 iptables.go:343] running iptables-save [-t nat]
10008 ipset.go:173] Successfully add entry: 192.168.1.241,tcp:80,192.168.1.241 to ip set: KUBE-LOOP-BACK

总结

  • Endpoints 与 EndpointSlices 均是为service提供端点的
  • Service规模越大,那么Endpoints中的 Pod 数量越大,传输的 EndPoints 对象就越大。集群中 Pod 更改的频率越高,也意味着传输在网络中发生的频率就越高
  • Endpoints 对象在大规模集群场景下存在下列问题:
    • 增加网络流量
    • 超大规模的 service 理论上会无法存储 该 Endpoints
    • 处理Endpoints资源的 worker 会消耗更多的计算资源
    • 隐性增加对控制平面的影响,service的可扩展性将降低
  • Endpointslices 解决了:
    • 部分更新,更少的网络流量
    • Worker 处理 Endpoints 更新所需的资源更少
    • 减少对控制平面的影响,提升的性能和 service 规模

Reference

[1] EndpointSlices

[2] EndpointSlice API

[3] Scaling Kubernetes Networking With EndpointSlices

[4] Scalability Limitations of the Endpoints API

本文发布于Cylon的收藏册,转载请著名原文链接~

链接:https://www.oomkill.com/2023/02/ch18-endpointslices/

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」 许可协议进行许可。