쿠버네티스 학습 노트 목차

워크로드 — 파드부터 Deployment까지

파드를 직접 만드는 일은 실무에서 거의 없다. 왜 그 위에 컨트롤러가 겹겹이 쌓여 있는지를 따라가면 각각의 존재 이유가 보인다.


1. 파드 — 배포의 최소 단위

파드는 '컨테이너 하나' 가 아니라 '함께 사는 컨테이너 묶음' 이다

같은 파드 안 컨테이너들은

  • 네트워크 네임스페이스를 공유한다 → localhost 로 서로를 부른다
  • 같은 노드에 함께 뜬다 (쪼개지지 않는다)
  • 볼륨을 공유할 수 있다
  • 함께 태어나고 함께 죽는다
apiVersion: v1
kind: Pod
metadata:
  name: web
spec:
  containers:
    - name: app
      image: myapp:1.4.2
      ports: [{ containerPort: 8080 }]

왜 파드라는 껍데기가 필요한가

컨테이너를 직접 스케줄하면 '반드시 붙어 있어야 하는 둘' 을 표현할 수 없다

  • 로그 수집 사이드카 — 앱과 같은 볼륨을 봐야 한다
  • 프록시 사이드카 — 앱의 localhost 를 가로채야 한다
  • 설정 동기화 — 앱이 읽는 파일을 갱신해야 한다

이 관계를 표현하려면 '함께 배치되는 단위' 가 필요하다. 그것이 파드다

파드는 일회용이다

파드는 고쳐지지 않는다. 문제가 생기면 지우고 새로 만든다

  • IP 가 바뀐다 → 그래서 서비스가 필요하다
  • 이름이 바뀐다 (web-7d4f8-x9k2l)
  • 안에 쓴 데이터는 사라진다 → 그래서 볼륨이 필요하다

"파드를 고친다" 는 개념이 없다는 것이 설계의 출발점이다

2. 초기화 컨테이너와 사이드카

spec:
  initContainers:
    - name: wait-db
      image: busybox:1.36
      command: ["sh", "-c", "until nc -z db 5432; do sleep 2; done"]
  containers:
    - name: app
      image: myapp:1.4.2
  • initContainer — 앱 컨테이너보다 '먼저' 순서대로 실행되고, 끝나야 다음이 뜬다

    • 준비 작업 — 마이그레이션 대기 · 설정 렌더링 · 권한 조정
    • 실패하면 재시작되며 앱은 시작되지 않는다
  • 사이드카 — 앱과 '함께' 도는 보조 컨테이너

    • restartPolicy: Always 를 가진 initContainer 로 선언하면
    • 앱보다 먼저 시작되고 나중에 종료되는 사이드카가 된다
    • 로그 수집기가 앱보다 먼저 죽어 마지막 로그를 놓치는 문제가 해결된다

3. 컨트롤러 계층

다이어그램 로딩 중…
  • ReplicaSet — "라벨이 맞는 파드가 N개 있어야 한다" 만 책임진다

  • Deployment — ReplicaSet 을 여러 세대 관리해 '전환' 과 '롤백' 을 책임진다

  • 직접 ReplicaSet 을 만들지 않는다 — Deployment 가 대신 만든다

롤링 업데이트가 실제로 하는 일

이미지를 바꾸면

  • ① 새 ReplicaSet 을 만든다 (replicas: 0 에서 시작)
  • ② 새 것을 늘리고 옛 것을 줄인다 — maxSurge · maxUnavailable 규칙대로
  • ③ 다 넘어가면 옛 ReplicaSet 은 replicas: 0 으로 남는다 (롤백용)
spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 25%          # 원하는 수보다 얼마나 더 띄울 수 있나
      maxUnavailable: 25%    # 얼마나 부족해도 되나
  revisionHistoryLimit: 10   # 남겨 둘 옛 ReplicaSet 수

maxUnavailable: 0 + maxSurge: 1

  • 항상 원하는 수를 유지하며 하나씩 교체한다 (가장 안전, 가장 느리다)

maxSurge: 0 + maxUnavailable: 1

  • 자원을 더 쓰지 않지만 잠시 용량이 준다

자원 여유가 있으면 앞쪽을, 빡빡하면 뒤쪽을 고른다

kubectl rollout status deploy/web
kubectl rollout history deploy/web
kubectl rollout undo deploy/web --to-revision=3
kubectl rollout restart deploy/web      # 이미지 그대로 파드만 교체

rollout restart 는 실무에서 자주 쓴다. 설정(ConfigMap)이 바뀌었을 때 파드를 새로 띄워 다시 읽게 하는 표준 방법이다.

롤아웃이 멈추는 경우

파드가 Ready 가 안 되면 롤아웃은 거기서 멈춘다

  • 옛 파드는 살아 있으므로 서비스는 계속된다 (좋은 설계다)
  • progressDeadlineSeconds(기본 600초)를 넘기면 실패로 표시된다

kubectl rollout status 가 안 끝나면

  • kubectl get pods 로 새 파드의 상태를 본다
  • Pending · ImagePullBackOff · CrashLoopBackOff · Ready 안 됨

4. 다른 워크로드 종류

종류언제 쓰나특징
Deployment상태 없는 애플리케이션파드가 서로 대체 가능하다
StatefulSet상태 있는 애플리케이션안정된 이름·순서·전용 볼륨
DaemonSet노드마다 하나씩로그 수집·모니터링·CNI
Job한 번 끝나는 작업완료되면 끝
CronJob주기적 작업Job 을 일정대로 만든다

