(4/4) Airflow on K8s 운영 — git-sync · 로그 영속화 · 모니터링 · 스케일
📚 Airflow on K8s (Helm) 시리즈 (전체 4편)
- (1/4) Airflow on K8s 시리즈 개요 — Helm 으로 올리고 워커는 컨테이너로 띄운다
- (2/4) Helm 으로 Airflow 를 K8s 에 설치하기 — KubernetesExecutor 셋업
- (3/4) Airflow 워커 이미지 만들고 pod_template_file 로 묶기
- (4/4) Airflow on K8s 운영 — git-sync · 로그 영속화 · 모니터링 · 스케일 ← 지금 글
Summary
설치와 워커 이미지까지 끝났으면 이제 운영 모드. 이 글에서는 DAG 동기화(git-sync 사이드카) · 로그 영속화(PVC 또는 객체 스토리지) · 모니터링 · 스케일 · 장애 패턴 을 한 번에 정리합니다.
📚 이번 편은 K8s YAML 입문 글 에서 짚었던 “다음 단계 친구” (
PersistentVolumeClaim) 영역까지 살짝 발을 들여요. 그리고 같은 글의 “Pod 에는 컨테이너가 여러 개 들어갈 수 있다” 는 사실이git-sync사이드카에서 진짜로 쓰입니다.
💡 이 글에서 다루는 것
- DAG 동기화 —
git-sync사이드카 패턴- 로그 영속화 — PV 방식 vs Remote logging(S3/GCS)
- 모니터링 — Airflow 내장 메트릭 + Prometheus exporter
- 스케일 — scheduler / worker / DB 의 병목 위치
- 자주 만나는 장애 패턴 + 처방
- 운영 체크리스트
1. DAG 동기화 — git-sync 사이드카
DAG 를 클러스터로 어떻게 흘려보낼지 정해야 해요. 크게 세 가지가 있어요.
| 방법 | 장점 | 단점 |
|---|---|---|
| 이미지에 같이 굽기 | 가장 단순, 변경 이력 = 이미지 태그 | DAG 한 줄 고치는데 이미지 재빌드/배포 |
| PV 마운트 (NFS/EFS) | DAG 만 갈아끼우면 됨 | PV 운영 부담, 권한 이슈 |
git-sync 사이드카 |
Git push → 자동 반영, 변경 이력 = Git 그대로 | 사설 repo 면 SSH 키 관리 필요 |
운영에서 가장 흔한 게 git-sync 예요. scheduler / webserver / 워커 Pod 안에 사이드카 컨테이너로 같이 떠서, 일정 주기로 git pull 해서 DAG 폴더를 갱신해요.
입문 글에서 잠깐 짚었던 “한 Pod 안에 컨테이너가 여러 개 들어갈 수 있다” 가 여기서 진짜로 쓰입니다. 메인 컨테이너(Airflow) 옆에 git-sync 컨테이너가 같이 떠 있고, 둘이 같은 빈 디렉토리 볼륨을 공유해요. git-sync 가 그 폴더에 DAG 를 떨어뜨리면 Airflow 가 그걸 읽어요.
Helm 차트가 기본 지원합니다.
# values.yaml (추가)
dags:
gitSync:
enabled: true
repo: git@github.com:<org>/<airflow-dags-repo>.git
branch: main
rev: HEAD
depth: 1
wait: 60 # 60초마다 pull
subPath: "dags" # repo 내 DAG 폴더
sshKeySecret: airflow-git-ssh-key
SSH 키는 Secret 으로 미리 박아둬요. 일반 Secret 패턴 그대로입니다.
kubectl -n airflow create secret generic airflow-git-ssh-key \
--from-file=gitSshKey=/path/to/id_ed25519
✅ git-sync 가 켜지면 scheduler / webserver / 워커가 같은 revision 을 봅니다. “스케줄러는 새 DAG 인데 워커는 옛 DAG 로 실행” 같은 사고가 안 나요.
2. 로그 영속화 — Pod 가 사라져도 로그가 남게
KubernetesExecutor 의 워커 Pod 는 태스크가 끝나면 사라져요. 그 안의 로그도 같이 사라진다는 뜻 이에요. UI 에서 어제 실패한 태스크 로그를 보려는데 “log not found” 가 뜨는 건 거의 이 문제예요.
해결책은 둘 중 하나예요.
2-1. PVC 로 로그 폴더 영속화
입문 글의 “다음 단계 친구” 표에서 본 PersistentVolumeClaim 이 여기 등장해요. 차트가 옵션 한 줄로 깔아줍니다.
# values.yaml
logs:
persistence:
enabled: true
size: 50Gi
storageClassName: standard # 또는 nfs, gp3 등
🚨
ReadWriteMany가 되는 스토리지(NFS, EFS, Azure Files, CephFS)여야 합니다. scheduler / webserver / 워커가 같은 볼륨을 동시에 마운트 해야 하니까. 일반 EBS 같은ReadWriteOnce는 동작 안 함.
2-2. Remote logging (S3 / GCS / Azure Blob)
운영에선 가장 깔끔한 방식이에요. 태스크가 끝날 때 워커가 객체 스토리지에 로그를 업로드하고, UI 가 거기서 가져옵니다.
# values.yaml
config:
logging:
remote_logging: "True"
remote_base_log_folder: "s3://my-airflow-logs/airflow"
remote_log_conn_id: "aws_default"
encrypt_s3_logs: "False"
aws_default connection 은 Airflow UI 에서 IAM 키로 만들거나, IRSA(EKS) / Workload Identity(GKE) 같이 클러스터 차원의 권한 위임 으로 풀 수도 있어요. 운영이면 후자가 안전합니다.
| 방식 | 추천 상황 |
|---|---|
| PVC 영속화 | 온프레미스 K8s, NFS/EFS 가 이미 운영 중 |
| Remote logging | 클라우드 K8s, 객체 스토리지 + IAM 권한 갖춰져 있음 |
3. 모니터링 — 무엇을 보고 있어야 하나
운영하면서 봐야 하는 신호는 크게 세 층으로 나뉘어요.
| 층 | 지표 | 어디서 |
|---|---|---|
| Airflow 잡 단위 | DAG 성공/실패율, 태스크 평균 실행시간, 큐잉 시간 | Airflow UI + statsd/prom exporter |
| 컴포넌트 단위 | scheduler heartbeat, triggerer 활성, webserver 응답 | /health 엔드포인트, K8s probe |
| 클러스터 단위 | 노드 CPU/메모리, Pod Pending 개수, OOM | Prometheus + node-exporter |
Airflow 메트릭을 Prometheus 로 빼는 가장 간단한 길은 차트의 statsd → Prometheus exporter 를 켜는 거예요.
# values.yaml
statsd:
enabled: true
extraMappings:
- match: "airflow.dag.*.*.duration"
name: "airflow_dag_task_duration"
labels:
dag_id: "$1"
task_id: "$2"
Prometheus 가 ServiceMonitor 로 긁어가게 해두면 Grafana 에서 다음 같은 패널을 만들 수 있어요.
- 시간대별 태스크 실패율
- 스케줄러 heartbeat 지연
- 큐에 들어가서 워커 Pod 가 뜨기까지 걸린 시간 (= queue lag)
- DAG 별 평균 실행 시간 추이
💡 가장 먼저 만들 패널 두 개: scheduler heartbeat 지연 + queue lag. 이 둘이 늘기 시작하면 곧 SLA 깨져요.
4. 스케일 — 어디가 병목인가
컴포넌트별로 병목이 다르다는 걸 기억해야 해요.
4-1. Scheduler
“늘리면 빨라진다” 가 아니에요. Airflow 가 멀티 스케줄러를 지원하긴 하지만, DB 락 경합 이 늘면 오히려 느려져요. 보통 1~3 개가 적정이고, 그 이상은 DB 튜닝(connection pool, parsing_processes) 부터 봐야 해요.
4-2. Worker
KubernetesExecutor 의 워커는 태스크당 Pod 라 자동으로 늘었다 줄어요. 우리가 조절하는 건 두 가지.
- 동시에 띄울 수 있는 최대 Pod 수 —
config.core.parallelism,config.core.max_active_tasks_per_dag - 워커 Pod 한 개의 리소스 —
workers.resources((3/4) 참고)
⚠️ 노드 풀이 부족하면 워커 Pod 가
Pending으로 쌓여요. Cluster Autoscaler / Karpenter 같은 노드 오토스케일러가 같은 노드풀에 붙어있어야 자동 확장이 진짜로 됩니다.
4-3. Metadata DB
운영에서 가장 자주 병목이 잡히는 곳이에요. 외부 RDS / CloudSQL 로 빼고, pgbouncer 같은 connection pool 을 같이 두는 게 표준이에요.
# values.yaml
pgbouncer:
enabled: true
maxClientConn: 200
poolSize: 50
5. 자주 만나는 장애 패턴
| 증상 | 원인 후보 | 처방 |
|---|---|---|
워커 Pod 가 Pending 으로 쌓임 |
노드 리소스 부족, 노드 셀렉터/toleration 불일치 | kubectl describe pod Events, 오토스케일러 / 노드풀 점검 |
| 태스크 끝나면 로그 사라짐 | remote logging 미설정 + Pod 휘발 | PVC 영속화 또는 remote logging 적용 |
ImagePullBackOff |
레지스트리 인증 / 태그 오타 | pull secret, 태그 재확인 |
| DAG 가 UI 에 안 뜸 | git-sync 실패 / 권한 X | scheduler pod 의 git-sync 사이드카 로그 확인 |
| 새 코드 배포 후 일부 워커는 옛 코드 | 동기화 시점 차이 (이미지 베이크 + PV 혼용 등) | 동기화 방식을 한 가지로 통일 |
OOMKilled 가 빈번 |
워커 메모리 limit 작음 | workers.resources.limits.memory 상향 또는 pod_override |
| 스케줄러 heartbeat 지연 | DAG 파싱 시간 초과, DB 락 경합 | dag_dir_list_interval 늘리기, DAG 파일 분할, pgbouncer 도입 |
airflow-run-airflow-migrations 가 실패 |
DB 비번 불일치, 외부 DB 권한 부족 | kubectl logs job/... |
6. 운영 체크리스트
마지막으로 한 번씩 다 짚어두면 좋은 것들.
defaultAirflowTag가 불변 태그(:latest금지)- Fernet / Webserver
Secret이 외부에 백업 - 메타데이터 DB 가 외부 관리형(RDS/CloudSQL) + 백업 정책
- 로그가
PVC또는 객체 스토리지로 영속화 - DAG 는 git-sync 또는 이미지 베이크 둘 중 하나로 통일
- Prometheus 로 scheduler heartbeat / queue lag 패널 존재
- Cluster Autoscaler / Karpenter 가 워커 노드풀에 붙어있음
- 사설 레지스트리 풀
Secret이 ServiceAccount 에 잘 붙음 - Webserver
defaultUser비번 교체 또는 SSO 로 대체
여기까지 들어맞으면 Airflow on K8s 운영 1차 셋업은 끝났다고 봐도 돼요.
일단 오늘은 여기까지…..
다음 글에서는 이번 시리즈에서 못 다룬 외부 메타데이터 DB 분리(RDS Postgres) 와 IRSA 기반 AWS 권한 위임 패턴을 정리해볼게요.