2026. 5. 9. 08:06ㆍk8s/EFK
한 번은 Elasticsearch Pending 문제를 복구한 뒤에도 cluster가 완전히 정상으로 돌아오지 않은 적이 있었다. 표면적으로는 리소스가 다시 올라왔고 custom resource도 Ready로 보였는데, 실제로는 특정 노드에 DiskPressure가 남아 있었고 Elasticsearch health도 yellow에서 멈춰 있었다.
이 글은 당시 내가 무엇을 먼저 오해했고, 어디서 판단을 바꿨고, 어떤 검증을 근거로 "이제 정말 끝났다"고 결론 내렸는지를 남기기 위한 기술 회고다. 공개용 문서이므로 노드명, IP, 도메인, 네임스페이스, 계정명, 내부 레지스트리 주소 같은 식별 정보는 모두 일반화했다.
시작점: Pending은 풀렸는데 왜 아직 불안했나
출발점은 직전 장애의 후속 확인이었다. 이전 이슈에서 Elasticsearch pod의 Pending 자체는 복구됐고, 그래서 처음에는 남은 yellow 상태도 shard 재배치가 끝나면 자연스럽게 정리될 것이라고 생각했다.
그런데 실제로는 두 가지가 같이 남아 있었다.
- Elasticsearch는 Ready로 보였다.
- 한 노드는 DiskPressure=True였고 cluster health는 yellow였다.
처음에는 이 둘을 같은 문제의 잔여 증상으로 묶어서 봤다. 즉 "Pending이 길었던 여파로 아직 재배치가 덜 끝났나 보다"라고 생각했다. 지금 돌아보면 이게 첫 번째 오해였다.
Pending 복구와 저장소 압박은 겹쳐 보일 수는 있어도 같은 원인일 필요는 없다. 그걸 너무 늦게 분리해서 본 것이 이번 회고의 시작점이다.
확인 순서를 바꾼 이유
판단이 바뀐 계기는 노드 루트 파일시스템 사용률이었다. 단순히 shard가 옮겨지는 중이라면 health가 잠시 흔들릴 수는 있어도, 노드 자체가 DiskPressure에 들어가는 현상까지 자연스럽게 설명되지는 않았다.
여기서부터는 시선을 스케줄링에서 저장소로 옮겼다. 순서는 의도적으로 이렇게 잡았다.
1. 노드가 실제로 왜 압박 상태인지 본다.
2. Elasticsearch가 어느 data node에서 디스크를 많이 쓰는지 본다.
3. 어떤 index 또는 data stream이 비정상적으로 큰지 본다.
4. 그다음에야 ILM과 rollover가 왜 멈췄는지 본다.
이 순서를 택한 이유는 간단했다. 처음부터 ILM 정책 이름이나 템플릿만 보면 "설정은 있는 것 같은데 왜 안 지워지지?"에서 오래 헤맬 수 있기 때문이다. 먼저 어디에 용량이 쌓였는지를 봐야, 나중에 정책 문제를 보더라도 맥락이 붙는다.
전환점 1: 문제는 재배치가 아니라 오래된 backing index 누적이었다
노드 상태와 Elasticsearch allocation을 같이 보니 그림이 훨씬 선명해졌다. local PV를 쓰는 data node 하나에서 사용량이 과도하게 커져 있었고, 큰 비중을 차지한 것은 로그성 data stream이었다.
특히 눈에 띈 것은 두 종류였다.
- metricbeat 계열 data stream
- 게임 서버 로그를 적재하던 minecraft-logs data stream
여기서 중요한 건 단순히 "인덱스가 크다"가 아니었다. 오래된 backing index가 계속 남아 있었고, 새 데이터가 들어오면서 전체 크기가 누적되는 구조가 보였다. 이 시점에서 문제를 "순간적인 트래픽 증가"가 아니라 "보관 정책 실패"로 보기 시작했다.
즉, 처음에 내가 따라가던 가설은 "cluster가 아직 덜 안정화됐다"였지만, 실제로는 "정리돼야 할 로그가 정리되지 않고 있었다"가 더 정확했다.
전환점 2: 둘 다 ILM 문제였지만 고장 방식은 달랐다
조금 더 들어가 보니 두 data stream은 결과는 비슷했지만 실패 방식이 서로 달랐다. 이 차이를 확인한 것이 가장 큰 판단 전환점이었다.
metricbeat: 정책은 있었지만 실행 권한이 깨져 있었다
처음 metricbeat를 봤을 때는 더 헷갈렸다. ILM policy 자체는 존재했기 때문이다. 그래서 한동안은 "정책이 있는데 아직 rollover 시점이 안 왔나" 같은 쪽으로 생각이 기울었다.
하지만 ILM explain을 확인하면서 상황이 달라졌다. rollover가 반복 실패하고 있었고, 오류는 권한 문제였다. 당시 핵심 오류는 이런 형태였다.
action [indices:admin/rollover] is unauthorized for API key
failed_step: check-rollover-ready
이 메시지를 보고 나서야 "정책 존재"와 "정책이 실제로 실행 가능함"은 완전히 다른 문제라는 점이 분명해졌다. metricbeat는 ILM이 없는 게 아니라, 과거 권한 상태로 저장된 실행 주체 때문에 rollover를 계속 못 하고 있었다.
minecraft 로그: 참조한 정책 이름은 있었지만 실제 policy는 없었다
반대로 minecraft-logs는 더 직접적인 오류였다. data stream 쪽 설정은 특정 ILM policy를 참조하고 있었지만, 정작 그 policy 객체가 클러스터에 존재하지 않았다.
오류는 아주 명확했다.
policy [minecraft-logs-ilm] does not exist
이 경우는 아예 정리 로직이 붙어 있지 않은 상태나 다름없었다. 데이터는 계속 쌓이는데 지워질 조건을 해석할 정책이 없으니, 오래된 backing index가 계속 남는다.
여기서 두 번째 오해도 정리됐다. 나는 처음에 둘 다 "로그가 많아서 디스크가 찼다" 정도로 뭉뚱그렸는데, 실제로는
- 하나는 "정책은 있으나 실행 실패"
- 다른 하나는 "정책 참조만 있고 실체 없음"
이라는 전혀 다른 실패였다.
왜 노드에서 임시 정리를 먼저 했는가
이 시점에는 근본 원인이 보였지만, 노드 압박도 동시에 풀어야 했다. 다만 여기서 조심한 것은 Elasticsearch local PV 아래 파일을 직접 지우지 않는 것이었다. 디스크가 급하다고 data path를 수동으로 건드리면 장애를 더 키울 가능성이 높다.
그래서 먼저 한 일은 운영체제 레벨에서 안전하게 줄일 수 있는 항목만 정리하는 것이었다.
- system journal 축소
- 사용하지 않는 container image 정리
- 패키지 캐시 정리
이 단계의 목적은 문제를 해결하는 것이 아니라, 노드가 DiskPressure에서 잠시 빠져나와 다시 운영 가능한 상태를 확보하는 것이었다. 실제로 이 조치 뒤에는 루트 파일시스템 사용률이 내려가고 DiskPressure=False로 돌아왔다.
중요했던 건 여기서 멈추지 않은 것이다. 이 상태만 보면 해결된 것처럼 느껴질 수 있지만, Elasticsearch 내부에는 여전히 오래된 backing index가 남아 있었고, 그대로 두면 같은 일이 다시 반복될 구조였다.
실제 해결: 보관 정책 재정의, rollover, 그리고 non-write backing index 삭제
근본 해결은 두 data stream의 보관 기준을 다시 명확히 두는 것이었다.
- rollover: 7일 또는 primary shard 10GB
- delete: 30일
metricbeat는 정책을 다시 써 넣은 뒤, 실패하던 ILM 단계를 재시도하고 rollover를 일으켰다. 그리고 새 write index가 생긴 것을 확인한 다음에 오래된 backing index를 삭제했다.
minecraft-logs는 누락된 policy를 만든 뒤 rollover를 수행하고, 더 이상 write index가 아닌 오래된 backing index들을 삭제했다.
여기서 내가 특히 신경 쓴 검증은 "삭제가 성공하느냐"가 아니라 "지워도 되는 대상을 고른 게 맞느냐"였다. 실제 작업 흐름은 아래와 같은 판단 순서를 따른다.
1. data stream의 현재 write index 확인
2. ILM policy 수정 또는 생성
3. rollover 수행
4. 새 backing index 생성 확인
5. 이전 non-write backing index 삭제
이 순서는 실제 작업 흐름을 일반화한 것이다. 환경마다 이름과 호출 방식은 다를 수 있고, 위 텍스트는 공개용 설명을 위한 예시다. 실제로 하지 않은 추가 조치나 자동화는 여기서 한 것처럼 적지 않았다.
무엇을 보고 "진짜 해결됐다"고 판단했나
이번에는 단순히 에러 메시지가 줄어든 것을 종료 기준으로 잡지 않았다. 대신 서로 다른 층위의 상태가 함께 좋아지는지를 확인했다.
- Elasticsearch cluster health가 green으로 돌아오는가
- custom resource phase가 계속 Ready인가
- 문제 노드의 DiskPressure가 해제됐는가
- node별 Elasticsearch disk allocation이 실제로 감소했는가
- 새 write index가 생성됐고 오래된 대형 backing index가 사라졌는가
이 중에서 특히 중요했던 것은 node별 disk 사용률 감소였다. 정책이 "설정돼 있다"는 것과, 실제로 디스크 누수가 멈췄다는 것은 다르다. 이번에는 후자까지 확인하고 나서야 종료할 수 있었다.
최종적으로 cluster health는 green으로 복구됐고, 문제 노드의 DiskPressure도 해제됐다. 그제야 직전 Pending 이슈와는 별개의 꼬리 문제까지 정리됐다고 볼 수 있었다.
이번 일에서 남은 교정 포인트
이번 사고는 복잡한 신기능 장애라기보다, 내가 상태를 너무 빨리 낙관적으로 해석한 사건에 가까웠다.
당시 내가 놓쳤던 포인트는 세 가지였다.
첫째, Pending이 풀렸다고 해서 storage pressure까지 같이 해결됐다고 보면 안 된다. 스케줄링 복구와 데이터 축적 문제는 별개의 축이다.
둘째, ILM은 정책 이름이 있느냐만 보면 안 된다. policy 존재, template 연결, rollover 조건, delete phase, 실행 권한이 모두 살아 있어야 실제 보관이 동작한다.
셋째, local PV를 쓰는 Elasticsearch에서는 인덱스 보관 정책이 곧 노드 운영 정책이다. Elasticsearch 안에서의 누적 문제가 곧 Kubernetes 노드 condition으로 드러난다.
다음에 비슷한 징후를 보면
이번 회고 이후로는 비슷한 상황에서 확인 순서를 이렇게 가져갈 생각이다.
1. Pending이나 yellow를 보더라도 바로 재배치 문제라고 단정하지 않는다.
2. node condition과 Elasticsearch allocation을 같이 본다.
3. 큰 index를 찾을 때는 현재 크기보다 오래된 backing index 누적 여부를 먼저 본다.
4. ILM은 정책 존재 여부와 실행 실패 원인을 분리해서 본다.
5. 디스크가 급해도 Elasticsearch data path는 직접 지우지 않는다.
이번 글의 목적은 정답을 미리 써 두는 것이 아니라, 당시 내가 어디서 막혔는지 나중에 다시 복원할 수 있게 만드는 것이다. 그 기준에서 보면 이번 사건의 핵심은 "명령어"보다 "잘못 묶어 본 가설을 언제 해체했는가"에 있었다.
Sources
- Original incident: docs/incidents/2026-04-25-elasticsearch-log-retention-disk-pressure.md
'k8s > EFK' 카테고리의 다른 글
| Elasticsearch Pod Pending 원인: local PV와 taint 충돌 해결 (1) | 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 |