sayu.day
Backend

Kubernetes 심화 시리즈 #1: 워크로드 컨트롤러의 내부 동작 원리

Deployment, StatefulSet, DaemonSet, CronJob의 내부 동작 원리를 깊이 있게 이해합니다. Reconciliation Loop, 컨트롤러 패턴, 그리고 실무 트러블슈팅까지.

Published 2026년 1월 3일9 min read1,721 words

Kubernetes 심화 시리즈 #1: 워크로드 컨트롤러의 내부 동작 원리

시리즈 개요

#주제핵심 내용
1워크로드 컨트롤러 심화Deployment, StatefulSet, DaemonSet, CronJob
2서비스 네트워킹 심화Service 타입, kube-proxy, AWS ALB/NLB
3설정 및 시크릿 관리ConfigMap, Secrets, AWS Secrets Manager CSI Driver
4Istio 서비스 메시VirtualService, DestinationRule, 와일드카드 서브도메인
5오토스케일링 심화HPA, VPA, Cluster Autoscaler, Karpenter, KEDA
6보안 심화RBAC, NetworkPolicy, Pod Security Standards

컨트롤러 패턴: Kubernetes의 핵심 철학

Kubernetes의 모든 것은 컨트롤러 패턴으로 동작합니다. 우리가 kubectl apply로 Deployment를 생성하면, 실제 Pod를 만드는 것은 Deployment Controller입니다.

Reconciliation Loop (조정 루프)

모든 컨트롤러는 동일한 패턴으로 동작합니다:

무한 루프:
  1. 현재 상태(Current State) 관찰
  2. 원하는 상태(Desired State)와 비교
  3. 차이가 있으면 조정(Reconcile)
  4. 다음 이벤트 대기
// 실제 Kubernetes 컨트롤러의 핵심 구조 (간략화)
func (c *Controller) Run(ctx context.Context) {
    for {
        select {
        case <-ctx.Done():
            return
        default:
            // 1. 작업 큐에서 아이템 가져오기
            key, shutdown := c.workqueue.Get()
            if shutdown {
                return
            }
            
            // 2. Reconcile 실행
            err := c.reconcile(key.(string))
            if err != nil {
                // 재시도 큐에 추가
                c.workqueue.AddRateLimited(key)
            } else {
                c.workqueue.Forget(key)
            }
            c.workqueue.Done(key)
        }
    }
}

[!IMPORTANT] Level-triggered vs Edge-triggered: Kubernetes 컨트롤러는 Level-triggered 방식입니다. "상태가 변했다"(Edge)가 아니라 "현재 상태가 이것이다"(Level)를 기준으로 동작합니다. 덕분에 컨트롤러가 재시작되어도 현재 상태를 다시 읽어서 정상적으로 조정할 수 있습니다.


Deployment: 가장 널리 쓰이는 워크로드

내부 구조: Deployment → ReplicaSet → Pod

Deployment는 Pod를 직접 관리하지 않습니다. ReplicaSet을 통해 간접적으로 관리합니다.

Rolling Update 동작 원리

Deployment를 업데이트하면 새 ReplicaSet이 생성되고, 점진적으로 트래픽이 이동합니다.

업데이트 전략 비교

# RollingUpdate (기본값) - 무중단 배포
spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 25%        # 최대 추가 Pod 수
      maxUnavailable: 25%  # 최대 사용 불가 Pod 수
# Recreate - 모든 Pod 삭제 후 재생성
spec:
  strategy:
    type: Recreate
    # 주의: 다운타임 발생!
    # 사용 사례: DB 마이그레이션, 싱글 인스턴스 제약
전략다운타임리소스 사용사용 사례
RollingUpdate없음일시적 증가대부분의 상황
Recreate있음동일싱글 인스턴스, 볼륨 공유 불가 시

롤백 동작 원리

# 롤아웃 히스토리 확인
kubectl rollout history deployment/my-app

# 특정 리비전으로 롤백
kubectl rollout undo deployment/my-app --to-revision=2

롤백은 새 ReplicaSet을 생성하는 것이 아닙니다. 기존 ReplicaSet을 다시 Scale Up합니다.

[!TIP] revisionHistoryLimit (기본값 10)으로 유지할 ReplicaSet 수를 제한할 수 있습니다. 너무 많으면 etcd 부담이 증가합니다.


StatefulSet: 상태 유지가 필요한 워크로드

Deployment와의 핵심 차이

특성DeploymentStatefulSet
Pod 이름랜덤 (app-7d4f5b8c9-abc12)순차적 (app-0, app-1, app-2)
생성/삭제 순서병렬순차적 (0→1→2, 삭제는 역순)
네트워크 ID없음Headless Service로 고정 DNS
스토리지Pod 삭제 시 PVC도 삭제 가능Pod 삭제해도 PVC 유지

순차적 생성과 삭제

Headless Service와 DNS

# Headless Service (ClusterIP: None)
apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  clusterIP: None  # Headless!
  selector:
    app: mysql
  ports:
    - port: 3306
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql  # Headless Service 이름
  replicas: 3
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        ports:
        - containerPort: 3306

이렇게 설정하면 각 Pod에 고정 DNS가 부여됩니다:

mysql-0.mysql.default.svc.cluster.local
mysql-1.mysql.default.svc.cluster.local
mysql-2.mysql.default.svc.cluster.local

[!IMPORTANT] 일반 Service는 Pod IP가 변해도 Service IP로 로드밸런싱됩니다. StatefulSet + Headless Service는 각 Pod를 직접 지정할 수 있어 MySQL 레플리케이션, Kafka 브로커 등에 필수입니다.

VolumeClaimTemplates

spec:
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: gp3
      resources:
        requests:
          storage: 100Gi

