Skip to main content

이 장은 설명보다 "지금 당장 터미널에 붙여 넣을 수 있는 명령어"에 초점을 둔다. 쿠버네티스 운영 중에는 원리를 아는 것만큼이나 손이 기억하는 명령어가 중요하다. 특히 AI 워크로드를 운영할 때는 일반 웹 서비스보다 확인해야 할 층이 많다. Pod는 떠 있는지, 모델 서버는 준비됐는지, GPU는 노드에 보이는지, DCGM 메트릭은 올라오는지, Helm으로 설치한 GPU Operator나 KServe는 어떤 values로 배포됐는지 빠르게 확인해야 한다.

쿠버네티스 공식 문서도 자주 쓰는 kubectl 명령과 플래그를 치트시트 형태로 정리해 두고 있다.1 이 장은 그 흐름을 AI 인프라 운영자의 현장 상황에 맞게 다시 묶은 것이다. 모든 명령어는 그대로 복사해서 쓰되, 네임스페이스와 리소스 이름은 자신의 환경에 맞게 바꾸면 된다.

kubectl과 Helm 치트시트

먼저 셸을 편하게 만들기

장애 대응은 대부분 급한 상황에서 시작된다. 급할수록 긴 명령어를 매번 정확히 입력하기 어렵다. 먼저 자동 완성과 별칭을 설정해 두자.

# bash
source <(kubectl completion bash)
echo 'source <(kubectl completion bash)' >> ~/.bashrc

# zsh
source <(kubectl completion zsh)
echo '[[ $commands[kubectl] ]] && source <(kubectl completion zsh)' >> ~/.zshrc

# kubectl 별칭
alias k=kubectl
complete -o default -F __start_kubectl k 2>/dev/null || true

네임스페이스를 자주 오간다면 현재 컨텍스트에 기본 네임스페이스를 지정해 두는 편이 좋다.

kubectl config current-context
kubectl config get-contexts
kubectl config use-context <context-name>

# 현재 컨텍스트의 기본 네임스페이스 지정
kubectl config set-context --current --namespace=llm-serving

# 현재 설정 확인
kubectl config view --minify

운영 환경에서 가장 위험한 실수는 "다른 클러스터에 명령을 날리는 것"이다. 프로덕션과 스테이징 컨텍스트 이름이 비슷하면 더 위험하다. 중요한 명령어를 실행하기 전에는 kubectl config current-context를 치는 습관을 들이자. GPU 노드를 drain하거나 Helm release를 rollback하는 명령은 손가락보다 컨텍스트 확인이 먼저다.

클러스터 전체 상태 빠르게 보기

클러스터를 처음 열었을 때는 곧바로 특정 Pod 로그로 들어가기보다 큰 그림을 먼저 봐야 한다. 노드가 정상인지, 네임스페이스별로 어떤 Pod가 깨졌는지, 최근 이벤트에 경고가 쌓였는지 확인한다.

# 노드 요약
kubectl get nodes -o wide

# 모든 네임스페이스의 Pod 보기
kubectl get pods -A

# Running이 아닌 Pod만 보기
kubectl get pods -A --field-selector=status.phase!=Running

# 최근 이벤트를 시간순으로 보기
kubectl get events -A --sort-by=.metadata.creationTimestamp

# Warning 이벤트만 보기
kubectl events -A --types=Warning

# API 리소스 목록 확인
kubectl api-resources

# 특정 리소스 문서 확인
kubectl explain pod.spec.containers.resources

kubectl get pods -A는 넓은 지도다. 하지만 장애 원인을 찾는 도구는 아니다. Pod 목록에서 CrashLoopBackOff, ImagePullBackOff, Pending, OOMKilled 같은 단어를 발견했다면 바로 describe와 logs로 좁혀 들어가야 한다.

# Pod 상세 상태와 이벤트
kubectl describe pod <pod-name> -n <namespace>

# Deployment 상세
kubectl describe deploy <deploy-name> -n <namespace>

# Service와 Endpoint 확인
kubectl get svc,endpoints -n <namespace>

# ReplicaSet까지 함께 보기
kubectl get deploy,rs,pod -n <namespace> -o wide

AI 추론 서버에서는 Pod가 Running이어도 실제로 준비된 것은 아닐 수 있다. 컨테이너는 떠 있지만 모델 가중치를 로딩 중이거나, /health는 응답하지만 첫 토큰 생성이 매우 느릴 수 있다. 그래서 READY 컬럼과 readiness probe 이벤트를 꼭 같이 봐야 한다.

Pod 로그 확인 패턴

로그는 가장 빠른 단서다. 다만 LLM 서버의 로그는 길고 시끄럽다. vLLM, SGLang, KServe, Envoy, router, sidecar가 한 Pod 안에 같이 있을 수 있다. 먼저 컨테이너 이름을 확인한 뒤 필요한 컨테이너만 본다.

# Pod 안의 컨테이너 이름 확인
kubectl get pod <pod-name> -n <namespace> \
  -o jsonpath='{.spec.containers[*].name}{"n"}'

# 기본 로그
kubectl logs <pod-name> -n <namespace>

# 특정 컨테이너 로그
kubectl logs <pod-name> -c <container-name> -n <namespace>

# 최근 200줄만 보기
kubectl logs <pod-name> -n <namespace> --tail=200

# 실시간 따라가기
kubectl logs -f <pod-name> -n <namespace>

# 이전에 죽은 컨테이너 로그 보기
kubectl logs <pod-name> -n <namespace> --previous

# 특정 라벨의 모든 Pod 로그 보기
kubectl logs -n <namespace> -l app=vllm --tail=100

