EKS NLB 트래픽 경로: instance·IP target과 conntrack 진단

반응형

EKS에서 Network Load Balancer 뒤의 Pod로 traffic이 들어올 때 timeout이나 일부 connection 실패가 생기면 흔히 “conntrack 문제” 또는 “NAT 문제”라고 부른다. 두 mechanism이 실제 경로에 참여할 수는 있지만, 증상만으로 원인을 먼저 정하면 조사 범위가 오히려 흐려진다.

NLB의 target type, Kubernetes Service 설정, externalTrafficPolicy, kube-proxy mode, CNI와 cross-node forwarding 여부에 따라 packet path와 주소 변환 지점이 달라진다.

먼저 해야 할 일은 “EKS traffic은 NAT를 두 번 탄다” 같은 고정된 공식을 적용하는 것이 아니라, 현재 cluster의 실제 경로를 그리는 것이다.

이 글의 범위

여기서는 AWS Load Balancer Controller가 관리하는 NLB와 Kubernetes Service를 다룬다. ALB는 request routing과 target binding 방식이 다르므로 같은 packet path라고 가정하지 않는다.

또한 command와 annotation은 작성 시점의 공식 문서를 기준으로 한다. controller, Kubernetes, CNI version에 따라 지원 범위와 default가 달라질 수 있으므로 실제 환경의 version과 manifest를 함께 확인해야 한다.

먼저 NLB target type을 확인한다

AWS Load Balancer Controller에서 NLB target type은 크게 instanceip로 나뉜다.

instance target

instance mode에서는 target group에 worker node가 instance target으로 등록되고 NLB가 Service의 NodePort로 보낸다.

client
  ↓
NLB
  ↓ target: node IP + NodePort
node의 Service datapath
  ↓ endpoint 선택·필요한 변환
Pod

선택된 Pod가 같은 node에 있을 수도, 다른 node에 있을 수도 있다. 실제 forwarding과 source address 처리는 externalTrafficPolicy, kube-proxy mode와 CNI 구현에 좌우된다.

IP target

IP mode에서는 target group에 Pod IP가 등록되고 NLB가 Pod로 직접 보내는 형태다.

client
  ↓
NLB
  ↓ target: Pod IP + targetPort
Pod

AWS EKS 문서는 IP target이 load balancer traffic에서 worker node의 추가 hop과 NodePort 처리를 피할 수 있다고 설명한다. Fargate Pod에는 instance target을 사용할 수 없으므로 IP target이 필요하다.

그렇다고 packet이 어떤 network state도 거치지 않거나 NAT가 항상 사라진다고 단정할 수는 없다. VPC CNI mode, security group for Pods, network policy와 host datapath가 실제 동작에 관여할 수 있다.

현재 권장 annotation 형태는 다음과 같다.

apiVersion: v1
kind: Service
metadata:
  name: api
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: "external"
    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip"
spec:
  type: LoadBalancer
  selector:
    app: api
  ports:
    - port: 443
      targetPort: 8443

instance target이라면 target type을 instance로 둔다. 예전 aws-load-balancer-type: nlb-ip 표기도 호환 목적으로 남아 있지만 AWS 문서는 새 배포에 external과 별도 target-type annotation을 사용하는 방식을 안내한다.

annotation을 live Service에서 무심코 바꾸는 것은 안전한 migration 방법이 아닐 수 있다. controller 문서는 load balancer type annotation을 Service 생성 뒤 수정하지 말고 새 Service로 옮기는 방식을 권고한다.

externalTrafficPolicy가 경로를 바꾼다

spec.externalTrafficPolicy는 외부 traffic을 node에서 어떻게 처리할지에 영향을 준다.

Cluster

Cluster는 cluster의 어느 ready endpoint로도 forwarding할 수 있다. 선택한 endpoint가 다른 node에 있으면 cross-node hop이 생길 수 있다. 전통적인 kube-proxy 경로에서는 이 과정에서 source NAT가 적용되어 Pod가 원래 client IP 대신 node IP를 볼 수 있다.

NLB → node A → node B의 Pod
              ↑ cross-node 가능

장점은 endpoint를 cluster 전체에서 활용할 수 있다는 점이다. 단점은 추가 hop과 source IP 보존 경계가 생길 수 있다는 점이다.

