Backend

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

2026-01-039 min read

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

Comments

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

© 2026 Seogyu Kim