EKS 서브넷 IP 고갈 — 파드 churn과 WARM_IP_TARGET
부하 테스트를 하느라 부하를 걸었다 내렸다 하면서 파드를 짧은 주기로 대량 생성·삭제하고
있었습니다. 그러다 어느 순간부터 새로 만든 파드가 전부 ContainerCreating 에서 안 넘어갔습니다.
파드가 안 뜨면 보통은 노드 쪽을 보게 되고, CPU 나 메모리가 모자라 스케줄러가 꽂을 자리를 못 찾은 경우가 대부분이고, 그러면 노드 스펙을 올리거나 대수를 늘려서 풀면 됩니다. 그런데 이번에는 스케줄까지는 됐고, 노드가 정해진 뒤 컨테이너를 못 시작하고 있었습니다. 자리가 없어서 못 뜬 게 아니라 붙일 IP 가 없어서 못 뜬 것이었고, 서브넷에 남은 사설 IP 가 바닥나 있었습니다.
EKS 에서 파드는 기본 CNI 인 VPC CNI 때문에 노드 안쪽 가상 네트워크가 아니라 VPC 서브넷의 사설 IP 를 하나씩 직접 받습니다. VPC 안에서 EC2 와 같은 자격으로 붙는 거라 보안 그룹이나 라우팅이 그대로 먹히는 게 장점이고, 뒤집으면 파드 하나가 뜰 때마다 서브넷 IP 가 하나씩 나간다는 뜻입니다. 파드 수가 완만하게 오르내리는 동안에는 티가 안 나다가, 이번처럼 churn 이 몰리면 한 번에 드러납니다.
여기서 처음 잡은 방향은 회수를 빨리 하자는 쪽이었습니다. 파드를 지워도 그 IP 가 곧바로 안 돌아온다는 건 알고 있었으니 반납만 당기면 모자란 만큼이 메워질 거라고 봤습니다. 결과적으로는 손댈 값도 아니었고 손대봐야 소용도 없었습니다.
회수를 당겨봐야 소용없는 이유
파드가 삭제돼도 그 IP 는 바로 다음 파드로 안 넘어가고 기본 30초 쿨다운을 둡니다. 그 IP 가 서비스 엔드포인트로 등록돼 있었으면 kube-proxy 가 iptables 규칙을 지울 시간이 필요하기 때문입니다.
IP_COOLDOWN_PERIOD 로 줄일 수는 있지만, 줄이면 죽은 파드로 가던 트래픽이 그 IP 를 물려받은 새
파드로 들어옵니다.
억지로 줄인다고 해도 churn 이 빠르면 30초든 5초든 그 사이에 가용 IP 가 다시 0 이 됩니다. 회수 속도로 따라잡을 수 있는 문제가 아니었다는 뜻이고, 그렇다면 파드가 쓰고 반납하는 IP 말고 아무도 안 쓰는데 나가 있는 IP 가 따로 있다는 얘기가 됩니다.
진짜 문제 — 노드가 쥐고만 있는 IP
VPC CNI 는 파드가 요청한 다음에 IP 를 붙이지 않습니다. 그때 가서 EC2 API 를 호출하면 파드 시작이 그만큼 느려지니 미리 확보해 둡니다. 노드마다 여유분(warm pool)을 붙여 놓고 파드가 뜨면 거기서 꺼내 주는 방식입니다.
기본값인 WARM_ENI_TARGET=1 은 이 여유분을 ENI 하나 분량으로 잡습니다. ENI(Elastic Network Interface)는 EC2 에 붙는 가상 랜카드고, 거기에 사설 IP 를 여러 개 답니다.
몇 개까지 붙는지는 인스턴스 타입이 정하는데 큰 타입이면 수십 개입니다. 그래서 노드가 실제로는 파드를 몇 개만 돌리고
있어도 그만큼을 통째로 쥐고 있게 되고, 노드가 많으면 이렇게 점유만 된 IP 가 쌓여서 서브넷의 가용
IP 를 소진합니다. 파드가 안 쓰고 있어도 서브넷 입장에서는 이미 나간 IP 입니다.
조치 — 여유분을 ENI 단위에서 IP 개수 단위로
여유분을 아예 없애면 IP 가 남겠지만 그러면 파드가 뜰 때마다 EC2 API 를 기다려야 하니, 없애는 대신 ENI 통째로 쥐던 걸 IP 개수 단위로 바꿉니다.
kubectl set env daemonset aws-node -n kube-system \
WARM_IP_TARGET=3 MINIMUM_IP_TARGET=5
WARM_IP_TARGET 은 지금 쓰는 IP 위에 몇 개를 여유로 둘지, MINIMUM_IP_TARGET 은 파드가 없어도
최소 몇 개는 붙여 둘지입니다. ENI 통째로 잡던 여유분을 IP 개수 단위로 바꾸면 노드가 안 쓰던 IP
를 서브넷에 돌려주고 그만큼이 다른 노드로 갑니다.
공짜는 아닌 게, 여유분이 적으면 파드가 갑자기 몰릴 때 IP 를 새로 붙이는 API 호출이 늘어 그만큼 파드 시작이 느려집니다. IP 를 아끼는 쪽과 빨리 뜨는 쪽을 맞바꾸는 건데, 애초에 IP 가 절대적으로 모자란 상황이면 아끼는 쪽으로 기울이는 게 맞습니다.
그래도 모자라면 — IP 공간 자체를 확장
그런데 warm target 을 조여도 서브넷이 작으면 한계가 옵니다. 노드가 쥔 여유분을 다 돌려받아도 파드 총량이 서브넷 크기를 넘으면 방법이 없습니다.
제일 단순한 건 워크로드용 서브넷을 /22 정도로 넉넉하게 잡는 쪽이고, 이미 돌고 있는 VPC 라
서브넷을 못 키우는 상황이면 100.64.0.0/10 같은 큰 대역을 secondary CIDR 로 VPC 에 붙이고
custom networking 으로 파드만 그 대역에서 IP 를 받게 합니다. 파드용 IP 공간을 노드용과 분리해서
크게 확보하는 방법입니다.
흔한 오답 — Prefix Delegation
IP 부족으로 검색하면 Prefix Delegation 이 자주 나오지만, 이 상황에서는 방향이 반대입니다.
Prefix Delegation 은 노드가 IP 를 하나씩이 아니라 /28(16개) 단위로 받게 해서 노드당 파드
밀도를 높이는 기능입니다. 노드 스펙, 정확히는 ENI 한도 때문에 파드를 더 못 꽂을 때 씁니다.
서브넷 IP 가 절대적으로 부족한 상황에 걸면 노드가 IP 를 16개씩 뭉텅이로 가져가서 서브넷의 가용
IP 를 더 빨리 소진합니다. 같은 “IP 부족” 이라는 말을 쓰지만 노드 쪽이 모자란 것과 서브넷 쪽이
모자란 것은 처방이 정반대입니다.
IP를 들고 있는 건 파드가 아니라 노드
IP 가 부족하다는 증상을 보면 쓰고 반납하는 속도가 안 맞는 거라고 생각해서 회수를 빠르게 할 방법부터 찾게 됩니다. 이번에도 그랬고, 그쪽은 건드릴 값이 아니었습니다.
같은 상황이면 순서를 이렇게 잡으면 됩니다. 먼저 지금 누가 IP 를 쥐고 있는지 봅니다. 파드가
실제로 쓰는 수보다 서브넷에서 나간 IP 가 훨씬 많으면 노드의 warm pool 이 들고 있는 것이고,
WARM_IP_TARGET 과 MINIMUM_IP_TARGET 을 조이는 것만으로 서브넷에 IP 가 돌아옵니다. 그러고도
모자랄 때 서브넷을 키우거나 secondary CIDR 을 붙이면 됩니다. Prefix Delegation 은 노드가 병목일
때 꺼내는 카드지 서브넷이 병목일 때 꺼낼 카드가 아닙니다.
댓글 0