Job 자동 정리 기능 개발자 가이드

진웅·2025년 12월 1일

K8S Basics

목록 보기
38/40

KubernetesJob을 자동으로 정리하는 기능 소개

1. TTL Controller를 이용한 자동 정리

Kubernetes는 ttlSecondsAfterFinished 필드를 통해 완료된 Job을 자동으로 정리하는 TTL(Time To Live) 메커니즘을 제공합니다.

기본 사용법

apiVersion: batch/v1
kind: Job
metadata:
  name: example-job
spec:
  ttlSecondsAfterFinished: 100  # 완료 후 100초 뒤 자동 삭제
  template:
    spec:
      containers:
      - name: job-container
        image: busybox
        command: ["sh", "-c", "echo Hello && sleep 10"]
      restartPolicy: Never

주요 특징

  • Job이 완료(성공 또는 실패)된 후 지정된 시간이 지나면 자동으로 삭제됩니다
  • Job과 연관된 Pod도 함께 삭제됩니다
  • ttlSecondsAfterFinished: 0으로 설정하면 완료 즉시 삭제됩니다

2. CronJob의 자동 정리 설정

CronJob에서는 완료된 Job의 보관 개수를 제어할 수 있습니다.

apiVersion: batch/v1
kind: CronJob
metadata:
  name: example-cronjob
spec:
  schedule: "*/5 * * * *"
  successfulJobsHistoryLimit: 3  # 성공한 Job 3개까지 보관
  failedJobsHistoryLimit: 1      # 실패한 Job 1개까지 보관
  jobTemplate:
    spec:
      ttlSecondsAfterFinished: 300  # 추가 안전장치
      template:
        spec:
          containers:
          - name: cronjob-container
            image: busybox
            command: ["sh", "-c", "date"]
          restartPolicy: OnFailure

3. 프로그래밍 방식 구현 (Go Client)

Go 클라이언트를 사용하여 Job을 생성하고 자동 정리를 설정하는 예제입니다.

package main

import (
    "context"
    "fmt"
    
    batchv1 "k8s.io/api/batch/v1"
    corev1 "k8s.io/api/core/v1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/client-go/kubernetes"
    "k8s.io/client-go/rest"
)

func createJobWithTTL(clientset *kubernetes.Clientset) error {
    ttlSeconds := int32(100)
    
    job := &batchv1.Job{
        ObjectMeta: metav1.ObjectMeta{
            Name:      "auto-cleanup-job",
            Namespace: "default",
        },
        Spec: batchv1.JobSpec{
            TTLSecondsAfterFinished: &ttlSeconds,
            Template: corev1.PodTemplateSpec{
                Spec: corev1.PodSpec{
                    Containers: []corev1.Container{
                        {
                            Name:    "worker",
                            Image:   "busybox",
                            Command: []string{"sh", "-c", "echo Processing && sleep 30"},
                        },
                    },
                    RestartPolicy: corev1.RestartPolicyNever,
                },
            },
        },
    }
    
    result, err := clientset.BatchV1().Jobs("default").Create(
        context.TODO(), 
        job, 
        metav1.CreateOptions{},
    )
    
    if err != nil {
        return fmt.Errorf("failed to create job: %v", err)
    }
    
    fmt.Printf("Job created: %s\n", result.Name)
    return nil
}

4. Python Client 예제

from kubernetes import client, config

def create_job_with_ttl():
    config.load_kube_config()
    batch_v1 = client.BatchV1Api()
    
    job = client.V1Job(
        api_version="batch/v1",
        kind="Job",
        metadata=client.V1ObjectMeta(name="python-auto-cleanup-job"),
        spec=client.V1JobSpec(
            ttl_seconds_after_finished=100,
            template=client.V1PodTemplateSpec(
                spec=client.V1PodSpec(
                    containers=[
                        client.V1Container(
                            name="worker",
                            image="busybox",
                            command=["sh", "-c", "echo Processing && sleep 30"]
                        )
                    ],
                    restart_policy="Never"
                )
            )
        )
    )
    
    batch_v1.create_namespaced_job(namespace="default", body=job)
    print("Job created with TTL")

5. 커스텀 자동 정리 컨트롤러 구현

더 복잡한 정리 로직이 필요한 경우 커스텀 컨트롤러를 구현할 수 있습니다.

package main

import (
    "context"
    "time"
    
    batchv1 "k8s.io/api/batch/v1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/client-go/kubernetes"
    "k8s.io/client-go/tools/cache"
    "k8s.io/client-go/util/workqueue"
)

type JobCleanupController struct {
    clientset      *kubernetes.Clientset
    maxAge         time.Duration
    checkInterval  time.Duration
}

func (c *JobCleanupController) Run(ctx context.Context) {
    ticker := time.NewTicker(c.checkInterval)
    defer ticker.Stop()
    
    for {
        select {
        case <-ticker.C:
            c.cleanupOldJobs(ctx)
        case <-ctx.Done():
            return
        }
    }
}