Local

Local은 외부 traffic을 받은 node의 local endpoint만 사용한다. 불필요한 cross-node hop을 피하고 원래 source IP를 보존하는 데 도움이 된다.

NLB → node A → node A의 Pod

그러나 local ready endpoint가 없는 node는 traffic을 처리할 수 없다. NLB health check와 target registration, Pod distribution이 맞물려야 한다. source IP 보존 하나만 보고 바꾸기 전에 node별 endpoint 수와 rollout behavior를 확인해야 한다.

IP target에서는 NLB가 Pod IP를 target으로 등록하므로 instance target의 externalTrafficPolicy 경로와 동일하게 해석하면 안 된다. 먼저 실제 target group의 target type과 health 상태를 확인한다.

conntrack은 연결 상태를 기억한다

Linux netfilter conntrack은 flow의 상태와 NAT translation에 필요한 정보를 추적한다. 일반적인 TCP flow는 source/destination address와 port, protocol을 바탕으로 entry가 만들어지고 reply direction을 연결한다.

NAT를 사용하면 처음 packet에서 translation rule을 결정하고 뒤의 packet과 reply에 일관된 변환을 적용하는 데 conntrack state가 쓰일 수 있다.

original direction tuple
        ↕ conntrack state
reply direction tuple

하지만 모든 CNI와 service implementation이 같은 Linux netfilter path와 table을 쓰는 것은 아니다. eBPF datapath는 별도의 connection-tracking map을 사용할 수 있다. 따라서 node의 nf_conntrack_count만 보고 모든 connection state를 대표한다고 생각하면 안 된다.

kernel에서 netfilter conntrack의 현재 entry 수와 limit는 다음처럼 읽을 수 있다.

sysctl net.netfilter.nf_conntrack_count
sysctl net.netfilter.nf_conntrack_max

nf_conntrack_count는 현재 할당된 flow entry 수, nf_conntrack_max는 허용하는 최대 entry 수다. 사용률이 높다는 사실만으로 장애 원인이 확정되지는 않는다. drop counter, kernel log, traffic pattern, timeout과 table별 분포를 함께 봐야 한다.

conntrack tool이 설치되어 있고 필요한 권한이 있다면 통계를 읽을 수 있다.

conntrack -S

production node에서 table을 flush하거나 timeout을 바꾸는 것은 기존 connection에 영향을 줄 수 있다. 진단과 mutation을 분리하고, 먼저 read-only evidence를 모은다.

고정된 ‘이중 NAT’ 공식이 위험한 이유

instance target의 한 경로에서는 NLB가 node의 NodePort로 보내고 node의 service datapath가 Pod endpoint를 선택하면서 DNAT·SNAT과 conntrack이 관여할 수 있다. 그러나 다음 값 하나만 달라져도 경로가 바뀐다.

  • instance 또는 ip target
  • externalTrafficPolicy: Cluster 또는 Local
  • endpoint가 local인지 remote인지
  • kube-proxy의 iptables, IPVS, nftables mode
  • AWS VPC CNI, Cilium 등 CNI와 service replacement 설정
  • NLB client IP preservation과 target group 설정

그래서 “EKS는 최소 NAT 두 번, conntrack 한 번” 같은 숫자는 환경을 설명하지 못한다. packet capture나 rule·map state로 확인하기 전에는 가능한 경로 중 하나일 뿐이다.

eBPF가 conntrack과 NAT를 없앤다는 오해

eBPF 기반 service implementation은 iptables rule traversal을 대체하거나 packet processing 위치를 바꿀 수 있다. 하지만 eBPF를 쓴다는 이유만으로 state tracking과 address translation이 사라지는 것은 아니다.

Cilium의 kube-proxy replacement를 예로 들면 service와 backend lookup, connection tracking, NAT를 BPF map으로 관리할 수 있다. 현재 stable 문서는 NodePort의 기본 forwarding mode를 SNAT으로 설명하고, DSR과 hybrid mode를 선택지로 제공한다.

Cilium SNAT mode: BPF conntrack/NAT state 사용 가능
Cilium DSR mode:  reply path와 source 보존 방식 변화
hybrid mode:      protocol에 따라 DSR·SNAT 조합