Pod가 삭제되어도 PVC는 그대로 유지됩니다. mysql-0이 다시 생성되면 같은 데이터를 사용합니다.

podManagementPolicy

spec:
  podManagementPolicy: Parallel  # 기본값: OrderedReady
정책설명사용 사례
OrderedReady순차적 생성/삭제, 이전 Pod Ready 대기MySQL, ZooKeeper
Parallel병렬 생성/삭제, 순서 무관Elasticsearch (빠른 스케일링 필요)

DaemonSet: 모든 노드에 Pod 배포

동작 원리

DaemonSet Controller는 각 노드에 정확히 하나의 Pod가 실행되도록 보장합니다.

  • 노드 추가 시: 자동으로 해당 노드에 Pod 생성
  • 노드 삭제 시: 해당 노드의 Pod도 함께 삭제

nodeSelector와 tolerations

특정 노드에만 배포하거나, taint가 있는 노드에도 배포하려면:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: nvidia-driver
spec:
  selector:
    matchLabels:
      app: nvidia-driver
  template:
    spec:
      # 특정 노드에만 배포
      nodeSelector:
        hardware: gpu
      
      # taint를 허용
      tolerations:
      - key: nvidia.com/gpu
        operator: Exists
        effect: NoSchedule
      
      containers:
      - name: nvidia-driver
        image: nvidia/driver:latest

주요 사용 사례

사용 사례예시
로그 수집Fluentd, Fluent Bit, Filebeat
모니터링Node Exporter, cAdvisor
네트워크Calico, Cilium (CNI 플러그인)
스토리지CSI 드라이버, Rook-Ceph
보안Falco, Sysdig

CronJob & Job: 배치 작업

Job: 일회성 작업

apiVersion: batch/v1
kind: Job
metadata:
  name: db-migration
spec:
  backoffLimit: 3           # 실패 시 최대 재시도 횟수
  activeDeadlineSeconds: 600  # 최대 실행 시간 (10분)
  ttlSecondsAfterFinished: 3600  # 완료 후 1시간 뒤 자동 삭제
  template:
    spec:
      restartPolicy: Never   # Job에서는 OnFailure 또는 Never만 가능
      containers:
      - name: migration
        image: my-app:latest
        command: ["./migrate.sh"]

CronJob: 정기적 작업

apiVersion: batch/v1
kind: CronJob
metadata:
  name: daily-report
spec:
  schedule: "0 2 * * *"      # 매일 새벽 2시 (UTC)
  timeZone: "Asia/Seoul"     # K8s 1.27+ 지원
  
  # 동시 실행 정책
  concurrencyPolicy: Forbid  # 이전 Job이 실행 중이면 새 Job 생성 안함
  
  # 히스토리 제한
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 1
  
  # 스케줄 놓쳤을 때 정책
  startingDeadlineSeconds: 300  # 5분 내 시작 못하면 스킵
  
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
          - name: report
            image: report-generator:latest

concurrencyPolicy 비교

정책동작사용 사례
Allow동시 실행 허용 (기본값)독립적인 작업
Forbid이전 Job 실행 중이면 스킵중복 실행 방지 필요 시
Replace이전 Job 중단하고 새로 시작최신 데이터만 중요할 때

트러블슈팅 가이드

Deployment 롤아웃이 멈춤

# 상태 확인
kubectl rollout status deployment/my-app

# 이벤트 확인
kubectl describe deployment my-app

# ReplicaSet 상태 확인
kubectl get rs -l app=my-app

흔한 원인:

  1. 이미지 Pull 실패: ImagePullBackOff
  2. 리소스 부족: Pending 상태
  3. Liveness/Readiness Probe 실패: CrashLoopBackOff
  4. maxUnavailable 0: 기존 Pod 삭제 안 됨

StatefulSet Pod가 Pending 상태

# PVC 상태 확인
kubectl get pvc

# 스토리지 이벤트 확인
kubectl describe pvc data-mysql-0

흔한 원인:

  1. StorageClass 없음: 기본 StorageClass 미설정
  2. 가용 영역(AZ) 불일치: EBS는 같은 AZ에서만 마운트
  3. 용량 부족: AWS EBS 한도 초과

CronJob이 실행되지 않음

# CronJob 상태 확인
kubectl get cronjob

# 최근 Job 확인
kubectl get jobs --sort-by=.metadata.creationTimestamp

# 마지막 스케줄 시간 확인
kubectl describe cronjob daily-report | grep "Last Schedule"

흔한 원인:

  1. startingDeadlineSeconds 초과: 스케줄 시간 놓침
  2. concurrencyPolicy: Forbid + 긴 실행 시간: 이전 Job이 계속 실행 중
  3. suspend: true: CronJob이 일시 중지됨

정리

워크로드핵심 특징사용 사례
DeploymentReplicaSet 관리, Rolling Update무상태 웹 서비스
StatefulSet순차적 생성, 고정 네트워크 ID, PVC 유지DB, 메시지 큐
DaemonSet노드당 1개 Pod 보장로그/모니터링 에이전트
Job일회성 완료 작업마이그레이션, 배치
CronJob정기적 Job 스케줄링리포트, 정리 작업

다음 편 예고

2편: 서비스 네트워킹 심화에서는 다음을 다룹니다:

  • Service 타입별 내부 동작 (ClusterIP, NodePort, LoadBalancer)
  • kube-proxy의 iptables vs IPVS 모드
  • AWS ALB/NLB Ingress Controller
  • ExternalDNS와 Route 53 통합

참고 자료

Share

Related Articles

이 블로그는 학습한 내용을 기록하는 공간입니다.
직접 작성한 글도 있고, AI의 도움을 받아 정리한 글도 있습니다.
정확하지 않은 내용이 있을 수 있으니 참고용으로 봐주세요.

© 2026 sayu.day