EKS aws-node ipamd 행(hang) — 파드 ContainerCreating 멈춤

Kubernetes5분조회 –

EKS 클러스터에서 부하 테스트를 돌리고 있었습니다. 요청을 몰아넣어 어디서부터 버티지 못하는지 보는 작업이라, 파드를 짧은 주기로 대량으로 만들었다 지웠다 합니다.

노드는 Karpenter 가 붙이고, 파드가 Pending 이면 그 파드 요구사항에 맞는 EC2 를 골라 띄우고 비면 다시 내리기 때문에 파드가 늘어나는 만큼 노드도 따라 늘어납니다.

그 파드에 IP 를 주는 건 VPC CNI 고, 노드마다 aws-node 파드가 하나씩 떠서 그 노드 ENI 에 붙은 IP 를 파드에 나눠 주니, 노드가 새로 뜨면 이 aws-node 도 같이 떠야 그 노드에 잡힌 파드들이 IP 를 받을 수 있습니다.

그런데 새로 붙은 노드에 잡힌 파드가 하나도 못 뜨고 전부 ContainerCreating 에서 멈췄습니다.

Warning  FailedCreatePodSandBox  kubelet  Failed to create pod sandbox:
plugin type="aws-cni" name="aws-cni" failed (add):
add cmd: failed to assign an IP address to container

CNI 가 컨테이너에 IP 를 못 붙였다는 건데, 이미지를 받거나 볼륨을 붙이는 단계가 아니라 네트워크를 붙이는 단계에서 걸렸다는 뜻이라, 그 노드의 aws-node 부터 봤습니다.

Running인데 멈춰 있던 ipamd

failed to assign an IP address 라고 찍혀 있으니 IP 를 나눠주는 쪽부터 봤습니다. EKS 에서 파드에 IP 를 주는 건 aws-node, 즉 VPC CNI 의 ipamd 이고, 이게 안 돌면 그 노드의 파드는 하나도 IP 를 못 받습니다. 기존 노드는 멀쩡하고 새로 뜬 노드에서만 나는 증상이라 더 그쪽이 의심됐습니다.

정작 파드 상태는 멀쩡해 보였습니다.

kubectl get po -n kube-system -o wide | grep <문제-노드>
# aws-node: 2/2 Running

2/2 Running 이지만 로그를 보니 초기화하다 멈춰 있었습니다.

level=info msg="Starting IPAM daemon..."
level=info msg="Checking for IPAM connectivity..."
level=info msg="Successfully copied CNI plugin binary and config file."
# 이후 로그 없음

파드가 Running 이어도 그 안의 프로세스는 응답을 멈출 수 있습니다. health check 는 프로세스가 실제로 일을 하고 있는지까지 보지 않으니 상태가 초록불이면 로그가 어디서 끊겼는지를 같이 봐야 합니다.

첫 조치 — 재시작, 그다음 노드 교체

프로세스가 멈춘 거면 다시 띄우면 풀릴 수 있고, DaemonSet 이 관리하니 파드를 지우면 같은 노드에 새로 뜹니다.

kubectl delete po -n kube-system <aws-node-pod>

새 파드도 똑같은 지점에서 멈췄고, 두 번 다 같은 자리라면 파드가 아니라 노드 쪽이나 그 바깥의 문제라는 뜻입니다. 부하 테스트 중이라 노드 하나에 오래 매달릴 상황도 아니어서 노드를 갈기로 했습니다.

노드를 지우면 그 위의 파드가 전부 쫓겨나니 무엇이 떠 있고 다른 노드에 복제본이 있는지부터 확인합니다.

kubectl get po -A -o wide | grep <문제-노드>

다른 노드에 Running 복제본이 있는 워크로드는 노드를 지워도 영향이 없고 단일 파드라도 어차피 IP 를 못 받아 서비스가 안 되는 상태면 잃을 게 없어서, 그걸 확인하고 나서 지웠습니다.

