k8s 1.34가 8월 말에 나왔다. 사실 나오자마자 리스트를 훑어보긴 했는데, 눈에 딱 들어온 건 spec.trafficDistribution에 추가된 PreferSameZone, PreferSameNode 두 값이었다. 우리 팀은 이미 PreferClose를 몇 달째 쓰고 있어서 zone-aware routing은 익숙한데, node 단위로 라우팅을 유도할 수 있다는 건 생각보다 흥미로운 시나리오였다. DaemonSet으로 뜨는 사이드카성 백엔드가 있으면 same-node에 딱 붙는 게 지연도, cross-AZ 비용도, 캐시 히트도 모두 유리하니까.
근데 "선호(prefer)"라는 단어가 항상 애매하다. 정확히 언제 same-node로 가고, 언제 안 가는지, 왜 kube-proxy가 판단할 수 있는지, 이 판단이 EndpointSlice 어디에 저장되는지. 문서만 봐서는 감이 안 잡혀서 KEP-3015와 코드를 조금 파봤다. 이 글은 그 정리다.
trafficDistribution이라는 힌트, 두 곳에서 해석된다
먼저 오해하기 쉬운 부분부터. spec.trafficDistribution: PreferSameNode는 kube-proxy가 직접 읽는 필드가 아니다. 이 필드는 EndpointSlice controller가 읽는다. 컨트롤러가 이걸 보고 EndpointSlice의 각 endpoint에 hint 필드를 채워 넣는 것이다.
그러니까 실제 데이터 흐름은 이렇다.
Service (trafficDistribution: PreferSameNode)
│
▼
EndpointSlice controller (kube-controller-manager)
│ 각 endpoint에 hints.forNodes / hints.forZones 채움
▼
EndpointSlice 오브젝트
│
▼
kube-proxy / Cilium / 다른 service proxy
│ hints를 보고 로컬 endpoint만 골라냄
▼
실제 트래픽
kube-proxy는 자기 노드 이름을 알고, EndpointSlice의 hints.forNodes에 그 노드 이름이 들어 있는 endpoint만 후보로 뽑는다. 판단은 프록시가 하지만, 판단의 근거는 컨트롤러가 미리 계산해서 넣어 준다는 얘기다. 이 분리 구조가 왜 중요하냐면, 예전 버전의 kube-proxy를 쓰는 노드도 최소한 same-zone 폴백 정도는 자연스럽게 해줄 수 있기 때문이다.
EndpointSlice의 hints 필드가 어떻게 채워지나
PreferSameNode 서비스의 EndpointSlice를 실제로 보면 이런 모양이 된다.
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: my-svc-abcde
endpoints:
- addresses: ["10.244.3.17"]
conditions:
ready: true
nodeName: worker-3
zone: ap-northeast-2a
hints:
forNodes:
- name: worker-3 # 컨트롤러가 채운 값
forZones:
- name: ap-northeast-2a
forNodes는 지금은 원소가 0개 또는 1개다. KEP에서는 "미래를 위해 배열로 뒀다"라고 언급하지만 현재 구현은 endpoint가 실제로 떠 있는 노드 이름 하나만 들어간다. forZones가 같이 채워지는 이유는 위에서 말한 폴백 때문이다. 프록시가 forNodes를 이해하지 못해도 PreferClose처럼 zone 단위 라우팅은 되게 하려고.
컨트롤러 관점에서 정리하면 이렇게 된다.
trafficDistribution: PreferSameNode→ forNodes(그 노드 이름) + forZones(그 존 이름)trafficDistribution: PreferSameZone→ forZones만trafficDistribution: PreferClose→ forZones만 (기존 동작과 동일)trafficDistribution미설정 → hints 없음
여기서 자주 놓치는 게 있다. hints 계산은 endpoint 전체가 "충분히 분포되어 있는지"에 대한 판단을 컨트롤러가 하지 않는다. 예전 topology.kubernetes.io/hints: Auto (Topology Aware Hints, 지금은 Traffic Distribution으로 대체된 그 기능)는 컨트롤러가 존별로 endpoint 수를 세서 "너무 편중되면 hint를 안 넣는" 안전장치가 있었다. PreferSameNode/PreferSameZone은 그 계산을 안 한다. 사용자가 명시적으로 요청한 거니 시키는 대로 hint를 채운다.
이 차이가 실무에선 중요하다. 뒤에서 다시 얘기하겠다.
kube-proxy가 hint를 소비하는 규칙
kube-proxy 쪽 로직은 KEP에 아주 명확하게 정의돼 있다. 요약하면 세 가지 조건이다.
- 서비스의 endpoint slice에서 모든 ready endpoint가
forNodeshint를 가지고 있어야 한다. - 그 hint 중 최소 하나가 로컬 노드 이름을 포함해야 한다.
- 위 두 조건이 만족되면 → 로컬 노드의 endpoint만 라우팅 대상.
조건 1이 왜 "모든"인지가 재밌다. hint가 있는 endpoint와 없는 endpoint가 섞여 있으면 프록시는 "이 서비스는 뭔가 롤아웃 중이거나 컨트롤러가 아직 못 따라잡은 상태"로 간주하고 아예 topology 필터링을 포기한다. cluster-wide 라우팅으로 폴백한다. 데이터 정합성이 깨져 있을 때 이상하게 라우팅하느니 안전하게 전부 뿌리자는 발상이다.
폴백 체인은 다음과 같이 흐른다.
로컬 노드에 healthy endpoint 있음? → 거기로만
│ 없음
▼
로컬 zone에 healthy endpoint 있음? (forZones hint 활용) → 같은 zone 내에서
│ 없음
▼
클러스터 전체 healthy endpoint로 로드밸런싱
여기서 "healthy"는 readiness gate 기준이다. 로컬 pod이 readiness fail 상태면 hint에 여전히 노드 이름이 있어도 ready: false이기 때문에 후보에서 빠지고, 자연스럽게 폴백 체인을 탄다. 그러니까 "same-node이지만 unhealthy로 죽어가는 pod로 트래픽을 보내버리는" 사고는 구조적으로 안 생긴다. 이 점은 안심되는 부분이다.
실제로 언제 same-node로 라우팅되지 않는가
이론적으로 same-node로 갈 것 같은데 실제론 안 가는 케이스가 몇 가지 있다. 우리 팀에서 스테이징에 붙여보면서 정리한 목록.
첫째, endpoint pod이 아직 안 뜬 노드에서 요청이 시작될 때. DaemonSet 롤아웃 중이거나, 새 노드가 방금 올라와서 아직 pod이 안 스케줄된 순간. 이 경우 로컬 endpoint 자체가 없으니 zone 폴백을 탄다. 당연하지만, 이걸 잊고 SLO를 same-node 기준으로 잡으면 롤링 중에 갑자기 P99가 튄다.
둘째, DaemonSet이 아닌 Deployment에 PreferSameNode를 붙였을 때. Deployment는 노드 분산이 스케줄러에 맡겨져 있어서, 100개 노드에 10개 pod이 뜨면 대부분 노드에는 로컬 endpoint가 없다. 그럼 사실상 90%의 트래픽이 zone 폴백을 탄다. PreferSameNode의 진짜 유즈케이스는 DaemonSet 또는 대규모 클러스터에 촘촘히 배치된 pod들이다. 이걸 안 지키면 그냥 PreferClose가 낫다.
셋째, hostNetwork pod이 클라이언트일 때. kube-proxy가 참조하는 "로컬 노드 이름"은 요청이 들어온 소켓의 컨텍스트로 잡히는데, hostNetwork를 쓰면 정상적으로 잡힌다. 다만 nodePort로 외부에서 들어와 CNI를 거쳐 오는 트래픽은 소스가 원래 어느 노드였는지 모르니 same-node의 의미가 흐려진다. 이건 KEP에도 "externalTrafficPolicy와의 상호작용은 별도"라고 언급돼 있다.
PreferSameNode가 만드는 새로운 형태의 SPOF
이건 개인적인 우려이자 실측 결과다. PreferSameNode는 그 노드의 endpoint pod 하나에 트래픽을 몰아준다. 그러면 로컬 pod의 부하 계산이 이상해진다.
기존에는 노드 A의 클라이언트 100개가 서비스로 요청을 보내면 zone 내 pod 여러 개로 나눠졌다. PreferSameNode를 켜면 그게 로컬 노드 A의 pod 1개로 다 간다. 이 pod이 HPA로 늘어나야 할 만큼 부하가 크더라도 HPA는 "서비스 전체 평균 CPU"로 판단하기 때문에 A의 로컬 pod만 hot spot이 될 수 있다. 다른 노드의 pod은 여유가 있고.
이 문제를 완화하려면 HPA metric을 per-pod로 조정하든지, DaemonSet으로 촘촘히 깔든지, 아니면 그냥 PreferClose를 쓰는 게 낫다. 우리 팀은 결국 특정 사이드카 성격의 서비스 두 개에만 PreferSameNode를 적용했다. 캐시 프리로더랑 로그 수집기. 나머지는 다 PreferClose로 남겨뒀다.
정리 대신 남기는 질문
PreferSameNode의 내부는 생각보다 심플하다. 컨트롤러가 hint를 채우고 프록시가 hint를 소비한다. 재밌는 건 이 힌트 시스템이 트래픽 분포를 관측 가능하게 만들어 준다는 점이다. EndpointSlice의 hints 필드를 스크레이핑하면 "지금 이 서비스는 topology-aware routing이 활성화된 상태인가"를 알 수 있다.
우리 팀에서는 이 hints 상태를 대시보드에 띄우는 게 다음 스텝이다. 롤아웃 중에 hints가 잠깐 빠졌다가 다시 채워지는 그 구간이 관측되면, 왜 그 순간 P99가 튄 이유를 설명하기 훨씬 편해진다. 아직 구현 전이고, 스케줄이 어떻게 될지도 모르겠다.
혹시 PreferSameNode를 이미 프로덕션에 쓰고 있는 팀 있으면 SPOF 이슈 어떻게 다루는지 궁금하다. 우리처럼 사이드카성 워크로드에만 쓰는지, 아니면 일반 Deployment에도 붙이는지.
'IT > Kubernets' 카테고리의 다른 글
| Karpenter가 새벽마다 노드를 뽑아버린 이유 (0) | 2026.09.10 |
|---|---|
| kubectl debug의 --profile 옵션, 이거 모르는 분 꽤 많더라 (0) | 2026.09.10 |
| KEDA HTTP add-on으로 워커 아닌 웹서비스를 scale-to-zero 하는 법 (0) | 2026.09.09 |
| Pod가 idle인데도 throttling 되는 이유, 사실 내부적으로는 (0) | 2026.09.08 |
| cert-manager DNS01 챌린지가 두 시간을 넘겨서야 통과했다 (0) | 2026.09.06 |