WIKIKubernetes
플레이북

Kubernetes

몇 개 띄우라고 말만 하면 그 개수를 알아서 지킨다.

Kubernetesk8sPodDeployment자원 배분인프라 기초
인프라백엔드 입문8분

한눈에

  • 선언형 — '띄워라'가 아니라 '이런 상태였으면 좋겠다'를 적으면 쿠버가 맞춘다
  • Pod = 컨테이너가 도는 한 덩이 / Deployment = 몇 개로 유지할지의 선언 / Service = 안 바뀌는 주소
  • 롤링 업데이트 — 새것 하나 띄우고 옛것 하나 내리고. 그래서 안 끊긴다
  • requests = 최소 보장 (스케줄러가 이걸 보고 노드를 고른다)
  • limits = 상한 (메모리는 넘으면 OOMKilled, CPU는 안 죽고 느려진다)
  • GPU는 쪼개지지 않는다. requests도 limits도 정수 하나뿐이라 replica 상한이 GPU 장수다

Docker로 상자를 만들었으면 다음 질문이 온다. 그 상자를 누가 띄우고, 죽으면 누가 살리고, 열 대에는 어떻게 뿌리나. 쿠버네티스는 그 답이다.

명령이 아니라 선언이다

여기가 제일 먼저 넘어야 하는 벽이다. 쿠버에 하는 말은 명령이 아니다.

명령형 (예전 방식)선언형 (쿠버)
서버에 들어간다"이미지 X를 3개로 유지해라"라고 적는다
프로세스를 죽이고 새로 띄운다쿠버가 현재 상태와 비교한다
죽으면 사람이 다시 띄운다모자라면 띄우고 남으면 내린다
하나하나가 내 책임이다죽어도 알아서 다시 뜬다

이 성질 때문에 ArgoCD 같은 도구가 성립한다. 원하는 상태를 Git에 적어 두면, 실제와 대조해서 차이를 메우는 일이 가능해진다.

오브젝트 셋만 알면 시작할 수 있다

Deployment는 개수를 말하고, 쿠버가 그 개수를 지킨다
Deployment는 개수를 말하고, 쿠버가 그 개수를 지킨다
오브젝트무엇
Pod컨테이너가 실제로 도는 한 덩이. 제일 작은 단위
Deployment"이 이미지를 Pod 몇 개로 유지해라"는 선언
Service그 Pod들 앞에 서는 고정 주소

Service가 왜 필요한지가 처음엔 잘 안 와닿는다. Pod은 죽고 살아나며 IP가 매번 바뀐다. 다른 서비스가 그 IP를 알고 있으면 Pod이 한 번 죽는 순간 연결이 끊긴다. 그래서 안 바뀌는 주소를 하나 앞에 세운다.

위 그림에서 위쪽 화살표와 아래쪽 점선이 다른 관계다. 위는 만드는 관계(Deployment가 Pod을 만든다)고, 아래는 묶는 관계(Service가 그 Pod들을 하나로 묶는다)다. 트래픽은 Service에서 Pod으로 흐른다.

롤링 업데이트

이미지 태그가 바뀌면 쿠버가 한 개씩 갈아 끼운다.

새 Pod 하나 띄운다
옛것은 아직 살아 있다
정상인지 확인
readinessProbe가 OK를 줄 때까지 트래픽을 안 보낸다
옛 Pod 하나 내린다
반복
replicas 만큼

끊김이 없는 이유가 여기 있다. 죽이고 띄우는 게 아니라 띄우고 죽인다.

자원 배분 — 여기가 실무의 핵심이다

Pod 하나가 CPU와 메모리를 얼마나 쓸지를 적는다. 값은 Helmvalues.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 의심이 첫 갈래다.

숫자를 어떻게 정하나

지어내면 안 된다. 재고 정한다.

일단 limits 없이 띄운다
실제로 얼마나 쓰는지부터 본다
부하를 주고 관찰
평상시와 최대치를 따로 잰다
requests = 평상시
노드 배치가 이 값 기준이라 너무 크면 자리를 못 잡는다
limits = 최대치의 1.5~2배
여유가 없으면 순간 스파이크에 죽는다

requests를 너무 크게 잡으면 Pod이 아예 안 뜬다. 스케줄러가 그만한 여유를 가진 노드를 못 찾으면 Pending 상태로 멈춰 있는다. 반대로 너무 작게 잡으면 자원이 빡빡할 때 먼저 쫓겨난다(축출 순서가 requests 대비 사용량으로 정해진다).

Pod을 몇 개 띄우나

replicas도 감으로 정하지 않는다.

최소 2 — 하나면 그 Pod이 재시작될 때 서비스가 끊긴다
노드 수를 넘겨도 뜻이 없다 — 한 노드에 몰리면 그 노드가 죽을 때 같이 죽는다
기준은 목표 처리량 ÷ Pod 하나의 처리량 — 부하 테스트로 뒤엣값을 먼저 잰다
HPA를 쓰면 최소·최대만 정하고 그 사이를 쿠버가 조절한다

오토스케일(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은 배울 것이 없다. 한계와 실패 모드가 지식이다.

memory limits를 실제 사용량보다 낮게 → OOMKilled를 직접 보기
cpu limits를 조이고 부하 → 응답 시간이 어떻게 꺾이는지 재기
requests를 노드 여유보다 크게 → Pending에서 안 뜨는 것 보기
replicas를 0으로 → Service는 살아 있는데 응답이 없는 상태 보기
kubectl describe pod 으로 죽은 이유 읽기 — Events 줄에 다 적혀 있다

맥에서는 k3d나 kind로 클러스터 하나면 위 다섯이 전부 된다.

전체 배포 흐름에서 쿠버가 어디에 서는지는 DevOps와 CI/CD에 있다.