Karpenter 가 관리하는 노드라 do-not-disrupt 어노테이션이 붙어 있으면 먼저 떼야 합니다.

kubectl annotate node <문제-노드> karpenter.sh/do-not-disrupt-
kubectl delete node <문제-노드>

노드를 지우자 Karpenter 가 대체 노드를 30초 남짓에 띄웠고, 파드가 거기서 IP 를 받아 Running 이 됐습니다.

진짜 원인 — 서브넷 IP 고갈

서비스는 돌아왔지만 노드를 갈았더니 됐다로 끝내면 다음에 또 당합니다. 새로 뜬 노드마저 IP 를 못 준 이유가 남아 있었고 답은 그 노드가 속한 서브넷의 사설 IP 가 고갈된 것이었습니다.

확인은 한 줄이면 됩니다.

aws ec2 describe-subnets --subnet-ids <subnet-id> \
  --query 'Subnets[].AvailableIpAddressCount'
# [ 3 ]   ← 거의 바닥
서브넷 ID 상태 가용 IPv4 주소 가용 영역
subnet-0ab3f9c1 Available 3 ap-northeast-2a

가용 IPv4 주소가 이 정도밖에 안 남으면 그 서브넷에 새로 뜬 노드는 파드에 줄 IP 를 확보하지 못합니다. 짧은 주기로 파드를 대량 생성·삭제하는 부하 테스트가 서브넷 가용 IP 를 바닥까지 끌어다 썼고 그 상태에서 뜬 노드는 ipamd 가 ENI 에 붙일 IP 자체를 못 받았습니다. 로그가 초기화 도중 끊긴 것처럼 보인 것도 이걸로 설명됩니다. 프로세스가 죽은 게 아니라 줄 IP 가 없어서 다음 단계로 못 넘어간 겁니다.

쿨다운을 0으로 내리면 안 되는 이유

여기에 하나가 더 겹칩니다. 파드를 지워도 그 IP 가 즉시 풀로 돌아오지 않습니다. VPC CNI 에 IP 쿨다운(IP_COOLDOWN_PERIOD, 기본 30초)이 있어서 회수된 IP 를 잠깐 붙잡아 둡니다. 파드 churn 이 그보다 빠르면 반납이 소비를 못 따라가니 가용 IP 가 0 근처에 머뭅니다.

그러면 쿨다운을 0 으로 두면 되지 않나 싶지만, 그게 위험합니다. 쿨다운은 방금 놓아준 IP 에 남아 있던 라우팅과 연결 흔적이 정리될 시간을 버는 장치입니다. 0 으로 만들면 반납되자마자 다른 파드에 재할당돼서 이전 파드로 가야 할 인플라이트 트래픽이 엉뚱한 새 파드로 갈 수 있습니다.

근본 대응은 회수 속도를 더 당기는 게 아니라 애초에 노드가 안 쓰는 IP 를 덜 쥐게 하는 쪽입니다. WARM_IP_TARGET 과 MINIMUM_IP_TARGET 을 조이고 그래도 모자라면 서브넷을 넓히거나 세컨더리 CIDR 을 붙입니다. 이쪽은 따로 정리했습니다 — EKS 서브넷 IP 고갈 — 파드 churn과 WARM_IP_TARGET.

2/2 Running은 프로세스가 일한다는 뜻이 아님

2/2 Running 은 컨테이너가 살아 있다는 뜻이지 그 안의 프로세스가 일을 하고 있다는 뜻이 아닙니다. 상태가 초록불인데도 증상이 계속되면 로그가 특정 지점에서 끊겨 있는지 먼저 확인합니다.

파드를 재생성해도 같은 자리에서 멈추면 파드가 아니라 노드나 그 바깥을 의심합니다. 이번에는 서브넷 IP 가 바닥나 있었고, 노드를 갈아 끼운 건 원인을 찾을 때까지 버티는 응급 처치였습니다.

참고

  1. 불러오는 중