Certified Kubernetes Application Developer (CKAD) #12 Observability: logging, kubectl debug, port-forward, ephemeral container
#11 Probes에서 liveness와 readiness로 앱의 건강 상태를 클러스터에 알리는 법을 익혔습니다. 그런데 probe가 실패하거나 Pod가 계속 재시작할 때, 그 안에서 무슨 일이 벌어지는지 직접 들여다봐야 원인을 찾습니다. CKAD 시험에서 관측(observability)은 비중이 15%인 도메인이지만, 다른 모든 문제를 풀다 막혔을 때 반드시 거쳐야 하는 길목이기도 합니다.
이번 글은 앱이 잘못 돌 때 꺼내 드는 도구를 명령 중심으로 정리하겠습니다. 로그를 읽고, 상태와 이벤트를 확인하고, 컨테이너 안으로 들어가고, 포트를 로컬로 끌어오고, 셸이 없는 컨테이너까지 디버깅하는 순서로 진행합니다.
로그: 가장 먼저 보는 곳 #
앱이 이상하면 가장 먼저 보는 것이 로그입니다. 컨테이너가 표준 출력(stdout)과 표준 에러(stderr)로 내보낸 내용을 kubectl logs가 그대로 보여 줍니다.
# 기본 로그
k logs mypod
# 실시간으로 따라가기 (-f = follow)
k logs -f mypod
# 마지막 50줄만
k logs mypod --tail=50
# 최근 10분 분량만
k logs mypod --since=10m-f는 로그를 실시간으로 따라가며, 앱이 요청을 처리하는 동안 무엇을 찍는지 지켜볼 때 씁니다. --tail과 --since는 로그가 길 때 필요한 부분만 잘라 봅니다.
재시작한 컨테이너의 이전 로그 #
Pod가 CrashLoopBackOff에 빠졌다면, 지금 떠 있는 컨테이너의 로그가 아니라 죽기 직전 컨테이너의 로그를 봐야 원인이 나옵니다. 이때 --previous를 씁니다.
# 직전에 종료된 컨테이너의 로그
k logs mypod --previous
# 짧게는 -p
k logs mypod -p크래시가 반복되는 Pod를 디버깅할 때 --previous는 실기에서 자주 쓰는 옵션입니다. 현재 컨테이너는 막 시작되어 로그가 비어 있는 경우가 많기 때문입니다.
멀티 컨테이너 Pod의 로그 #
한 Pod에 컨테이너가 여러 개라면 어느 컨테이너의 로그인지 -c로 지정해야 합니다. 지정하지 않으면 컨테이너를 골라 달라는 오류가 납니다.
# 특정 컨테이너만
k logs mypod -c sidecar
# 모든 컨테이너를 한 번에
k logs mypod --all-containers=true
# 라벨로 여러 Pod의 로그를 동시에
k logs -l app=web --all-containers=true --tail=20--all-containers는 Pod 안 모든 컨테이너의 로그를 합쳐서 보여 주고, -l로 라벨을 주면 그 라벨을 가진 여러 Pod의 로그를 한꺼번에 따라갈 수 있습니다.
상태와 이벤트: 로그에 안 찍히는 원인 #
로그는 컨테이너가 실행되기 시작한 뒤의 이야기입니다. 이미지 풀 실패, 스케줄링 실패, probe 실패처럼 컨테이너가 뜨기 전이나 클러스터 차원에서 벌어진 문제는 로그에 안 찍힙니다. 이때는 describe와 events를 봅니다.
# Pod의 상세 상태와 최근 이벤트
k describe pod mypodk describe pod의 출력 맨 아래 Events 섹션이 핵심입니다. Failed to pull image, Liveness probe failed, Insufficient memory 같은 메시지가 여기에 그대로 나옵니다. 컨테이너의 State, Last State, Exit Code, Reason도 함께 읽어 둡니다.
# 네임스페이스 전체 이벤트를 시간순으로
k get events --sort-by=.lastTimestamp
# 특정 Pod의 이벤트만
k get events --field-selector involvedObject.name=mypod--sort-by=.lastTimestamp를 붙이면 이벤트가 최신순으로 정렬되어, 방금 무슨 일이 있었는지 한눈에 들어옵니다. 정렬을 안 하면 순서가 뒤섞여 읽기 어렵습니다.
객체의 실제 정의 확인 #
매니페스트가 의도대로 적용됐는지 확신이 안 설 때는 클러스터에 저장된 실제 객체를 YAML로 꺼내 봅니다.
# 적용된 전체 정의를 YAML로
k get pod mypod -o yaml
# 특정 필드만 jsonpath로 (이미지, 상태 단계 등)
k get pod mypod -o jsonpath='{.spec.containers[*].image}'
k get pod mypod -o jsonpath='{.status.phase}'-o yaml은 기본값과 컨트롤러가 채운 필드까지 모두 보여 주므로, 내가 쓴 매니페스트와 실제 적용 결과의 차이를 확인할 때 유용합니다.
컨테이너 안으로 들어가기 #
로그와 상태로도 원인이 안 잡히면 컨테이너 안에 들어가 직접 확인합니다. kubectl exec로 실행 중인 컨테이너에서 명령을 돌립니다.
# 대화형 셸 열기 (-it = interactive + tty)
k exec -it mypod -- sh
# 한 줄 명령만 실행
k exec mypod -- env
k exec mypod -- cat /etc/config/app.conf
# 멀티 컨테이너면 -c로 컨테이너 지정
k exec -it mypod -c sidecar -- sh-- 뒤에 실행할 명령을 둡니다. 셸을 여는 대신 env로 환경 변수를 확인하거나 cat으로 마운트된 설정 파일을 읽는 식으로, 한 줄 명령만 던지는 편이 빠를 때가 많습니다. 컨테이너에 bash가 없으면 sh로 엽니다. 안에서는 보통 환경 변수가 ConfigMap,Secret에서 제대로 주입됐는지(env), 마운트된 volume이 기대한 경로에 있는지(ls, cat), 다른 Service에 닿는지(wget -qO- http://svc:80, nslookup svc)를 확인합니다.
포트 포워딩: 로컬에서 직접 테스트 #
클러스터 내부의 Pod 나 Service에 외부 노출 없이 접근해 보고 싶을 때 kubectl port-forward를 씁니다. 로컬 포트로 들어온 요청을 대상까지 그대로 전달합니다.
# Pod의 80 포트를 로컬 8080으로
k port-forward pod/mypod 8080:80
# Service 대상도 가능
k port-forward svc/web 8080:80
# Deployment를 대상으로 하면 그 뒤 Pod 하나로 연결
k port-forward deploy/web 8080:80이렇게 띄워 두고 다른 터미널에서 curl http://localhost:8080으로 응답을 확인합니다. Service의 selector나 targetPort가 잘못돼 트래픽이 안 닿는지, 아니면 앱 자체가 응답을 못 하는지 구분할 때 유용합니다. port-forward는 명령이 떠 있는 동안만 유지됩니다.
ephemeral container: 셸 없는 컨테이너 디버깅 #
요즘 운영 이미지는 보안과 크기를 위해 distroless처럼 셸도 디버깅 도구도 없는 형태가 많습니다. 이런 컨테이너에는 k exec -- sh가 통하지 않습니다. 셸 자체가 없기 때문입니다. 이때 kubectl debug로 ephemeral container를 붙입니다.
ephemeral container는 실행 중인 Pod에 임시로 추가되는 컨테이너입니다. 원본 컨테이너는 그대로 두고, 도구가 든 다른 이미지를 같은 Pod에 끼워 넣어 함께 들여다봅니다.
# busybox를 임시 컨테이너로 붙여 셸 열기
k debug -it mypod --image=busybox
# --target으로 대상 컨테이너의 프로세스 네임스페이스 공유
k debug -it mypod --image=busybox --target=app--target=app은 ephemeral container가 app이라는 원본 컨테이너의 프로세스 네임스페이스를 공유하게 합니다. 그러면 임시 컨테이너 안에서 원본 컨테이너의 프로세스(ps)와 파일을 들여다볼 수 있어, 셸 없는 컨테이너도 디버깅이 됩니다. 원본을 건드리지 않고 사본 위에서 실험하려면 --copy-to=mypod-debug로 복제본을 만들어 디버깅합니다.
노드 디버깅 #
Pod가 아니라 노드 자체를 들여다봐야 할 때는 노드에 디버그 Pod를 띄웁니다.
# 노드의 파일시스템을 /host에 마운트한 디버그 Pod
k debug node/node01 -it --image=busybox이 명령은 노드 파일시스템을 컨테이너의 /host 아래에 붙여 주므로, 노드의 로그나 설정 파일을 직접 확인할 수 있습니다.
리소스 사용량 보기 #
Pod가 OOMKilled로 죽거나 느려질 때는 CPU,메모리 사용량을 봐야 합니다. kubectl top이 실시간 사용량을 보여 줍니다.
# Pod별 사용량 (--containers로 컨테이너 단위까지)
k top pod
k top pod mypod --containers
# 노드별 사용량
k top nodek top은 클러스터에 metrics-server가 설치되어 있어야 동작합니다. 없으면 Metrics API not available 오류가 납니다. 실기 환경에는 보통 metrics-server가 준비되어 있지만, 직접 띄운 로컬 클러스터에서는 따로 설치해야 합니다. 사용량이 limits에 닿아 throttle되거나 OOM으로 죽는지 판단할 때 k top pod가 출발점입니다.
트러블슈팅 흐름 정리 #
도구를 따로 외우기보다 증상에서 시작하는 순서로 익히는 편이 시험에서 빠릅니다.
- 증상 확인:
k get pod로 상태를 본다.Pending,ImagePullBackOff,CrashLoopBackOff,Running이지만READY 0/1등 무엇인지 파악한다. - describe:
k describe pod mypod로Events와 컨테이너State,Exit Code를 읽는다. 스케줄링,이미지,probe 문제는 대개 여기서 드러난다. - logs: 컨테이너가 떴다면
k logs mypod로 앱 로그를 본다. 재시작 중이면--previous로 직전 로그를 본다. - exec / debug: 로그로도 안 잡히면
k exec -it mypod -- sh로 안에서 환경 변수,파일,연결을 확인한다. 셸이 없으면k debug로 ephemeral container를 붙인다. - port-forward / top: 네트워크 경로는
k port-forward로, 리소스 한계는k top으로 좁힌다.
각 단계는 앞 단계에서 원인이 안 나올 때만 다음으로 넘어갑니다. 대부분의 문제는 2단계 describe와 3단계 logs에서 끝납니다.
시험 포인트 #
--previous는 크래시 디버깅의 핵심입니다. CrashLoopBackOff Pod는 현재 컨테이너 로그가 비어 있으니 직전 로그를 봅니다.- 멀티 컨테이너에는
-c가 필수입니다. 지정하지 않으면 오류가 나거나 첫 컨테이너만 잡힙니다. - **
k get events --sort-by=.lastTimestamp**로 이벤트를 시간순 정렬해 방금 일어난 일을 빠르게 찾습니다. - **
k debug --image=... --target=...**의 형태를 기억합니다. distroless처럼 셸 없는 컨테이너는 exec로 못 들어가고 ephemeral container로 디버깅합니다. k port-forward의 대상 표기(pod/,svc/,deploy/)와로컬:대상포트 순서를 헷갈리지 않습니다.k top은 metrics-server 의존임을 기억합니다. 안 되면 도구가 아니라 환경 문제일 수 있습니다.- describe와 logs로 90%가 끝납니다. 막히면 가장 먼저 이 두 명령을 칩니다.
관측을 더 넓은 운영 맥락에서 보고 싶다면 K8s 실무 트랙의 트러블슈팅 편에서 클러스터 차원의 진단까지 함께 다룹니다.
정리 #
이번 글에서 잡은 것:
- 로그.
k logs의-f,--previous,-c,--tail,--since,--all-containers로 상황별 로그를 골라 봅니다. - 상태와 이벤트.
k describe pod의Events,k get events --sort-by=.lastTimestamp,k get pod -o yaml로 로그에 안 찍히는 원인을 찾습니다. - 안으로 들어가기.
k exec -it -- sh와 한 줄 명령으로 환경 변수,파일,연결을 확인합니다. - 포트 포워딩.
k port-forward로 노출 없이 로컬에서 직접 응답을 테스트합니다. - ephemeral container.
k debug --image=... --target=...으로 셸 없는 컨테이너와 노드까지 디버깅합니다. - 리소스 사용량.
k top pod,k top node로 CPU,메모리를 봅니다(metrics-server 필요). - 흐름. 증상 확인 → describe → logs → exec/debug → port-forward/top 순서로 좁혀 갑니다.
다음: ConfigMap과 Secret 깊이 #
앱을 들여다보는 도구는 갖췄습니다. 이제 그 앱에 설정과 비밀 값을 주입하는 가장 큰 도메인으로 들어갑니다.
#13 ConfigMap과 Secret 깊이: volume vs env, 자동 갱신에서는 ConfigMap과 Secret을 환경 변수로 넣을 때와 volume으로 마운트할 때의 차이, volume 마운트가 값 변경을 자동으로 반영하는 원리, Secret의 인코딩과 타입, 그리고 시험에서 자주 나오는 주입 형식까지 직접 만들어 보며 정리하겠습니다.