StatefulSet 이 Deployment 와 다른 점

  • 이름 — web-0 · web-1 · web-2 — 순서가 있고 재생성돼도 같은 이름

  • 순서 — 0 → 1 → 2 순으로 만들고, 역순으로 지운다

  • 볼륨volumeClaimTemplates파드마다 전용 PVC 를 만든다

    • 파드가 재생성돼도 같은 볼륨에 다시 붙는다
  • 네트워크 — 헤드리스 서비스와 함께 쓰면 web-0.svc 로 개별 접근이 가능하다

  • 클러스터 멤버가 서로를 '개체' 로 인식해야 하는 것들에 쓴다

    • (DB 복제 세트 · Kafka · ZooKeeper · Elasticsearch)

주의 — StatefulSet 을 지워도 PVC 는 남는다 (의도된 동작이다)

  • 데이터를 지키기 위해서이고, 그래서 정리를 잊으면 볼륨이 쌓인다

Job 과 CronJob

apiVersion: batch/v1
kind: Job
spec:
  completions: 5        # 총 5번 성공해야 완료
  parallelism: 2        # 동시에 2개씩
  backoffLimit: 3       # 실패 재시도 횟수
  activeDeadlineSeconds: 600
  template:
    spec:
      restartPolicy: OnFailure    # Job 은 Always 를 못 쓴다
      containers: [...]

CronJob 의 함정

  • concurrencyPolicy 기본이 Allow — 이전 실행이 안 끝났는데 또 시작한다
    • Forbid(건너뛴다) · Replace(교체) 중 하나를 명시하는 편이 안전하다
  • startingDeadlineSeconds 를 안 주면 컨트롤러 정지 후 밀린 작업이 한꺼번에 돈다
  • 시간대는 클러스터 기준이다 (timeZone 필드로 지정 가능)
  • 완료된 Job 이 쌓인다 → successfulJobsHistoryLimit 로 제한한다

5. 프로브 — 살아 있는가 vs 받을 수 있는가

livenessProbe:
  httpGet: { path: /healthz, port: 8080 }
  initialDelaySeconds: 10
  periodSeconds: 10
  failureThreshold: 3
readinessProbe:
  httpGet: { path: /ready, port: 8080 }
  periodSeconds: 5
startupProbe:
  httpGet: { path: /healthz, port: 8080 }
  failureThreshold: 30
  periodSeconds: 10        # 최대 300초까지 기동을 기다린다
  • liveness — 실패하면 '컨테이너를 재시작' 한다
    • 재시작으로 나아질 문제만 넣는다 (데드락 · 무한 루프)
  • readiness — 실패하면 '엔드포인트에서 뺀다' (재시작하지 않는다)
    • 의존 자원 확인은 여기에
  • startup — 통과할 때까지 liveness·readiness 를 유예한다
    • 기동이 느린 애플리케이션에 필수

가장 흔한 사고 두 가지

  • liveness 에 DB 확인을 넣었다

    • DB 장애 → 모든 파드가 재시작 → 재시작해도 DB 는 여전히 죽어 있다
    • CrashLoopBackOff 무한 루프. 상황만 악화된다
  • ② startupProbe 없이 initialDelaySeconds 로 버틴다

    • 기동 시간이 들쭉날쭉하면 어떤 파드는 기동 중에 죽는다
    • startupProbe 로 '기동 완료' 를 명시적으로 다룬다

liveness 는 자기 자신만, readiness 는 필수 의존까지. 이 한 줄이 가용성 설계의 핵심이다. 공통 의존을 liveness 에 넣으면 전 인스턴스가 동시에 죽는다.

6. 종료 절차

파드를 지우면

  • ① 엔드포인트에서 제거된다 (새 트래픽이 안 온다)
  • ② preStop 훅이 실행된다
  • 컨테이너SIGTERM
  • ④ terminationGracePeriodSeconds(기본 30초) 대기
  • ⑤ 남아 있으면 SIGKILL

주의 — ①과 ③은 '동시에' 시작된다. 순서가 보장되지 않는다

  • 엔드포인트 전파(kube-proxy 규칙 갱신)에 시간이 걸리기 때문에
  • SIGTERM 을 받은 뒤에도 잠시 트래픽이 올 수 있다
lifecycle:
  preStop:
    exec:
      command: ["sh", "-c", "sleep 5"]    # 전파를 기다린다

preStop 에 짧은 sleep 을 두는 것이 관용적 해법이다

  • 그 사이 엔드포인트 제거가 전파된다
  • 그다음 SIGTERM 이 가서 애플리케이션이 안전하게 종료한다

grace period 는 '최장 요청 처리 시간 + preStop sleep + 여유' 로 잡는다


한눈에 정리

  • 파드 — 함께 사는 컨테이너 묶음. localhost 공유. 일회용이다
  • init/사이드카 — 준비 작업은 initContainer, 보조 프로세스는 사이드카
  • Deployment — ReplicaSet 을 세대 관리해 롤링 업데이트·롤백을 한다
  • 롤링 — maxSurge/maxUnavailable. 새 파드가 Ready 안 되면 멈춘다(안전)
  • StatefulSet — 안정된 이름·순서·전용 볼륨. 지워도 PVC 는 남는다
  • Job/CronJob — concurrencyPolicy 기본 Allow 주의. restartPolicy 는 OnFailure
  • 프로브 — liveness=재시작(자기 문제만) · readiness=트래픽 제외
    • startup 으로 기동을 유예한다
  • 종료 — 엔드포인트 제거와 SIGTERM 이 동시다 → preStop sleep 으로 메운다

출처 — Kubernetes Docs: "Pods" · "Deployments" · "StatefulSets" · "Jobs" · "Sidecar Containers" · "Configure Liveness, Readiness and Startup Probes" · "Pod Lifecycle" · CNCF CKA Curriculum v1.35 (Workloads and Scheduling 15%)

무엇을 대신해 주나 — 선언적 모델과 클러스터 구조스케줄링과 리소스 — 파드는 어느 노드로 가나