운영 배포 직후 파드가 CrashLoopBackOff가 되었지만 팀원이 kubectl get, describe, logs의 차이를 몰라 같은 명령만 반복하고 있습니다. Kubernetes 장애 대응의 첫 5분은 대부분 kubectl 출력 읽기에서 결정됩니다. 기본 명령을 손에 익혀야 다음 단계의 진단이 가능합니다.
kubectl 기본 명령어
팀에 합류한 첫 날, 선임이 말했다. "Pod가 자꾸 죽는데, 로그 좀 봐줄 수 있어?" kubectl을 처음 본 신입은 화면을 뚫어져라 쳐다봤다. 명령어를 모르니 아무것도 할 수 없었다. Kubernetes는 아무리 강력한 플랫폼이어도, kubectl을 다루지 못하면 블랙박스일 뿐이다. kubectl은 Kubernetes를 조종하는 유일한 손이다. get으로 상태를 확인하고, describe로 원인을 찾고, logs로 앱 내부를 들여다보고, exec로 컨테이너 안으로 들어간다. 이 네 개 명령어만 능숙해도, 대부분의 Pod 장애는 10분 안에 원인을 찾을 수 있다.
매일 쓰는 kubectl 명령어를 체계적으로 익히고, 각 명령어가 장애 진단의 어느 단계에서 쓰이는지 파악합니다.
- 1kubectl get — 리소스 목록 조회와 출력 형식 조정 (-o wide, -o yaml, -o json)
- 2kubectl describe — 이벤트와 상세 정보로 장애 원인 파악
- 3kubectl logs — 컨테이너 로그 조회 (-f, --previous, --since)
- 4kubectl exec — 실행 중인 컨테이너 내부 접속
- 5kubectl apply/delete — 선언적 리소스 관리
- 6--dry-run=client -o yaml 패턴으로 YAML 템플릿 생성
minikube 사용 시: minikube start로 클러스터를 시작하세요. kubectl이 설치되어 있고 클러스터에 연결되어 있으면 바로 시작할 수 있습니다.
kubectl version --clientkubectl cluster-infokubectl config current-contextkubectl get allkubectl get — 상태 확인의 시작점
장애 대응 첫 5분, 아무것도 모르는 상황에서 가장 먼저 해야 할 일은 현재 상태를 파악하는 것입니다. 파드가 Running인지, 몇 개가 Ready인지, 어느 노드에 배치됐는지를 빠르게 확인해야 다음 단계로 진단을 좁힐 수 있습니다. kubectl get은 클러스터의 리소스 목록과 기본 상태를 한눈에 보여주는 가장 자주 쓰이는 명령어입니다. -o wide, -o yaml, -l 플래그를 조합하면 IP, 노드, 레이블까지 원하는 정보를 골라 볼 수 있습니다. 이 CB에서는 일상적인 상태 확인부터 장애 시 빠른 파악까지 kubectl get의 핵심 사용 패턴을 다룹니다.

