본문 바로가기

DevOps

[AEWS 4기] EKS 스터디 5주차 [ EKS 트러블슈팅 ]

EKS 환경 DNS 오류(EAI_AGAIN) 원인 분석 및 개선 권고

2026년 4월 15일 | AWS EKS Kubernetes DNS 장애분석


EKS 기반 서비스 환경의 Next.js Pod에서 getaddrinfo EAI_AGAIN 오류가 지속 발생하는 케이스를 분석하였습니다. 이벤트성 트래픽이 급증하는 상황에서 DNS 조회 실패로 인한 서비스 지연이 발생할 수 있으며, 해당 원인과 개선 권고사항을 공유합니다.


현황 및 배경

4월 15일 07시 토이스토리 MD를 판매 할 것이라고 고객사에게 유선 및 모니터링 대응 요청 받았다.

전달 이벤트 당시 coreDNS 이슈가 있었다고 전달 받았음. DB 세션 timeout 에러와.. 이벤트 시간이 한참 지나도

APM에서 트래픽이 빠지지 않는 현상.. + coreDNS 파드 2대 모두 CPU 100% 초과

CoreDNS 파드 재기동 이후 현상은 해소되었다고 한다..

해당 정보를 가지고 4월 15일 06:45분 부터 coreDNS와 같은 노드에 netshoot 파드를 하나 만들고 기다렸다.

curl -v 192.168.1.107/metrics 명령어를 대기해놓고..

grep으로 request / response / error 등을 확인할 생각이였다.

아니나 다를까 ...

netshoot-pod-:~ # curl -s 192.168.20.218:9153/metrics | grep SERFAIL

메트릭이 미친듯이.. 오르는게 보였다.

next JS 파드에서 EAI AGAIN 에러가 뜨는 만큼 SERFAIL 카운트가 오르는것 처럼 보였다.

VPC DNS 스로틀링

AWS VPC DNS Resolver는 EC2 인스턴스당 최대 1,024 패킷/초의 처리 한도를 가집니다. 대규모 트래픽 이벤트에서 파드 수가 수백~수천 단위로 증가하면 CoreDNS를 거쳐 VPC DNS Resolver로 전달되는 쿼리량이 한도를 초과하고, 스로틀링이 발생하여 DNS 응답 지연 및 EAI_AGAIN 오류로 이어집니다.

따라서 현재 ndots 5 기본설정이기 때문에 search domain에 대한 모든 쿼리를 하고 나서 결과값을 받는다고

가정했다. 따라서 나의 가설을 증명하기 위하여 테스트 계획을 세웠다.

ndot 설정을 2로 가져가고, NodeLocalCache를 설치하기로 계획 했다.

현재 검증계에서 부터 적용을 단계적으로 진행하며 poc를 진행중에 있다.

1. ndots:5 기본 설정에 의한 쿼리 증폭

Kubernetes의 기본 DNS 설정인 ndots:5 환경에서는 단일 DNS 질의가 최대 5회의 쿼리로 증폭됩니다. 아래는 실제 DNS 쿼리 흐름 예시입니다.

/etc/resolv.conf 설정 예시

nameserver 172.20.0.10  (CoreDNS ClusterIP)
search fo.svc.cluster.local svc.cluster.local cluster.local
options ndots:5          ← 문제 원인

DNS 쿼리 흐름

my-api.fo.svc.cluster.local 조회 시:

1차: my-api.fo.svc.cluster.local.fo.svc.cluster.local  → NXDOMAIN
2차: my-api.fo.svc.cluster.local.svc.cluster.local     → NXDOMAIN
3차: my-api.fo.svc.cluster.local.cluster.local         → NXDOMAIN
4차: my-api.fo.svc.cluster.local.                      → 정답 ✓

총 4번의 헛탐색 후 정답 도달

하나의 서비스 호출이 실제로는 CoreDNS에 4~5배의 쿼리를 유발하며, 이벤트 트래픽 급증 시 CoreDNS 과부하 및 VPC DNS 스로틀링의 복합적 원인이 됩니다.


개선 권고사항 (Best Practices)

AWS 권장 기준에 따라 아래 3가지 조치를 순차적으로 적용할 것을 권고합니다.

