Certified Kubernetes Application Developer (CKAD) #6 Workloads 2: DaemonSet, StatefulSet

#5 Workloads 1: Deployment, ReplicaSet, rolling update와 rollback에서는 Deployment로 stateless 한 Pod 묶음을 굴리고 롤링 업데이트와 롤백을 다뤘습니다. 그러나 쿠버네티스의 워크로드 컨트롤러가 Deployment 하나만 있는 것은 아닙니다. 모든 노드에 똑같이 Pod를 한 개씩 깔아야 하는 작업, 그리고 Pod마다 고유한 정체성과 전용 스토리지가 필요한 작업은 Deployment로 풀 수 없습니다.

이번 글에서는 그 두 가지를 책임지는 컨트롤러인 DaemonSetStatefulSet을 실기 관점에서 정리하겠습니다. CKAD는 빈 터미널에 직접 만들어 내는 시험이므로, 두 리소스의 개념을 짚은 뒤 곧바로 YAML과 kubectl로 손에 익혀 보겠습니다.

Deployment로는 풀 수 없는 두 가지 #

Deployment는 상호 교체가 가능한 stateless Pod들의 집합을 가정합니다. replicas를 3으로 두면 어느 노드에 몇 번째 Pod가 뜨는지는 스케줄러가 알아서 정하고, 어느 Pod가 죽어도 동일한 새 Pod로 대체되면 그만입니다. 이름도 web-7d8f...처럼 무작위 해시가 붙습니다.

그러나 실무에는 이 가정이 맞지 않는 작업이 있습니다.

  • 모든 노드에 정확히 하나씩 돌아야 하는 Pod. 로그 수집기나 노드 모니터링 에이전트는 노드 수와 Pod 수가 같아야 합니다. 이때는 DaemonSet을 씁니다.
  • 각 Pod가 고유한 이름과 전용 디스크를 유지해야 하는 Pod. 데이터베이스 클러스터의 각 노드는 자기만의 데이터를 갖고, 재시작해도 같은 정체성으로 돌아와야 합니다. 이때는 StatefulSet을 씁니다.

이 두 컨트롤러가 이번 글의 주제입니다.

DaemonSet: 모든 노드에 Pod 하나씩 #

DaemonSet은 클러스터의 모든(또는 일부) 노드에 Pod를 정확히 한 개씩 배치하는 컨트롤러입니다. 노드가 새로 추가되면 그 노드에도 자동으로 Pod가 뜨고, 노드가 빠지면 해당 Pod도 함께 사라집니다. replicas라는 개념이 없습니다. 노드 수가 곧 Pod 수입니다.

어디에 쓰나 #

DaemonSet은 노드 단위로 동작해야 하는 시스템 컴포넌트에 주로 쓰입니다.

  • 로그 수집기. Fluentd, Fluent Bit처럼 각 노드의 로그를 긁어 중앙으로 보내는 에이전트
  • 노드 모니터링. node-exporter처럼 노드의 CPU,메모리,디스크 지표를 수집하는 에이전트
  • CNI와 스토리지 플러그인. 각 노드에 네트워크나 스토리지 기능을 설치하는 컴포넌트

nodeSelector와 tolerations로 일부 노드만 한정 #

기본적으로 DaemonSet은 control plane의 taint가 걸린 노드를 제외한 모든 워커 노드에 Pod를 띄웁니다. 특정 노드에만 띄우고 싶다면 nodeSelector로 라벨을 지정합니다.

spec:
  template:
    spec:
      nodeSelector:
        disktype: ssd

반대로 control plane 노드처럼 taint가 걸린 노드에도 Pod를 띄워야 한다면 tolerations로 해당 taint를 허용합니다. 모니터링 에이전트는 control plane 노드도 봐야 하므로 이 설정을 자주 씁니다.

spec:
  template:
    spec:
      tolerations:
        - key: node-role.kubernetes.io/control-plane
          operator: Exists
          effect: NoSchedule

업데이트 전략 #

DaemonSet의 updateStrategy는 두 가지입니다.

  • RollingUpdate(기본값). maxUnavailable로 한 번에 교체할 Pod 수를 제한하며 순차 교체합니다.
  • OnDelete. 자동으로 교체하지 않고, 사용자가 기존 Pod를 직접 삭제할 때만 새 버전으로 뜹니다.

DaemonSet YAML 예제 #

DaemonSet에는 kubectl create generator가 없습니다. 시험에서는 Deployment 뼈대를 generator로 뽑은 뒤 kindDaemonSet으로 바꾸고 replicasstrategy 줄을 지우는 방식이 빠릅니다.

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: log-agent
  namespace: logging
