소개
Pending 또는 CrashLoopBackOff와 같은 Pod 상태는 근본 원인이 아니라 증상입니다. 효과적인 문제 해결은 객체 구성, 조건, 이벤트, 컨테이너별 상태, 로그, 실행 중인 컨테이너 내부 점검 순서로 일관되게 진행해야 합니다.
이 실습에서는 미리 준비된 두 가지 장애를 진단하고 수정한 다음, 정상적으로 동작하는 다중 컨테이너 Pod 를 점검합니다. 특정 수정 방법을 암기하는 것이 아니라, 여러 상황에 재사용할 수 있는 문제 해결 절차를 익히는 것이 목표입니다.
각 장애는 같은 순서로 처리합니다. 먼저 전체 상황을 넓게 확인하고, 하나의 객체로 범위를 좁힌 뒤 현재 상태와 이벤트를 읽습니다. 그다음 컨테이너별 근거를 확인하고, 마지막으로 해당 리소스를 관리하는 컨트롤러를 수정합니다. 이렇게 하면 무작위로 변경하는 일을 피하고, 왜 수정이 효과가 있었는지도 설명할 수 있습니다.
Pod 상태부터 확인하기
이 단계에서는 정상적으로 동작하는 워크로드와 조사가 필요한 워크로드를 구분합니다. 먼저 네임스페이스 전체를 확인합니다. -o wide 옵션은 익숙한 표 형식을 유지하면서 Pod IP 와 할당된 노드 같은 유용한 열을 추가합니다.
cd /home/labex/project/pod-troubleshooting
kubectl get deployments,pods -n practice-debug -o wide
정상 Pod 는 2/2 Running으로 표시되어야 합니다. 하나의 Deployment Pod 는 이미지 가져오기 문제를 나타내고, worker 는 반복적으로 재시작해야 합니다.
테스트 환경에서는 대체로 다음과 같은 결과가 표시됩니다. Pod 접미사, 생성 후 경과 시간, 재시작 횟수는 다를 수 있습니다.
NAME READY STATUS RESTARTS AGE
pod/broken-image-58758b4d69-c7j7g 0/1 ImagePullBackOff 0 33s
pod/crashy-worker-5557cfb974-xwpst 0/1 Error 2 33s
pod/web-with-sidecar 2/2 Running 0 33s
worker 의 현재 상태가 CrashLoopBackOff가 아니라 Error로 표시되어도 걱정하지 마세요. 컨테이너가 잠시 실행된 뒤 종료되고, Kubernetes 가 다시 시작하기 전 잠시 대기하므로 관찰하는 시점에 따라 표시 상태가 달라집니다. 이 컨테이너가 반복적으로 실패하고 있다는 점을 보여 주는 더 강한 단서는 계속 증가하는 RESTARTS 값입니다.
전체 Pod YAML 을 읽지 않고 컨테이너 수준의 대기 원인을 표시합니다.
kubectl get pods -n practice-debug -o custom-columns='POD:.metadata.name,PHASE:.status.phase,READY:.status.containerStatuses[*].ready,WAITING_REASON:.status.containerStatuses[*].state.waiting.reason,RESTARTS:.status.containerStatuses[*].restartCount'
[*]는“목록의 각 항목”을 의미하므로, 이 표는 다중 컨테이너 Pod 에도 그대로 사용할 수 있습니다. 먼저 이 화면을 확인하여 다음에 실행할 진단 명령을 결정하세요.
예를 들어 WAITING_REASON=ImagePullBackOff는 이미지 구성이나 레지스트리 접근 문제를 의심하게 합니다. worker 는 최근 시도가 Terminated 상태인 동안 대기 원인이 표시되지 않을 수 있습니다. 따라서 이 표는 출발점일 뿐 완전한 진단 결과는 아닙니다.
지금은 아무것도 수정하지 마세요. 이미지 장애와 재시작하는 worker 는 서로 다른 증상이며, 각기 다른 근거를 확인해야 합니다. 첫 번째 명령은 원인을 미리 단정하지 않으면서 조사 범위를 줄여 주어야 합니다.
이미지 가져오기 실패 진단하기
이 단계에서는 상태에서 이벤트까지 추적하며 이미지 장애를 진단합니다. 이후 명령이 같은 객체를 대상으로 실행되도록 생성된 Pod 이름을 변수에 저장합니다.
IMAGE_POD=$(kubectl get pods -n practice-debug -l app=broken-image -o jsonpath='{.items[0].metadata.name}')
echo "$IMAGE_POD"
kubectl describe pod "$IMAGE_POD" -n practice-debug
아래쪽에서 위쪽 방향으로 읽으세요. Events 섹션에는 Kubernetes 가 nginx:not-a-real-tag를 가져오지 못했다는 내용이 표시되어야 합니다. 구성된 이미지를 직접 확인합니다.
kubectl get pod "$IMAGE_POD" -n practice-debug -o jsonpath='{.spec.containers[0].image}{"\n"}'
kubectl events -n practice-debug --for "pod/${IMAGE_POD}"
이벤트는 어떤 작업이 실패했는지 설명하고, JSONPath 조회는 실패를 일으킨 원하는 이미지가 무엇인지 입증합니다. 두 가지를 함께 사용하면 일시적인 레지스트리 문제를 영구적으로 잘못된 태그 문제로 오인하는 일을 피할 수 있습니다.
테스트 환경에서 결정적인 내용은 대략 다음과 같았습니다.
Image: nginx:not-a-real-tag
State: Waiting
Reason: ErrImagePull
Failed to pull image "nginx:not-a-real-tag": ... manifest unknown
Error: ImagePullBackOff
ErrImagePull은 이미지 가져오기 시도가 실패했음을 기록합니다. ImagePullBackOff는 Kubernetes 가 같은 작업을 다시 시도하기 전에 대기 시간을 늘리고 있음을 의미합니다. manifest unknown 메시지와 잘못된 태그를 함께 보면 증상과 원인을 연결할 수 있습니다.
생성된 Pod 를 직접 수정하지 말고 Deployment 를 수정합니다. kubectl set image는 Pod 템플릿을 변경하므로 Deployment 컨트롤러가 장애가 발생한 Pod 를 새 Pod 로 교체합니다.
kubectl set image deployment/broken-image web=nginx:1.27-alpine -n practice-debug
kubectl rollout status deployment/broken-image -n practice-debug --timeout=60s
명령이 성공적으로 완료되면 다음과 같은 내용이 표시되어야 합니다.
deployment.apps/broken-image image updated
deployment "broken-image" successfully rolled out
기존 Pod 는 교체될 수 있는 임시 객체이고, Deployment 템플릿은 지속적으로 유지되는 원하는 상태입니다. 롤아웃이 끝나면 새로운 Pod 이름이 나타나고 Deployment 에는 사용 가능한 복제본이 하나 있다고 표시되어야 합니다.
현재 로그와 이전 로그 읽기
이 단계에서는 시작한 뒤 종료되는 컨테이너를 진단합니다. 해당 Pod 를 찾고 재시작 정보를 확인합니다.
WORKER_POD=$(kubectl get pods -n practice-debug -l app=crashy-worker -o jsonpath='{.items[0].metadata.name}')
kubectl get pod "$WORKER_POD" -n practice-debug
kubectl describe pod "$WORKER_POD" -n practice-debug
재시작하는 컨테이너에서 일반적인 kubectl logs는 현재 실행 시도의 로그를 읽습니다. --previous는 컨테이너 런타임에 현재 시도 직전에 종료된 실행 시도의 로그를 요청합니다.
kubectl logs "$WORKER_POD" -n practice-debug -c worker
kubectl logs "$WORKER_POD" -n practice-debug -c worker --previous || \
echo "The previous container log is no longer available; continue with the current log and describe output."
현재 또는 이전 로그에는 애플리케이션이 직접 출력한 다음 메시지가 나타나야 합니다.
configuration missing
새로운 실행 시도가 시작되기 전에 실패한 시도를 확인하기 어려운 경우 --previous가 특히 유용합니다. 다만 이는 영구적으로 보존된 기록이 아니라 과거 상태에 대한 근거입니다. 로컬 런타임이 해당 컨테이너 기록을 이미 삭제했을 수도 있습니다. unable to retrieve container logs가 표시되면 대체 메시지가 출력되는 것이 정상입니다. 사용 가능한 현재 로그와 describe 결과를 함께 확인하세요. describe의 Last State, Exit Code, Restart Count는 프로세스가 반복적으로 종료되었음을 여전히 입증합니다.
-c worker 옵션은 대상 컨테이너를 명시적으로 지정합니다. 실패하는 명령을 장시간 실행되는 루프로 바꾸어 컨트롤러를 수정합니다.
kubectl patch deployment crashy-worker -n practice-debug --type='json' -p='[{"op":"replace","path":"/spec/template/spec/containers/0/command","value":["sh","-c","echo worker-ready; while true; do sleep 30; done"]}]'
kubectl rollout status deployment/crashy-worker -n practice-debug --timeout=60s
긴 패치 명령이 복잡해 보여도 안쪽부터 읽으면 됩니다. path는 첫 번째 컨테이너의 command를 선택하고, op: replace는 변경 유형이 교체임을 나타내며, value는 교체할 명령 배열을 제공합니다. 변경하는 필드는 하나뿐이며 나머지 Deployment 설정은 그대로 유지됩니다.
JSON 패치는 Deployment Pod 템플릿의 특정 필드 하나만 정확히 교체합니다. 실제 운영 환경에서는 소스 매니페스트에도 같은 변경을 반영해야 합니다.
명령을 실행하면 원하는 상태는 즉시 변경되지만, Pod 교체와 준비 상태 전환은 비동기적으로 진행됩니다. 따라서 API 가 성공적으로 응답했다고 해서 워크로드가 정상이라고 단정할 수는 없습니다. 반드시 롤아웃 상태가 완료될 때까지 기다리세요.
다중 컨테이너 Pod 에서 대상 컨테이너 지정하기
이 단계에서는 서로 다른 역할을 담당하는 컨테이너가 포함된 Pod 를 점검합니다. 먼저 컨테이너 이름을 확인합니다.
kubectl get pod web-with-sidecar -n practice-debug -o jsonpath='{range .spec.containers[*]}{.name}{"\n"}{end}'
JSONPath 의 range는 컨테이너 목록을 순회합니다. 각 컨테이너의 로그를 독립적으로 읽습니다.
kubectl logs web-with-sidecar -n practice-debug -c reporter --tail=5
kubectl logs web-with-sidecar -n practice-debug -c web --tail=5
--tail=5는 마지막 다섯 줄만 출력하도록 제한합니다. reporter 는 일반적으로 reporter-alive 메시지를 반복해서 출력합니다. NGINX 는 시작 알림을 표시하거나 아직 요청 로그가 없을 수 있습니다. 아직 HTTP 요청을 보낸 클라이언트가 없기 때문에 둘 다 정상입니다.
web 컨테이너 내부에서 명령을 실행합니다.
kubectl exec web-with-sidecar -n practice-debug -c web -- nginx -v
테스트된 이미지에서는 다음과 같은 결과가 반환되었습니다.
nginx version: nginx/1.27.5
이미지가 갱신되면 세부 버전은 달라질 수 있습니다. 중요한 점은 web 컨테이너 내부에서 명령이 성공적으로 실행되었다는 사실입니다.
첫 번째 --는 kubectl 옵션의 끝을 나타냅니다. 그 뒤에 오는 모든 내용은 선택한 컨테이너 안에서 실행되는 명령입니다.
이 구분은 중요합니다. -c web은 kubectl 이 해석하는 옵션인 반면, nginx -v는 컨테이너 내부에서 실행됩니다. 다중 컨테이너 Pod 에서 -c를 생략하면 kubectl 이 기본 컨테이너를 선택할 수 있으므로, 의도하지 않은 프로세스의 근거를 확인하게 될 수 있습니다.
하나의 Pod 에 속한 컨테이너는 네트워크 네임스페이스를 공유하므로 BusyBox reporter 도 루프백 주소를 통해 NGINX 에 요청할 수 있습니다. wget -qO-에서 -q는 진행 메시지를 숨기고, -O-는 응답 본문을 표준 출력으로 보냅니다. 파이프는 이 본문을 head로 전달하고, head는 앞부분만 출력합니다.
kubectl exec web-with-sidecar -n practice-debug -c reporter -- wget -qO- http://127.0.0.1 | head
응답은 <!DOCTYPE html>로 시작해야 합니다. 이는 이후 과제에서도 재사용할 수 있는 진단 패턴입니다. 준비된 Pod 내부에서 간단한 HTTP 클라이언트를 실행하고, 외부 포트를 새로 노출하지 않은 채 애플리케이션의 동작을 확인하는 방법입니다.
여러 관점에서 복구 상태 확인하기
이 단계에서는 컨트롤러를 수정한 결과 정상적인 교체 Pod 가 생성되었는지 확인합니다. 컨트롤러의 가용성과 Pod 준비 상태를 함께 점검합니다.
kubectl get deployments,pods -n practice-debug
kubectl get pods -n practice-debug -o custom-columns='POD:.metadata.name,READY:.status.containerStatuses[*].ready,RESTARTS:.status.containerStatuses[*].restartCount,OWNER:.metadata.ownerReferences[0].name'
마지막으로 최근 경고 이벤트가 여전히 활성 장애를 나타내는지 확인합니다.
--field-selector type=Warning은 API 서버에 Warning 유형의 이벤트만 반환하도록 요청합니다. 이어서 --sort-by=.metadata.creationTimestamp는 결과를 오래된 이벤트부터 최신 이벤트 순으로 정렬하므로, 과거 장애와 현재 상태를 구분할 수 있습니다.
kubectl get events -n practice-debug --field-selector type=Warning --sort-by=.metadata.creationTimestamp
복구된 뒤에도 오래된 경고 이벤트는 남아 있을 수 있습니다. 현재 상태를 판단할 때는 현재 Pod 조건과 컨트롤러의 가용성을 기준으로 삼고, 이벤트는 시스템이 그 상태에 도달한 과정을 설명하는 자료로 활용하세요.
두 가지 수정을 마친 뒤 현재 상태 출력은 다음과 같은 형태여야 합니다.
NAME READY UP-TO-DATE AVAILABLE
deployment.apps/broken-image 1/1 1 1
deployment.apps/crashy-worker 1/1 1 1
NAME READY STATUS RESTARTS
pod/broken-image-... 1/1 Running 0
pod/crashy-worker-... 1/1 Running 0
pod/web-with-sidecar 2/2 Running 0
복구 후에도 이전의 Failed, BackOff, 또는 클러스터 시작 과정에서 발생한 초기 FailedCreatePodSandBox 경고가 남아 있을 수 있습니다. 객체 이름과 타임스탬프를 비교하세요. 해당 경고는 교체된 이전 Pod 를 가리키지만, 새 Pod 는 Running 상태이고 두 Deployment 모두 사용 가능한 상태입니다. 이벤트는 이력이지 실시간 상태 대시보드가 아닙니다.
최종 결론은 현재의 여러 근거를 종합해야 합니다. 두 Deployment 가 모두 사용 가능하고, 의도한 모든 컨테이너가 준비 상태이며, 이미지 가져오기 실패나 반복적인 충돌 상태에 남아 있는 활성 Pod 가 없어야 합니다. 문제 해결은 수정 명령을 실행하는 것으로 끝나는 것이 아니라, 복구 여부를 검증해야 완료됩니다.
요약
조건, 이벤트, 컨테이너 상태, 현재 로그 및 이전 로그를 사용하여 이미지와 시작 장애를 진단했습니다. 생성된 Pod 를 직접 수정하는 대신 컨트롤러 템플릿을 수정했으며, logs와 exec를 사용해 개별 컨테이너를 대상으로 점검하는 방법도 익혔습니다. 다음 과제에서는 안내 없이 이처럼 근거를 우선하는 문제 해결 절차를 적용하게 됩니다.


