2026. 5. 9. 08:38ㆍk8s/EFK
ECK로 운영 중인 Elasticsearch에서 data pod가 Pending에 머물고 custom resource는 ApplyingChanges 상태를 벗어나지 못하는 경우가 있다. 이때 ECK 설정만 보면 원인을 놓칠 수 있다. 실제 원인은 local PV의 node affinity와 Kubernetes node taint가 충돌해서 scheduler가 pod를 배치하지 못하는 상황일 수 있다.
아래는 이 문제를 빠르게 분리하기 위한 요약이다. 이후 본문에는 당시 확인했던 과정과 판단 변경 지점을 그대로 남겼다.
빠른 결론
ApplyingChanges는 ECK 관점의 상태이고,Pending은 scheduler 관점의 상태다.- data pod가
Pending이면 먼저 pod event를 확인한다. - local PV를 쓰는 경우 PV의 node affinity가 특정 노드로 pod를 묶는다.
- 해당 노드에 taint가 있고 pod에 toleration이 없으면 scheduler는 pod를 올릴 수 없다.
- ECK 설정 오류와 스케줄링 불가능 상태를 분리해서 봐야 한다.
먼저 볼 명령
kubectl describe pod <pod-name> -n <namespace>
kubectl get pv <pv-name> -o yaml
kubectl describe node <node-name>
describe pod의 event에서 taint, node affinity, volume node affinity conflict 같은 메시지가 보이면 ECK 설정보다 Kubernetes 스케줄링 조건을 먼저 확인하는 편이 빠르다.
관련 글
- Elasticsearch DiskPressure 회고: ILM 보관 정책으로 해결한 사례
- Kubernetes 노드 디스크 부족 해결: containerd 이미지와 로그 정리
- Kubernetes 현황판 Root Disk가 N/A일 때: Metricbeat와 Elasticsearch 연결 문제 해결
Kubernetes에서 ECK로 운영하던 Elasticsearch가 한동안 ApplyingChanges 상태에 머물렀고, 일부 data pod는 Pending에서 올라오지 못했다. 처음에는 ECK 설정 오류가 먼저 눈에 들어왔지만, 실제로 pod를 막고 있던 직접 원인은 따로 있었다.
이 글은 당시 무엇을 오해했고, 어디서 판단을 바꿨고, 어떤 검증을 근거로 복구를 마무리했는지를 남기기 위한 기술 회고다. 공개용 문서이므로 노드명, IP, 도메인, 네임스페이스, 계정, 내부 레지스트리 주소 같은 식별 정보는 모두 일반화했다.
시작점: ApplyingChanges와 Pending을 같은 층위로 보면 헷갈린다
장애 당시 표면 증상은 두 가지였다.
- Elasticsearch custom resource는 ApplyingChanges에 머물렀다.
- 일부 hot data pod는 Pending이었다.
겉으로 보면 둘 다 "Elasticsearch가 아직 정상화되지 않았다"는 하나의 문장으로 묶인다. 실제로 나도 초반에는 그렇게 봤다. 그런데 이 둘은 같은 시점에 보일 수는 있어도, 같은 원인일 필요는 없다.
이번 일에서 첫 번째로 막힌 지점이 바로 여기였다. ECK condition에 validation 오류가 보이니 그쪽을 먼저 고쳐야 한다고 생각하기 쉬웠다. 하지만 Pending은 scheduler가 pod를 어디에도 올리지 못하고 있다는 뜻이고, 이 문제는 ECK 설정 validation과는 별개로 먼저 확인해야 했다.
처음 확인한 것: 리소스 부족이 아니라 스케줄링 정책 충돌인지
우선 본 것은 pod 이벤트였다. 이유는 단순했다. Pending은 CPU나 메모리 부족일 수도 있지만, taint, affinity, PVC 제약처럼 scheduler 정책 문제일 때도 많기 때문이다.
당시 이벤트에서 핵심적으로 보인 메시지는 이런 종류였다.
FailedScheduling
had untolerated taint
Preemption is not helpful for scheduling
여기서 판단이 한 번 바뀌었다. 선점으로도 해결되지 않는다는 메시지가 붙어 있으면, 단순한 리소스 경쟁이 아니라 "배치 규칙 자체가 맞지 않는다"는 쪽으로 봐야 한다. 즉, 이 시점부터는 "무슨 리소스가 부족한가"보다 "이 pod가 원래 어디에 올라가야 하는가"를 보는 쪽이 맞았다.
전환점 1: PVC가 local PV에 묶여 있다는 사실
다음으로 확인한 것은 PVC와 PV였다. Elasticsearch data pod는 영속 볼륨에 묶여 있고, 특히 local PV를 쓰는 경우에는 storage가 곧 스케줄링 제약이 된다.
확인 결과, 문제 pod의 PVC는 특정 local PV에 바인딩되어 있었고, 그 PV에는 특정 노드만 허용하는 node affinity가 걸려 있었다. 공개용으로 일반화하면 구조는 이렇다.
PVC -> 특정 local PV에 바인딩
local PV -> 특정 worker node에만 연결 가능
여기서 상황이 꽤 명확해졌다. 이 pod는 "아무 노드에나 못 가는" 상태였다. 이미 storage 때문에 목적지가 거의 고정되어 있었고, 따라서 확인 범위도 그 노드 하나로 좁혀졌다.
전환점 2: local PV가 붙은 노드에 남아 있던 NoSchedule taint
PVC가 특정 노드로 pod를 사실상 고정하고 있다는 것을 확인한 뒤, 그 노드의 taint를 봤다. 거기서 실제 직접 원인이 나왔다.
문제 노드에는 불필요한 NoSchedule taint가 남아 있었고, Elasticsearch pod에는 그 taint를 허용하는 toleration이 없었다. 결과는 단순했다.
1. storage 때문에 pod는 특정 노드에 가야 함
2. 그런데 그 노드에는 NoSchedule taint가 남아 있음
3. pod는 toleration이 없음
4. 그래서 scheduler가 배치하지 못함
이번 장애에서 핵심은 이 충돌이었다. Pending의 직접 원인은 local PV와 taint 정책이 어긋난 것이었고, ECK condition 오류는 같은 시점에 보인 별도 문제였다.
내가 초반에 오해한 부분: ECK 오류가 먼저라고 본 것
incident 기록에는 ECK condition에서도 두 가지가 잡혀 있었다.
- xpack.security.enabled가 사용자 config에 포함되어 있어 forbidden validation이 발생한 점
- NodeSet의 memory request와 limit이 달라 resource-aware management 조건이 깨진 점
이 정보만 보면 "설정 오류부터 고치면 pod도 올라오겠지"라고 생각하기 쉽다. 나도 초반에는 그쪽이 더 근본 원인처럼 보였다. 하지만 실제로는 순서가 달랐다.
이 validation 오류들은 분명 정리해야 했지만, 문제 pod를 Pending에 묶어 둔 직접 이유는 아니었다. scheduler 이벤트, PVC 바인딩, PV node affinity, node taint를 같이 놓고 보니 우선순위가 분명해졌다.
즉, 이번 사고에서 중요한 판단 전환점은 이거였다.
- "ECK가 ApplyingChanges니까 operator 설정부터 고쳐야 한다"가 아니라
- "pod가 특정 노드에만 갈 수 있는데 그 노드가 정책상 막혀 있다"를 먼저 해결해야 했다.
실제 조치: taint 제거와 ECK spec 정상화를 분리해서 처리
실제 복구는 한 번에 끝난 것이 아니라 두 층위로 나눠서 진행됐다.
첫 번째는 스케줄링을 막는 직접 원인을 제거하는 일이었다. local PV가 붙은 노드에 잘못 남아 있던 taint를 제거했다. 이 조치의 의미는 workload에 새 toleration을 추가했다기보다, 해당 노드에 남아 있던 불필요한 격리 설정을 원래 의도에 맞게 되돌린 것이다.
두 번째는 ECK spec을 정상화하는 일이었다. 사용자 config에서 제거해야 하는 reserved 설정을 빼고, resource-aware management가 다시 성립하도록 NodeSet memory request와 limit을 맞췄다.
여기서 중요한 점은 둘을 섞어 생각하지 않는 것이었다.
- taint 제거는 Pending의 직접 원인 제거
- ECK spec 수정은 operator reconciliation을 정상화하기 위한 정리
둘 다 필요했지만, 같은 문제를 두 번 푸는 조치는 아니었다.
왜 StatefulSet replica 조정과 pod 재생성을 했는가
원인 조치 이후에는 hot StatefulSet replica를 복구하고, 오래 남아 있던 pod를 다시 생성했다. 이 부분은 단순 재시작이 목적이 아니라, 수정된 스케줄링 조건과 ECK spec이 실제 상태에 반영되도록 흐름을 정리하는 의미가 있었다.
이 조치를 할 때도 기준은 분명했다. "설정이 바뀌었다"만으로 끝내지 않고, 실제 pod가 다시 생성되어 올바른 노드에 배치되는지까지 봐야 했다.
무엇을 보고 해결됐다고 판단했나
이번에는 단순히 pod 하나가 뜨는 것만으로 종료하지 않았다. 복구 판단은 세 층위에서 같이 확인했다.
첫째, pod 배치가 정상인지 봤다. hot pod와 warm pod가 각각 의도한 노드에 Running으로 올라왔는지가 가장 직접적인 확인이었다.
둘째, Elasticsearch custom resource 상태를 봤다. ApplyingChanges에서 벗어나 Ready로 돌아왔는지, 그리고 data node 수가 기대한 값으로 복구됐는지를 확인했다.
셋째, ECK condition이 실제로 정리됐는지를 봤다. 당시 기준으로는 다음 조건들이 다시 정상이어야 했다.
- ReconciliationComplete=True
- RunningDesiredVersion=True
- ElasticsearchIsReachable=True
- ResourcesAwareManagement=True
이렇게 서로 다른 층위를 같이 봐야 "scheduler 문제만 잠깐 풀렸다"거나 "operator 조건만 좋아 보인다"는 착시를 피할 수 있었다.
이번 일에서 남긴 운영 기준
이번 장애는 복잡한 버그라기보다, 서로 다른 문제를 한 덩어리로 보면 복구 순서가 흐려진다는 점을 보여줬다.
다음에 비슷한 상황을 다시 보면 확인 순서는 이렇게 가져가는 편이 낫다.
1. Pending pod의 event부터 본다.
2. 선점 불가 메시지가 보이면 리소스 부족보다 정책 충돌을 먼저 의심한다.
3. Stateful workload라면 PVC와 PV binding을 확인한다.
4. local PV라면 node affinity와 실제 대상 노드의 taint를 같이 본다.
5. 그 다음에 ECK condition과 validation 오류를 별도 축으로 정리한다.
정리하면, 이번 사고의 직접 원인은 local PV node affinity와 node taint 충돌이었다. 하지만 실제로 오래 막혔던 이유는 Pending과 ApplyingChanges를 너무 오래 같은 문제처럼 본 데 있었다. 나중에 다시 비슷한 상황을 만나면, 그 둘을 먼저 분리해서 보는 것이 가장 큰 시간 절약 포인트가 될 것이다.
Sources
- Original incident: docs/incidents/2026-04-25-elasticsearch-pending.md
'k8s > EFK' 카테고리의 다른 글
| Elasticsearch DiskPressure 회고: Pending 복구 뒤에도 끝나지 않았던 로그 보관 문제 (0) | 2026.05.09 |
|---|---|
| Kubernetes 현황판 Root Disk가 N/A일 때: Metricbeat와 Elasticsearch 연결 문제 해결 (0) | 2026.04.25 |
| Elasticsearch DiskPressure 원인 분석: ILM 보관 정책으로 해결한 사례 (0) | 2026.04.25 |
| Elasticsearch Pod Pending 원인 분석: local PV와 taint 충돌 해결 (0) | 2026.04.25 |
| Elasticsearch Index Template 생성 방법 (API / UI) (0) | 2026.02.14 |