
새벽마다 반복되는 데이터 배치 작업, 수동으로 관리하고 계신가요?
매일 새벽 3시, 어김없이 돌아오는 데이터 동기화 작업이 실패했다는 알람을 보고 잠에서 깬 적이 있나요? 로그를 확인하러 접속하니 클러스터 자원이 부족해서 작업이 아예 시작조차 되지 않았거나, 이전 작업이 끝나지도 않았는데 다음 작업이 겹쳐서 데이터가 꼬여버린 상황을 마주하면 눈앞이 아득해지곤 해요. 이런 문제는 데이터 엔지니어에게 단순한 실수 이상의 스트레스를 줘요.
단순히 kubectl run 명령어를 반복하거나 스크립트를 직접 실행하는 방식은 규모가 커질수록 한계가 명확해요. 쿠버네티스 환경에서 가장 안정적으로 정기 작업을 운영하려면 크론잡 YAML 설정 파일을 완벽하게 이해하고 제어할 수 있어야 해요. 매니페스트 하나를 어떻게 작성하느냐에 따라 자동화의 안정성이 완전히 달라지니까요.
이 글은 단순히 문법을 나열하는 데 그치지 않아요. 실제 운영 환경에서 마주하는 변수들을 어떻게 설정값으로 녹여내는지, 그리고 실수를 줄이기 위해 무엇을 확인해야 하는지 실무자의 관점에서 다뤄요. 글을 모두 읽고 나면 여러분의 파이프라인을 위한 탄탄한 템플릿을 손에 넣을 수 있어요.
오늘 다룰 내용은 다음과 같아요.
- 크론잡 매니페스트 구성을 위한 필수 필드 분석
- 실무에서 바로 복사해서 쓰는 단계별 YAML 예제
- 작업 중복과 실패를 방지하는 고급 옵션 활용법
- 배포 후 검증하고 트러블슈팅하는 방법
작성 전 반드시 체크해야 할 기본 개념과 준비물
크론잡을 작성하기 전에 우리가 무엇을 제어하려 하는지 명확히 정의해야 해요. 크론잡은 단순히 명령어를 실행하는 도구가 아니에요. 쿠버네티스 컨트롤러가 정해진 시간에 Job 객체를 생성하고, 그 Job이 다시 Pod를 만들어 작업을 수행하는 계층적인 구조를 가지고 있어요.
무턱대고 YAML을 쓰기 시작하면 나중에 자원 낭비나 작업 충돌로 고생할 수 있어요. 작업을 설계하기 전에 아래의 비교 기준을 먼저 살펴보세요. 어떤 객체를 사용할지 결정하는 데 큰 도움이 돼요.
| 비교 항목 | Job (잡) | CronJob (크론잡) |
|---|---|---|
| 실행 방식 | 단발성 실행 | 스케줄 기반 반복 실행 |
| 주요 용도 | 일회성 데이터 마이그레이션 | 매일/매주 정기 배치 작업 |
| 관리 주체 | 사용자가 직접 실행 | 쿠버네티스 컨트롤러가 자동 관리 |
준비물은 간단해요. 하지만 환경이 갖춰지지 않으면 테스트조차 할 수 없어요. 쿠버네티스 클러스터 접근 권한과 작업을 실행할 컨테이너 이미지가 반드시 준비되어 있어야 해요. 특히 데이터 엔지니어라면 작업에 필요한 DB 접속 정보나 API 키를 담은 Secret이나 ConfigMap을 미리 만들어 두는 것이 훨씬 효율적이에요.
크론잡은 직접 실행되는 것이 아니라, 스케줄에 맞춰 Job을 생성하는 ‘관리자’ 역할을 한다는 점을 기억하세요. 실제 작업의 상세 설정은 JobTemplate 안에 들어있어요.
마지막으로 고려해야 할 기준은 타임존(Timezone)이에요. 대부분의 쿠버네티스 클러스터는 UTC 기준으로 동작해요. 만약 한국 시간(KST) 기준으로 새벽 3시에 작업을 돌리고 싶다면, YAML의 스케줄 설정을 UTC에 맞춰서 계산하거나 클러스터 설정을 확인해야 해요. 이 부분을 놓치면 엉뚱한 시간에 데이터가 처리되는 대참사가 발생해요.
실무 적용을 위한 단계별 크론잡 YAML 작성법
이제 본격적으로 크론잡 YAML 설정 파일을 한 줄씩 뜯어보며 작성해 볼게요. 단순히 코드를 따라 쓰는 것이 아니라, 각 설정값이 어떤 의미를 갖는지 이해하는 것이 중요해요.
STEP 1. 기본 뼈대와 메타데이터 구성하기
모든 쿠버네티스 매니페스트는 API 버전과 종류를 명시하는 것으로 시작해요. 크론잡의 경우 apiVersion: batch/v1과 kind: CronJob을 사용해요. 메타데이터 섹션에서는 작업의 이름을 정의하는데, 이때 이름은 클러스터 내에서 고유해야 하며 너무 길지 않게 작성하는 것이 좋아요.
이 단계에서는 아직 아무런 동작도 하지 않지만, 이 뼈대가 틀리면 이후 모든 설정이 무효화돼요. 이름을 정할 때는 data-sync-daily처럼 작업의 목적을 명확히 드러내는 이름을 추천해요.
STEP 2. 스케줄(Schedule) 필드 정복하기
크론잡의 심장은 바로 spec.schedule 필드예요. 표준 크론(Cron) 표현식을 따르며, 다섯 개의 별(minute, hour, day of month, month, day of week)로 구성돼요.
예를 들어, 0 3 * * *라고 적으면 매일 새벽 3시 0분에 실행하라는 뜻이에요. 만약 10분마다 실행하고 싶다면 */10 * * * *라고 쓰면 돼요. 여기서 주의할 점은 작성한 스케줄이 UTC 기준인지 다시 한번 확인하는 습ety예요. 한국 시간(KST) 새벽 3시는 UTC 기준으로 전날 오후 6시(18:00)라는 점을 잊지 마세요.
STEP 3. 작업 실행 정책(Concurrency Policy) 결정하기
데이터 엔지니어에게 가장 중요한 단계예요. 이전 작업이 아직 끝나지 않았는데 다음 스케줄이 돌아오면 어떻게 할 것인가를 결정해야 해요. spec.concurrencyPolicy 필드에는 세 가지 선택지가 있어요.
- Allow (허용): 이전 작업의 완료 여부와 상관없이 새로운 작업을 실행해요. 작업이 겹쳐도 상관없는 가벼운 작업에 적합해요.
- Forbid (금지): 이전 작업이 진행 중이면 새로운 작업을 실행하지 않고 건너뛰어요. 데이터 정합성이 중요한 배치 작업에 가장 권장되는 방식이에요.
- Replace (교체): 새로운 작업이 들어오면 기존에 돌고 있던 작업을 강제로 종료하고 새 작업을 시작해요. 항상 최신 상태를 유지해야 하는 작업에 유용해요.
데이터 정합성이 핵심인 파이프라인이라면 반드시
Forbid를 고려하세요. 중복 실행으로 인해 데이터가 이중으로 삽입되거나 충돌하는 것을 막아줘요.STEP 4. 결과 관리 및 히스토리 제한하기
크론잡이 성공하든 실패하든, 그 흔적은 클러스터에 남아요. 관리를 제대로 안 하면 수많은 Pod가 쌓여서 클러스터 자원을 갉아먹게 돼요. spec.successfulJobsHistoryLimit와 spec.failedJobsHistoryLimit를 통해 이를 조절할 수 있어요.
보통 성공한 기록은 3개 정도만 남기고, 실패한 기록은 디버깅을 위해 5개 정도 남겨두는 것이 적당해요. 이 설정을 빠뜨리면 나중에 kubectl get pods를 쳤을 때 수백 개의 완료된 Pod 목록을 보며 한숨을 쉬게 될 거예요.
STEP 5. 실무형 완성 예제 코드 살펴보기
지금까지 배운 내용을 모두 녹여낸 실제 크론잡 yaml 예제를 보여드릴게요. 이 예제는 매일 새벽 3시(UTC 기준)에 실행되며, 작업이 중복되지 않도록 설정된 데이터 파이프라인용 템플릿이에요.
apiVersion: batch/v1
kind: CronJob
metadata:
name: nightly-data-pipeline
spec:
# 매일 03:00 (UTC 기준) 실행
schedule: "0 3 * * *"
# 작업 중복 방지
concurrencyPolicy: Forbid
# 성공한 기록은 3개만 유지
successfulJobsHistoryLimit: 3
# 실패한 기록은 5개 유지
failedJobsHistoryLimit: 5
jobTemplate:
spec:
template:
spec:
containers:
- name: pipeline-worker
image: my-registry/data-worker:v1.2.3
command: ["python", "/app/main.py"]
env:
- name: DB_URL
valueFrom:
secretKeyRef:
name: db-secret
key: url
resources:
requests:
cpu: "250m"
memory: "512Mi"
limits:
cpu: "500m"
memory: "1Gi"
# 작업 실패 시 최대 3번 재시도
restartPolicy: OnFailure
위 코드에서 주목할 점은 resources 설정이에요. 크론잡도 결국 Pod를 생성하므로, 적절한 CPU와 메모리 요청(requests) 및 제한(limits)을 설정하지 않으면 다른 서비스의 자원을 침범하거나, 반대로 자원이 부족해 작업이 계속 실패할 수 있어요. 또한 restartPolicy: OnFailure를 통해 작업이 완전히 실패했을 때 자동으로 재시도할 수 있도록 설계했어요.
자주 하는 실수와 해결법
크론잡을 운영하다 보면 예상치 못한 상황이 빈번하게 발생해요. 경험 많은 엔지니어들도 놓치기 쉬운 대표적인 실수들을 정리했어요.
- ❌ 잘못된 크론 표현식 사용 →
crontab.guru같은 도구를 활용해 작성한 스케줄이 의도한 대로 작동하는지 미리 검증하세요. - ❌ 타임존 불일치로 인한 실행 시점 오류 → 클러스터의 시스템 시간과 작업 기준 시간을 반드시 대조하고, 가급적 환경 변수로
TZ를 명시하는 것이 안전해요. - ❌ 작업 중복 실행으로 인한 데이터 오염 →
concurrencyPolicy를 설정하지 않아 발생하는 문제입니다. 반드시Forbid나Replace중 하나를 선택하세요. - ❌ 과도한 히스토리 누적으로 인한 자원 낭비 →
historyLimit설정을 생략하면 수많은 Pod가 클러스터에 남아 관리 효율을 떨어뜨려요. - ❌ 리소스 부족으로 인한 작업 지연 → Pod가 Pending 상태로 오래 머문다면
resources.requests를 현실적으로 높여주세요.
자주 묻는 질문
Q. 스케줄된 시간에 작업이 실행되지 않고 건너뛰어졌어요. 왜 그런가요?
가장 흔한 원인은 concurrencyPolicy: Forbid 설정 때문이에요. 이전 작업이 아직 끝나지 않았는데 다음 스케줄이 돌아오면, 쿠버네티스는 새로운 작업을 만들지 않고 건너뛰어요. 작업이 너무 오래 걸린다면 작업 로직을 최적화하거나, 스케줄 간격을 조정해야 해요.
Q. 크론잡이 실행 중인 Pod의 로그는 어떻게 확인하나요?
kubectl get cronjob으로 이름을 확인한 뒤, kubectl get pods를 통해 해당 크론잡에 의해 생성된 Pod 이름을 찾으세요. 그 후 kubectl logs [Pod이름] 명령어를 사용하면 실시간 로그를 볼 수 있어요. 만약 작업이 이미 끝났다면 failedJobsHistoryLimit에 의해 삭제되었을 수도 있으니 주의하세요.
Q. 작업을 즉시 수동으로 실행해 볼 수 있는 방법이 있나요?
네, 가능해요. 크론잡 객체를 직접 실행하는 대신, 해당 크론잡의 jobTemplate을 기반으로 kubectl create job --from=cronjob/[크론잡이름] [새로운잡이름] 명령어를 사용하면 스케줄과 상관없이 즉시 테스트를 진행할 수 있어요.
Q. 특정 시간까지 작업이 완료되지 않으면 강제로 종료하고 싶어요.
spec.jobTemplate.spec.activeDeadlineSeconds 필드를 사용하세요. 이 필드에 초 단위 시간을 설정하면, 작업이 지정된 시간 내에 완료되지 않을 경우 쿠버네티스가 해당 작업을 강제로 종료시켜요.
안정적인 자동화를 위한 마지막 체크리스트
지금까지 쿠버네티스 크론잡의 기초부터 실무 활용법까지 자세히 살펴봤어요. 매니페스트를 작성하고 클러스터에 적용하기 전에, 이 글에서 강조한 핵심 사항들을 다시 한번 점검해 보세요. 작은 설정 하나가 여러분의 퇴근 시간을 결정할 거예요.
- 스케줄링 시 UTC와 KST 타임존 차이를 반드시 확인하세요.
- 작업 중복이 우려된다면
concurrencyPolicy: Forbid를 사용하세요. - 클러스터 정리를 위해
historyLimit를 꼭 설정하세요. - Pod의 안정성을 위해
resources(requests/limits)를 명시하세요. - 테스트 시에는
--from=cronjob옵션으로 즉시 실행해 보세요.
오늘 배운 내용을 바탕으로 지금 바로 실습 환경에서 테스트용 크론잡을 하나 만들어 보는 건 어떨까요? 직접 apply 하고 diff로 설정을 검증하는 과정에서 훨씬 많은 것을 배울 수 있어요. 만약 설정을 적용하다가 예상치 못한 오류가 발생하거나, 특정 옵션의 동작 방식이 궁금하다면 언제든 댓글로 질문을 남겨 주세요. 함께 고민해 드릴게요.
더 깊이 있는 쿠버네티스 운영이 궁금하다면 아래 글들도 함께 읽어보시는 것을 추천해요.
- 쿠버네티스 크론잡 기본 개념과 Job의 차이점
- 클러스터 구축 입문: 데이터 엔지니어를 위한 환경 구성