① ndots 설정 최적화 (1순위 · 즉시 적용)

애플리케이션 Pod의 dnsConfigndots: 2를 설정합니다. 불필요한 DNS Search Path 탐색을 줄여 CoreDNS 요청 수를 약 80% 절감할 수 있으며, 클러스터 내부 서비스 호출 시 즉시 FQDN으로 인식하여 응답 속도도 개선됩니다. 파드 레벨에서 적용 가능하여 즉시 효과를 기대할 수 있습니다.

spec:
  dnsConfig:
    options:
      - name: ndots
        value: "2"

② CoreDNS 리소스 상향 및 CPA 적용 (2순위 · 안정성 확보)

현재 파드당 CPU 100m200m~500m 이상으로 상향 조정합니다. 노드 수 증가 또는 고부하 이벤트 대비 안정적인 DNS 처리를 보장하며, Cluster Proportional Autoscaler(CPA)를 통해 클러스터 규모에 비례한 CoreDNS 자동 스케일링 구성을 권장합니다.

③ NodeLocal DNSCache 도입 (3순위 · 근본 해결 · 강력 권장)

노드마다 로컬 DNS 캐시 파드를 DaemonSet으로 배포하여 CoreDNS 부하를 70~80% 이상 경감합니다. VPC DNS Resolver까지 도달하는 쿼리 수를 대폭 감소시켜 스로틀링 문제를 근본적으로 해소하는 가장 효과적인 방법입니다.

참고 문서


적용 우선순위 요약

순위 조치 항목 적용 범위 기대 효과
1순위 ndots: 2 설정 파드 레벨, 즉시 적용 DNS 쿼리 수 약 80% 절감
2순위 CoreDNS 리소스 상향 + CPA 클러스터 레벨 이벤트 대비 DNS 안정성 확보
3순위 NodeLocal DNSCache 도입 노드 레벨 (DaemonSet) CoreDNS 부하 70~80% 이상 경감, 스로틀링 근본 해소

대규모 이벤트 트래픽 환경에서 DNS는 종종 간과되는 병목 지점입니다. ndots 최적화만으로도 즉각적인 효과를 얻을 수 있으며, NodeLocal DNSCache 도입을 통해 근본적인 안정성을 확보하는 것을 강력히 권장합니다.

EKS Worker Node 이상 동작 원인 분석 (H 자동차)

대상 노드: ccs-prd-06r09-32c-worker1 | 분류: Kubernetes 노드 CPU 부하 / DNS / DiskIO 복합 장애

본 문서는 CloudNet@ AEWS 3기 스터디 자료를 참고하여 작성하였습니다.


개요

ccs-prd-06r09-32c-worker1 노드에서 CPU 사용률 및 Load Average가 비정상적으로 급등하는 이슈가 발생하였습니다. 단일 원인이 아닌 DiskIO 급증, DNS Lookup 실패, UDP Race Condition 세 가지 요인이 복합적으로 작용한 것으로 가정하며, 각 가설과 근거를 정리합니다.

본 문서는 근거 수집 과정에서의 가설 공유를 목적으로 작성되었습니다. 일부 항목은 추가 검증이 필요합니다.


장애 요약 (발생 당시 증상)

항목 관측 값
CPU Load Average 100 이상 급등
CPU 사용률 상대적으로 높지 않음 (iowait 우세)
캐시 Miss율 매우 높음
DNS 상태 해당 노드에서 DNS timeout 지속 발생
해결 방법 노드 재부팅 후 정상 복귀

CPU 사용률은 낮은데 Load Average가 100을 넘는 전형적인 I/O Wait 병목 패턴입니다. 이 현상을 단서로 DiskIO 조사를 시작하였습니다.


0. 왜 CPU 사용률은 낮은데 Load Average는 높은가?

이 현상을 이해하려면 Load Average의 역사적 변천을 먼저 짚어야 합니다.

1973년 이전: Load Average = CPU Demand

RFC 546 (1973년 8월) 기준으로, 당시 Load Average는 단순히 실행 가능한(runnable) 프로세스의 평균 개수를 의미했습니다.

*"The TENEX load average is a measure of CPU demand. The load average is an average of the number of runnable processes over a given time period."*
— RFC 546, August 1973