func (c *JobCleanupController) cleanupOldJobs(ctx context.Context) {
    jobs, err := c.clientset.BatchV1().Jobs("").List(ctx, metav1.ListOptions{})
    if err != nil {
        return
    }
    
    now := time.Now()
    for _, job := range jobs.Items {
        // 완료된 Job 확인
        if job.Status.CompletionTime == nil {
            continue
        }
        
        completionTime := job.Status.CompletionTime.Time
        age := now.Sub(completionTime)
        
        // 설정된 시간보다 오래된 경우 삭제
        if age > c.maxAge {
            deletePolicy := metav1.DeletePropagationForeground
            err := c.clientset.BatchV1().Jobs(job.Namespace).Delete(
                ctx,
                job.Name,
                metav1.DeleteOptions{
                    PropagationPolicy: &deletePolicy,
                },
            )
            if err == nil {
                // 로깅
            }
        }
    }
}

6. 베스트 프랙티스

TTL 설정 권장사항

  • 개발 환경: ttlSecondsAfterFinished: 300 (5분) - 빠른 정리
  • 스테이징 환경: ttlSecondsAfterFinished: 3600 (1시간) - 디버깅 가능
  • 프로덕션 환경: ttlSecondsAfterFinished: 86400 (24시간) - 충분한 감사 기간

모니터링 레이블 추가

apiVersion: batch/v1
kind: Job
metadata:
  name: monitored-job
  labels:
    app: data-processor
    environment: production
    cleanup-policy: auto
spec:
  ttlSecondsAfterFinished: 3600
  template:
    metadata:
      labels:
        app: data-processor
    spec:
      containers:
      - name: processor
        image: your-image:latest
      restartPolicy: Never

7. 트러블슈팅

TTL Controller가 동작하지 않는 경우

클러스터에서 TTL Controller가 활성화되어 있는지 확인합니다.

# kube-controller-manager 로그 확인
kubectl logs -n kube-system kube-controller-manager-<node-name> | grep ttl

# Feature Gate 확인 (1.34에서는 GA이므로 기본 활성화)
kubectl get pod -n kube-system kube-controller-manager-<node> -o yaml | grep feature-gates

완료된 Job 수동 조회 및 정리

# 완료된 Job 조회
kubectl get jobs --field-selector status.successful=1

# 7일 이상 된 완료된 Job 삭제 (kubectl 1.28+)
kubectl delete jobs --field-selector status.successful=1 \
  --all-namespaces \
  --dry-run=client

TTL Controller 동작 방식 및 장점

1. 삭제 대상 상태

ttlSecondsAfterFinished는 Job이 완료(Finished) 상태가 되면 작동합니다. 여기서 완료란:

삭제되는 경우

apiVersion: batch/v1
kind: Job
metadata:
  name: test-job
spec:
  ttlSecondsAfterFinished: 100  # 아래 모든 경우에 적용
  template:
    spec:
      containers:
      - name: worker
        image: busybox
        command: ["sh", "-c", "exit 0"]  # 또는 exit 1
      restartPolicy: Never

✅ 성공 (Completed): status.succeeded > 0

  • Pod가 정상 종료 (exit code 0)
  • Job 완료 후 100초 뒤 삭제

✅ 실패 (Failed): status.failed > 0

  • Pod가 에러로 종료 (exit code non-zero)
  • backoffLimit 도달
  • Job 완료 후 100초 뒤 삭제

✅ 수동 종료: kubectl delete로 중단된 경우도 완료로 간주

삭제되지 않는 경우

❌ 실행 중 (Running): Pod가 아직 실행 중
❌ 대기 중 (Pending): Pod가 스케줄링 대기 중
❌ Unknown: 상태를 알 수 없는 경우

2. 상태별 동작 확인 예제

# Job 생성 - 성공 케이스
cat <<EOF | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata:
  name: success-job
spec:
  ttlSecondsAfterFinished: 60
  template:
    spec:
      containers:
      - name: success
        image: busybox
        command: ["sh", "-c", "echo Success && exit 0"]
      restartPolicy: Never
EOF

# Job 생성 - 실패 케이스
cat <<EOF | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata:
  name: failed-job
spec:
  ttlSecondsAfterFinished: 60
  backoffLimit: 2
  template:
    spec:
      containers:
      - name: failure
        image: busybox
        command: ["sh", "-c", "echo Failed && exit 1"]
      restartPolicy: Never
EOF

# 상태 모니터링
watch kubectl get jobs,pods

60초 후 두 Job 모두 자동으로 삭제됩니다.

3. TTL Controller 사용의 장점

3.1 리소스 효율성

문제: Job과 Pod가 무한정 누적되어 etcd 부하 증가

# TTL 없이 6개월 운영 시
$ kubectl get jobs --all-namespaces | wc -l
15847  # 😱 수만 개의 완료된 Job

# etcd 데이터베이스 크기 증가
$ ETCDCTL_API=3 etcdctl endpoint status --write-out=table
+------------------+------------------+-----------+---------+
|    ENDPOINT      |        ID        | DB SIZE   | LEADER  |
+------------------+------------------+-----------+---------+
| 127.0.0.1:2379   | 8e9e05c52164694d | 8.2 GB    | true    |  # 😱
+------------------+------------------+-----------+---------+

