왜 기존 Endpoints가 문제인가

쿠버네티스의 Service는 뒤에 매달린 Pod IP 목록을 Endpoints 오브젝트 하나에 모두 담는다. 문제는 이 오브젝트가 단일 객체라는 점이다. Pod가 1만 개 붙은 Service라면 IP 1만 개가 한 오브젝트에 들어가고, Pod 하나가 뜨거나 죽을 때마다 전체 오브젝트가 통째로 다시 쓰인다.

이 변경은 해당 Service를 watch하는 모든 kube-proxy와 컨트롤러에 전량 전송된다. 즉 Pod 1개 변화가 노드 수천 대에 수 MB 트래픽을 유발한다. 롤링 업데이트처럼 변화가 잦은 순간 apiserver와 etcd의 부하가 폭증하고, 규모가 커지면 etcd의 단일 객체 크기 제한(기본 1.5MB)에도 걸린다.

EndpointSlice의 구조

EndpointSlice는 하나의 큰 목록을 여러 개의 작은 조각으로 나눈다. 기본적으로 슬라이스당 최대 100개 엔드포인트를 담고, 초과하면 새 슬라이스가 생성된다. Pod 하나가 바뀌면 해당 슬라이스(최대 100개)만 갱신·전파되므로 변경 전파량이 수백 배 줄어든다.

kubectl get endpointslices -l kubernetes.io/service-name=my-svc

NAME            ADDRESSTYPE   PORTS   ENDPOINTS   AGE
my-svc-abc12    IPv4          8080    100         3d
my-svc-def34    IPv4          8080    100         3d
my-svc-gh567    IPv4          8080    41          3d

각 슬라이스는 addresses, conditions(ready/serving/terminating), nodeName, zone 같은 메타데이터를 엔드포인트별로 담는다. 특히 serving/terminating 조건 덕분에 종료 중인 Pod로 트래픽을 계속 흘려보내는 우아한 종료가 가능해졌다.

Endpoints와의 비교

항목EndpointsEndpointSlice
저장 단위Service당 객체 1개여러 슬라이스(기본 100/개)
변경 전파량전체 목록 재전송변경된 슬라이스만
토폴로지 정보제한적zone/hints 기본 포함
종료 상태 구분ready만serving/terminating 분리

슬라이스 크기 튜닝

슬라이스당 최대 엔드포인트 수는 kube-controller-manager 플래그로 조정한다. 변화가 극히 잦으면 값을 낮춰 전파 단위를 줄이고, 엔드포인트가 안정적이고 많다면 값을 키워 슬라이스 개수를 줄인다(최대 1000).

# kube-controller-manager 실행 인자
--max-endpoints-per-slice=100   # 기본값, 1~1000 범위

# 값이 클수록 슬라이스 수↓, 개별 변경 전파량↑
# 값이 작을수록 변경 전파량↓, watch 대상 객체 수↑

토폴로지 인지 라우팅

대규모 클러스터에서는 존(zone) 간 네트워크 비용과 지연이 실제 부담이 된다. EndpointSlice에는 hints가 붙어 kube-proxy가 같은 존의 엔드포인트로 우선 라우팅하도록 유도할 수 있다.

apiVersion: v1
kind: Service
metadata:
  name: my-svc
  annotations:
    service.kubernetes.io/topology-mode: Auto
spec:
  selector:
    app: my-app
  ports:
    - port: 80
      targetPort: 8080

Auto 모드는 존별 CPU 용량 비율에 따라 힌트를 자동 배분한다. 다만 특정 존에 엔드포인트가 부족하면 안전을 위해 힌트를 제거하고 전체 존으로 분산하므로, 존별 Pod 수가 지나치게 불균형하면 기대만큼 로컬 라우팅이 되지 않는다.

실무 주의점

  • 직접 watch하는 컨트롤러: 자체 컨트롤러가 여전히 Endpoints를 watch한다면 이점이 사라진다. discovery.k8s.io/v1의 EndpointSlice를 informer 대상으로 바꿔야 한다.
  • 미러링 부하: 사용자가 Endpoints를 수동 생성하면 EndpointSlice로 미러링되며, 이 경우엔 슬라이스 분할 이점이 제한된다.
  • 레이블 셀렉터 사용: 슬라이스 이름은 해시 접미사가 붙으므로, 조회 시 이름이 아니라 kubernetes.io/service-name 레이블로 필터링해야 한다.
  • 단일 Service 상한: 한 Service의 엔드포인트가 매우 많으면 슬라이스 수 자체가 늘어난다. 초대형 서비스는 Service를 샤딩하는 설계를 함께 고려한다.

정리하면, EndpointSlice는 "큰 목록을 잘게 쪼개 변경분만 전파한다"는 단순한 원리로 대규모 서비스의 컨트롤 플레인 부하를 근본적으로 줄인다. 슬라이스 크기와 토폴로지 모드만 워크로드 특성에 맞게 조정해도 롤링 업데이트 구간의 apiserver 부하를 눈에 띄게 낮출 수 있다.