즉, 디스크 I/O 대기 등으로 블로킹된(uninterruptible) 프로세스는 Load Average 계산에서 제외되었습니다.

상태 1973년 당시 Load Average 포함 여부
CPU 실행 중 ✅ 포함
CPU 대기 중 (runnable) ✅ 포함
I/O 대기 중 (TASK_UNINTERRUPTIBLE) ❌ 제외

당시에는 CPU 속도와 디스크 I/O 속도 간의 성능 차이가 지금처럼 극단적이지 않았기 때문에, I/O 대기를 제외해도 큰 문제가 없었습니다.

현대 Linux: Load Average에 I/O Wait 포함

기술이 발전하면서 CPU 성능은 기하급수적으로 향상되었지만, 디스크 I/O 속도는 상대적으로 느리게 발전했습니다. 이에 따라 I/O로 인한 블로킹이 시스템 체감 성능을 좌우하는 핵심 지표가 되었습니다.

현대 Linux 커널은 TASK_UNINTERRUPTIBLE 상태(디스크 I/O 대기 중인 프로세스)를 Load Average에 포함합니다.

*"TASK_UNINTERRUPTIBLE means (meant?) that the process is waiting for something like a disk read which contributes to system load. A heavily disk-bound system might be extremely sluggish but only have a TASK_RUNNING average of 0.1, which doesn't help anybody."*

실제 관측값으로 이해하기

top 출력:
%Cpu(s):  1.8 us,  1.0 sy,  0.0 ni,  3.6 id, 91.3 wa,  0.0 hi,  0.0 si,  2.3 st
→ wa(iowait) 91.3% : CPU가 I/O 응답을 기다리며 멈춰있는 시간

vmstat 출력:
 r  b   swpd   free ...
 1  9      0 338308 ...
 1  6      0 281948 ...
 1  8      0 266208 ...
→ r(running): 1개만 실행 중
→ b(blocked): 6~10개가 I/O 대기로 블로킹

Load Average가 높은 이유:

I/O 대기 프로세스(TASK_UNINTERRUPTIBLE) 급증
    → 현대 Linux는 이를 Load Average에 포함
    → CPU 사용률(us+sy)은 낮지만 Load Average는 급등
    → "CPU는 바쁘지 않은데 시스템은 느리다"는 역설적 상황 발생
지표 해석
CPU 사용률 (us+sy) ~3% CPU 자체는 한가함
iowait (wa) 91.3% CPU가 I/O 응답 대기에 묶여 있음
vmstat b 컬럼 6~10 I/O 대기로 블로킹된 프로세스 수
Load Average 8.54 I/O 대기 포함으로 높게 집계됨

핵심: CPU 사용률과 Load Average가 괴리를 보일 때, iowait와 vmstat의 b 컬럼을 반드시 확인해야 합니다. 이 경우 DiskIO 또는 메모리 I/O 병목을 먼저 의심해야 합니다.


1. DiskIO 급증으로 인한 CPU 부하 — 캐시미스를 확인하다

근거

filebeat 프로세스의 /proc/{PID}/io 값을 확인한 결과, cancelled_write_bytes 수치가 비정상적으로 높게 측정되었습니다.

# /proc/3054058/io (filebeat)

rchar:                  59,810,608,081  bytes
wchar:                 552,855,435,236  bytes
syscr:                      44,320,387
syscw:                     911,105,061
read_bytes:                          0
write_bytes:           415,140,614,144  bytes
cancelled_write_bytes: 303,127,040,000  bytes  ← 주목

비교: Java 프로세스의 cancelled_write_bytes는 0

/proc/{PID}/io 수치 해석
핵심 이상 징후: cancelled_write_bytes가 write_bytes의 73%

write_bytes:           415 GB  (페이지 캐시에 쓰려 한 양)
cancelled_write_bytes: 303 GB  (실제로 취소된 양)
→ 실제 디스크 반영률: 27%에 불과
추측 가능한 현상
1. 메모리 압박 (Memory Pressure)
페이지 캐시가 쓰기를 받아들였지만, 메모리가 부족해 캐시가 강제로 무효화(evict)됨.