해결: TTL로 자동 정리

# TTL 사용 시
$ kubectl get jobs --all-namespaces | wc -l
45  # ✅ 최근 24시간 내 Job만 유지

# etcd 크기 감소
DB SIZE: 2.1 GB  # ✅ 70% 감소

3.2 운영 자동화

Before (수동 정리):

# 매주 크론잡으로 수동 정리 필요
0 2 * * 0 kubectl delete jobs --field-selector status.successful=1 -A

# 또는 스크립트 작성
#!/bin/bash
for ns in $(kubectl get ns -o name | cut -d'/' -f2); do
    kubectl delete jobs --field-selector status.successful=1 -n $ns
done

After (TTL 사용):

# Job 정의에 한 줄만 추가하면 끝
spec:
  ttlSecondsAfterFinished: 86400  # 끝!

3.3 일관성 있는 정책 적용

조직 전체 표준 정책 설정 가능:

# ArgoCD/Flux로 배포되는 모든 Job에 자동 적용
apiVersion: v1
kind: ConfigMap
metadata:
  name: job-defaults
data:
  ttl-dev: "300"      # 개발: 5분
  ttl-staging: "3600" # 스테이징: 1시간
  ttl-prod: "86400"   # 프로덕션: 24시간

3.4 디버깅 시간 확보

상황에 맞게 TTL 조정:

# 프로덕션 - 충분한 디버깅 시간
apiVersion: batch/v1
kind: Job
metadata:
  name: payment-processor
spec:
  ttlSecondsAfterFinished: 86400  # 24시간 보관
  template:
    spec:
      containers:
      - name: processor
        image: payment-app:v1
# 개발 환경 - 빠른 정리
apiVersion: batch/v1
kind: Job
metadata:
  name: dev-test-job
spec:
  ttlSecondsAfterFinished: 300  # 5분만 보관

3.5 네임스페이스별 격리

# 각 팀이 자신의 정책 적용 가능
---
# Team A - 보수적
apiVersion: batch/v1
kind: Job
metadata:
  name: critical-job
  namespace: team-a
spec:
  ttlSecondsAfterFinished: 604800  # 7일
---
# Team B - 공격적
apiVersion: batch/v1
kind: Job
metadata:
  name: temp-job
  namespace: team-b
spec:
  ttlSecondsAfterFinished: 0  # 즉시 삭제

3.6 API Server 부하 감소

측정 가능한 개선:

# TTL 적용 전 - API 요청 수
$ kubectl top pods -n kube-system | grep apiserver
kube-apiserver-master-1   850m   2Gi

# TTL 적용 후 - 완료된 리소스 감소로 LIST 요청 부하 감소
kube-apiserver-master-1   620m   1.5Gi  # ✅ 약 27% CPU 감소

3.7 비용 절감 (매니지드 클러스터)

일부 클라우드 프로바이더는 etcd 크기나 API 요청 수로 과금:

# 예: EKS 비용 절감
- etcd 스토리지: 8GB → 2GB (월 $30 절감)
- API 요청: 30% 감소 (월 $50 절감)
총 월 $80 절감 (연간 $960)

4. 실제 운영 시나리오

시나리오 1: 데이터 파이프라인

apiVersion: batch/v1
kind: CronJob
metadata:
  name: daily-etl
spec:
  schedule: "0 2 * * *"  # 매일 새벽 2시
  successfulJobsHistoryLimit: 3  # 최근 3개 성공 보관
  failedJobsHistoryLimit: 1      # 최근 1개 실패 보관
  jobTemplate:
    spec:
      ttlSecondsAfterFinished: 172800  # 추가로 2일간 보관
      template:
        spec:
          containers:
          - name: etl
            image: data-pipeline:latest
          restartPolicy: OnFailure

장점:

  • 성공한 Job 3개 + 실패 1개만 CronJob이 관리
  • 그 외는 TTL로 2일 후 자동 정리
  • 디버깅에 충분한 시간 확보

시나리오 2: 대량 배치 작업

# 수천 개의 Job이 생성되는 경우
apiVersion: batch/v1
kind: Job
metadata:
  name: batch-processor-{{ .ItemID }}
spec:
  ttlSecondsAfterFinished: 3600  # 1시간 후 삭제
  template:
    spec:
      containers:
      - name<: worker
        image: batch-worker:latest

장점:

  • 1시간마다 자동 정리되어 etcd 부담 최소화
  • 수동 정리 스크립트 불필요

5. 모니터링 예제

TTL Controller 동작 모니터링:

# Prometheus 메트릭
apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-rules
data:
  job-cleanup.yaml: |
    groups:
    - name: job_cleanup
      rules:
      - alert: JobTTLNotWorking
        expr: count(kube_job_complete{job_ttl_seconds_after_finished!=""} == 1) > 100
        for: 1h
        annotations:
          summary: "완료된 Job이 100개 이상 누적됨"

이처럼 TTL Controller는 운영 효율성, 리소스 관리, 자동화 측면에서 큰 장점을 제공합니다. 특히 100+ 노드 규모의 대형 클러스터에서는 필수적인 기능입니다.

profile
bytebliss

0개의 댓글