Kubernetes
몇 개 띄우라고 말만 하면 그 개수를 알아서 지킨다.
한눈에
- 선언형 — '띄워라'가 아니라 '이런 상태였으면 좋겠다'를 적으면 쿠버가 맞춘다
- Pod = 컨테이너가 도는 한 덩이 / Deployment = 몇 개로 유지할지의 선언 / Service = 안 바뀌는 주소
- 롤링 업데이트 — 새것 하나 띄우고 옛것 하나 내리고. 그래서 안 끊긴다
- requests = 최소 보장 (스케줄러가 이걸 보고 노드를 고른다)
- limits = 상한 (메모리는 넘으면 OOMKilled, CPU는 안 죽고 느려진다)
- GPU는 쪼개지지 않는다. requests도 limits도 정수 하나뿐이라 replica 상한이 GPU 장수다
Docker로 상자를 만들었으면 다음 질문이 온다. 그 상자를 누가 띄우고, 죽으면 누가 살리고, 열 대에는 어떻게 뿌리나. 쿠버네티스는 그 답이다.
명령이 아니라 선언이다
여기가 제일 먼저 넘어야 하는 벽이다. 쿠버에 하는 말은 명령이 아니다.
| 명령형 (예전 방식) | 선언형 (쿠버) |
|---|---|
| 서버에 들어간다 | "이미지 X를 3개로 유지해라"라고 적는다 |
| 프로세스를 죽이고 새로 띄운다 | 쿠버가 현재 상태와 비교한다 |
| 죽으면 사람이 다시 띄운다 | 모자라면 띄우고 남으면 내린다 |
| 하나하나가 내 책임이다 | 죽어도 알아서 다시 뜬다 |
이 성질 때문에 ArgoCD 같은 도구가 성립한다. 원하는 상태를 Git에 적어 두면, 실제와 대조해서 차이를 메우는 일이 가능해진다.
오브젝트 셋만 알면 시작할 수 있다

| 오브젝트 | 무엇 |
|---|---|
| Pod | 컨테이너가 실제로 도는 한 덩이. 제일 작은 단위 |
| Deployment | "이 이미지를 Pod 몇 개로 유지해라"는 선언 |
| Service | 그 Pod들 앞에 서는 고정 주소 |
Service가 왜 필요한지가 처음엔 잘 안 와닿는다. Pod은 죽고 살아나며 IP가 매번 바뀐다. 다른 서비스가 그 IP를 알고 있으면 Pod이 한 번 죽는 순간 연결이 끊긴다. 그래서 안 바뀌는 주소를 하나 앞에 세운다.
위 그림에서 위쪽 화살표와 아래쪽 점선이 다른 관계다. 위는 만드는 관계(Deployment가 Pod을 만든다)고, 아래는 묶는 관계(Service가 그 Pod들을 하나로 묶는다)다. 트래픽은 Service에서 Pod으로 흐른다.
롤링 업데이트
이미지 태그가 바뀌면 쿠버가 한 개씩 갈아 끼운다.
끊김이 없는 이유가 여기 있다. 죽이고 띄우는 게 아니라 띄우고 죽인다.
자원 배분 — 여기가 실무의 핵심이다
Pod 하나가 CPU와 메모리를 얼마나 쓸지를 적는다. 값은 Helm의 values.yaml에 있다.
resources:
requests: # 최소 보장. 스케줄러가 이걸 보고 노드를 고른다
cpu: 500m # 0.5 코어
memory: 1Gi
limits: # 상한
cpu: "2"
memory: 4Gi
requests와 limits는 하는 일이 다르다.
| 무엇 | 넘으면 | |
|---|---|---|
| requests | 이만큼은 보장받는다. 노드 배치의 기준 | 넘어도 된다. 여유가 있으면 더 쓴다 |
| limits (메모리) | 상한 | OOMKilled. 컨테이너가 죽고 재시작된다 |
| limits (CPU) | 상한 | 안 죽는다. throttling 으로 느려진다 |
메모리와 CPU의 실패 모드가 다른 것이 중요하다. 메모리는 갑자기 죽고, CPU는 조용히 느려진다. "가끔 죽어요"는 메모리 의심, "가끔 느려요"는 CPU 의심이 첫 갈래다.
숫자를 어떻게 정하나
지어내면 안 된다. 재고 정한다.
requests를 너무 크게 잡으면 Pod이 아예 안 뜬다. 스케줄러가 그만한 여유를 가진 노드를 못 찾으면 Pending 상태로 멈춰 있는다. 반대로 너무 작게 잡으면 자원이 빡빡할 때 먼저 쫓겨난다(축출 순서가 requests 대비 사용량으로 정해진다).
Pod을 몇 개 띄우나
replicas도 감으로 정하지 않는다.
오토스케일(HPA)
부하에 따라 replicas를 늘리고 줄인다.
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 70
여기서 70%는 requests 대비 비율이다. 노드 전체가 아니다. requests를 작게 잡아 두면 실제로는 여유가 있는데도 계속 늘어난다. 두 값이 얽혀 있어서 따로 정하면 안 된다.
GPU는 규칙이 다르다
LLM 서빙을 하게 되면 위 감각이 통째로 안 맞는다.
| CPU · 메모리 | GPU | |
|---|---|---|
| 쪼개기 | 500m 처럼 소수점으로 나눈다 | 정수 하나뿐이다. 0.5장은 없다 |
| requests와 limits | 다르게 줄 수 있다 | 같아야 한다 |
| replicas 상한 | 노드 여유만큼 | GPU 장수가 곧 상한 |
| 오토스케일 기준 | CPU 사용률 | GPU는 늘 100%다. 큐 대기시간으로 봐야 한다 |
resources:
limits:
nvidia.com/gpu: 1 # 이 Pod이 GPU 한 장을 통째로 잡는다
한 장을 나눠 쓰려면 MIG(Multi-Instance GPU)로 물리적으로 쪼개 놓거나, 한 Pod 안에서 여러 요청을 배치로 묶어 처리해야 한다. 후자가 LLM 서빙의 기본이고, 그때 처리량을 정하는 건 replicas가 아니라 서빙 엔진의 배치 설정이다.
손으로 익히려면
정상 동작하는 YAML은 배울 것이 없다. 한계와 실패 모드가 지식이다.
맥에서는 k3d나 kind로 클러스터 하나면 위 다섯이 전부 된다.
전체 배포 흐름에서 쿠버가 어디에 서는지는 DevOps와 CI/CD에 있다.