DSR도 단순한 “NAT 제거 button”이 아니다. reply path, MTU, underlying network의 source/destination check와 dispatch method를 고려해야 한다. cloud별 제약도 있다.

eBPF verifier의 안전 경계와 program type별 효과는 eBPF verifier가 보장하는 것에서 이어서 볼 수 있다.

일부 request만 timeout일 때 확인 순서

partial failure는 conntrack saturation 외에도 여러 이유로 생길 수 있다. 추측 대신 control plane에서 data plane으로 좁혀 간다.

1. Service와 annotation을 확인한다

kubectl get service api -n app -o yaml

다음을 기록한다.

  • load balancer type과 NLB target type annotation
  • externalTrafficPolicy
  • port, targetPort, NodePort
  • health check annotation
  • selector

2. ready endpoint와 node 분포를 확인한다

kubectl get endpointslice \
  -n app \
  -l kubernetes.io/service-name=api \
  -o wide

kubectl get pod -n app -l app=api -o wide

timeout이 특정 zone, node, Pod version과 겹치는지 본다. readiness가 흔들리거나 EndpointSlice 반영과 rollout timing이 맞지 않으면 일부 target만 실패할 수 있다.

3. controller가 만든 binding과 event를 확인한다

kubectl get targetgroupbinding -A
kubectl describe service api -n app

CRD 이름과 field는 controller version에 따라 달라질 수 있다. controller log에서는 reconciliation error와 target registration 실패를 확인한다.

4. AWS target health를 확인한다

target group ARN을 정확히 식별한 뒤 read-only API로 health를 본다.

aws elbv2 describe-target-health \
  --target-group-arn <target-group-arn>

unhealthy reason, target address·port, zone을 Kubernetes endpoint와 대조한다. 이 command에는 조회 권한과 올바른 account·region이 필요하다.

5. node datapath와 conntrack evidence를 모은다

환경에 맞게 다음을 확인한다.

  • kube-proxy mode와 rule sync error
  • netfilter conntrack count·max와 insert/drop 통계
  • CNI agent health와 BPF map pressure
  • security group, NACL, route와 return path
  • MTU와 fragmentation 관련 counter
  • node·Pod interface drop과 retransmission

Cilium을 쓴다면 generic nf_conntrack_count뿐 아니라 Cilium status, service와 CT/NAT map 상태를 Cilium version에 맞는 command로 확인해야 한다.

6. packet capture는 가설을 세운 뒤 제한적으로 한다

packet capture는 강력하지만 production traffic과 민감정보를 포함할 수 있다. 대상 node, interface, port와 시간을 좁히고 승인된 절차에 따라 수행한다.

확인하고 싶은 지점은 다음과 같다.

NLB target에 SYN이 도착하는가
Pod까지 같은 connection이 전달되는가
reply가 예상 interface와 address로 나가는가
retransmission 또는 reset이 어느 구간에서 시작되는가

path를 기록하는 표

조사 중에는 구성과 관측 결과를 한 표에 모으면 도움이 된다.

항목 실제 값 evidence
NLB target type instance / ip Service annotation, target group
externalTrafficPolicy Cluster / Local Service spec
target health healthy / unhealthy ELBv2 API
endpoint locality local / cross-node EndpointSlice, Pod node
service datapath kube-proxy / CNI replacement component config
conntrack implementation netfilter / BPF map / both node·CNI evidence
source IP at Pod observed address application log or approved capture
failing scope zone / node / Pod / version correlated telemetry

NLB의 기본 개념은 EKS에서 NLB 구성하기, ALB와의 차이는 EKS ALB 구성에서 이어서 볼 수 있다.

EKS network 문제는 제품 이름보다 packet이 지난 실제 경로를 복원해야 풀린다. target type과 endpoint부터 확인하고, 주소가 바뀌는 지점과 state를 가진 component를 표시한 뒤, 각 지점의 counter와 health를 대조한다. conntrack과 NAT는 그 그림 안에서 확인할 대상이지, 증상을 듣자마자 정해지는 결론이 아니다.

참고 자료

반응형
KEEP READING
카테고리 전체 보기 →

댓글