EKS 노드 Ready/NotReady Flapping - 배치 작업 Disk I/O 병목과 EBS IOPS 증가로 해결
에러 스레드: Slack
1. 서론
새벽 2시에 EAP-EKS-TEST 클러스터에서 Node Ready Flapping Alert이 발생했다. 특정 노드가 Ready/NotReady를 반복하며 6회 상태가 바뀌었고, 메트릭 수집까지 끊겼다. 처음엔 노드 CPU 사용량 92%를 보고 CPU 과부하를 의심했지만, 실제 원인은 새벽 배치 CronJob이 유발한 Disk I/O 병목이었다.
2. 요약
| 항목 | 내용 |
|---|---|
| 발생 시간 | 2026-01-26 02:14 ~ 02:37 KST (약 23분) |
| 영향 노드 | ip-10-20-46-107.ap-northeast-2.compute.internal |
| Alert | EAP-EKS-TEST Node Ready Flapping |
| Root Cause | Disk I/O 병목 - update-accumulated-rank 배치 작업의 과도한 I/O가 노드를 포화시킴 |
인과관계 요약
flowchart LR A[update-accumulated-rank] --> B[Disk I/O 급증] B --> C[iowait 58%] C --> D[PLEG 10초] D --> E[Node Flapping]
update-accumulated-rank단독으로도 Node 장애 유발 가능update-total-step동시 실행은 악화 요인
핵심 수치
| 지표 | 정상 | 문제 시 | 변화 |
|---|---|---|---|
| worker-app Disk Read | 160 KB/s | 7.4 MB/s | 46배 |
| 노드 iowait | 6% | 52% | 8.7배 |
| PLEG relist duration | 0.04초 | 10초 | 250배 |
| PLEG relist 횟수 | 1회/초 | 0.1회/초 | 90% 감소 |
| Node Ready changes | 0 | 6회 | - |
3. 탐구 과정
3.1 처음 의심: CPU 병목
Alert 확인 직후 노드 CPU 사용량이 92%에 달했다. 자연스럽게 CPU 과부하를 의심했다. 그런데 실제 worker-app 컨테이너 CPU 사용량은 최대 0.38 cores — 노드 전체의 19%였다.
| 지표 | 값 | 해석 |
|---|---|---|
| worker-app CPU | 최대 0.38 cores | 노드(2 cores)의 19% |
| Node CPU Usage | 최대 92% | 높아 보이지만 대부분 I/O 대기 |
| iowait 비중 | 52% | CPU 사용의 절반 이상이 I/O 대기 |
CPU 사용량이 높아 보인 이유는 CPU의 절반 이상이 I/O 대기(iowait)였기 때문이다. CPU가 바빠서 92%가 아니라, I/O를 기다리느라 block된 것이다.
3.2 전환점: iowait 52%
iowait 52%를 확인한 순간 병목이 Disk I/O임을 파악했다. 다음 질문은 “어떤 Pod가 이 I/O를 만들고 있는가”였다.
| 근거 | 데이터 |
|---|---|
| iowait 52% | CPU의 절반 이상이 I/O 대기 상태 |
| Disk Read 46배 증가 | 160KB/s → 7.4MB/s |
| 메트릭 수집 실패 | kubelet/cadvisor/node-exporter 모두 응답 불가 |
Pod별 네트워크 수신량을 분리해보니 worker-app-hwl69 Pod로 트래픽이 집중되어 있었고, 해당 Pod가 위치한 노드가 문제 노드와 일치했다.
3.3 CronJob 2개인가, 1개인가
02:00에 두 CronJob이 동시 실행됐다는 사실이 확인됐다. 처음엔 두 작업의 동시 실행이 원인이라고 생각했다. 해결책이 스케줄 분산이라면 상대적으로 간단하다.
그런데 update-total-step이 02:11에 완료된 후에도 문제가 02:26까지 지속됐다. update-accumulated-rank 단독으로도 Node 장애를 유발할 수 있다는 뜻이다. 스케줄 분산만으로는 충분하지 않고, 근본 원인은 단일 작업의 I/O 자체임이 드러났다.
4. 타임라인 분석
시간순 지표 변화
| 시간 (KST) | worker-app Disk Read | iowait | PLEG | Node Ready | Scrape |
|---|---|---|---|---|---|
| 02:00 | 160 KB/s | 6% | 0.04초 | Ready | Up |
| 02:08 | 2.3 MB/s | 16% | 0.09초 | Ready | Up |
| 02:10 | 3.6 MB/s | 28% | 6.85초 | Ready | Down |
| 02:11 | 5.2 MB/s | 51% | 10초 | Ready | Down |
| 02:14 | 7.0 MB/s | 51% | 10초 | NotReady | Down |
| 02:21 | 7.6 MB/s (최대) | 58% | 10초 | NotReady (6회) | Down |
| 02:27 | 1.9 MB/s | 18% | 0.36초 | 불안정 | 불안정 |
| 02:37 | 1.5 KB/s | 4% | 0.02초 | 복구 | Up |
지표 선후관계
sequenceDiagram participant APP as worker-app participant IO as iowait participant PLEG as PLEG participant NODE as Node Note over APP: 02:08 APP->>IO: Disk Read 14배 증가 Note over IO: 02:09 IO->>PLEG: iowait 28% Note over PLEG: 02:10 PLEG->>PLEG: PLEG 6.85초 Note over NODE: 02:14 PLEG->>NODE: NotReady
5. 원인 분석
5.1 CronJob 실행 현황 (02:00~02:30 KST)
| CronJob | 스케줄 | 시작 | 완료 | 소요 시간 |
|---|---|---|---|---|
| update-accumulated-rank | 0 17 * * * | 02:00 | 02:26 | 26분 |
| update-membership-weekly-rank | 0 17 * * * | 02:00 | 02:00 | 8초 |
| update-membership-total-step | 5 17 * * * | 02:05 | 02:11 | 6분 |
5.2 update-accumulated-rank 단독으로도 문제가 발생하는가?
| 시간 (KST) | 실행 중인 작업 | iowait | PLEG | 상태 |
|---|---|---|---|---|
| 02:05~02:11 | accumulated-rank + total-step | 52% | 10초 | 장애 |
| 02:11~02:26 | accumulated-rank 단독 | 3~58% | 0.3~10초 | 불안정 |
| 02:26~ | 없음 | 3% | 0.02초 | 정상 |
상세 데이터 (02:11 이후, accumulated-rank만 실행 중):
| 시간 (KST) | iowait | PLEG | 분석 |
|---|---|---|---|
| 02:21 | 2.9% | 10초 | PLEG 지연 지속 |
| 02:24 | 46% | 9.3초 | iowait 다시 급등 |
| 02:25 | 58% | 9.3초 | 최대치 도달 |
| 02:37 | 3.8% | 0.04초 | 복구 (작업 완료) |
update-accumulated-rank 단독 실행만으로도 Node 장애 유발 가능. 02:11에 total-step 완료 후에도 02:26까지 15분간 문제 지속. total-step 동시 실행은 문제를 악화시켰지만 근본 원인이 아니다.
5.3 왜 특정 노드만 문제가 발생했는가?
| Pod | 노드 | 역할 |
|---|---|---|
| worker-app-hwl69 | ip-10-20-46-107 (문제 노드) | 실제 작업 처리 |
| worker-app-j99p6 | ip-10-20-32-96 (정상 노드) | 실제 작업 처리 |
Pod별 네트워크 수신량 Explorer (01:30~03:00 KST)
| 시간 (KST) | hwl69 (문제 노드) | j99p6 (정상 노드) | 비율 |
|---|---|---|---|
| 02:07 | 14 KB | 13 KB | 1:1 |
| 02:08 | 25 MB | 4 MB | 6:1 |
| 02:11 | 80 MB | 20 KB | 4000:1 |
update-accumulated-rank가 hwl69 Pod로 라우팅되어 해당 노드에서만 문제 발생.
6. PLEG → Node Ready 인과관계
Kubernetes 메커니즘
flowchart LR subgraph 정상 K1[kubelet] -->|1초마다| P1[PLEG] P1 --> H1[heartbeat] end subgraph 문제 K2[kubelet] -->|10초마다| P2[PLEG] P2 -.->|지연| H2[heartbeat 실패] end
왜 50초 grace period를 초과했는가?
| 시간 | relist 횟수/초 | 50초 동안 기회 | 상태 |
|---|---|---|---|
| 정상 | 1.0 | 50회 | heartbeat 충분 |
| 02:12~02:21 | 0.1 | 5회 | heartbeat 부족 |
PLEG가 10초씩 걸리면서 relist 횟수 자체가 90% 감소했다.
7. 메트릭 수집 실패
| 시간 (KST) | kubelet | cadvisor | node-exporter |
|---|---|---|---|
| 02:08 | Down | Up | Up |
| 02:13~02:29 | 불안정 | 불안정 | 불안정 |
| 02:37 이후 | Up | Up | Up |
그래프가 끊긴 것 자체가 문제의 심각성을 증명한다. 노드가 메트릭 수집 요청에도 응답 불가 상태였다.
8. Root Cause 인과관계 체인
flowchart TD A[update-accumulated-rank<br/>26분 소요] --> B[Disk I/O 46배] B --> C[iowait 58%] C --> D[kubelet 응답 지연] D --> E[PLEG 10초] D --> F[메트릭 수집 실패] E --> G[Node Flapping 6회]
update-total-step 완료(02:11) 후에도 update-accumulated-rank 완료(02:26)까지 문제 지속 → 단일 작업만으로도 Node 장애 유발.
9. 배제된 원인
| 가설 | 검증 결과 | 배제 이유 |
|---|---|---|
| CPU 병목 | 배제 | worker-app CPU 최대 0.38 cores (노드의 19%) |
| Memory Pressure | 배제 | 가용 메모리 충분 |
| Disk Pressure | 배제 | 디스크 용량 문제 없음 |
| 네트워크 문제 | 배제 | 다른 노드 정상 |
| 2개 작업 동시 실행이 필수 조건 | 배제 | 단독 실행 시에도 문제 지속 확인 |
10. 해결 방안
즉시 조치
- 분석 대시보드 생성: EAP-EKS Node Health Analysis
단기 조치: EBS IOPS/Throughput 증가
병목이 Disk I/O이므로 디스크 성능 향상이 근본 해결책이다.
IaC 변경 (2026-01-27)
/services/eap/test/variables.tf에서 worker 노드 그룹에 block_device_mappings 추가:
worker = {
instance_types = ["t4g.medium"]
# ... 기존 설정 ...
block_device_mappings = {
xvda = {
device_name = "/dev/xvda"
ebs = {
volume_size = 20
volume_type = "gp3"
iops = 6000
throughput = 250
}
}
}
}/services/eap/test/main.tf의 eks_managed_node_groups에 전달:
block_device_mappings = config.block_device_mappingsterraform plan 결과:
# module.eks.module.eks_managed_node_group["worker"].aws_launch_template.this[0]
+ block_device_mappings {
+ device_name = "/dev/xvda"
+ ebs {
+ iops = 6000
+ throughput = 250
+ volume_size = 20
+ volume_type = "gp3"
}
}
Plan: 0 to add, 2 to change, 0 to destroy.
| 설정 | 현재 | 변경 후 | 추가 비용 |
|---|---|---|---|
| IOPS | 3,000 | 6,000 | +$15/월 |
| Throughput | 125 MB/s | 250 MB/s | +$5/월 |
적용 방법:
cd /Users/nudge_947/gitRepo/SRE/Nudge-IaC/services/eap/test
terraform apply11. 대시보드 해석 가이드
지표 확인 순서
flowchart TD A[Node Ready?] -->|NotReady| B[PLEG Duration?] B -->|3초 이상| C[iowait?] C -->|30% 이상| D[어떤 컨테이너?]
임계값 기준
| 지표 | 정상 | 주의 | 위험 |
|---|---|---|---|
| Node Ready | 1 | - | 0 |
| PLEG Duration | <0.1초 | 1~3초 | >3초 |
| iowait | <5% | 10~30% | >30% |
| Scrape Target | 1 | - | 0 |
12. 교훈
- Node NotReady가 발생하면 PLEG Duration부터 확인해라. PLEG 지연이 있으면 kubelet 자체가 느린 것이고, CPU가 아닌 I/O 병목일 가능성이 높다.
- 노드 CPU 90% 이상을 보고 CPU 병목이라고 단정하지 마라. iowait 비중을 함께 확인해라. iowait이 높으면 CPU가 바쁜 게 아니라 I/O를 기다리고 있는 것이다.
- 컨테이너 CPU/메모리 limit이 정상이어도 Disk I/O는 노드 전체를 포화시킬 수 있다. 배치 작업처럼 대량 I/O를 유발하는 작업이 있다면 EBS IOPS를 넉넉히 잡아야 한다.
- 두 작업의 동시 실행을 의심했다면, 한 작업이 끝난 후에도 문제가 지속되는지 확인해라. 단독 실행 시에도 문제가 있다면 스케줄 분산이 아닌 작업 자체의 최적화가 필요하다.
13. 결론
| 항목 | 내용 |
|---|---|
| Root Cause | update-accumulated-rank 작업의 과도한 Disk I/O (26분간 지속) |
| 악화 요인 | update-total-step 동시 실행 (02:05~02:11) |
| 병목 | Disk I/O 포화 (iowait 최대 58%) |
| 결과 | PLEG 지연 → Node Ready Flapping 6회 |
| 해결 | EBS IOPS 3000 → 6000 증가 |
재발 방지
| 우선순위 | 조치 | 효과 |
|---|---|---|
| 1 | EBS IOPS 6000 증가 | I/O 병목 근본 해결 |
| 2 | update-accumulated-rank 작업 최적화 | 근본 원인 제거 |
| 3 | CronJob 스케줄 분산 | 동시 부하 방지 |
| 4 | worker-app 전용 노드 분리 | 다른 Pod 영향 차단 |
참고 자료
- 분석 대시보드
- Alert Rule
- Slack 스레드
- Node Status Conditions
- kubelet
-node-status-update-frequency: 기본 10초 - kube-controller-manager
-node-monitor-grace-period: 기본 50초