엔드포인트슬라이스
엔드포인트슬라이스 (EndpointSlices)
EndpointSlice API는 Kubernetes가 Service가 많은 수의 백엔드를 처리하도록 확장하게 해 주는 메커니즘이에요. 또 클러스터가 정상 백엔드 목록을 효율적으로 갱신할 수 있게 해 주죠.
FEATURE STATE: Kubernetes v1.21 [stable]
EndpointSlice는 백엔드 엔드포인트의 IP 주소를 추적해요. EndpointSlice는 보통 Service와 연결되고, 백엔드 엔드포인트는 일반적으로 Pod를 나타내요.
EndpointSlice API
Kubernetes에서 EndpointSlice는 네트워크 엔드포인트 집합에 대한 참조를 담아요. 컨트롤 플레인은 셀렉터가 지정된 Kubernetes Service에 대해 자동으로 EndpointSlice를 만들어요. 이 EndpointSlice들은 Service 셀렉터와 매칭되는 모든 Pod에 대한 참조를 포함해요. EndpointSlice는 IP 패밀리, 프로토콜, 포트 번호, Service 이름의 고유한 조합으로 네트워크 엔드포인트를 묶어요. EndpointSlice 객체의 이름은 유효한 DNS 서브도메인 이름이어야 해요.
예를 들어, example Kubernetes Service가 소유한 샘플 EndpointSlice 객체를 볼게요.
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: example-abc
labels:
kubernetes.io/service-name: example
addressType: IPv4
ports:
- name: http
protocol: TCP
port: 80
endpoints:
- addresses:
- "10.1.2.3"
conditions:
ready: true
hostname: pod-1
nodeName: node-1
zone: us-west2-a
기본적으로 컨트롤 플레인은 각 EndpointSlice가 100개 이하의 엔드포인트를 갖도록 만들고 관리해요. 이 값은 kube-controller-manager의 --max-endpoints-per-slice 플래그로 설정할 수 있고, 최대 1000개까지 가능해요.
EndpointSlice는 내부 트래픽을 어떻게 라우팅할지에 대해 kube-proxy의 진실의 원천(source of truth) 역할을 해요.
주소 유형
EndpointSlice는 두 가지 주소 유형을 지원해요.
- IPv4
- IPv6
각 EndpointSlice 객체는 특정 IP 주소 유형 하나를 나타내요. IPv4와 IPv6 양쪽으로 접근 가능한 Service가 있다면, EndpointSlice 객체가 최소 두 개(Ipv4용 하나, IPv6용 하나) 생겨요.
조건 (Conditions)
EndpointSlice API는 소비자에게 유용할 수 있는 엔드포인트 조건을 저장해요. 세 가지 조건은 serving, terminating, ready예요.
Serving
FEATURE STATE: Kubernetes v1.26 [stable]
serving 조건은 엔드포인트가 현재 응답을 서비스 중이라서 Service 트래픽의 대상으로 사용해야 한다는 걸 나타내요. Pod가 백엔드인 엔드포인트의 경우, 이 조건은 Pod의 Ready 조건에 대응돼요.
Terminating
FEATURE STATE: Kubernetes v1.26 [stable]
terminating 조건은 엔드포인트가 종료 중이라는 걸 나타내요. Pod가 백엔드인 엔드포인트의 경우, 이 조건은 Pod가 처음 삭제될 때(즉 삭제 타임스탬프를 받았지만 대개 Pod의 컨테이너가 종료되기 전에) 설정돼요.
Service 프록시는 보통 terminating인 엔드포인트를 무시하지만, 사용 가능한 모든 엔드포인트가 terminating이라면 serving이면서 terminating인 엔드포인트로 트래픽을 보낼 수 있어요. (이렇게 해서 기본 Pod들의 롤링 업데이트 중에도 Service 트래픽이 유실되지 않도록 보장하는 거예요.)
Ready
ready 조건은 기본적으로 "serving이면서 terminating이 아님"을 확인하는 단축 표기예요. 다만 spec.publishNotReadyAddresses가 true로 설정된 Service에서는 항상 true가 돼요.
토폴로지 정보
EndpointSlice 안의 각 엔드포인트는 관련 토폴로지 정보를 담을 수 있어요. 토폴로지 정보에는 엔드포인트의 위치와 해당 Node 및 zone에 대한 정보가 포함돼요. 이 정보들은 EndpointSlice의 다음과 같은 엔드포인트별 필드에서 확인할 수 있어요.
nodeName- 이 엔드포인트가 있는 Node의 이름zone- 이 엔드포인트가 있는 zone
관리
대부분의 경우 컨트롤 플레인(특히 엔드포인트 슬라이스 컨트롤러)이 EndpointSlice 객체를 만들고 관리해요. 서비스 메시 구현 같은 다양한 사용 사례가 있어서, 다른 엔티티나 컨트롤러가 추가 EndpointSlice 집합을 관리하는 경우도 있어요.
여러 엔티티가 서로 간섭하지 않고 EndpointSlice를 관리할 수 있도록, Kubernetes는 레이블 endpointslice.kubernetes.io/managed-by를 정의해요. 이 레이블은 EndpointSlice를 관리하는 엔티티를 나타내요. 엔드포인트 슬라이스 컨트롤러는 자신이 관리하는 모든 EndpointSlice에 endpointslice-controller.k8s.io 값을 이 레이블에 설정해요. EndpointSlice를 관리하는 다른 엔티티도 이 레이블에 고유한 값을 설정해야 해요.
소유권
대부분의 사용 사례에서 EndpointSlice는 엔드포인트 슬라이스 객체가 추적하는 엔드포인트에 대한 Service가 소유해요. 이 소유권은 각 EndpointSlice의 owner reference와 kubernetes.io/service-name 레이블로 표시돼요. 이 레이블 덕분에 Service에 속한 모든 EndpointSlice를 쉽게 찾아낼 수 있어요.
EndpointSlice 분포
각 EndpointSlice는 리소스 안의 모든 엔드포인트에 적용되는 포트 집합을 가져요. Service에 이름이 붙은 포트(named port)를 쓰면, 같은 이름 포트에 대해 Pod들이 서로 다른 target port 번호를 가질 수 있어서 서로 다른 EndpointSlice가 필요해져요.
컨트롤 플레인은 EndpointSlice를 가능한 한 가득 채우려고 하지만, 적극적으로 재균형을 맞추지는 않아요. 로직은 꽤 단순해요.
- 기존 EndpointSlice를 순회하면서 더 이상 필요하지 않은 엔드포인트를 제거하고, 변경된 매칭 엔드포인트를 갱신해요.
- 첫 단계에서 수정된 EndpointSlice를 순회하면서 필요한 새 엔드포인트를 채워 넣어요.
- 아직 추가할 새 엔드포인트가 남아 있으면, 이전에 변경되지 않은 슬라이스에 넣거나 새 슬라이스를 만들어요.
여기서 중요한 건 세 번째 단계가 완벽하게 가득 찬 분포보다 EndpointSlice 갱신 횟수 제한을 우선한다는 점이에요. 예를 들어 추가할 새 엔드포인트 10개가 있고 각각 5개씩 더 담을 수 있는 EndpointSlice 2개가 있다면, 이 접근 방식은 기존 2개를 채우는 대신 새 EndpointSlice를 만들어요. 바꿔 말하면, EndpointSlice 여러 번 갱신하는 것보다 단일 EndpointSlice 생성이 더 선호된다는 뜻이에요.
각 Node에서 실행되는 kube-proxy는 EndpointSlice를 감시하기 때문에, EndpointSlice의 모든 변경은 클러스터의 모든 Node로 전송되므로 상대적으로 비싸요. 이 접근 방식은 슬라이스가 가득 차지 않아 여러 EndpointSlice가 생기더라도, 모든 Node로 보내야 하는 변경 횟수를 줄이려는 의도예요.
실무에서는 이렇게 이상적이지 않은 분포가 드물어요. EndpointSlice 컨트롤러가 처리하는 대부분의 변경은 기존 EndpointSlice에 들어갈 만큼 작고, 그렇지 않다면 어차피 새 EndpointSlice가 곧 필요해질 가능성이 높아요. Deployment의 롤링 업데이트도 모든 Pod와 해당 엔드포인트가 교체되면서 EndpointSlice를 자연스럽게 다시 포장해 줘요.
중복 엔드포인트
EndpointSlice 변경의 특성상, 엔드포인트가 동시에 둘 이상의 EndpointSlice에 나타날 수 있어요. 서로 다른 EndpointSlice 객체에 대한 변경이 Kubernetes 클라이언트 watch/cache에 서로 다른 시점에 도달할 수 있기 때문에 자연스럽게 발생해요.
참고:
EndpointSlice API의 클라이언트는 Service와 연결된 모든 기존 EndpointSlice를 순회하면서 고유한 네트워크 엔드포인트의 완전한 목록을 만들어야 해요. 서로 다른 EndpointSlice에서 엔드포인트가 중복될 수 있다는 점을 기억하는 게 중요해요.
이 엔드포인트 집계와 중복 제거를 수행하는 참조 구현을 kube-proxy의 EndpointSliceCache 코드에서 찾아볼 수 있어요.
EndpointSlice 미러링
FEATURE STATE: Kubernetes v1.33 [deprecated]
EndpointSlice API는 더 오래된 Endpoints API를 대체하는 거예요. kube-proxy가 Endpoints 리소스에 기반해 트래픽을 라우팅하리라고 기대하는 오래된 컨트롤러와 사용자 워크로드와의 호환성을 유지하기 위해, 클러스터의 컨트롤 플레인은 사용자가 만든 대부분의 Endpoints 리소스를 해당 EndpointSlice로 미러링해요.
(다만 이 기능도 나머지 Endpoints API와 마찬가지로 deprecated예요. 셀렉터 없는 Service에 엔드포인트를 수동으로 지정하는 사용자는 Endpoints 리소스를 만들고 미러링되도록 두는 대신, EndpointSlice 리소스를 직접 만들어야 해요.)
컨트롤 플레인은 다음과 같은 경우가 아니면 Endpoints 리소스를 미러링해요.
- Endpoints 리소스에
true로 설정된endpointslice.kubernetes.io/skip-mirror레이블이 있는 경우 - Endpoints 리소스에
control-plane.alpha.kubernetes.io/leader어노테이션이 있는 경우 - 해당 Service 리소스가 존재하지 않는 경우
- 해당 Service 리소스에 nil이 아닌 셀렉터가 있는 경우
개별 Endpoints 리소스가 여러 EndpointSlice로 변환될 수도 있어요. 이는 Endpoints 리소스에 여러 서브셋이 있거나 IPv4와 IPv6 같은 여러 IP 패밀리의 엔드포인트가 포함된 경우 발생해요. 서브셋당 최대 1000개의 주소가 EndpointSlice로 미러링돼요.
더 알아보기
- Connecting Applications with Services 튜토리얼을 따라 해 보세요.
- EndpointSlice API의 API 참조를 읽어 보세요.
- Endpoints API의 API 참조를 읽어 보세요.