본문 바로가기
Cloud Native/Kubernetes

8. DaemonSet, Job, CronJob

by jys275 2026. 1. 24.

서비스가 항상 떠 있어야 하는지, 아니면 할 일만 하고 퇴근해도 되는지에 따라 사용하는 도구가 달라진다. 각 오브젝트의 '생명 주기'와 '목적'을 명확히 구분하는 연습부터 한다.

 


 

 

1. 컨트롤러별 핵심 개념과 사용 시점

 

쿠버네티스에는 파드를 관리하는 여러 관리자가 있다. 상황에 맞춰 골라 쓰는 법을 익혀야 한다.

  • DaemonSet (데몬셋): 노드의 자원 상태와 상관없이 모든 노드에 파드를 하나씩 배치한다.
    • 성능 수집(Prometheus), 로그 수집(Fluentd), 네트워크 프록시(Kube-proxy)처럼 노드 전체를 관리해야 하는 에이전트 설치에 필수적이다. 즉, 이렇게 각각의 노드마다 하나씩 설치해야하는 서비스들이 있는 것을 때 필수적이다..
    • ReplicaSet은 자원이 많이 남은 곳에 파드를 많이 배치할 것이다. 또, 자원이 별로 없으면 파드를 배치를 잘 안할 것이다. 하지만 DaemonSet은 그냥 모든 노드에 파들르 하나씩 배치하는 것이다.
  • Job (잡): 할 일이 끝나면 파드를 종료시킨다. ReplicaSet과 Job 같은 컨트롤러에 의해 생성된 파드들은 장애가 감지되면 다른 노드에 재생성되어 서비스는 계속 유지가 된다
    1. ReplicaSet을 통해 만들어진 Pod
      • ReplicaSet은 파드를 어떻게든 살려두려(Restart)하기 때문에 이 파드에 있는 서비스들은 절대 종료되면 안될 때 사용한다. 
    2. Job을 통해 만들어진 Pod
      • 프로세스가 일을 하지 않으면 자원을 반환하고 종료(멈춘다)된다.
      • 즉, 종료가 아니라 멈추는 이유는 결과 확인을 위해이며, 파드가 바로 삭제되지는 않고 로그를 확인할 수 있는 상태로 남는다.
  • CronJob (크론잡): Job을 주기적인 특정 시간에 맞춰 실행한다.
    • 새벽마다 수행하는 DB 백업, 정기 업데이트 확인, 예약 메시지 발송 등에 최적화되어 있다.

 

 

 

2. 오브젝트별 실무 상세 옵션

1. DaemonSet의 세밀한 제어

Selectortemplate이 있는 걸 관찰 가능하다. 그래서, 모든 Node에 template으로 파드를 만든다.

  • nodeSelector: 특정 라벨이 붙은 노드에만 데몬셋 파드를 띄우거나, 우분투(Ubuntu) 등 특정 OS 노드를 제외하고 싶을 때 사용한다. "모든 노드에 띄우는 것이 기본"이지만, 원치 않는 곳은 뺄 수 있다는 뜻이다.
  • hostPort: 노드의 특정 포트를 파드에 직접 연결한다. 서비스(Service)를 통하지 않고 노드 IP와 포트로 "파드에 즉시 접근"해야 할 때 가능하다는 의미가 되겠고, 유용하다.

 

 

2. Job의 작업 관리 전략

Job도 마찬가지로 Selector template이 있는데,

  1. Selector는 직접 만들지 않아도 된다. Job이 알아서 만들어 준다. 
  2. template은 "특정 작업만 하고 종료가 되는 일"을 하는 파드들을 담는다.
  • completions: 작업이 총 몇 번 성공해야 잡이 끝나는지 지정한다. 순차적으로 여러 번 실행할 때 쓴다.
    • 6을 주면 6개의 파드를 하나씩 순차적으로 실행 후, 모두 작업이 끝나야, Job도 종료된다.
  • parallelism: 한 번에 몇 개의 파드를 동시에 띄울지 결정한다. 병렬 처리가 필요할 때 핵심이다.
    • 2를 주면 2개씩 파드가 생성된다.
  • activeDeadlineSeconds: 지정한 시간(초) 내에 작업이 끝나지 않으면 강제로 종료한다. 무한 루프나 행(Hang)이 걸린 작업을 방지하는 안전장치다.
    • 30을 주면 이 Job은 30초 후에 기능이 정지된다. 그리고 실행되고 있는 파드, 아직 실행되지 못한 파드까지 모두 삭제된다.
  • restartPolicy: 잡은 반드시 Never 또는 OnFailure로 설정해야 한다. 중급 과정에서 다룰 파드 라이프 사이클과 밀접한 연관이 있다.

 

 

