(3/4) Airflow 워커 이미지 만들고 pod_template_file 로 묶기
📚 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
워커 Pod 는 K8s YAML 입문 글 에서 본 그 Pod 와 똑같이 생긴 친구예요. 다만 KubernetesExecutor 환경에선 우리가 딱 두 가지 를 정해줘야 해요.
- (1) 이미지 —
pip install까지 다 끝난 우리 워커용 Airflow 이미지 - (2) 스펙 — 그 이미지가 어떤 리소스/볼륨/시크릿/노드 위에서 뜰지를 적은
pod_template_file(= 그냥 Pod 스펙)
💡 이 글에서 다루는 것
- 왜 커스텀 워커 이미지가 거의 항상 필요한지
- Airflow 베이스 이미지 위에 라이브러리 얹는
Dockerfile- 빌드 → 레지스트리 푸시 → Helm
values.yaml에 박기pod_template_file로 워커 Pod 스펙 잡기 (K8s 의 평범한 Pod 모양 그대로)- 태스크 단위
pod_override— 특정 태스크만 더 큰 메모리/다른 노드
1. 왜 커스텀 이미지가 필요한가
기본 apache/airflow:3.x 이미지엔 정말 기본만 들어있어요. 우리 파이프라인이 보통 필요로 하는 건 한참 더 많죠.
- 일반 패키지 —
pandas,numpy,pyarrow,requests - DB 드라이버 —
psycopg2-binary,pymssql,snowflake-connector-python - 클라우드 SDK —
boto3,google-cloud-storage - 사내 PyPI 의 사내 패키지
- 시스템 바이너리 —
git,unixodbc,libxml2
그래서 거의 항상 커스텀 이미지 한 장 을 굽게 됩니다. 그리고 이 이미지는 워커 전용 이 아니에요. scheduler / webserver / triggerer / worker 가 모두 같은 이미지 를 씁니다. DAG 코드 파싱은 스케줄러도 같이 해야 하니까.
2. Dockerfile
공식 apache/airflow 베이스 위에 의존성을 얹어요.
# Dockerfile
FROM apache/airflow:3.1.8-python3.11
# 1) 시스템 의존성 — root 로 잠깐
USER root
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
git \
curl \
unixodbc \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
# 2) 다시 airflow 유저 — pip 는 절대 root 로 깔지 말 것
USER airflow
# 3) 파이썬 의존성
COPY requirements.txt /requirements.txt
RUN pip install --no-cache-dir -r /requirements.txt
requirements.txt 예시.
pandas==2.2.2
pyarrow==16.1.0
boto3==1.34.140
psycopg2-binary==2.9.9
apache-airflow-providers-amazon==9.4.0
apache-airflow-providers-postgres==6.0.0
⚠️
apache-airflow-providers-*는 베이스 Airflow 버전에 민감해요. 너무 신 버전을 박으면 import 단계에서 죽습니다.
💡
USER airflow로 다시 돌아오는 것 까먹지 마세요. root 상태로pip install하면 권한 이슈로 컨테이너 실행 중 추가 설치/캐시 쓰기가 막혀요.
3. 빌드 / 푸시
레지스트리 주소는 환경에 맞게 바꿔주세요. 예시는 사내 Harbor 를 가정했어요.
REG=registry.<internal>/data-platform
TAG=3.1.8-py311-1
docker build -t $REG/airflow:$TAG .
docker push $REG/airflow:$TAG
운영에서는 :latest 같은 태그 쓰지 말고 불변 태그 (날짜+빌드넘버, 커밋 SHA) 권장. K8s 가 이미지 캐시를 적극적으로 쓰는데 :latest 면 노드별 캐시 시점이 어긋나서 같은 태그인데 다른 이미지가 떠 있는 사고가 나요.
사설 레지스트리면 K8s 가 풀할 수 있게 Secret 으로 자격증명을 박아둬야 해요. 일반 Secret 위에 docker-registry 타입을 얹은 모양이에요.
kubectl -n airflow create secret docker-registry harbor-creds \
--docker-server=registry.<internal> \
--docker-username=<user> \
--docker-password=<PASSWORD> \
--docker-email=<your-email>
4. Helm values 에 이미지 박기
차트가 새 이미지를 쓰도록 values.yaml 업데이트.
# values.yaml (추가)
defaultAirflowRepository: registry.<internal>/data-platform/airflow
defaultAirflowTag: "3.1.8-py311-1"
airflowVersion: "3.1.8" # 차트 호환성 체크에 사용
# 사설 레지스트리 풀 시크릿
registry:
secretName: harbor-creds
defaultAirflowRepository + defaultAirflowTag 한 쌍만 잡아도 scheduler/webserver/triggerer/worker 가 모두 같은 이미지를 받아요. 바로 반영해볼게요.
helm upgrade --install airflow apache-airflow/airflow \
--namespace airflow \
--values values.yaml
kubectl -n airflow rollout status deploy/airflow-scheduler
5. pod_template_file — 워커 Pod 스펙 잡기
여기서부터가 진짜 핵심. pod_template_file 은 모든 워커 Pod 가 따라갈 YAML 템플릿 이에요. K8s 의 평범한 Pod 스펙과 문법이 같습니다.
# 워커 Pod 스펙 예시 — K8s 의 평범한 Pod 와 같은 모양
apiVersion: v1
kind: Pod
metadata:
name: airflow-worker
spec:
containers:
- name: base # ← 이 이름 고정. Airflow 가 여기에 명령/이미지/env 를 자동 주입
image: registry.<internal>/data-platform/airflow:3.1.8-py311-1
resources:
requests: { cpu: "500m", memory: "1Gi" }
limits: { cpu: "2", memory: "4Gi" }
직접 ConfigMap 으로 위 YAML 을 마운트해도 되지만, Helm 차트의 workers.* 옵션으로 같은 결과를 만들 수 있어요. 이게 가장 깔끔해요.
# values.yaml (추가)
workers:
resources:
requests: { cpu: "500m", memory: "1Gi" }
limits: { cpu: "2", memory: "4Gi" }
# 환경변수 — ConfigMap/Secret 에서 가져오기 (envFrom 표준 패턴)
extraEnv: |
- name: TZ
value: Asia/Seoul
- name: AIRFLOW__CORE__DEFAULT_TIMEZONE
value: Asia/Seoul
extraEnvFrom: |
- secretRef:
name: airflow-app-secrets # DB 비번, API 토큰 등
# 워커 전용 노드풀이 있을 때
nodeSelector:
workload: airflow-worker
tolerations:
- key: workload
operator: Equal
value: airflow-worker
effect: NoSchedule
차트가 위 값들을 받아서 알아서 pod_template_file.yaml 을 만들고 컨테이너에 마운트해주고, Airflow config 에 core.pod_template_file=... 경로를 박아줘요. 우리는 그냥 values 만 채우면 끝.
💡 가장 중요한 한 줄:
containers[0].name은 반드시base여야 합니다. Airflow 가 이 이름을 기준으로 메인 컨테이너를 찾아서 명령어/이미지/환경변수를 채워요.
6. 태스크 단위 — pod_override
기본 스펙은 pod_template_file 로 잡고, 특정 태스크만 더 큰 메모리/다른 이미지/다른 노드가 필요할 수 있어요. DAG 코드 안에서 부분 override.
from airflow.decorators import task
from kubernetes.client import models as k8s
@task(
executor_config={
"pod_override": k8s.V1Pod(
spec=k8s.V1PodSpec(
containers=[
k8s.V1Container(
name="base",
resources=k8s.V1ResourceRequirements(
requests={"cpu": "2", "memory": "8Gi"},
limits={"cpu": "4", "memory": "16Gi"},
),
)
],
node_selector={"workload": "airflow-worker-heavy"},
)
)
}
)
def heavy_aggregation():
...
여기서도 containers[0].name 은 "base". Airflow 가 워커 Pod 의 메인 컨테이너를 그 이름으로 잡고 override 를 머지합니다.
✅ 운영 팁: 기본 스펙은 보수적으로 작게, 진짜 무거운 태스크만
pod_override로 키우는 패턴이 비용/안정성 둘 다 좋아요. 모든 태스크에 큰 리소스를 깔면 K8s 가 스케줄을 못 잡고Pending으로 쌓여요.
7. 확인 — 워커 Pod 가 떴을 때 잡아보기
가벼운 DAG 한 번 돌려놓고 워커 Pod 가 떴다 사라지는 순간을 잡아보면 적용 여부가 즉시 보여요.
# scheduler/webserver/triggerer/postgresql 제외하고 워커 Pod 만 보기
kubectl -n airflow get pods -w | grep -v -E "scheduler|webserver|triggerer|postgresql"
태스크가 돌면 <dag_id>-<task_id>-<runid>-<suffix> 패턴 Pod 가 잠깐 뜨고 사라져요. 떠 있는 동안 한 번 describe 찍어봐요.
kubectl -n airflow describe pod <pod-name>
확인 포인트.
Image:가 우리가 푸시한 레지스트리 주소 + 태그Requests:/Limits:가 values 의 값과 같음Node-Selectors:가 의도한 노드풀Volumes:/Mounts:에 Secret/ConfigMap 이 잘 붙음
여기까지 맞으면 워커 이미지 + 템플릿 셋업은 끝나요.
일단 오늘은 여기까지…..
다음 글에서는 DAG 동기화(git-sync), 로그 영속화, 모니터링/스케일 같은 운영 쪽을 정리할게요.
← 이전 글: (2/4) Helm 으로 Airflow 를 K8s 에 설치하기 — KubernetesExecutor 셋업 | 다음 글 →: (4/4) Airflow on K8s 운영 — git-sync · 로그 영속화 · 모니터링 · 스케일