--previous는 CrashLoopBackOff를 볼 때 특히 중요하다. 현재 컨테이너는 막 재시작되어 로그가 비어 있을 수 있다. 진짜 에러는 직전에 죽은 컨테이너 로그에 남아 있다.

LLM 서버 로그에서 자주 찾는 문자열은 다음과 같다.

# OOM, CUDA, NCCL, 모델 로딩 오류 중심으로 필터링
kubectl logs <pod-name> -n <namespace> --tail=1000 | \
  grep -Ei 'oom|cuda|nccl|out of memory|error|exception|traceback|failed'

# vLLM 서버가 실제로 올라왔는지 확인
kubectl logs <pod-name> -n <namespace> --tail=300 | \
  grep -Ei 'Uvicorn|OpenAI|server|engine|ready|listening'

# Hugging Face 모델 다운로드 오류 확인
kubectl logs <pod-name> -n <namespace> --tail=500 | \
  grep -Ei 'huggingface|hf_token|401|403|not found|snapshot|download'

여러 Pod에서 동시에 오류가 나는 경우에는 라벨 기반으로 모아서 본다.

# 같은 앱의 최근 에러만 모아 보기
for p in $(kubectl get pod -n llm-serving -l app=vllm \
  -o jsonpath='{.items[*].metadata.name}'); do
  echo "===== $p ====="
  kubectl logs "$p" -n llm-serving --tail=200 | grep -Ei 'error|exception|oom|cuda' || true
done

리소스 사용량 확인

kubectl top은 metrics-server가 설치되어 있어야 동작한다. CPU와 메모리 문제를 빠르게 볼 때는 가장 먼저 쓰는 명령이다.

# 노드 사용량
kubectl top nodes

# 전체 Pod 사용량
kubectl top pods -A

# 특정 네임스페이스 Pod 사용량
kubectl top pods -n llm-serving

# 컨테이너별 사용량
kubectl top pod <pod-name> -n <namespace> --containers

# CPU 사용량 기준 정렬
kubectl top pods -A --sort-by=cpu

# 메모리 사용량 기준 정렬
kubectl top pods -A --sort-by=memory

토크나이저, API gateway, router는 CPU 병목이 자주 생긴다. 반대로 모델 서버는 GPU 메모리가 먼저 막히는 경우가 많다. kubectl top은 GPU 사용률을 보여주지 않으므로, GPU는 뒤에서 다룰 DCGM과 nvidia.com/gpu 리소스 확인 명령으로 따로 봐야 한다.

CPU throttling이 의심될 때는 Pod의 request와 limit도 함께 확인한다.

kubectl get pod <pod-name> -n <namespace> \
  -o jsonpath='{range .spec.containers[*]}{.name}{"nrequests: "}{.resources.requests}{"nlimits: "}{.resources.limits}{"nn"}{end}'

운영 중에는 watch를 붙여 상태 변화를 보는 일이 많다.

# 2초마다 Pod 상태 갱신
watch -n 2 'kubectl get pods -n llm-serving -o wide'

# 5초마다 리소스 사용량 보기
watch -n 5 'kubectl top pods -n llm-serving --sort-by=memory'

# Pending Pod가 사라지는지 보기
watch -n 2 'kubectl get pods -A | grep -E "Pending|CrashLoopBackOff|ImagePullBackOff|Error" || true'

Node 상태와 스케줄링 문제 확인

Pod가 Pending이면 대개 스케줄러가 놓을 자리를 찾지 못한 것이다. 일반적인 원인은 CPU·메모리 부족, taint/toleration 불일치, nodeSelector 또는 affinity 불일치, PVC 바인딩 실패, GPU 리소스 부족이다.

# Pending Pod 상세 이벤트 확인
kubectl describe pod <pod-name> -n <namespace>

# 노드 taint 확인
kubectl describe node <node-name> | grep -i taint -A2

# 모든 노드의 taint 요약
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints

# 노드 라벨 보기
kubectl get nodes --show-labels

# 특정 노드의 조건 보기
kubectl describe node <node-name> | grep -A8 Conditions

# 노드별 allocatable 리소스 보기
kubectl get node <node-name> -o jsonpath='{.status.allocatable}{"n"}'

GPU 노드는 일반 워크로드가 올라가지 않도록 taint를 걸어 두는 경우가 많다.

# GPU 노드에 taint 부여
kubectl taint node <gpu-node-name> nvidia.com/gpu=true:NoSchedule

# taint 제거
kubectl taint node <gpu-node-name> nvidia.com/gpu=true:NoSchedule-

이 taint가 있는 노드에 LLM Pod를 올리려면 Pod spec에 toleration이 있어야 한다.

 tolerations:
 - key: nvidia.com/gpu
   operator: Equal
   value: "true"
   effect: NoSchedule

GPU 모델별로 워크로드를 나누는 클러스터라면 node label이 중요하다. 예를 들어 7B 모델은 L4에, 70B 모델은 H100에 배치하고 싶을 수 있다.

# GPU 관련 라벨만 보기
kubectl get nodes --show-labels | grep -i nvidia

# 특정 라벨을 가진 노드만 보기
kubectl get nodes -l nvidia.com/gpu.present=true

# GPU 제품명 라벨 확인
kubectl get nodes -o custom-columns=NAME:.metadata.name,GPU_PRODUCT:.metadata.labels.nvidia.com/gpu.product

GPU 노드와 GPU Pod 확인

쿠버네티스는 기본적으로 CPU와 메모리는 알고 있지만 GPU는 device plugin이나 GPU Operator를 통해 확장 리소스로 등록된다. NVIDIA GPU Operator는 드라이버, 컨테이너 툴킷, device plugin, DCGM exporter 같은 GPU 운영 구성요소를 Helm으로 설치해 관리하는 대표적인 방식이다.2