dirty page가 flush되기 전에 drop → cancelled_write_bytes 증가
증거: read_bytes: 0 → 디스크에서 직접 읽은 게 없음 (캐시에서만 동작)
2. filebeat가 로그 파일을 추적하다 파일이 삭제/로테이션됨

filebeat가 파일 읽는 중
    → 로그 로테이션으로 파일 삭제
    → 페이지 캐시에 남은 dirty page가 무효화
    → cancelled_write_bytes 누적
wchar(552GB) >> rchar(59GB) → 읽는 것보다 훨씬 많이 쓰는 비정상 패턴

3. 페이지 캐시 경합 → CPU iowait 상승

cancelled_write_bytes 폭증
    → 커널이 dirty page 재처리 반복
    → kworker / writeback 스레드 과부하
    → CPU iowait 상승 → Load Average 급등
CPU 사용률은 낮은데 Load Average 100 넘는 패턴과 정확히 일치

4. syscw(9억 회) vs write_bytes 불일치
syscw는 911만(실제: 9억)회인데 write_bytes가 415GB면 syscall당 ~456 byte → 매우 잦은 소량 쓰기. 이는 filebeat가 버퍼링 없이 flush를 반복했다는 의미.

종합 추측
항목    판단
파일 로테이션 중 캐시 무효화    가능성 높음
메모리 부족으로 인한 페이지 캐시 eviction    가능성 높음
filebeat 버퍼 설정 부재    거의 확실
실제 디스크 I/O 병목    read_bytes: 0이므로 디스크 읽기 병목은 아님
결론: filebeat가 메모리 압박 상황에서 페이지 캐시를 과도하게 사용하며 쓰기 작업을 반복했고, 캐시 무효화가 연쇄적으로 발생해 CPU Load를 끌어올린 것으로 추측됩니다. filebeat.yml의 bulk_max_size, flush_timeout 튜닝이 필요한 상황입니다.

해석

항목 설명
cancelled_write_bytes 페이지 캐시에 기록하려 했으나 실제 디스크에 반영되지 않고 취소된 바이트 수
발생 원인 파일 삭제, 프로세스 종료, 메모리 압박으로 인한 캐시 무효화 등
의미 filebeat가 정상적인 I/O 처리 동작을 수행하지 못한 시점이 있었음을 시사

cancelled_write_bytes가 장애 발생 시점에 기록된 것으로 단정할 수는 없었으나, filebeat의 I/O 비정상 동작 정황 증거로 판단합니다.

장애 흐름

filebeat DiskIO 급증
    → 페이지 캐시 경합 발생
    → CPU 사용률 / Load Average 급등 (28~36분 지속)
    → Pod 스로틀링 발생
    → Pod 스로틀링 해소 후 노드 CPU Load 정상 복귀

대응 : 노드 재기동 시 캐시 및 스로틀링 정상 복귀, CPU load 정상 복귀

근본적인 원인이 diskio에 있다고 판단하여 NFS 사용을 권고함 ( 속도 때문에 node 임시 directory (empty dir )를 사용하고 있었음 )

NFS pvc 사용 후 문제 해결 됬지만 성능을 포기함


2. DNS Lookup 실패와 노드 CPU 부하의 연관성

현황

  • DNS Lookup 실패 건수: 100건 미만 (전체 트래픽의 약 0.2%)
  • 수치만 보면 미미하지만, 그 파급 효과는 무시할 수 없습니다.

영향 분석

CoreDNS 노드 관점

DNS Lookup 실패가 발생하면 해당 CoreDNS가 위치한 노드의 컴퓨팅 리소스가 일시적으로 상승합니다. 실패 건수 자체는 적더라도, Retry 및 Timeout 대기가 누적되면 부하가 집중됩니다.

애플리케이션 관점

DNS Lookup 실패
    → 애플리케이션 Retry 반복 (timeout 설정이 없는 경우)
    → 연결 대기 Thread 누적
    → 서비스 응답 지연 → 크리티컬 장애로 escalation 가능

권고

애플리케이션 레벨에서 DNS 실패에 대한 Retry 횟수 제한Timeout 설정이 없으면 소수의 DNS 실패도 치명적인 장애로 이어질 수 있습니다.


3. UDP Race Condition (conntrack) 가설

배경