3. CronJob의 동시성 제어 (concurrencyPolicy)

CronJob은 jobTemplate이 있어서 이 내용을 통해서 Job을 만들어주고, schedule까지 있어서 이 시간을 주기로 Job을 만든다.

  • concurrencyPolicy라는 잡이 겹쳤을 때의 동작을 정의하는 가장 중요한 옵션이 있다.
    • Allow (기본값): 이전 작업이 안 끝났어도 시간이 되면 새 잡을 또 만든다.
    • Forbid: 이전 작업이 실행 중이면 다음 스케줄을 건너뛴다.
    • Replace: 이전 작업을 죽이고 새 작업을 시작한다. 즉, 기존 Pod를 삭제하고, 새로운 Pod가 만들어진다.

 

 

💡꼬리 질문

Q: Job이나 CronJob에서 파드의 restartPolicy를 왜 Always로 설정할 수 없을까? 만약 강제로 설정한다면 어떤 문제가 생길까?

 

 

결론: 잡의 근본적인 목적(작업 완료 후 종료)과 충돌하기 때문이다.

  1. 무한 루프 발생: Always는 파드가 종료되면 쿠버네티스가 무조건 다시 살려내라는 뜻이다. 잡은 할 일이 끝나면 종료되어야 하는데, Always가 설정되어 있으면 작업이 끝나도 계속 살아나서 불필요한 자원을 무한히 소모하게 된다.
  2. 작업 성공 판단 불가: 잡은 파드가 성공적으로 종료(Exit 0)되는 것을 보고 작업 완료를 판단한다. 하지만 리스타트 정책이 Always면 '종료'라는 상태에 도달할 수 없으므로, 잡은 영원히 끝나지 않는 상태(Running)에 빠지게 된다.

따라서 잡 기반 컨트롤러에서는 성공 시 종료되거나(Never), 실패 시에만 재시도하는(OnFailure) 정책만 허용되는 것이다.

 

 


 

 

인프라 운영의 자동화는 '상태 유지(DaemonSet)'와 '목표 달성(Job/CronJob)'을 얼마나 정교하게 설계하느냐에 달렸다. 세 가지 오브젝트의 실무적 핵심을 알아보자.

 

 

 

1. DaemonSet: 노드 밀착형 에이전트 관리

인프런_대세는 쿠버네티스

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: daemonset-1
spec:
  selector:
    matchLabels:
      type: app
  template:
    metadata:
      labels:
        type: app
    spec:
      containers:
      - name: container
        image: kubetm/app
        ports:
        - containerPort: 8080
          hostPort: 18080
  • hostPort의 위력: 서비스(Service) 없이 노드의 IP와 포트로 파드에 직접 접근한다. 실습에서 노드 IP(31번, 32번)의 18080 포트로 접속했을 때 각 노드에 떠 있는 파드의 호스트네임이 리턴되는 것을 확인했다.
  • nodeSelector를 통한 타겟팅: kubectl label nodes 명령으로 노드에 os=centos 같은 라벨을 부여하고, nodeSelector를 설정하면 특정 환경의 노드에만 에이전트를 배포할 수 있다. 실습 중 노드 2에 라벨을 추가하자마자 데몬셋이 이를 감지하고 파드를 즉시 생성하는 역동성을 기억하자.
  • 업데이트: 기본적으로 롤링 업데이트가 적용되어 파드가 하나씩 교체된다.

 

 

 

2. Job: 확실한 일 처리와 리소스 관리

  • 일시적 작업의 표준: 파드가 일을 마치고 종료(Completed)되어도 파드를 바로 지우지 않는다. 이는 엔지니어가 로그(kubectl logs)를 확인하여 작업 결과를 검증할 수 있게 하기 위함이다.
apiVersion: batch/v1
kind: Job
metadata:
  name: job-2
spec:
  completions: 6
  parallelism: 2
  activeDeadlineSeconds: 30
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: container
        image: kubetm/init
        command: ["sh", "-c", "echo 'job start';sleep 20; echo 'job end'"]
      terminationGracePeriodSeconds: 0
  • 속도와 양 조절: completions로 총 작업 횟수를, parallelism으로 동시 실행 파드 수를 제어한다.
  • 안전장치: activeDeadlineSeconds는 작업이 무한 루프에 빠져 자원을 점유하는 것을 막는 보험이다. 실습에서 30초가 지나자마자 실행 중이던 파드들이 강제 삭제되는 것을 확인했다.

 

 

 

3. CronJob: 정기적 실행과 예외 처리

인프런_대세는 쿠버네티스