spec:
  selector:
    matchLabels:
      app: log-agent
  template:
    metadata:
      labels:
        app: log-agent
    spec:
      tolerations:
        - key: node-role.kubernetes.io/control-plane
          operator: Exists
          effect: NoSchedule
      containers:
        - name: fluent-bit
          image: fluent/fluent-bit:2.2
          resources:
            limits:
              memory: 200Mi
            requests:
              cpu: 100m
              memory: 200Mi

생성과 확인은 다음과 같습니다.

k apply -f ds.yaml
k get daemonset -n logging
k get pods -n logging -o wide   # 노드마다 한 개씩 떠 있는지 확인

DESIREDCURRENT 값이 노드 수와 일치하면 정상입니다.

StatefulSet: 정체성과 스토리지를 가진 Pod #

StatefulSet은 각 Pod에 안정적이고 고유한 정체성을 부여하는 컨트롤러입니다. Deployment가 무작위 해시 이름을 붙이는 것과 달리, StatefulSet은 Pod에 web-0, web-1, web-2처럼 순서가 있는 고정 이름을 줍니다.

StatefulSet이 보장하는 세 가지 #

  • 안정적 네트워크 ID. 각 Pod는 web-0 같은 고정 이름과, web-0.web.default.svc.cluster.local 형태의 고정 DNS 이름을 갖습니다. Pod가 재시작되어도 이름과 DNS는 그대로 유지됩니다.
  • 순서 보장 생성과 삭제. Pod는 web-0web-1web-2 순서로 차례대로 생성되고, 삭제와 스케일 다운은 역순으로 진행됩니다. 앞 Pod가 Running이 되어야 다음 Pod가 뜹니다.
  • 안정적 스토리지. 각 Pod는 volumeClaimTemplates로 만들어진 자기 전용 PVC를 갖습니다. Pod가 다시 스케줄되어도 같은 PVC에 다시 연결되어 데이터가 보존됩니다.

headless Service가 필요한 이유 #

StatefulSet은 headless Service를 함께 정의해야 합니다. headless Service는 clusterIP: None으로 지정한 Service로, 클러스터 IP를 할당하지 않고 각 Pod의 개별 DNS 레코드를 만들어 줍니다. 이 Service의 이름을 StatefulSet의 serviceName에 적으면 web-0.web, web-1.web처럼 Pod 하나하나에 직접 접근할 수 있습니다. 데이터베이스 클러스터에서 특정 노드(예: 프라이머리)에 직접 접속해야 할 때 이 고정 주소가 필수입니다.

volumeClaimTemplates로 Pod 별 PVC #

volumeClaimTemplates는 StatefulSet이 Pod마다 PVC를 자동으로 찍어내는 틀입니다. replicas가 3이면 data-web-0, data-web-1, data-web-2라는 PVC가 각각 생성됩니다. 주의할 점은 StatefulSet을 삭제해도 이 PVC는 자동으로 지워지지 않는다는 것입니다. 데이터 보존이 목적이므로 의도된 동작이며, 정리하려면 PVC를 직접 삭제해야 합니다.

언제 쓰나 #

StatefulSet은 각 인스턴스가 고유한 정체성과 데이터를 갖는 워크로드에 씁니다.

  • 데이터베이스. PostgreSQL, MySQL의 복제 구성처럼 노드마다 데이터가 다른 경우
  • 분산 시스템. Kafka, ZooKeeper, Elasticsearch처럼 멤버 간 역할과 순서가 중요한 클러스터

StatefulSet YAML 예제 (headless Service + volumeClaimTemplates) #

StatefulSet도 generator가 없으므로 직접 작성합니다. headless Service와 StatefulSet을 한 파일에 함께 두면 관리가 편합니다.

apiVersion: v1
kind: Service
metadata:
  name: web          # serviceName과 일치해야 함
  namespace: default
spec:
  clusterIP: None    # headless: 클러스터 IP 미할당
  selector:
    app: web
  ports:
    - port: 80
      name: http
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
  namespace: default
spec:
  serviceName: web   # 위 headless Service 이름
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: nginx
          image: nginx:1.25
          ports:
            - containerPort: 80
              name: http
          volumeMounts:
            - name: data
              mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 1Gi

생성 후 Pod 이름과 PVC가 순서대로 만들어졌는지 확인합니다.

k apply -f sts.yaml
k get statefulset web
k get pods -l app=web        # web-0, web-1, web-2 순서로 생성
k get pvc                    # data-web-0, data-web-1, data-web-2

