소개
이전 실습에서는 로그, 이벤트, kubectl exec, 포트 포워딩 및 프로브 (Probe) 에 대해 다루었습니다. CrashLooping(반복적인 충돌) 상태인 애플리케이션은 이러한 기술들을 종합적으로 활용해야 해결할 수 있습니다. 파드 (Pod) 가 시작되고 컨테이너가 종료되면 Kubernetes 는 이를 다시 시작합니다. 따라서 무언가를 변경하기 전에 먼저 증거를 수집해야 합니다.
이번 실습에서는 필수 환경 변수가 누락되어 종료되는 애플리케이션을 배포합니다. 재시작 동작을 관찰하고, 현재 및 이전 로그를 읽고, 이벤트를 검사하여 디플로이먼트 (Deployment) 에서 누락된 설정을 식별한 다음, 디플로이먼트를 수정하고 애플리케이션이 정상적으로 작동하는지 확인합니다.
CrashLoop 디버깅을 위한 네임스페이스 준비
이 단계에서는 CrashLoop 디버깅 연습을 위한 깨끗한 네임스페이스를 준비합니다.
/home/labex/project에서 시작합니다. 이 디렉토리에서 애플리케이션 매니페스트를 생성하고 업데이트하게 됩니다.
mkdir -p /home/labex/project
cd /home/labex/project
Kubernetes API 서버에 접근할 수 있는지 확인합니다. VM 이 막 시작되어 노드가 아직 준비되지 않았다면 몇 초 기다린 후 명령어를 다시 실행하세요.
kubectl get nodes --request-timeout=5s
예상 출력:
NAME STATUS ROLES AGE VERSION
labex-v135 Ready control-plane ... v1.35...
클러스터 네트워킹 파드가 준비될 때까지 기다립니다. 수정된 애플리케이션은 나중에 서비스를 통해 테스트됩니다.
kubectl wait --namespace kube-system --for=condition=Ready pod -l k8s-app=calico-node --timeout=120s
예상 출력:
pod/calico-node-... condition met
ckad-prep-crashloop라는 이름의 네임스페이스를 생성하고 현재 네임스페이스로 설정합니다:
kubectl create namespace ckad-prep-crashloop
kubectl config set-context --current --namespace=ckad-prep-crashloop
현재 네임스페이스를 확인합니다:
kubectl config view --minify --output 'jsonpath={..namespace}'; echo
예상 출력:
ckad-prep-crashloop
CrashLoop 배포 및 관찰
이 단계에서는 반복적으로 종료되는 애플리케이션을 배포합니다.
컨테이너 스크립트는 APP_MESSAGE 환경 변수를 필요로 합니다. 이 변수가 누락되면 스크립트는 명확한 오류를 출력하고 상태 코드 1로 종료됩니다. Kubernetes 는 컨테이너를 다시 시작하며, 이로 인해 디버깅이 필요한 CrashLoop 동작이 발생합니다.
crash-demo.yaml을 생성합니다:
cat > crash-demo.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: crash-demo
spec:
replicas: 1
selector:
matchLabels:
app: crash-demo
template:
metadata:
labels:
app: crash-demo
spec:
terminationGracePeriodSeconds: 0
containers:
- name: web
image: busybox:1.36
command:
- /bin/sh
- -c
- |
if [ -z "${APP_MESSAGE:-}" ]; then
echo "startup failed: APP_MESSAGE is required"
exit 1
fi
mkdir -p /www
echo "$APP_MESSAGE" > /www/index.html
httpd -f -p 8080 -h /www
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: crash-demo
spec:
selector:
app: crash-demo
ports:
- port: 80
targetPort: 8080
EOF
매니페스트를 적용합니다:
kubectl apply -f crash-demo.yaml
예상 출력:
deployment.apps/crash-demo created
service/crash-demo created
Kubernetes 가 실패한 컨테이너를 다시 시작할 시간을 잠시 준 뒤, 파드를 검사합니다:
sleep 12
kubectl get pods -l app=crash-demo
예상 출력에는 정상적이지 않은 파드가 포함됩니다. 타이밍에 따라 상태는 Error 또는 CrashLoopBackOff일 수 있습니다:
NAME READY STATUS RESTARTS AGE
crash-demo-... 0/1 CrashLoopBackOff ... ...
파드 이름을 저장하고 컨테이너 상태를 검사합니다. JSONPath 를 사용하면 전체 객체를 훑어볼 필요 없이 중요한 필드만 읽을 수 있습니다. 첫 번째 값은 재시작 횟수이고, 두 번째 값은 대기 이유입니다. 파드가 CrashLoopBackOff 상태가 되기 전 Error 상태일 때 확인하면 대기 이유가 비어 있을 수 있습니다.
CRASH_POD="$(kubectl get pods -l app=crash-demo -o jsonpath='{.items[0].metadata.name}')"
echo "$CRASH_POD"
kubectl get pod "$CRASH_POD" -o jsonpath='{.status.containerStatuses[0].restartCount}{" "}{.status.containerStatuses[0].state.waiting.reason}{"\n"}'
예상 출력에는 0보다 큰 재시작 횟수가 포함됩니다. 타이밍에 따라 이유는 비어 있거나 CrashLoopBackOff와 같은 값이 표시될 수 있습니다:
2
또는:
... CrashLoopBackOff
로그와 이벤트를 읽어 원인 파악하기
이 단계에서는 로그, 이전 로그 및 이벤트를 사용하여 컨테이너가 종료되는 이유를 식별합니다.
컨테이너가 빠르게 재시작될 때 kubectl logs는 현재 컨테이너 시도 내용을 보여주지만, kubectl logs --previous는 이전에 종료된 컨테이너의 시도 내용을 읽어옵니다. CrashLoop 디버깅 시 --previous는 유용한 시작 오류를 보여주는 경우가 많습니다.
현재 로그를 읽습니다:
kubectl logs "$CRASH_POD" --container web
예상 출력:
startup failed: APP_MESSAGE is required
이전 컨테이너 로그를 읽습니다. Kubernetes 가 마지막으로 종료된 컨테이너 로그를 보관하고 있다면, 이전 시도에서 발생한 동일한 시작 오류를 확인할 수 있습니다. 매우 빠르게 CrashLoop 가 발생하는 경우, 런타임이 이전 로그를 검색할 수 없다고 보고할 수도 있습니다. 이 경우에도 중요한 디버깅 포인트를 배울 수 있습니다. 즉, 가능할 때는 --previous를 사용하되, 항상 현재 로그 및 파드 이벤트와 비교해야 한다는 점입니다.
kubectl logs "$CRASH_POD" --container web --previous
예상 출력:
startup failed: APP_MESSAGE is required
다음과 유사한 메시지가 표시되어도 정상입니다:
unable to retrieve container logs for ...
파드에 대한 이벤트를 검사합니다. 이벤트는 Kubernetes 가 실패한 컨테이너를 다시 시작하는 과정을 보여줍니다.
kubectl describe pod "$CRASH_POD" | sed -n '/Events:/,$p'
예상 출력에는 BackOff 또는 Started와 같은 이벤트가 포함됩니다:
Warning BackOff ... Back-off restarting failed container
이제 디플로이먼트의 환경 설정을 검사합니다. env 목록이 누락되어 있으며, 이것이 로그 메시지의 원인입니다.
kubectl get deployment crash-demo -o jsonpath='{.spec.template.spec.containers[0].env}'; echo
예상 출력은 비어 있습니다:
디플로이먼트 수정 및 복구 확인
이 단계에서는 누락된 환경 변수를 수정하고 애플리케이션이 복구되는지 확인합니다.
오류 메시지에서 누락된 설정인 APP_MESSAGE를 확인했습니다. 컨테이너 명령어를 변경하는 대신 디플로이먼트에 필요한 환경 변수를 설정합니다. 파드 템플릿이 변경되므로 Kubernetes 는 새로운 레플리카셋 (ReplicaSet) 을 생성합니다.
환경 변수를 설정합니다:
kubectl set env deployment/crash-demo APP_MESSAGE=crashloop-fixed
예상 출력:
deployment.apps/crash-demo env updated
롤아웃이 완료될 때까지 기다립니다:
kubectl rollout status deployment/crash-demo --timeout=120s
예상 출력:
deployment "crash-demo" successfully rolled out
새로운 파드를 검사합니다. Running 및 Ready 상태여야 합니다:
kubectl get pods -l app=crash-demo
예상 출력:
NAME READY STATUS RESTARTS AGE
crash-demo-... 1/1 Running 0 ...
클라이언트 파드를 생성하여 서비스를 테스트합니다:
kubectl run crash-client --image=busybox:1.36 --restart=Never --command -- sleep 3600
kubectl wait --for=condition=Ready pod/crash-client --timeout=120s
kubectl exec crash-client -- wget -qO- http://crash-demo
예상 출력:
crashloop-fixed
마지막으로 디플로이먼트에 환경 변수가 포함되어 있는지 확인합니다:
kubectl get deployment crash-demo -o jsonpath='{.spec.template.spec.containers[0].env[0].name}{"="}{.spec.template.spec.containers[0].env[0].value}{"\n"}'
예상 출력:
APP_MESSAGE=crashloop-fixed
요약
이전 단계에서 소개된 증거 소스들을 사용하여 CrashLooping 애플리케이션을 디버깅했습니다. 실패한 파드 상태를 관찰하고, 현재 및 이전 컨테이너 로그를 읽고, 이벤트를 검사하고, 디플로이먼트에서 누락된 환경 변수를 식별한 뒤, kubectl set env를 사용하여 파드 템플릿을 수정하고 서비스를 통해 복구된 애플리케이션을 검증했습니다.


