마루옵스서버 · 네트워크 · AI 기술 노트

소개문의

[쿠버네티스 학습 09] Pod 장애 진단과 노드 drain

TSUBAME 4.0 서버 랙 뒷면의 배선

참고 사진 · TSUBAME 4.0 서버 랙 뒷면의 배선 · 사진: Fukumoto / 원본 / CC BY-SA 4.0 · 목록용 크기 조정

전체 연재 안내 · 실습 파일 ZIP

Windows 11 · VMware · Rocky Linux Kubernetes 연재 09/10

Pod가 Pending이면 아직 컨테이너 로그가 없을 수 있습니다. CrashLoopBackOff라면 반복 재시작 전의 종료 사유와 로그를 봐야 해요. 기존 웹 앱을 유지한 채 별도의 실패 예제를 만들고, 상태에 맞춰 확인할 곳을 찾아보겠습니다. 마지막에는 워커 한 대를 비우고 복귀시킵니다.

1. 노드·Pod·이벤트 확인

kubectl get nodes -o wide
kubectl -n lab get deploy,pods,svc,pvc -o wide
kubectl -n lab get events --sort-by=.metadata.creationTimestamp

노드 전체인지 특정 네임스페이스나 Pod에 한정됐는지 먼저 나눕니다. 이벤트는 시간이 지나면 없어지고 컨테이너가 바뀌면 로그 조회 방식도 달라질 수 있어요. 장애 시각과 직전 변경을 함께 남깁니다. 장기 보관이 필요하면 Events와 컨테이너 로그를 중앙 저장소에 보내는 구성을 따로 마련합니다.

관찰주요 후보첫 확인
Pending자원 요청·taint·볼륨·선택 조건describe pod의 Events
ImagePullBackOff이미지 이름·레지스트리·인증·통신pull 실패 이벤트
CrashLoopBackOff앱 종료·설정·프로브·OOMlogs –previous, lastState
Running인데 접속 실패readiness·Service selector·정책·DNSEndpointSlice와 직접 통신
NotReadykubelet·런타임·CNI·디스크 압박describe node와 노드 journal

2. 잘못된 nodeSelector와 Pending

pending-demo.yaml에는 restricted 환경에서 생성할 수 있는 비특권 조건을 넣었습니다. 배치가 실패하는 이유는 존재하지 않는 노드 레이블입니다. 리소스 부족과 구분해 이벤트를 살펴봅니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: pending-demo
  namespace: lab
spec:
  replicas: 1
  selector:
    matchLabels:
      app: pending-demo
  template:
    metadata:
      labels:
        app: pending-demo
    spec:
      automountServiceAccountToken: false
      nodeSelector:
        lab.maruops.com/pool: missing
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: sleeper
          image: busybox:1.37.0
          command: [sh, -c, "sleep 3600"]
          resources:
            requests:
              cpu: 10m
              memory: 16Mi
            limits:
              cpu: 100m
              memory: 64Mi
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              drop: [ALL]
kubectl apply -f ~/k8s-lab/app/pending-demo.yaml
kubectl -n lab get pods -l app=pending-demo
kubectl -n lab describe pod -l app=pending-demo

이벤트에서 node selector 또는 affinity 조건을 만족하는 노드가 없다는 설명을 찾습니다. 아직 실행 노드가 정해지지 않았으므로 앱 로그보다 배치 조건을 먼저 봐야 합니다. 여기서는 의도적으로 넣은 조건만 제거합니다. 실제 전용 노드가 필요한 앱이라면 레이블을 없애기보다 배치 설계를 확인해야 합니다.

kubectl -n lab patch deployment pending-demo --type=merge \
  -p '{"spec":{"template":{"spec":{"nodeSelector":null}}}}'
kubectl -n lab rollout status deployment/pending-demo --timeout=120s
kubectl -n lab delete deployment pending-demo

3. 종료 사유·이전 로그·디스크