스케일 조정은 Deployment와 같은 명령으로 하지만, Pod는 역순으로 정리됩니다.

k scale statefulset web --replicas=5   # web-3, web-4 추가
k scale statefulset web --replicas=2   # web-4, web-3, web-2 역순 삭제

Deployment vs StatefulSet vs DaemonSet 비교 #

세 컨트롤러의 차이를 한 표로 정리하면 다음과 같습니다.

항목DeploymentStatefulSetDaemonSet
Pod 정체성무작위 해시 이름순서 있는 고정 이름(web-0,1,2)노드별 한 개
Pod 수replicas로 지정replicas로 지정노드 수와 동일
생성,삭제 순서순서 보장 없음순서 보장(생성 정순, 삭제 역순)노드 추가,삭제에 연동
전용 스토리지없음(공유 또는 무상태)Pod 별 PVC(volumeClaimTemplates)보통 노드 hostPath
headless Service불필요필수(serviceName)불필요
대표 용도stateless 웹,APIDB,분산 시스템로그,모니터링,CNI

핵심 판단 기준은 간단합니다. 상호 교체 가능하면 Deployment, 고유 정체성과 전용 데이터가 필요하면 StatefulSet, 노드마다 하나씩 깔아야 하면 DaemonSet 입니다.

시험 포인트 #

CKAD에서 이 두 컨트롤러가 나올 때 자주 걸리는 부분을 정리하겠습니다.

  • generator가 없다. DaemonSet과 StatefulSet은 kubectl create로 뼈대를 못 만듭니다. Deployment 뼈대를 k create deploy ... $do > x.yaml로 뽑은 뒤 kind를 바꾸고 불필요한 필드를 손보는 방식이 가장 빠릅니다.
  • DaemonSet으로 바꿀 때 지울 것. Deployment 뼈대에서 replicasstrategy, status 줄을 삭제해야 유효한 DaemonSet이 됩니다.
  • StatefulSet의 serviceName 누락. serviceName은 필수 필드이며, 가리키는 headless Service(clusterIP: None)가 실제로 존재해야 Pod의 DNS가 동작합니다. 둘 중 하나라도 빠지면 감점입니다.
  • volumeClaimTemplates와 volumeMounts의 name 일치. volumeClaimTemplatesmetadata.name과 컨테이너 volumeMountsname이 같아야 PVC가 마운트됩니다.
  • PVC는 남는다. StatefulSet을 지워도 PVC는 자동 삭제되지 않습니다. 문제에서 정리까지 요구하면 PVC를 별도로 삭제합니다.
  • DaemonSet의 Pod 수 확인. 정답 검증은 k get pods -o wide로 노드마다 한 개씩 떠 있는지를 봅니다. control plane 노드에도 떠야 하면 tolerations를 빠뜨리지 않습니다.

정리 #

이번 글에서 잡은 것은 다음과 같습니다.

  • DaemonSet은 모든(또는 일부) 노드에 Pod를 하나씩 배치하는 컨트롤러. 로그 수집기,노드 모니터링,CNI에 쓰며, nodeSelectortolerations로 대상 노드를 한정
  • StatefulSet은 안정적 네트워크 ID, 순서 보장 생성,삭제, 전용 스토리지를 제공. DB,분산 시스템에 쓰며 headless Service와 volumeClaimTemplates가 핵심
  • headless ServiceclusterIP: None으로 각 Pod의 개별 DNS를 만들어 web-0.web 같은 고정 주소를 제공
  • 세 컨트롤러 선택 기준. 상호 교체면 Deployment, 고유 정체성,데이터면 StatefulSet, 노드별 한 개면 DaemonSet
  • 시험 주의. generator 없음, serviceName 필수, volume name 일치, PVC 잔존

워크로드 컨트롤러의 큰 그림이 더 필요하다면 Kubernetes 중급 트랙에서 같은 리소스를 운영 관점으로 더 깊게 다룹니다.

다음: Workloads 3 #

DaemonSet과 StatefulSet까지 익히며 상시 실행되는 워크로드는 정리했습니다. 이제 남은 것은 한 번 또는 주기적으로 실행되고 끝나는 작업입니다.

#7 Workloads 3: Job, CronJob (백오프, 동시성)에서는 배치 작업을 담당하는 Job과 CronJob을 다루겠습니다. completionsparallelism으로 병렬 실행을 제어하는 법, backoffLimitactiveDeadlineSeconds로 실패와 타임아웃을 다루는 법, CronJob의 concurrencyPolicy와 스케줄 표기까지 YAML로 만들어 보며 정리하겠습니다.

X