쿠버네티스 실력은 kubectl 명령어가 아니라 '장애를 설명하는 능력'에서 드러난다
면접관 자리에 앉아 이 질문을 여러 번 던져봤습니다.
"kubectl delete pod를 실행하면 내부에서는 어떤 일이 벌어질까요?"
돌아오는 답은 대개 짧습니다.
"Pod가 삭제됩니다."
틀린 말은 아닙니다. 다만 운영 관점에서 보면 아무것도 설명하지 못한 답입니다. 그 명령을 수천 번 쳐본 사람에게 물어도 여기서 답이 갈리더라고요.
API Server는 그 요청을 받아 무엇을 바꾸는가. Pod에 deletionTimestamp는 언제 찍히는가. kubelet은 그걸 어떻게 인지하는가. preStop Hook은 언제 실행되며 컨테이너 프로세스에는 어떤 Signal이 전달되는가. terminationGracePeriodSeconds가 지나면 무슨 일이 벌어지는가. Deployment가 관리하던 Pod였다면 새 Pod는 누가 만드는가.
이 순서를 말로 풀기 시작하면 명령어 사용법이 아니라 Kubernetes 동작 구조를 아는지가 드러납니다.
실무에서 필요한 것도 그쪽입니다. 명령어를 외우는 능력이 아니라, 무엇이 → 왜 → 어떤 순서로 → 어떻게 실패하는지 그리는 능력.
프로덕션 Kubernetes를 운영해 본 사람과 Kubernetes를 써본 사람의 차이는 여기서 벌어집니다.
1. 노드의 메모리가 부족해지면 무슨 일이 벌어질까?
가장 흔한 답은 이렇습니다.
"메모리가 부족하면 Pod가 죽습니다."
결과만 보면 맞습니다. 다만 운영에서 알아야 하는 건 누가, 어떤 기준으로, 어떤 Pod를 먼저 걷어내느냐입니다.
노드에 메모리 압박이 오면 kubelet이 eviction 정책에 따라 Pod를 퇴출시킵니다. 여기서 QoS 개념이 나옵니다. BestEffort, Burstable, Guaranteed.
저도 한동안 "BestEffort부터 순서대로 죽는다"고 외워서 말하고 다녔습니다. 정확한 설명이 아니었습니다. 실제 eviction 후보 선정에는 Pod가 자기 요청량을 얼마나 초과해서 쓰고 있는지, Priority가 무엇인지가 함께 들어갑니다. QoS는 그 계산의 결과에 가깝고, 뒤에는 requests/limits와 Priority라는 원인이 있습니다.
한 단계 더 가면 질문이 이렇게 바뀝니다.
"MemoryPressure가 뜨기 전에 무엇을 보고 있어야 하는가?"
그 지점부터 면접 문제가 운영 문제가 됩니다. 노드의 MemoryPressure, working set, available memory, Pod별 사용량, OOMKilled 발생 여부를 같이 놓고 봐야 합니다.
하나 더. PDB가 있으면 리소스 압박으로 인한 eviction까지 막아준다고 오해하는 경우를 자주 봤습니다. PDB는 drain 같은 자발적 중단(Voluntary Disruption)에서 가용성을 지키는 장치입니다.
2. CrashLoopBackOff가 떴습니다. 무엇부터 볼 것인가?
초보자는 명령어부터 말합니다. kubectl logs, kubectl describe pod, kubectl get events. 다 필요한 명령어입니다. 실무에서 갈리는 건 명령어 개수가 아니라 조사 순서입니다.
실패 영역부터 잘라야 합니다.
Step 1. 컨테이너가 시작조차 못 했는가?
이미지 Pull 실패, Secret 문제, Volume Mount 실패, 설정 오류를 확인합니다.
Step 2. 실행은 됐는데 프로세스가 종료되는가?
kubectl describe pod <pod> 에서 Last State, Reason, Exit Code를 봅니다.
Step 3. 이전 컨테이너 로그가 필요한가?
CrashLoopBackOff에서는 이게 꽤 중요합니다.
kubectl logs <pod> --previous
현재 컨테이너 로그만 들여다보다가 직전 Crash의 단서를 놓친 적이 저도 있습니다. 새벽에 30분쯤 그렇게 버렸습니다.
Step 4. Probe가 컨테이너를 죽이고 있는가?
애플리케이션은 멀쩡한데 잘못 잡은 livenessProbe 때문에 kubelet이 컨테이너를 계속 재시작시키는 경우가 있습니다.
Step 5. Init Container는 정상인가?
Init Container가 끝나지 않으면 App Container 실행 단계까지 가지도 못합니다.
좋은 장애 분석은 도구를 많이 아는 일이 아닙니다. 가능한 원인을 계층으로 쌓아놓고 하나씩 지워나가는 과정입니다.
3. 운영 중인 노드를 안전하게 Drain하려면
명령어 자체는 간단합니다.
kubectl cordon node-47
kubectl drain node-47
프로덕션에서는 이 두 줄보다 먼저 답해야 할 게 있습니다.
"이 노드에서 Pod를 빼도 서비스가 살아 있는가?"
Node A/B/C에 API Pod가 하나씩 떠 있으면 Replica가 3개니까 얼핏 안전해 보입니다. 여러 노드를 동시에 Drain하는 순간 이야기가 달라집니다.
그래서 Replica 수가 실제로 충분한지, PDB가 걸려 있는지, Pod가 특정 노드에 몰려 있지는 않은지를 먼저 봅니다. StatefulSet이나 PV를 쓰는 워크로드가 섞여 있는지, DaemonSet은 어떻게 처리할지, emptyDir에 데이터를 들고 있는 Pod는 없는지도 확인 대상입니다. Control Plane이나 중요 인프라 컴포넌트가 올라간 노드라면 그때부터는 아예 다른 작업입니다.
cordon과 drain도 구분해야 합니다. cordon은 새 일반 Pod가 그 노드에 스케줄링되지 않게 막는 것이고, drain은 유지보수를 위해 기존 워크로드 Pod를 축출하는 것입니다.
Drain 명령을 실행하는 것보다 Drain해도 되는 상태를 만들어두는 쪽이 훨씬 어렵습니다.
4. Rolling Update는 왜 실패하는가
RollingUpdate를 설명해달라고 하면 보통 maxSurge와 maxUnavailable 두 값이 나옵니다. 거기까지면 설정값을 아는 겁니다.
운영에서의 질문은 다릅니다.
"RollingUpdate 중 장애가 난다면 어디에서 날까?"
Readiness Probe 실패가 대표적입니다. 새 Pod는 떴는데 Ready가 안 됩니다. Service 트래픽을 못 받고 rollout도 앞으로 안 나갑니다.
Liveness Probe도 자주 문제를 만듭니다. 애플리케이션 초기화가 오래 걸리는데 Probe가 공격적으로 잡혀 있으면 컨테이너가 계속 재시작됩니다. 이 경우엔 startupProbe까지 같이 봐야 합니다.
maxUnavailable을 크게 잡아두고 배포하면 기존 Pod가 한꺼번에 빠지면서 처리 용량이 순간적으로 주저앉습니다.
가장 골치 아픈 쪽은 애플리케이션은 살아 있는데 서비스는 고장 난 경우입니다. HTTP 200만 던지는 Probe를 쓰면 DB나 외부 의존성이 끊겨도 Kubernetes 눈에는 정상 Pod입니다.
Deployment 성공 ≠ 서비스 정상. Rolling Update에 Metrics, Logs, Traces 같은 Observability가 붙어 있어야 하는 이유가 여기 있습니다.
5. DaemonSet과 Deployment의 차이
교과서적으로는 간단합니다.
Deployment → 원하는 Replica 수만큼 Pod 실행 DaemonSet → 대상 노드마다 Pod 실행
정작 중요한 건 왜 두 종류가 따로 있느냐입니다.
Deployment는 대개 애플리케이션을 띄웁니다. API Server, Web Application, Backend, Worker 같은 것들. DaemonSet은 노드 자체에 붙는 기능 쪽에 많이 씁니다. Log Collector, Monitoring Agent, Node Exporter, CNI 구성요소, Security Agent.
그래서 DaemonSet을 제대로 이해하려면 Taint/Toleration, NodeSelector, Node Affinity, HostPath, HostNetwork까지 딸려 옵니다.
둘의 차이는 YAML의 kind 한 줄이 아닙니다. 워크로드를 어떤 단위로 배치할 것인가라는 아키텍처 판단입니다.
6. Kubernetes Networking은 어떻게 동작하는가
이 질문이 생각보다 셉니다. YAML 수준에서 아는지, 네트워크까지 내려가서 아는지가 몇 마디면 갈리거든요.
큰 흐름은 이렇습니다.
외부 사용자 → Load Balancer / Ingress → Service → Pod
Pod Network에서는 각 Pod가 IP를 받고 CNI가 그 구성을 담당합니다. Kubernetes 네트워크 모델은 Pod 간 통신이 NAT 없이 가능해야 한다는 전제를 깔고 갑니다.
Service Network는 Pod IP가 언제든 바뀐다는 사실에서 출발합니다. 안정적인 접근 지점을 하나 두고, Service → EndpointSlice → Pod로 이어집니다. 트래픽 전달은 환경에 따라 kube-proxy의 iptables/IPVS 방식이 쓰이고, 최신 CNI 중에는 eBPF로 이 자리를 대체하는 것도 있습니다.
Ingress는 HTTP/HTTPS 라우팅 규칙입니다. Ingress 리소스만 만들어놓고 트래픽이 왜 안 오냐고 하는 경우를 여러 번 봤는데, 실제로 일하는 건 Ingress Controller입니다.
운영 장애에서는 여기서 더 내려갑니다.
DNS → Service → EndpointSlice → CNI → Route → MTU → NIC
Overlay Network에서는 MTU가 특히 아픕니다. VXLAN, Geneve 같은 Encapsulation이 붙으면 패킷에 헤더가 더해지니까요. MTU가 어긋나면 이런 증상이 나옵니다.
"통신이 아예 안 되는 게 아니라 어떤 요청만 이상하게 실패하는 현상"
이런 장애가 사람을 제일 오래 붙잡습니다.
7. 서비스가 가끔 Timeout 됩니다
가장 위험한 접근은 확인하기 전에 원인을 정해버리는 것입니다.
"네트워크 문제 같습니다."
아무것도 안 봤는데 말입니다. 저도 예전에 이 말을 먼저 뱉었다가 반나절을 엉뚱한 데서 보낸 적이 있습니다.
장애 영역부터 나눕니다.
Client → Ingress/LB → Service → Pod → Application → DB/External API
그다음 질문을 던집니다. 특정 Pod에서만 발생하는가 — Pod별 latency, error, restart 차이를 봅니다. Service를 통할 때만 발생하는가 — EndpointSlice와 Service 경로를 확인하고, 필요하면 Pod IP로 직접 때려서 경로를 분리합니다. 특정 크기의 요청에서만 나는가 — MTU를 의심할 자리입니다. CPU 사용률은 낮은데 느린가 — CPU Limit throttling을 봅니다. 애플리케이션 뒤쪽인가 — DB Connection Pool, 외부 API, Thread Pool을 봅니다.
Tracing이 깔려 있으면 훨씬 빠릅니다. Request 10ms → Ingress 5ms → Application 20ms → Database 2,300ms. 이 줄이 눈에 들어오는 순간 Kubernetes를 의심할 이유가 확 줄어듭니다.
운영에서 오래 버티는 사람은 추측을 잘하는 사람이 아니라 장애 범위를 빨리 줄이는 사람이었습니다.
8. Requests와 Limits는 왜 따로 있는가
간단하게 쓰면 이렇습니다.
Requests = 스케줄링의 기준 Limits = 실행 중 사용 가능한 상한
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1"
memory: "1Gi"
Scheduler는 최소한 requests를 받아줄 노드를 찾습니다. 실행 이후에는 CPU와 Memory Limit이 서로 다르게 굴러갑니다.
CPU Limit 초과: CPU Throttling Memory Limit 초과: OOM → 컨테이너 종료
CPU 그래프만 보고 "100%도 아닌데 왜 느리지"라고 결론 내면 안 됩니다. CFS throttling 지표를 같이 봐야 합니다.
Java 쪽은 Memory Limit이 더 예민합니다. Container Memory Limit 안에는 JVM Heap만 들어 있는 게 아니라 Metaspace, Direct Memory, Thread Stack, Native Memory가 같이 들어갑니다. Heap만 보고 Limit을 잡았다가 OOMKilled를 맞는 걸 저는 여러 번 봤습니다. 제가 그렇게 잡아둔 것도 있었고요.
9. HA를 어떻게 설계할 것인가
"Replica를 3개 만들겠습니다."
시작으로는 맞는데 HA 설계라고 하기엔 모자랍니다. Replica 세 개가 전부 같은 노드에 있으면 Node A 장애 한 번에 다 사라집니다.
장애 도메인을 갈라야 합니다. AZ-A, AZ-B, AZ-C에 하나씩. 여기에 topologySpreadConstraints, Pod Anti-Affinity, PodDisruptionBudget, Readiness/Liveness/Startup Probe, Requests/Limits, 그리고 적당한 Replica 수가 엮입니다.
이것만으로 끝나지도 않습니다. 애플리케이션이 실패를 견뎌야 합니다. Timeout, Retry, Circuit Breaker, Connection Pool, Graceful Degradation. 운영 체계도 있어야 합니다. Metrics, Logs, Tracing, Alert, Runbook, Incident Response.
HA는 안 죽는 시스템이 아닙니다. 일부가 죽어도 서비스가 계속 나가고, 죽은 걸 빨리 찾아 되살리는 시스템입니다.
10. 운영 클러스터를 어떻게 업그레이드할 것인가
실제 운영 경험이 가장 잘 드러나는 질문입니다.
숫자만 보면 1.x를 1.y로 올리는 일입니다. 실제로는 버전 숫자를 바꾸는 작업이 아닙니다.
호환성부터 봅니다. Deprecated API는 없는지, Operator와 CNI/CSI, Ingress, Monitoring 구성요소가 새 버전을 지원하는지. 검증은 개발이나 별도 환경에서 먼저 돌립니다. 중요 워크로드와 네트워크, 스토리지까지 다 지나가봐야 합니다.
백업은 존재 여부만 보면 안 됩니다. etcd와 애플리케이션 데이터가 실제로 복구되는지까지 확인해야 합니다. 이걸 안 해보고 백업이 있다고 말하는 자리를 꽤 봤습니다.
진행은 단계적으로. Control Plane과 Worker를 플랫폼이 지원하는 공식 업그레이드 절차와 version skew 정책에 맞춰 순서대로 처리합니다.
여기서 놓치기 쉬운 게 하나 있습니다. 업그레이드 방식은 배포판마다 다릅니다. Upstream Kubernetes, OpenShift/OKD, EKS, AKS, GKE를 한 절차로 묶어서 설명하면 안 됩니다. OpenShift 계열이면 Cluster Version Operator와 MachineConfigOperator 같은 플랫폼 Operator가 업그레이드 과정에 깊게 끼어듭니다.
명령어를 외우는 게 아니라, 지금 내가 운영하는 플랫폼이 Control Plane과 Node, Operator를 각각 어떤 방식으로 올리는지 아는 것. 실무에서 필요한 건 그쪽입니다.
그래서 Kubernetes 실력이란 뭘까요
몇 년 운영하면서 느낀 건 Kubernetes 자체가 어렵지는 않다는 것입니다. 어려운 건 장애가 여러 계층을 한 번에 통과한다는 점입니다.
사용자가 Timeout을 겪었다고 해봅시다. 원인이 Kubernetes가 아닐 수도 있습니다.
사용자 Timeout → Ingress? → Service? → CNI? → Pod CPU Throttling? → JVM GC? → DB Connection Pool? → Database?
결과는 한 줄입니다. "느리다." 원인은 수십 가지입니다.
운영 경험이 쌓이면 명령어를 많이 외우게 되는 게 아니라 시스템의 인과관계가 머릿속에 그려집니다. 저는 이게 Kubernetes 실력을 가르는 가장 큰 기준이라고 생각합니다.
초보자는 장애가 나면 명령어를 검색합니다. 경험자는 가설을 먼저 세우고 장애 도메인을 자릅니다.
현상 → 어느 계층인가? → 어떤 조건에서 재현되는가? → 정상과 무엇이 다른가? → 변수를 하나씩 제거 → Root Cause
Kubernetes 운영은 kubectl을 잘 쓰는 일이 아닙니다. 분산 시스템의 실패를 구조로 이해하는 일입니다. 좋은 면접 질문 역시 "이 명령어를 아십니까"가 아니라 "이 시스템이 왜 이렇게 동작하고, 장애가 나면 어디부터 보시겠습니까"여야 합니다.
그 답에서 Kubernetes를 써본 사람과 운영해 본 사람이 갈립니다. 솔직히 저도 매번 깔끔하게 답하지는 못합니다. 지금도 그렇습니다.
이 글이 도움이 되셨나요?
버튼 하나가 다음 글을 쓰는 힘이 됩니다