POD=$(kubectl -n lab get pods -l app=web -o jsonpath='{.items[0].metadata.name}')
kubectl -n lab describe pod "$POD"
kubectl -n lab logs "$POD" -c nginx --tail=100
kubectl -n lab get pod "$POD" -o jsonpath='{.status.containerStatuses[*].lastState}'
# 재시작 이력이 있는 컨테이너에만 이전 로그가 존재
# kubectl -n lab logs "$POD" -c nginx --previous --tail=100

종료 코드 137만으로 OOM을 확정하지 않습니다. lastState의 reason, limit, 노드 OOM 기록을 함께 봅니다. liveness로 재시작했다면 이벤트의 프로브 실패도 확인합니다. kubectl top에는 별도의 metrics-server가 필요합니다. kubeadm 기본 설치에 포함되지 않으므로 메트릭 API 오류를 앱 장애와 구분합니다.

# 문제가 있는 해당 노드에서 실행
sudo journalctl -u kubelet -b --since '-30 min' --no-pager
sudo journalctl -u containerd -b --since '-30 min' --no-pager
sudo crictl ps -a
df -h
df -i

용량이 남아도 inode가 차면 파일을 만들 수 없습니다. 로그와 이미지, 볼륨 중 어디가 늘었는지 확인하고 정리합니다. /var/lib/containerd나 /var/lib/etcd 전체를 삭제하면 실행 상태와 복구 자료까지 잃을 수 있습니다.

4. PDB와 drain

먼저 웹 Deployment 복제본이 2이고 두 Pod가 Ready인지 확인합니다. 7편의 단독 volume-check Pod와 8편의 테스트 Pod가 남아 있다면 실습 절차대로 삭제합니다. 아래를 web-pdb.yaml로 저장합니다.

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web-budget
  namespace: lab
spec:
  minAvailable: 1
  selector:
    matchLabels:
      app: web
kubectl apply -f ~/k8s-lab/app/web-pdb.yaml
kubectl -n lab get pdb
kubectl -n lab get pods -o wide
kubectl drain wk01 --ignore-daemonsets --timeout=180s
kubectl -n lab get pods -o wide

drain은 노드를 새 배치 대상에서 빼고 일반 Pod를 eviction으로 내보냅니다. DaemonSet은 --ignore-daemonsets로 남깁니다. PDB는 이런 자발적 중단에서 최소 가용 수를 지키는 데 도움을 줍니다. VM 전원 차단이나 디스크 고장을 막는 기능은 아니고, 롤링 업데이트의 가용성 설정도 별도로 관리합니다.

단독 Pod나 emptyDir 때문에 멈추면 소유자와 데이터 용도부터 확인합니다. --force, --delete-emptydir-data를 바로 붙이지 않습니다. local PV 데이터는 다른 워커로 자동 이동하지 않으므로 별도 계획이 필요합니다. 실습에서는 7편의 단독 Pod를 정리한 뒤 진행합니다.

5. 노드 복귀와 앱 응답

# wk01에서 실행
sudo reboot
# cp01에서 wk01이 돌아온 뒤 실행
kubectl wait --for=condition=Ready node/wk01 --timeout=300s
kubectl uncordon wk01
kubectl get nodes
kubectl -n lab get pods -o wide

uncordon은 새 배치를 허용할 뿐, 다른 워커로 옮겨간 Pod를 자동으로 되돌리지는 않습니다. 두 워커가 Ready라면 필요할 때 kubectl -n lab rollout restart deployment/web로 재생성해 분포를 확인할 수 있어요. 노드 복귀와 앱 readiness, Windows의 NodePort 응답을 모두 확인하고 작업을 마칩니다.

참고 자료

공식 문서와 배포 파일 확인 기준: 2026년 10월 18일. 출력은 완료 여부를 판단하기 위한 예시이며 환경별로 달라질 수 있습니다.


← 08편 · 10편 →

전체 10편 목차 펼치기

전체 연재 안내와 실습 기준

댓글

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

닉네임·이메일·댓글 내용·IP 주소·브라우저 정보는 댓글 게시 및 관리에 사용됩니다. 이메일과 접속 정보는 공개 댓글에 표시하지 않습니다. 개인정보 요청은 운영자 이메일로 보내 주세요. 개인정보처리방침