트러블슈팅 방법론 — 장애를 체계적으로 진단하기
쿠버네티스 장애를 클러스터→노드→워크로드→Pod→컨테이너 계층으로 범위를 좁히는 사고법과, get→describe→logs→exec로 이어지는 진단 4종 세트, Events를 읽는 법, 그리고 추측 대신 증거로 원인을 찾는 체계적 트러블슈팅 방법론을 정리합니다.
지난 글까지 운영·비용·재해라는 클러스터 차원의 큰 그림을 그렸다. 이제 시야를 좁혀, 실제 장애가 눈앞에 닥쳤을 때의 이야기를 한다. “Pod가 안 떠요”, “서비스에 연결이 안 돼요” 같은 신고를 받았을 때, 경험 많은 운영자와 그렇지 않은 사람의 차이는 지식의 양보다 순서에 있다. 무엇을 먼저 보고 어떻게 범위를 좁히는지 — 이번 글은 개별 에러가 아니라 그 바탕이 되는 트러블슈팅 방법론을 다룬다.
추측하지 말고 좁혀라
가장 흔한 실수는 증상을 보자마자 원인을 추측하고 거기에 매달리는 것이다. “아마 메모리 문제일 거야” 하고 메모리만 파다가 정작 노드가 죽어 있었음을 한참 뒤에 발견한다. 체계적 트러블슈팅의 첫 원칙은 추측이 아니라 증거로 범위를 좁히는 것이다.
쿠버네티스는 계층 구조라 범위 좁히기에 유리하다. 클러스터 위에 노드가, 노드 위에 워크로드가, 워크로드 아래에 Pod가, Pod 안에 컨테이너가 있다. 위쪽 계층이 깨졌으면 아래쪽을 아무리 들여다봐도 소용없다. 그래서 위에서 아래로 훑으며 “어느 계층에서 문제가 시작됐는가”를 먼저 가린다.
예를 들어 Pod가 Pending이라면, Pod 로그를 보는 건 의미가 없다(아직 시작도 안 했으니까). 한 계층 위로 올라가 “스케줄될 노드가 있는가, 노드에 자원이 있는가”를 봐야 한다. 반대로 Pod가 계속 재시작한다면 노드는 멀쩡하다는 뜻이니, 아래로 내려가 컨테이너 로그를 본다. 증상이 어느 계층의 신호인지 읽는 것이 첫걸음이다.
진단 4종 세트 — get, describe, logs, exec
계층을 좁혔으면, 각 지점에서 던지는 질문은 거의 정해져 있다. kubectl의 네 가지 명령이 순서대로 점점 더 깊은 질문에 답한다.
get은 “무엇이 비정상인가”에 답한다. 상태를 한눈에 보고 이상한 놈을 찾는다. -o wide를 붙이면 어느 노드에 있는지, IP는 무엇인지까지 보인다.
describe는 “왜 그 상태인가”에 답한다. 출력 맨 아래의 Events 섹션이 핵심이다. 스케줄 실패, 이미지 풀 실패, 프로브 실패, OOM 같은 원인이 대부분 여기에 사람이 읽을 수 있는 문장으로 적혀 있다. 트러블슈팅의 8할은 사실 이 Events를 제대로 읽는 것에서 끝난다.
logs는 “애플리케이션이 뭐라고 하는가”에 답한다. 컨테이너가 크래시했다면 --previous로 죽기 직전의 로그를 봐야 한다. 현재 컨테이너는 막 재시작한 새것이라 원인이 안 보일 수 있다.
exec는 “안에서 직접 확인”이다. 컨테이너 내부에 들어가 설정 파일, 환경변수, 네트워크 연결을 직접 점검한다.
# 1) 무엇이 비정상인가 — 상태·노드·재시작 횟수
kubectl get pods -o wide
# 2) 왜 그런가 — 출력 끝의 Events를 정독
kubectl describe pod <pod>
# 3) 앱은 뭐라 하나 — 크래시면 직전 로그
kubectl logs <pod> --previous
# 4) 안에서 직접 — 설정·연결 점검
kubectl exec -it <pod> -- sh
Events — 가장 먼저 펼쳐야 할 단서
방법론에서 가장 강조하고 싶은 한 가지를 꼽으라면 Events다. 쿠버네티스의 컨트롤러들은 자신이 한 일과 실패를 Event로 남긴다. 특정 객체의 describe에 딸려 나오는 Events뿐 아니라, 네임스페이스 전체의 Events를 시간순으로 보면 “지금 이 클러스터에서 무슨 일이 벌어지고 있는가”가 한눈에 들어온다.
# 네임스페이스의 모든 이벤트를 최신순으로
kubectl get events --sort-by='.lastTimestamp'
# 경고만 추려서 보기
kubectl get events --field-selector type=Warning
Events는 기본적으로 일정 시간(보통 1시간) 뒤 사라진다. 그래서 사후 분석을 위해서는 다음 글들에서 다룰 로깅·이벤트 수집 체계가 필요하지만, 실시간 장애 대응에서는 이 명령들이 첫 단서다.
Pod의 상태 문자열을 단서로 읽기
kubectl get pods의 STATUS 열은 그 자체로 진단의 출발점이다. 자주 마주치는 상태들은 각각 다른 계층을 가리킨다.
- Pending — 아직 스케줄·시작 전. 노드 자원 부족, 스케줄링 제약(taint/affinity), PVC 미바인딩을 의심한다(노드·스케줄러 계층)
- ContainerCreating — 스케줄은 됐으나 시작 중. 길게 머물면 이미지 풀, 볼륨 마운트, Secret 누락을 본다
- CrashLoopBackOff — 컨테이너가 시작 후 반복해서 죽음. 앱 로그(
--previous)와 프로브 설정을 본다(컨테이너 계층) - ImagePullBackOff — 이미지를 못 가져옴. 이미지 이름 오타, 레지스트리 인증(pull secret)을 본다
- OOMKilled — 메모리 한계 초과로 종료. 메모리 limit과 실제 사용을 본다
- Terminating(지속) — 종료가 안 끝남. finalizer나 graceful 종료 지연을 본다
각 상태가 “어느 계층, 어떤 명령으로 가야 하는가”를 가리키는 이정표다. 다음 글들에서 이 상태들을 하나씩 깊게 파고들 예정이다.
변경을 의심하라 — “마지막으로 무엇이 바뀌었나”
기술적 진단과 별개로, 장애 대응에서 가장 강력한 질문은 종종 이것이다 — “마지막으로 무엇이 바뀌었나?” 잘 돌던 시스템이 갑자기 깨졌다면 십중팔구 직전의 변경(배포, 설정 변경, 인증서 만료, 노드 추가)이 원인이다. GitOps를 쓴다면 최근 커밋을, 배포 이력이 있다면 kubectl rollout history를 본다. 원인 추적보다 빠른 복구가 우선인 상황에서는 일단 마지막 정상 상태로 롤백하고 원인 분석은 그 뒤에 하는 것이 옳을 때가 많다.
# Deployment 변경 이력과 직전 버전으로 롤백
kubectl rollout history deployment/<name>
kubectl rollout undo deployment/<name>
정리 — 그리고 다음
트러블슈팅은 지식보다 순서다. 추측 대신 증거로, 클러스터→노드→워크로드→Pod→컨테이너 계층을 위에서 아래로 좁히고, 각 지점에서 get→describe→logs→exec를 순서대로 던진다. describe의 Events가 가장 빠른 단서이고, Pod의 상태 문자열이 어느 계층으로 갈지 알려주며, “마지막으로 무엇이 바뀌었나”는 늘 던져야 할 질문이다. 급할 때는 원인 분석보다 롤백이 먼저다.
이 방법론을 손에 쥐었으니, 이제 구체적인 증상들을 하나씩 깊이 들여다볼 차례다. 다음 글들에서는 kubectl 디버깅 도구부터 CrashLoopBackOff, ImagePullBackOff, OOMKilled, Pending 같은 단골 장애를 차례로 해부한다.
지난 글: 재해 복구 — 최악의 상황에 대비하는 DR 전략
읽어주셔서 감사합니다. 😊