apiVersion: batch/v1
kind: CronJob
metadata:
  name: cron-job
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: Never
          containers:
          - name: container
            image: kubetm/init
            command: ["sh", "-c", "echo 'job start';sleep 20; echo 'job end'"]
          terminationGracePeriodSeconds: 0
  • 스케줄링과 매뉴얼: schedule 설정에 따라 주기적으로 실행되지만, kubectl create job --from=cronjob 명령을 통해 필요할 때 즉시 수동 실행(Manual Trigger)도 가능하다.
kubectl create job --from=cronjob/cron-job cron-job-manual-001
  • 일시 정지(Suspend): kubectl patch 명령으로 suspend: true 설정을 주면 스케줄링을 잠시 멈출 수 있다. 장애 대응 시 유용한 기능이다.
kubectl patch cronjobs cron-job -p '{"spec" : {"suspend" : true }}'
  • 동시성 정책 (concurrencyPolicy): * Allow: 앞선 작업이 안 끝났어도 새 잡을 만든다. 위에 했던 것이다.
apiVersion: batch/v1
kind: CronJob
metadata:
  name: cron-job-2
spec:
  schedule: "20,21,22 * * * *"
  concurrencyPolicy: Forbid
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: Never
          containers:
          - name: container
            image: kubetm/init
            command: ["sh", "-c", "echo 'job start';sleep 140; echo 'job end'"]
          terminationGracePeriodSeconds: 0
  • 동시성 정책 (concurrencyPolicy): Forbid: 이전 작업이 실행 중이면 다음 스케줄을 건너뛴다.
    • 가장 안전한 방식이다. 이전 작업이 살아있으면 다음 스케줄을 과감히 포기한다.
      • 20분 00초: 첫 번째 잡 생성. 파드가 실행된다. (이 파드는 22분 20초까지 140 동안 일을 한다.)
      • 21분 00초: 새 스케줄이 도달했다. 쿠버네티스가 확인하니 20분에 만든 파드가 아직 한창 일하는 중(Running)이다.
        • 결과: 21분 스케줄은 스킵(Skip)된다. 잡이 아예 생성되지 않는다.
      •  22분 00초: 다시 새 스케줄이 도달했다. 20분 파드는 여전히 실행 중이다(22분 20초 종료 예정).
        • 결과: 스크립트 실습 결과에 따르면, 20분 파드가 종료되는 시점 이후의 스케줄인 22분 대에 새로운 잡이 생성되는 것을 확인할 수 있다. 즉, 겹치는 시간대(21분)는 확실히 건너뛴다.
  • 동시성 정책 (concurrencyPolicy): Replace: 이전 작업을 죽이고 새 작업을 시작한다.
    • 최신 데이터를 유지하는 게 더 중요할 때 사용한다.
      • 20분 00초: 첫 번째 잡 생성 및 파드 실행.
      • 21분 00초: 새 스케줄 도달. 20분 파드가 아직 실행 중이다.
        • 결과: 실행 중이던 20분 파드를 강제로 종료(Kill)시킨다. 그리고 즉시 21분 파드를 새로 생성하여 실행한다.
      •  22분 00초: 또 스케줄 도달. 21분 파드가 140초를 못 채웠으므로 아직 실행 중이다.
        • 결과: 21분 파드를 죽이고 22분 파드를 새로 띄운다. 결국 최종적으로는 22분 파드만 완주하게 될 확률이 높다.

 


 

 

💡 꼬리 질문

Q: DaemonSet에서 hostPort를 사용할 때, 한 노드에 해당 포트를 사용하는 다른 프로세스가 이미 있다면 어떤 일이 벌어질까? 그리고 실무에서 LoadBalancer 타입 서비스 대신 hostPort를 쓰는 이유는 무엇일까?

 

 

1. 포트 충돌 문제

  • hostPort는 노드의 네트워크 네임스페이스를 직접 사용한다. 따라서 해당 포트가 이미 사용 중이라면 파드는 PortConflict 에러와 함께 생성에 실패한다. 이는 hostPort가 노드 자원을 독점적으로 점유하기 때문이다.

2. hostPort 사용 이유

  • 성능 극대화와 복잡성 감소 때문이다. 로드밸런서를 거치면 여러 네트워크 홉(Hop)을 지나야 하지만, hostPort는 노드 IP로 찌르는 순간 바로 파드 컨테이너로 연결된다. 네트워크 레이턴시에 민감한 성능 수집기나, 클라우드 로드밸런서를 쓰기 어려운 베어메탈 환경의 네트워크 프록시를 구성할 때 이 방식을 선택한다.

 

 

 

 

위 학습용 정리 내용 및 사진 자료는 "인프런_대세는 쿠버네티스(https://inf.run/Lv5RV)" 강의를 소스로 작성하였습니다.