기본 사용법
# Pod 목록
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# nginx-xxx 1/1 Running 0 5m
# broken-xxx 0/1 Error 3 2m
# 모든 네임스페이스
kubectl get pods -A # 또는 --all-namespaces
# NAMESPACE NAME READY STATUS RESTARTS
# kube-system coredns-xxx 1/1 Running 0
# default nginx-xxx 1/1 Running 0
# 특정 네임스페이스
kubectl get pods -n kube-system
# 여러 리소스 동시 조회
kubectl get pods,services,deployments
# 모든 리소스 타입 조회
kubectl get all
- NAME—조회 대상 리소스 이름이 예상한 대상과 일치하는지 확인합니다.
- STATUS/READY—Running, Ready, Available처럼 정상 상태를 나타내는 필드가 있는지 봅니다.
- RESTARTS/EVENTS—재시작 횟수나 Warning 이벤트가 증가하지 않는지 확인합니다.
출력 형식 조정
# 넓은 형식 (노드 IP 포함)
kubectl get pods -o wide
# NAME READY STATUS IP NODE
# nginx-xxx 1/1 Running 10.244.0.5 minikube
# YAML 형식 (전체 스펙 확인)
kubectl get pod nginx-xxx -o yaml
# JSON 형식 (스크립트에서 파싱 시)
kubectl get pods -o json
# 특정 필드만 추출
kubectl get pods -o jsonpath='{.items[*].metadata.name}'
# nginx-xxx broken-xxx
# 테이블 커스텀 컬럼
kubectl get pods -o custom-columns=NAME:.metadata.name,STATUS:.status.phase,NODE:.spec.nodeName
# NAME STATUS NODE
# nginx-xxx Running minikube
라벨 필터링
# 라벨로 필터
kubectl get pods -l app=nginx
kubectl get pods -l app=nginx,env=prod
# 라벨 표시
kubectl get pods --show-labels
# 실시간 감시 (상태 변화 추적)
kubectl get pods -w # watch
kubectl describe — 장애 원인의 보물창고
파드가 Pending인데 kubectl get만 보면 이유를 알 수 없습니다. 노드 자원 부족인지, 이미지 pull 실패인지, Affinity 조건 미충족인지 구분이 되지 않아 엉뚱한 방향으로 디버깅을 시작하게 됩니다. kubectl describe의 Events 섹션은 스케줄러와 kubelet이 파드에 무슨 일을 했는지 타임라인으로 기록합니다. "왜 그 상태인가"를 알려주는 핵심 도구입니다. 이 CB에서는 kubectl describe의 출력 구조와, Events 섹션에서 장애 원인을 빠르게 읽어내는 방법을 다룹니다. 특히 Events 섹션이 핵심입니다.
기본 사용법
kubectl describe pod <pod-name>
kubectl describe node <node-name>
kubectl describe deployment <deployment-name>
kubectl describe service <service-name>
describe 출력 구조 이해
kubectl describe pod nginx-xxx
# Name: nginx-xxx
# Namespace: default
# Priority: 0
# Node: minikube/192.168.49.2 ← 어느 노드에서 실행 중인지
# Start Time: ...
# Labels: app=nginx
# Status: Running
# IP: 10.244.0.5
#
# Containers:
# nginx:
# Image: nginx:alpine
# Port: 80/TCP
# State: Running
# Ready: True
# Restart Count: 0 ← 재시작 횟수 (높으면 문제)
# Limits: ...
# Requests: ...
# Liveness: http-get ... ← 헬스체크 설정
#
# Conditions:
# Type Status
# Initialized True
# Ready True ← Ready가 False면 문제
# ContainersReady True
# PodScheduled True
#
# Events: ← 여기가 핵심!
# Type Reason Age Message
# Normal Scheduled 5m Successfully assigned default/nginx-xxx to minikube
# Normal Pulling 5m Pulling image "nginx:alpine"
# Normal Pulled 4m Successfully pulled image
# Normal Created 4m Created container nginx
# Normal Started 4m Started container nginx
이벤트로 장애 진단
# 이미지 pull 실패 예시
# Events:
# Warning Failed 2m Failed to pull image "nginx:nonexistent": ...
# ImagePullBackOff
# 리소스 부족으로 스케줄 실패
# Events:
# Warning FailedScheduling ... 0/1 nodes available: 1 Insufficient memory.
# 네임스페이스 전체 이벤트 확인 (장애 시 빠른 파악)
kubectl get events --sort-by='.lastTimestamp'
kubectl get events --field-selector type=Warning # 경고만
kubectl logs, exec — 컨테이너 내부 들여다보기
파드가 Running인데 API가 500을 반환합니다. kubectl get과 kubectl describe에는 이상이 없고, 문제는 컨테이너 안에서 돌고 있는 애플리케이션에 있습니다. 이때 필요한 것이 kubectl logs와 kubectl exec입니다. logs는 앱이 stdout/stderr에 출력한 내용을 보여주고, exec는 컨테이너 내부로 직접 들어가 환경변수, 파일, 네트워크 연결을 확인합니다. CrashLoopBackOff 상황에서는 --previous 플래그로 죽기 직전 로그를 볼 수 있습니다. 이 CB에서는 로그 조회와 컨테이너 내부 접속의 핵심 패턴을 다룹니다.
kubectl logs — 앱 로그 확인
# 기본 로그 조회
kubectl logs <pod-name>
# 실시간 스트리밍
kubectl logs -f <pod-name>
# 마지막 N줄
kubectl logs --tail=100 <pod-name>
# 특정 시간 이후 로그
kubectl logs --since=1h <pod-name>
kubectl logs --since-time="2024-01-15T10:00:00Z" <pod-name>
# 이전 컨테이너 로그 (CrashLoopBackOff 시 필수)
kubectl logs --previous <pod-name>
# ← 현재 컨테이너가 죽기 전 로그를 봄
# 멀티 컨테이너 Pod에서 특정 컨테이너 로그
kubectl logs <pod-name> -c <container-name>
# 라벨 셀렉터로 여러 Pod 로그 동시
kubectl logs -l app=nginx --tail=50
kubectl exec — 컨테이너 내부 접속
# 인터랙티브 셸 접속
kubectl exec -it <pod-name> -- bash
kubectl exec -it <pod-name> -- sh # bash 없을 때
# 단일 명령어 실행 (셸 없이)
kubectl exec <pod-name> -- ls /app
kubectl exec <pod-name> -- cat /etc/nginx/nginx.conf
kubectl exec <pod-name> -- env | grep DB
# 멀티 컨테이너 Pod에서 특정 컨테이너
kubectl exec -it <pod-name> -c <container-name> -- bash
# 환경변수 확인 (설정 디버깅 시)
kubectl exec <pod-name> -- env
# 네트워크 확인 (내부에서 다른 서비스 접근 테스트)
kubectl exec -it <pod-name> -- curl http://other-service:8080/health
kubectl exec -it <pod-name> -- nslookup other-service
실습: 핵심 명령어 마스터하기
실습 1: 기본 CRUD 패턴
# 실습용 리소스 생성
cat <<EOF > /tmp/practice-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: practice-nginx
labels:
app: practice
env: dev
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
env:
- name: MY_ENV
value: "hello-k8s"
EOF
# 적용
kubectl apply -f /tmp/practice-pod.yaml
# pod/practice-nginx created
# 상태 확인
kubectl get pod practice-nginx -o wide
# 상세 확인
kubectl describe pod practice-nginx
# 라벨 필터링
kubectl get pods -l app=practice
실습 2: 로그와 exec 실습
# nginx 로그 확인
kubectl logs practice-nginx
# 실시간 로그 (다른 터미널에서 curl을 날리면서 확인)
kubectl logs -f practice-nginx
# 컨테이너 내부 접속
kubectl exec -it practice-nginx -- sh
# 내부에서 실행할 명령어들:
# ls /usr/share/nginx/html
# cat /etc/nginx/nginx.conf | head -20
# env | grep MY_ENV # MY_ENV=hello-k8s 확인
# exit
실습 3: --dry-run과 -o yaml 패턴
# 실제 생성 없이 YAML 생성 (템플릿 작성 시 유용)
kubectl run my-app --image=nginx:alpine --dry-run=client -o yaml
# apiVersion: v1
# kind: Pod
# metadata:
# creationTimestamp: null
# labels:
# run: my-app
# name: my-app
# spec:
# containers:
# - image: nginx:alpine
# name: my-app
# resources: {}
# ...
# 파일로 저장해서 수정 후 사용
kubectl run my-app --image=nginx:alpine --dry-run=client -o yaml > /tmp/my-app.yaml
# 기존 리소스의 YAML 추출 (현재 설정 확인)
kubectl get pod practice-nginx -o yaml > /tmp/practice-nginx-export.yaml
# 정리
kubectl delete pod practice-nginx
실습 4: kubectl apply vs create 비교
cat <<EOF > /tmp/test-cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: test-config
data:
key: "value1"
EOF
# apply: 없으면 생성, 있으면 업데이트
kubectl apply -f /tmp/test-cm.yaml
# configmap/test-config created
kubectl apply -f /tmp/test-cm.yaml
# configmap/test-config unchanged ← 이미 동일하면 변경 없음
# 내용 변경 후 재apply
sed -i 's/value1/value2/' /tmp/test-cm.yaml
kubectl apply -f /tmp/test-cm.yaml
# configmap/test-config configured ← 변경됨
# create는 이미 존재하면 에러
kubectl create -f /tmp/test-cm.yaml
# Error from server (AlreadyExists): configmaps "test-config" already exists
# 정리
kubectl delete configmap test-config
상황
kubectl logs my-pod
# Error from server (NotFound): pods "my-pod" not found
kubectl describe pod my-pod
# Error from server (NotFound): pods "my-pod" not found
분명히 배포했는데 Pod를 찾을 수 없다는 에러입니다.
진단: 네임스페이스 확인
# 1. 현재 컨텍스트의 네임스페이스 확인
kubectl config get-contexts
# CURRENT NAME CLUSTER AUTHINFO NAMESPACE
# * minikube minikube minikube (비어있으면 default)
# 2. 모든 네임스페이스에서 검색
kubectl get pods -A | grep my-pod
# production my-pod 1/1 Running 0 5m
# ↑ 'production' 네임스페이스에 있었음!
# 3. 네임스페이스를 명시해서 명령 실행
kubectl logs my-pod -n production
kubectl describe pod my-pod -n production
원인과 해결
| 원인 | 해결 |
|---|---|
| 다른 네임스페이스에 배포됨 | -n <namespace> 플래그 추가 |
| Pod가 실제로 없음 (배포 실패) | kubectl get events로 배포 오류 확인 |
| 이름 오타 | kubectl get pods -A | grep <partial-name> |
| 이미 종료되어 삭제됨 | Job/CronJob이면 정상 (--previous 로그 확인) |
기본 네임스페이스 변경 (자주 쓰는 네임스페이스가 있을 때)
# 현재 컨텍스트의 기본 네임스페이스 변경
kubectl config set-context --current --namespace=production
# 이후 -n 생략 가능
kubectl get pods # production 네임스페이스가 기본
# 다시 default로
kubectl config set-context --current --namespace=default
핵심 교훈: NotFound 에러의 80%는 네임스페이스 문제입니다. 항상 -A 플래그로 모든 네임스페이스를 먼저 확인하세요.
실무 시나리오: kubectl 장애 대응 플로우
팀장: "API 서버에서 500 에러가 납니다."
1단계: 빠른 상태 파악 (30초)
kubectl get pods -n production
# NAME READY STATUS RESTARTS AGE
# api-server-xxx 0/1 CrashLoopBackOff 5 10m
2단계: 이벤트로 원인 파악 (1분)
kubectl describe pod api-server-xxx -n production | grep -A20 Events
# Events:
# Warning BackOff ... Back-off restarting failed container
3단계: 로그로 앱 오류 확인 (2분)
# 현재 컨테이너 로그
kubectl logs api-server-xxx -n production --tail=50
# Error: Cannot connect to database at postgres:5432
# 이전 컨테이너 로그 (죽기 직전)
kubectl logs api-server-xxx -n production --previous | tail-20
4단계: 내부 진단 (필요시)
# DB 연결 확인
kubectl exec -it api-server-xxx -n production -- nc -zv postgres 5432
# postgres (5432:) open ← 연결 가능
# postgres (5432:) refused ← DB 다운
# 환경변수 확인
kubectl exec api-server-xxx -n production -- env | grep DB
총 소요 시간: 5분 이내에 "DB 연결 문제" 파악 완료.
kubectl get → describe → logs → exec 이 4단계 흐름이 K8s 장애 진단의 표준 패턴입니다.
다음 모듈 pod-lifecycle에서는 Pod가 Pending → Running → Succeeded/Failed로 전환되는 상태 머신을 이해하고, CrashLoopBackOff와 ImagePullBackOff 같은 실무에서 자주 만나는 에러를 체계적으로 진단합니다.