먼저 노드가 GPU를 인식하는지 본다.

# 노드별 할당 가능한 NVIDIA GPU 수
kubectl get nodes \
  -o custom-columns=NAME:.metadata.name,GPU:.status.allocatable.nvidia.com/gpu

# jq로 보기 좋게 출력
kubectl get nodes -o json | jq -r '
  .items[] |
  [.metadata.name, (.status.allocatable["nvidia.com/gpu"] // "0")] |
  @tsv'

# 특정 노드에서 nvidia.com 리소스 확인
kubectl describe node <node-name> | grep -A10 -E 'Capacity|Allocatable|nvidia.com'

GPU를 요청한 Pod를 찾는 명령도 자주 쓴다.

# 전체 네임스페이스에서 GPU limit이 있는 Pod 찾기
kubectl get pods -A -o json | jq -r '
  .items[] as $pod |
  $pod.spec.containers[]? as $c |
  select($c.resources.limits["nvidia.com/gpu"] != null) |
  [$pod.metadata.namespace,
   $pod.metadata.name,
   $c.name,
   $c.resources.limits["nvidia.com/gpu"]] |
  @tsv'

# 어떤 노드에 GPU Pod가 올라갔는지 보기
kubectl get pods -A -o wide | grep -E 'vllm|sglang|kserve|llm|inference'

MIG를 쓰는 환경에서는 리소스 이름이 nvidia.com/gpu가 아니라 nvidia.com/mig-1g.10gb 같은 형태로 보일 수 있다.

# 노드 allocatable에서 MIG 리소스 확인
kubectl get nodes -o json | jq -r '
  .items[] |
  .metadata.name as $name |
  .status.allocatable |
  to_entries[] |
  select(.key | test("nvidia.com/mig")) |
  [$name, .key, .value] | @tsv'

# Pod가 요청한 모든 nvidia.com 리소스 보기
kubectl get pods -A -o json | jq -r '
  .items[] as $pod |
  $pod.spec.containers[]? as $c |
  ($c.resources.limits // {}) |
  to_entries[] |
  select(.key | startswith("nvidia.com/")) |
  [$pod.metadata.namespace, $pod.metadata.name, $c.name, .key, .value] |
  @tsv'

GPU가 노드에 있는데 Pod가 Pending이면 다음 순서로 확인한다.

# 1. Pod 이벤트에서 스케줄링 실패 이유 확인
kubectl describe pod <pod-name> -n <namespace>

# 2. 노드에 GPU allocatable이 있는지 확인
kubectl get nodes -o custom-columns=NAME:.metadata.name,GPU:.status.allocatable.nvidia.com/gpu

# 3. Pod가 요청한 GPU 수 확인
kubectl get pod <pod-name> -n <namespace> \
  -o jsonpath='{range .spec.containers[*]}{.name}{": "}{.resources.limits.nvidia.com/gpu}{"n"}{end}'

# 4. nodeSelector, affinity, tolerations 확인
kubectl get pod <pod-name> -n <namespace> -o yaml | \
  yq '.spec.nodeSelector, .spec.affinity, .spec.tolerations'

yq가 없다면 kubectl get pod ... -o yaml로 직접 확인해도 된다.

DCGM exporter 점검

GPU가 "있다"와 "잘 쓰고 있다"는 다르다. GPU 사용률, 메모리 사용량, 전력, 온도, XID 오류를 봐야 한다. NVIDIA DCGM exporter는 이런 지표를 Prometheus 형식으로 노출하는 대표적인 도구다.3

GPU Operator로 설치했다면 보통 gpu-operator 또는 gpu-operator-resources 네임스페이스에 관련 Pod가 있다.

# GPU Operator 관련 Pod 확인
kubectl get pods -n gpu-operator
kubectl get pods -n gpu-operator-resources

# DCGM exporter 찾기
kubectl get pods -A | grep -i dcgm

# DCGM exporter 로그
kubectl logs -n gpu-operator-resources -l app=nvidia-dcgm-exporter --tail=100

서비스가 노출되어 있다면 포트 포워딩으로 메트릭을 직접 확인한다.

# 서비스 이름 확인
kubectl get svc -A | grep -i dcgm

# 예시: dcgm-exporter 서비스 포워딩
kubectl port-forward -n gpu-operator-resources svc/nvidia-dcgm-exporter 9400:9400

# 다른 터미널에서 메트릭 확인
curl -s http://127.0.0.1:9400/metrics | head

# 자주 보는 GPU 지표 필터링
curl -s http://127.0.0.1:9400/metrics | \
  grep -E 'DCGM_FI_DEV_GPU_UTIL|DCGM_FI_DEV_FB_USED|DCGM_FI_DEV_FB_FREE|DCGM_FI_DEV_POWER_USAGE|DCGM_FI_DEV_XID_ERRORS'

XID 오류는 특히 중요하다. GPU 드라이버나 하드웨어 수준 문제의 단서일 수 있다.

curl -s http://127.0.0.1:9400/metrics | grep DCGM_FI_DEV_XID_ERRORS

Prometheus가 있다면 대략 이런 PromQL을 자주 쓴다.

# GPU 사용률
DCGM_FI_DEV_GPU_UTIL

# GPU 메모리 사용량 MiB
DCGM_FI_DEV_FB_USED

# GPU 전력 사용량
DCGM_FI_DEV_POWER_USAGE

# XID 오류
DCGM_FI_DEV_XID_ERRORS

LLM 서버 장애에서 "GPU 사용률이 낮다"는 항상 좋은 신호가 아니다. 요청이 없어서 낮을 수도 있지만, 토크나이저나 router가 CPU에서 막혀 GPU가 놀고 있을 수도 있다. 반대로 GPU 메모리가 가득 찼는데 사용률이 낮다면 KV 캐시나 모델 로딩 구조를 의심해야 한다.

LLM 서빙 Pod 확인

vLLM, SGLang, KServe 같은 모델 서버는 일반 웹 서버보다 준비 시간이 길다. 이미지 pull, 모델 다운로드, 가중치 로딩, CUDA 초기화, 엔진 준비까지 시간이 걸린다. 그래서 다음 명령어를 자주 쓴다.

# LLM 서빙 네임스페이스 전체 상태
kubectl get deploy,sts,svc,pod -n llm-serving -o wide

# readiness 상태 확인
kubectl get pod -n llm-serving \
  -o custom-columns=NAME:.metadata.name,READY:.status.containerStatuses[*].ready,RESTARTS:.status.containerStatuses[*].restartCount,PHASE:.status.phase,NODE:.spec.nodeName

# 특정 서비스의 endpoint 확인
kubectl get endpoints -n llm-serving <service-name> -o wide

# Pod 내부 health endpoint 확인
kubectl exec -n llm-serving <pod-name> -- curl -s localhost:8000/health

# 로컬 포트 포워딩 후 OpenAI 호환 endpoint 확인
kubectl port-forward -n llm-serving svc/<service-name> 8000:8000
curl -s http://127.0.0.1:8000/v1/models | jq

OpenAI 호환 API를 제공하는 모델 서버라면 간단한 completion 요청으로 end-to-end를 확인할 수 있다.

curl -s http://127.0.0.1:8000/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "<served-model-name>",
    "messages": [{"role": "user", "content": "Say hello in one sentence."}],
    "max_tokens": 32
  }' | jq

이 요청이 실패한다면 실패 지점을 나눠서 봐야 한다. Service가 없는지, endpoint가 비어 있는지, Pod는 떠 있지만 readiness가 실패하는지, 모델 서버는 떠 있지만 모델 이름이 다른지 순서대로 확인한다.

jsonpath로 필요한 값만 뽑기

장애 상황에서 YAML 전체를 보는 것은 피곤하다. jsonpath는 필요한 값만 빠르게 뽑아낼 때 유용하다.

# Pod 이름만 출력
kubectl get pods -n llm-serving -o jsonpath='{.items[*].metadata.name}{"n"}'

# Pod와 노드 매핑
kubectl get pods -n llm-serving \
  -o jsonpath='{range .items[*]}{.metadata.name}{"t"}{.spec.nodeName}{"n"}{end}'

# Pod IP 출력
kubectl get pods -n llm-serving \
  -o jsonpath='{range .items[*]}{.metadata.name}{"t"}{.status.podIP}{"n"}{end}'

# 컨테이너별 restart count
kubectl get pods -n llm-serving \
  -o jsonpath='{range .items[*]}{.metadata.name}{"t"}{range .status.containerStatuses[*]}{.name}{":"}{.restartCount}{" "}{end}{"n"}{end}'

# 이미지 목록
kubectl get pods -n llm-serving \
  -o jsonpath='{range .items[*]}{.metadata.name}{"t"}{.spec.containers[*].image}{"n"}{end}'

점이 들어간 키는 이스케이프해야 한다. 예를 들어 nvidia.com/gpu는 다음처럼 쓴다.

kubectl get nodes \
  -o jsonpath='{range .items[*]}{.metadata.name}{"t"}{.status.allocatable.nvidia.com/gpu}{"n"}{end}'

ConfigMap이나 Secret에서 점이 들어간 키를 꺼낼 때도 같은 원리다.

kubectl get configmap <name> -n <namespace> \
  -o jsonpath='{.data.ca.crt}'

Secret 값은 base64로 인코딩되어 있다. 운영 중에는 조심해서 다뤄야 한다.

# 특정 Secret key 디코딩
kubectl get secret <secret-name> -n <namespace> \
  -o jsonpath='{.data.token}' | base64 -d

# Secret의 key 목록만 보기
kubectl get secret <secret-name> -n <namespace> \
  -o jsonpath='{.data}' | jq 'keys'

jq로 복잡한 상태 분석하기

jsonpath가 짧은 추출에 좋다면, jq는 조건 필터링과 표 형태 출력에 좋다.

# 재시작이 0보다 큰 Pod 찾기
kubectl get pods -A -o json | jq -r '
  .items[] as $pod |
  $pod.status.containerStatuses[]? |
  select(.restartCount > 0) |
  [$pod.metadata.namespace, $pod.metadata.name, .name, .restartCount] |
  @tsv'

# OOMKilled 이력이 있는 Pod 찾기
kubectl get pods -A -o json | jq -r '
  .items[] as $pod |
  $pod.status.containerStatuses[]? |
  select(.lastState.terminated.reason == "OOMKilled") |
  [$pod.metadata.namespace, $pod.metadata.name, .name, .lastState.terminated.exitCode] |
  @tsv'

# ImagePullBackOff 또는 ErrImagePull Pod 찾기
kubectl get pods -A -o json | jq -r '
  .items[] as $pod |
  $pod.status.containerStatuses[]? |
  select(.state.waiting.reason == "ImagePullBackOff" or .state.waiting.reason == "ErrImagePull") |
  [$pod.metadata.namespace, $pod.metadata.name, .name, .state.waiting.reason, .state.waiting.message] |
  @tsv'

GPU 운영에서는 "누가 GPU를 요청하고 있는가"가 중요하다.

# 네임스페이스별 GPU 요청 합계 대략 보기
kubectl get pods -A -o json | jq -r '
  .items[] as $pod |
  $pod.spec.containers[]? |
  select(.resources.limits["nvidia.com/gpu"] != null) |
  [$pod.metadata.namespace, (.resources.limits["nvidia.com/gpu"] | tonumber)] |
  @tsv' | awk '{sum[$1]+=$2} END {for (ns in sum) print ns, sum[ns]}'

Pod가 어떤 owner에 의해 만들어졌는지도 자주 확인한다.

kubectl get pods -n llm-serving -o json | jq -r '
  .items[] |
  [.metadata.name,
   (.metadata.ownerReferences[0].kind // "none"),
   (.metadata.ownerReferences[0].name // "none")] |
  @tsv'

Deployment가 아니라 Job, StatefulSet, KServe InferenceService, RayCluster 같은 상위 리소스가 Pod를 만들 수 있다. Pod만 지워도 다시 살아난다면 owner를 찾아야 한다.

apply, diff, rollout 기본기

운영에서는 kubectl apply 전 kubectl diff를 보는 습관이 중요하다.

# 적용 전 차이 확인
kubectl diff -f manifest.yaml

# 적용
kubectl apply -f manifest.yaml

# 디렉터리 전체 적용
kubectl apply -f ./manifests

# URL에서 적용
kubectl apply -f https://example.com/manifest.yaml

Deployment 변경 후에는 rollout 상태를 본다.

# rollout 상태 확인
kubectl rollout status deploy/<deploy-name> -n <namespace>

# rollout 이력
kubectl rollout history deploy/<deploy-name> -n <namespace>

# 특정 revision 상세
kubectl rollout history deploy/<deploy-name> -n <namespace> --revision=3

# 이전 revision으로 롤백
kubectl rollout undo deploy/<deploy-name> -n <namespace>

# 특정 revision으로 롤백
kubectl rollout undo deploy/<deploy-name> -n <namespace> --to-revision=2

# 재시작
kubectl rollout restart deploy/<deploy-name> -n <namespace>

LLM 서버에서는 rollout이 오래 걸리는 것이 정상일 수 있다. 모델 로딩이 2분 걸리는 서비스에 readiness initialDelaySeconds가 5초라면, 쿠버네티스는 아직 준비 중인 컨테이너를 실패로 오해할 수 있다. rollout이 멈췄다면 probe 설정과 이벤트를 함께 보자.

kubectl describe deploy <deploy-name> -n <namespace>
kubectl get rs -n <namespace> -l app=<app-label>
kubectl describe pod <new-pod-name> -n <namespace>

임시 디버깅 Pod와 exec

컨테이너 안에 들어가 확인할 때는 exec를 쓴다.

# 셸 접속
kubectl exec -it <pod-name> -n <namespace> -- /bin/bash
kubectl exec -it <pod-name> -n <namespace> -- /bin/sh

# 특정 컨테이너 접속
kubectl exec -it <pod-name> -c <container-name> -n <namespace> -- /bin/bash

# 환경변수 확인
kubectl exec <pod-name> -n <namespace> -- env | sort

# DNS 확인
kubectl exec <pod-name> -n <namespace> -- nslookup kubernetes.default.svc.cluster.local

# 서비스 호출 확인
kubectl exec <pod-name> -n <namespace> -- curl -s http://<service-name>:8000/health

이미지가 distroless라서 셸이 없다면 kubectl debug를 쓴다.

# 실행 중인 Pod에 ephemeral container 붙이기
kubectl debug -it <pod-name> -n <namespace> \
  --image=busybox:1.36 \
  --target=<container-name> \
  -- sh

# 노드 디버깅
kubectl debug node/<node-name> -it --image=ubuntu -- bash

임시 네트워크 테스트 Pod도 유용하다.

# curl 테스트용 Pod
kubectl run curl -n llm-serving --rm -it \
  --image=curlimages/curl:8.10.1 \
  --restart=Never -- sh

# DNS 도구 Pod
kubectl run dnsutils -n llm-serving --rm -it \
  --image=registry.k8s.io/e2e-test-images/jessie-dnsutils:1.3 \
  --restart=Never -- bash

Helm 기본 명령어

Helm은 쿠버네티스 패키지 매니저다. GPU Operator, KServe, KEDA, vLLM production stack처럼 여러 리소스를 묶어 설치할 때 자주 쓴다. vLLM Helm 문서도 같은 차트를 여러 네임스페이스에 배포하면서 values를 덮어쓰는 패턴을 안내한다.4

먼저 저장소와 release를 확인한다.

# Helm 버전
helm version

# repo 목록
helm repo list

# repo 추가
helm repo add nvidia https://helm.ngc.nvidia.com/nvidia
helm repo add kedacore https://kedacore.github.io/charts
helm repo add kserve https://kserve.github.io/charts
helm repo update

# 설치된 release 보기
helm list -A

# 특정 네임스페이스 release 보기
helm list -n gpu-operator

차트를 설치할 때는 네임스페이스를 명시하고, values를 파일로 남기는 편이 좋다.

helm install <release-name> <repo/chart> \
  -n <namespace> \
  --create-namespace \
  -f values.yaml

이미 설치된 release를 바꾸거나 없으면 설치하려면 upgrade --install을 쓴다.

helm upgrade --install <release-name> <repo/chart> \
  -n <namespace> \
  --create-namespace \
  -f values.yaml

운영 환경에서는 다음 옵션을 자주 붙인다.

helm upgrade --install <release-name> <repo/chart> \
  -n <namespace> \
  --create-namespace \
  -f values.yaml \
  --atomic \
  --timeout 10m \
  --history-max 10

--atomic은 업그레이드가 실패하면 자동으로 롤백한다. GPU Operator나 KServe처럼 구성요소가 많은 차트에서는 유용하다. 다만 실패 원인을 확인하기 전에 리소스가 롤백될 수 있으므로, 처음 설치하는 테스트 환경에서는 일부러 빼고 관찰하는 것도 방법이다.

Helm values 확인과 override

Helm 운영에서 가장 중요한 질문은 "지금 이 release가 어떤 values로 설치됐는가"다.

# 사용자가 넣은 values 확인
helm get values <release-name> -n <namespace>

# 기본값까지 포함한 전체 values 확인
helm get values <release-name> -n <namespace> --all

# 설치된 manifest 확인
helm get manifest <release-name> -n <namespace>

# release 정보
helm status <release-name> -n <namespace>

# revision 이력
helm history <release-name> -n <namespace>

차트의 기본 values를 보고 싶다면 다음을 쓴다.

helm show values <repo/chart> > values.default.yaml
helm show chart <repo/chart>
helm show readme <repo/chart>

간단한 값은 --set으로 덮어쓸 수 있다.

helm upgrade --install gpu-operator nvidia/gpu-operator \
  -n gpu-operator \
  --create-namespace \
  --set driver.enabled=false

하지만 운영 환경에서는 --set이 길어질수록 추적이 어려워진다. 가능하면 values 파일로 남기자.

# gpu-operator-values.yaml 예시
driver:
  enabled: false

toolkit:
  enabled: true

devicePlugin:
  enabled: true

dcgmExporter:
  enabled: true
helm upgrade --install gpu-operator nvidia/gpu-operator \
  -n gpu-operator \
  --create-namespace \
  -f gpu-operator-values.yaml

이미 호스트에 NVIDIA 드라이버를 수동 설치한 베어메탈 환경에서는 driver.enabled=false를 쓰는 경우가 있다. 반대로 클라우드 GPU 노드에서 드라이버까지 Operator가 관리하게 하려면 driver.enabled=true가 맞을 수 있다. 중요한 것은 "우리 노드에서 드라이버를 누가 관리하는가"를 명확히 하는 것이다.

Helm diff와 template으로 미리 보기

가능하다면 운영 클러스터에 바로 helm upgrade를 치지 말고 렌더링 결과를 먼저 보자.

# 실제 적용 없이 manifest 렌더링
helm template <release-name> <repo/chart> \
  -n <namespace> \
  -f values.yaml > rendered.yaml

# 렌더링 결과를 kubeconform/kubeval 등으로 검증할 수 있음
kubectl apply --dry-run=server -f rendered.yaml

helm diff 플러그인을 설치해 두면 업그레이드 전 변경점을 볼 수 있다.

# 플러그인 설치
helm plugin install https://github.com/databus23/helm-diff

# 업그레이드 차이 확인
helm diff upgrade <release-name> <repo/chart> \
  -n <namespace> \
  -f values.yaml

GitOps를 쓰는 팀이라도 로컬에서 helm template을 보는 습관은 도움이 된다. 특히 KServe, KEDA, GPU Operator는 CRD, RBAC, Webhook, Controller, DaemonSet이 함께 생성되므로 "어떤 리소스가 생기는지"를 눈으로 확인하는 것이 좋다.

Helm rollback과 uninstall

Helm release가 잘못 올라갔다면 이력을 보고 롤백한다.

# revision 이력
helm history <release-name> -n <namespace>

# 직전 revision으로 롤백
helm rollback <release-name> -n <namespace>

# 특정 revision으로 롤백
helm rollback <release-name> 3 -n <namespace>

# 롤백 후 상태 확인
helm status <release-name> -n <namespace>
kubectl get pods -n <namespace>

삭제는 신중해야 한다.

helm uninstall <release-name> -n <namespace>

CRD를 설치하는 차트는 uninstall 후에도 CRD가 남을 수 있다. 이는 의도된 경우가 많다. CRD를 지우면 해당 CRD로 만든 모든 Custom Resource도 영향을 받을 수 있으므로, 운영에서는 함부로 지우지 않는다.

# CRD 확인
kubectl get crd | grep -E 'kserve|keda|nvidia|gateway|inference'

GPU Operator Helm 패턴

GPU Operator 설치 후에는 Pod와 노드 리소스를 확인한다.

helm repo add nvidia https://helm.ngc.nvidia.com/nvidia
helm repo update

helm upgrade --install gpu-operator nvidia/gpu-operator \
  -n gpu-operator \
  --create-namespace \
  -f gpu-operator-values.yaml

kubectl get pods -n gpu-operator
kubectl get pods -n gpu-operator-resources
kubectl get nodes -o custom-columns=NAME:.metadata.name,GPU:.status.allocatable.nvidia.com/gpu

문제가 생기면 Helm release와 Kubernetes 리소스를 같이 본다.

helm status gpu-operator -n gpu-operator
helm get values gpu-operator -n gpu-operator --all
kubectl get events -n gpu-operator --sort-by=.metadata.creationTimestamp
kubectl get events -n gpu-operator-resources --sort-by=.metadata.creationTimestamp

GPU Operator는 DaemonSet을 통해 노드마다 구성요소를 배포한다. GPU 노드 일부에서만 문제가 생기면 -o wide로 어느 노드의 Pod가 실패하는지 본다.

kubectl get pods -n gpu-operator-resources -o wide
kubectl describe pod <failed-pod> -n gpu-operator-resources
kubectl logs <failed-pod> -n gpu-operator-resources --tail=200

KServe와 KEDA Helm 패턴

KServe는 모델 서빙 추상화, KEDA는 이벤트 기반 오토스케일링에 자주 쓰인다. 설치 방식은 클러스터 정책에 따라 다르지만, Helm 운영 패턴은 비슷하다.

# KEDA
helm repo add kedacore https://kedacore.github.io/charts
helm repo update

helm upgrade --install keda kedacore/keda \
  -n keda \
  --create-namespace \
  --wait \
  --timeout 5m

kubectl get pods -n keda
kubectl get crd | grep keda

KEDA가 설치되면 ScaledObject와 TriggerAuthentication을 확인한다.

kubectl get scaledobject -A
kubectl describe scaledobject <name> -n <namespace>
kubectl get hpa -A | grep keda

KServe는 의존성이 있는 경우가 많다. Gateway API, cert-manager, Knative, Istio, model serving runtime 구성 등 팀마다 구성이 달라진다. 그래서 설치 전 values와 CRD를 꼭 확인해야 한다.

helm repo add kserve https://kserve.github.io/charts
helm repo update

helm show values kserve/kserve > kserve-values.default.yaml

helm upgrade --install kserve kserve/kserve \
  -n kserve \
  --create-namespace \
  -f kserve-values.yaml

kubectl get pods -n kserve
kubectl get crd | grep serving.kserve

KServe 리소스는 다음처럼 확인한다.

kubectl get inferenceservice -A
kubectl describe inferenceservice <name> -n <namespace>
kubectl get pods -n <namespace> -l serving.kserve.io/inferenceservice=<name>

vLLM Helm 패턴

vLLM Helm chart는 모델 서버 이미지, 모델 경로, GPU 수, PVC, readiness/liveness probe, autoscaling 같은 값을 values로 관리한다. vLLM 문서에서도 GPU 리소스, 모델 다운로드용 init 설정, probe, service port 등을 values 항목으로 제공한다.4

단일 GPU 모델 서버 values는 대략 이런 형태가 된다.

# vllm-values.yaml 예시
replicaCount: 1

image:
  repository: vllm/vllm-openai
  tag: latest
  command:
    - vllm
    - serve
    - meta-llama/Llama-3.1-8B-Instruct
    - --host
    - 0.0.0.0
    - --port
    - "8000"
    - --gpu-memory-utilization
    - "0.9"

resources:
  requests:
    cpu: 4
    memory: 16Gi
    nvidia.com/gpu: 1
  limits:
    cpu: 8
    memory: 24Gi
    nvidia.com/gpu: 1

readinessProbe:
  httpGet:
    path: /health
    port: 8000
  initialDelaySeconds: 120
  periodSeconds: 5

livenessProbe:
  httpGet:
    path: /health
    port: 8000
  initialDelaySeconds: 180
  periodSeconds: 10

설치와 확인은 다음 흐름이다.

helm upgrade --install vllm <chart-path-or-repo/chart> \
  -n llm-serving \
  --create-namespace \
  -f vllm-values.yaml

helm status vllm -n llm-serving
kubectl get pods,svc -n llm-serving
kubectl logs -n llm-serving -l app.kubernetes.io/instance=vllm --tail=200

멀티 GPU tensor parallel을 쓰면 GPU 수와 shared memory 설정을 함께 봐야 한다.

# GPU 요청 확인
kubectl get pods -n llm-serving -o json | jq -r '
  .items[] |
  [.metadata.name,
   (.spec.containers[]?.resources.limits["nvidia.com/gpu"] // "0")] |
  @tsv'

# /dev/shm 볼륨 확인
kubectl get pod <pod-name> -n llm-serving -o yaml | grep -A20 'mountPath: /dev/shm'

LLM 서버 Helm values에서 자주 바꾸는 값은 image.tag, model, tensorParallelSize, gpu-memory-utilization, max-model-len, readinessProbe.initialDelaySeconds, resources.limits.nvidia.com/gpu, pvcStorage다. 모델이 바뀌면 GPU 메모리 요구량과 readiness 시간이 함께 바뀐다.

자주 만나는 상태별 명령어

CrashLoopBackOff를 보면 다음 순서로 본다.

kubectl describe pod <pod> -n <ns>
kubectl logs <pod> -n <ns> --previous --tail=200
kubectl get pod <pod> -n <ns> -o json | jq '.status.containerStatuses[] | {name, restartCount, lastState}'

ImagePullBackOff는 이미지 이름, 태그, registry 인증, imagePullSecret을 본다.

kubectl describe pod <pod> -n <ns>
kubectl get pod <pod> -n <ns> -o jsonpath='{.spec.imagePullSecrets}{"n"}'
kubectl get secret -n <ns>

Pending은 스케줄링 이벤트가 핵심이다.

kubectl describe pod <pod> -n <ns> | sed -n '/Events:/,$p'
kubectl get nodes -o wide
kubectl get nodes -o custom-columns=NAME:.metadata.name,GPU:.status.allocatable.nvidia.com/gpu,TAINTS:.spec.taints

OOMKilled는 메모리 limit, 로그, 사용량을 함께 본다.

kubectl describe pod <pod> -n <ns> | grep -i oom -A5 -B5
kubectl logs <pod> -n <ns> --previous --tail=200
kubectl top pod <pod> -n <ns> --containers
kubectl get pod <pod> -n <ns> -o json | jq '.spec.containers[] | {name, resources}'

Service가 연결되지 않으면 selector와 endpoint를 본다.

kubectl get svc <svc> -n <ns> -o yaml
kubectl get endpoints <svc> -n <ns> -o yaml
kubectl get pods -n <ns> --show-labels

Ingress나 Gateway 뒤에서 502가 나면 backend endpoint와 controller 로그를 같이 본다.

kubectl get gateway,httproute -A
kubectl describe httproute <route> -n <ns>
kubectl get endpoints -n <ns>
kubectl logs -n <gateway-namespace> -l app=<gateway-controller-label> --tail=200

PVC가 Pending이면 StorageClass와 이벤트를 본다.

kubectl get pvc -A
kubectl describe pvc <pvc> -n <ns>
kubectl get storageclass
kubectl get events -n <ns> --sort-by=.metadata.creationTimestamp

위험한 명령어는 한 번 더 확인하기

다음 명령어들은 운영 환경에서 특히 조심해야 한다.

# Pod 강제 삭제
kubectl delete pod <pod> -n <ns> --force --grace-period=0

# 노드 drain
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data

# 노드 cordon / uncordon
kubectl cordon <node>
kubectl uncordon <node>

# 네임스페이스 삭제
kubectl delete namespace <ns>

# Helm release 삭제
helm uninstall <release> -n <namespace>

# CRD 삭제: 매우 주의
kubectl delete crd <crd-name>

강제 삭제는 "고장 난 것을 치운다"는 느낌이지만, 실제로는 상태를 잃거나 스토리지 attach 문제가 생길 수 있다. StatefulSet, 데이터베이스, 벡터 DB, 모델 캐시 PVC가 연결된 Pod는 특히 조심해야 한다.

AI 도구와 함께 쓰기

2026년에는 kubectl-ai 같은 도구로 자연어 질문을 kubectl 명령으로 바꾸거나 클러스터 상태를 분석하는 흐름도 점점 흔해지고 있다. kubectl-ai는 Gemini, Vertex AI, Azure OpenAI, OpenAI, Bedrock, Ollama 같은 여러 LLM provider를 지원하며, 자연어 요청을 실제 Kubernetes 작업으로 변환할 수 있다.5

# 설치 예시
curl -sSL https://raw.githubusercontent.com/GoogleCloudPlatform/kubectl-ai/main/install.sh | bash

# Gemini API 키 설정 예시
export GEMINI_API_KEY=<your-api-key>

# 자연어로 조회
kubectl-ai --quiet "list pods in llm-serving namespace"

# 로그 설명 요청
kubectl logs <pod> -n <namespace> --tail=100 | \
  kubectl-ai --quiet "explain these Kubernetes logs and suggest next commands"

다만 운영 환경에서 AI 도구를 쓸 때는 두 가지 원칙을 지켜야 한다. 첫째, 읽기 명령과 쓰기 명령을 구분한다. 둘째, AI가 제안한 명령을 그대로 실행하지 말고 컨텍스트, 네임스페이스, 대상 리소스를 확인한다. 특히 delete, patch, scale, rollback, helm upgrade는 사람이 최종 승인해야 한다.

나만의 운영 스니펫 만들기

치트시트의 마지막 단계는 자신의 클러스터에 맞게 줄이는 것이다. 모든 팀이 같은 명령어를 쓰지 않는다. 어떤 팀은 KServe를 쓰고, 어떤 팀은 vLLM Deployment를 직접 관리한다. 어떤 팀은 GPU Operator가 드라이버까지 관리하고, 어떤 팀은 베어메탈 노드에 드라이버를 직접 설치한다.

다음처럼 팀 공용 스니펫 파일을 만들어 두면 좋다.

# ~/.k8s-ai-ops.sh 예시
alias k=kubectl
alias kgp='kubectl get pods'
alias kgpa='kubectl get pods -A'
alias kgn='kubectl get nodes -o wide'

kgpu() {
  kubectl get nodes \
    -o custom-columns=NAME:.metadata.name,GPU:.status.allocatable.nvidia.com/gpu
}

kgpugpods() {
  kubectl get pods -A -o json | jq -r '
    .items[] as $pod |
    $pod.spec.containers[]? as $c |
    select($c.resources.limits["nvidia.com/gpu"] != null) |
    [$pod.metadata.namespace,
     $pod.metadata.name,
     $c.name,
     $c.resources.limits["nvidia.com/gpu"]] |
    @tsv'
}

kwarn() {
  kubectl events -A --types=Warning
}

kbad() {
  kubectl get pods -A | grep -E 'CrashLoopBackOff|ImagePullBackOff|ErrImagePull|Pending|Error|OOMKilled' || true
}

그리고 셸 시작 파일에 추가한다.

echo 'source ~/.k8s-ai-ops.sh' >> ~/.bashrc

잘 만든 스니펫은 작은 자동화다. 장애가 났을 때 "어떤 명령어였지?" 하고 검색하는 시간을 줄여준다. 그리고 그 몇 분이 새벽 알람의 길이를 결정한다.

마무리: 명령어는 운영자의 근육이다

쿠버네티스 운영은 거대한 이론처럼 보이지만, 실제 장애 대응은 작은 명령어의 연속이다. get으로 보고, describe로 이유를 찾고, logs로 애플리케이션의 목소리를 듣고, top과 DCGM으로 자원 상태를 확인한다. Helm으로 설치된 구성요소는 helm get values, helm history, helm rollback으로 추적한다. 복잡한 출력은 jsonpath, jq, watch로 필요한 정보만 남긴다.

AI 시대의 쿠버네티스 운영자는 GPU와 모델 서버를 이해해야 하지만, 결국 손끝에서는 여전히 kubectl과 helm이 움직인다. 이 장의 명령어를 자신의 환경에 맞게 고쳐 두자. 좋은 치트시트는 책갈피가 아니라, 운영자의 두 번째 기억이다.

참고

1kubectl 치트 시트

2NVIDIA GPU Operator Helm 설치 가이드

3DCGM exporter로 Kubernetes GPU 메트릭 수집

4vLLM Helm chart 문서

5GoogleCloudPlatform kubectl-ai

책 목록

AI 시대의 쿠버네티스: LLM 추론과 에이전트를 운영하는 인프라 전략

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기