iptables + kube-proxy 환경에서는 netfilter의 conntrack 테이블이 클러스터 규모가 커질수록 관리 복잡도가 증가합니다. 이로 인해 DNS 쿼리에 사용되는 UDP 패킷이 드롭되는 Race Condition이 발생할 수 있습니다.

가설

conntrack 테이블 경합 (UDP Race Condition)
    → DNS UDP 패킷 drop
    → DNS Lookup 실패 (EAI_AGAIN)
    → 애플리케이션 Retry 폭증
    → CoreDNS 부하 상승 → 노드 CPU 영향

참고 문서: Linux UDP Packet Drop & conntrack Race Condition

검증 방법 (진행 중)

conntrack 수준의 패킷 드롭을 확인하려면 커널 레벨 Observability 도구가 필요합니다.

도구 용도
conntrack -S conntrack 테이블 drop 통계 확인
perf / bpftrace 커널 레벨 이벤트 트레이싱
Cilium / Hubble eBPF 기반 네트워크 흐름 가시성 확보
Retina (Microsoft) Kubernetes 네트워크 Observability

2번,3번 증상에 대해서는 노드 재기동이 되면서 자연스럽게 해소가 되어 원인이 아닌 증상으로 가정하였고, CPU 부하 증가 가 패킷 드롭과 DNS 실패로 상관관계를 확정짓기 위한 정확한 근거는 규정하기 어려워 보여서 각각 다른 증상으로 규정하고 원인 분석 및 대응을 했습니다.


4. 종합 원인 가설 흐름도

[DiskIO 급증 (filebeat)]
    └→ 페이지 캐시 경합 → CPU Load 상승

[conntrack UDP Race Condition]
    └→ DNS UDP 패킷 drop
        └→ DNS Lookup 실패 (소수지만 고영향)
            └→ 애플리케이션 Retry 폭증
                └→ CoreDNS 노드 CPU 상승

두 경로가 동시에 작용하여 노드 CPU/Load 비정상 급등
    → Pod 스로틀링 발생
    → 스로틀링 해소 후 정상 복귀

5. 개선 권고사항

coreDNS + DNS time out의 단기 대응 — ndots + FQDN 적용 (즉시)

불필요한 DNS Search Path 탐색을 제거하여 CoreDNS 쿼리 부하를 즉시 감소시킵니다.

spec:
  dnsConfig:
    options:
      - name: ndots
        value: "2"

coreDNS + DNS time out의 중기 대응 — NodeLocal DNSCache 도입 (강력 권장)

NodeLocal DNSCache를 도입하면 DNS 쿼리가 UDP → TCP로 전환됩니다.
TCP는 conntrack Race Condition의 영향을 받지 않으므로, UDP 패킷 드롭으로 인한 DNS 실패 가능성을 근본적으로 차단할 수 있습니다.

  • CoreDNS 부하 70~80% 이상 경감
  • VPC DNS 스로틀링 해소
  • conntrack UDP Race Condition 우회

coreDNS + DNS time out의 장기 대응 — eBPF 기반 네트워킹 도입 검토 (내년도 로드맵)

최근 eBPF 기반 성능 개선 사례가 다수 발표되고 있습니다. iptables/kube-proxy를 eBPF로 대체하면 conntrack 테이블 경합 문제를 구조적으로 해소할 수 있습니다.

솔루션 특징
Cilium eBPF 기반 CNI, kube-proxy 완전 대체, Hubble로 네트워크 가시성 확보
AWS VPC CNI + Network Policy EKS 네이티브, eBPF 기반 네트워크 정책 지원

6. 검증 현황

가설 상태 비고
filebeat DiskIO 급증 → CPU 부하 정황 확인 cancelled_write_bytes 수치 이상 확인
DNS Lookup 실패 → 노드 CPU 영향 부분 확인 CoreDNS 노드 리소스 상승 확인
UDP Race Condition → DNS 패킷 드롭 검증 중 커널 레벨 Observability 도구로 추가 확인 필요

참고 문서


원인 분석은 복잡하지만, FQDN 적용 + NodeLocal DNSCache 도입만으로도 DNS 관련 장애 가능성을 대폭 낮출 수 있습니다. conntrack Race Condition 근본 해결을 위해서는 중장기적으로 eBPF 기반 네트워킹 도입을 권장합니다.