LLM WikiAccess-protected knowledge portal
← 스터디 홈
3편 · 약 23분

Airflow DAG 배포 자동화: GitSync, 패키지 배포, 버전 관리

DAG 배포가 어려운 이유

Airflow를 처음 도입하면 DAG 배포는 간단해 보인다. 파이썬 파일을 dags/ 폴더에 복사하면 끝이다. 하지만 팀이 커지고 환경이 늘어나면 다음 문제가 생긴다.

배포 창(sync window): Airflow scheduler는 dags/ 폴더를 주기적으로 스캔한다. 파일을 직접 복사하면 scheduler가 인식하기까지 시차가 생기고, 그 사이에 다른 버전의 DAG이 동시에 파싱될 수 있다.

패키지 의존성 충돌: DAG마다 다른 버전의 라이브러리를 쓰는 경우 공유 Python 환경에서 충돌이 발생한다. Airflow 자체 의존성과 custom operator 의존성이 섞이는 경우도 있다.

환경 분리: dev, staging, prod 환경의 DAG이 서로 다른 브랜치 또는 설정을 써야 하는데, 수동 복사는 실수를 유발한다.

버전 추적: 어떤 버전의 DAG이 어느 환경에 배포됐는지 기록이 없으면 사고 원인 분석이 어렵다.

이 문제를 해결하는 방법은 크게 세 가지로 나뉜다.

방식동작장점단점
Image bakeDAG 파일을 Docker 이미지에 포함버전 고정, 재현성배포마다 이미지 빌드 필요, 빠른 반영 어려움
GitSyncgit-sync sidecar가 주기적으로 git pull빠른 반영, 구성 단순DAG만 버전 관리; 패키지 의존성 별도 관리 필요
PVC mount공유 PersistentVolume에 rsync/cp구성 단순스케일 아웃 시 공유 PVC 관리 복잡

대부분의 Kubernetes 기반 Airflow 환경에서는 GitSync가 기본 선택이다. 빠른 DAG 반영이 가능하면서 패키지 의존성은 이미지에 굽는 방식으로 분리할 수 있다.


GitSync 사이드카 패턴

GitSync는 Kubernetes sidecar 컨테이너로 실행된다. Scheduler, Worker, Triggerer, DAG processor 파드마다 git-sync 컨테이너가 함께 실행되어 emptyDir 볼륨을 통해 DAG 파일을 공유한다.

Git Repository main/prod branch dags/*.py git pull (5~30s) Scheduler Pod airflow-scheduler /opt/airflow/dags ← emptyDir symlink git-sync /git/repo 에 pull emptyDir 볼륨 공유 Worker Pod airflow-worker /opt/airflow/dags ← emptyDir symlink git-sync /git/repo 에 pull emptyDir 볼륨 공유 컨테이너 레지스트리 airflow:2.9-providers-v2.3 패키지·provider 포함 이미지 emptyDir 볼륨: scheduler ↔ git-sync, worker ↔ git-sync 각각 독립
Airflow GitSync 사이드카 아키텍처

KubernetesExecutor를 사용할 때는 Worker Pod가 task 실행 때마다 새로 생성된다. 이때 git-sync는 init container로 동작해 Pod 시작 시 한 번만 pull한다.


Helm 차트로 GitSync 설정하기

공식 Apache Airflow Helm 차트는 dags.gitSync로 GitSync를 활성화한다. Helm 차트 공식 문서는 dags.persistence.enabled=falsedags.gitSync.enabled=true를 함께 설정하라고 안내한다.

# values.yaml (핵심 설정만 발췌)
dags:
  persistence:
    enabled: false       # PVC 대신 git-sync 사용
  gitSync:
    enabled: true
    repo: "[email protected]:myorg/airflow-dags.git"
    branch: main
    rev: HEAD
    depth: 1             # shallow clone, 속도 절약
    wait: 10             # 동기화 간격 (초)
    subPath: "dags"      # 저장소 내 DAG 파일 경로
    containerName: git-sync
    uid: 65533
    # SSH 인증 (권장: deploy key 사용)
    sshKeySecret: airflow-ssh-secret

images:
  airflow:
    repository: myregistry/airflow
    tag: "2.9.2-providers-v1.5"   # 패키지 포함 커스텀 이미지
    pullPolicy: IfNotPresent

SSH 인증 설정

공개 저장소가 아니면 SSH deploy key 또는 HTTPS token이 필요하다.

# 1. SSH 키 생성
ssh-keygen -t ed25519 -f airflow-gitsync-key -N ""

# 2. GitHub repository → Settings → Deploy keys 에 공개 키 등록
cat airflow-gitsync-key.pub

# 3. 비밀 키를 Kubernetes Secret으로 등록
kubectl create secret generic airflow-ssh-secret \
  --from-file=gitSshKey=airflow-gitsync-key \
  -n airflow

# 4. known_hosts 추가 (MITM 방지)
# Helm 차트 dags.gitSync.knownHosts 에 github.com 의 ssh 공개 키 지문 입력

Helm 차트 공식 문서는 dags.gitSync.knownHosts를 설정하지 않으면 man-in-the-middle 공격에 취약해질 수 있다고 경고한다.


CI/CD 파이프라인: GitHub Actions 연동

GitSync는 DAG 코드를 자동 반영하지만, 배포 전에 코드가 올바른지 검증해야 한다. GitHub Actions를 사용한 전형적인 파이프라인은 다음과 같다.

# .github/workflows/dag-ci.yml
name: DAG CI

on:
  push:
    branches: [main, staging]
    paths:
      - 'dags/**'
      - 'plugins/**'
      - 'requirements*.txt'
  pull_request:
    branches: [main, staging]
    paths:
      - 'dags/**'

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Python 설치
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: 의존성 설치
        run: |
          pip install apache-airflow==2.9.2 \
            apache-airflow-providers-google==10.3.0 \
            apache-airflow-providers-amazon==8.7.0
          pip install -r requirements-dev.txt

      - name: DAG 파싱 검증
        run: |
          python -c "
          import os, glob, importlib.util, sys
          dag_files = glob.glob('dags/**/*.py', recursive=True)
          errors = []
          for f in dag_files:
              try:
                  spec = importlib.util.spec_from_file_location('dag', f)
                  mod = importlib.util.module_from_spec(spec)
                  spec.loader.exec_module(mod)
              except Exception as e:
                  errors.append(f'{f}: {e}')
          if errors:
              print('\n'.join(errors))
              sys.exit(1)
          print(f'OK: {len(dag_files)} DAGs parsed successfully')
          "

      - name: flake8 lint
        run: flake8 dags/ plugins/ --max-line-length=120

      - name: DAG 단위 테스트
        run: pytest tests/ -v --tb=short

패키지가 변경됐을 때는 이미지 빌드도 같이 트리거한다.

  build-image:
    needs: validate
    if: github.ref == 'refs/heads/main' && contains(github.event.head_commit.message, '[rebuild]')
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Docker 빌드 및 push
        run: |
          docker build -t myregistry/airflow:2.9.2-providers-${{ github.sha }} .
          docker push myregistry/airflow:2.9.2-providers-${{ github.sha }}

      - name: Helm 업데이트
        run: |
          helm upgrade airflow apache-airflow/airflow \
            --set images.airflow.tag=2.9.2-providers-${{ github.sha }} \
            --reuse-values \
            -n airflow

커밋 메시지에 [rebuild]를 붙일 때만 이미지를 다시 빌드하는 방식이다. 대부분의 DAG 코드 변경은 GitSync로만 반영되므로 이미지 빌드 없이 배포된다.


커스텀 패키지와 Provider 관리

Airflow의 패키지는 두 종류다.

  1. Airflow Provider: apache-airflow-providers-* 패키지. Google, AWS, Slack, dbt 등 외부 시스템 연동 operator와 hook을 제공한다.
  2. Custom library: 팀에서 만든 공통 operator, hook, sensor.

Dockerfile: 커스텀 이미지 구성

FROM apache/airflow:2.9.2-python3.11

# 공식 provider
RUN pip install --no-cache-dir \
    apache-airflow-providers-google==10.3.0 \
    apache-airflow-providers-amazon==8.7.0 \
    apache-airflow-providers-slack==7.3.0

# 사내 공통 라이브러리 (private PyPI 또는 wheel)
COPY dist/myorg_airflow_common-1.5.0-py3-none-any.whl /tmp/
RUN pip install --no-cache-dir /tmp/myorg_airflow_common-1.5.0-py3-none-any.whl

# 추가 의존성
COPY requirements.txt /requirements.txt
RUN pip install --no-cache-dir -r /requirements.txt

이미지 태그에 provider 버전을 반영하면 어떤 패키지 버전이 배포됐는지 추적하기 쉽다.

airflow:2.9.2-providers-v1.5   # 이미지 버전
                ^      ^
           Airflow   사내 패키지 버전

requirements.txt 핀닝 전략

# requirements.txt
apache-airflow-providers-google==10.3.0   # exact pin
apache-airflow-providers-amazon==8.7.0
pandas>=2.0,<3.0                          # 범위 허용

버전 범위를 너무 느슨하게 두면 자동 업그레이드가 예상치 못한 동작을 유발할 수 있다. 주요 패키지는 exact pin을 권장하고, 월 단위 의존성 업데이트 PR을 별도로 만드는 편이 안전하다.


DAG 버전 관리 전략

GitSync는 항상 최신 commit을 pull한다. 이 특성은 DAG 버전 관리 방식을 결정한다.

브랜치 기반 환경 분리

각 환경(dev, staging, prod)이 다른 브랜치를 추적한다.

# dev 환경 values.yaml
dags:
  gitSync:
    branch: develop

# staging 환경
dags:
  gitSync:
    branch: staging

# prod 환경
dags:
  gitSync:
    branch: main

장점: 브랜치 전략이 환경 분리와 직접 연결된다. 단점: 브랜치 간 병합 충돌 관리가 필요하다.

태그 기반 배포

staging 승인 후 prod에는 태그로 배포한다.

# 배포 승인 시 태그 생성
git tag -a v2026.07.08.1 -m "2026-07-08 1차 배포"
git push origin v2026.07.08.1
# prod 환경: 태그 추적
dags:
  gitSync:
    rev: v2026.07.08.1   # HEAD 대신 특정 태그

태그를 사용하면 prod가 특정 커밋에 고정되어 예측 가능성이 높아진다. 단점은 매 배포마다 Helm 값을 바꿔야 한다는 점이다. 이를 해결하기 위해 GitHub Actions에서 태그 생성과 Helm 업데이트를 자동화할 수 있다.

DAG 파일 내 버전 명시

DAG 파일에 버전 정보를 명시하면 어떤 버전의 코드로 실행됐는지 추적이 가능하다.

from airflow import DAG

DAG_VERSION = "2026-07-08-v1"

with DAG(
    dag_id="my_pipeline",
    tags=["version:" + DAG_VERSION, "team:data-eng"],
    # ...
) as dag:
    pass

Airflow UI의 DAG 목록에서 태그로 필터링할 수 있어 버전 확인이 편리하다.


배포 문제 진단

1. DAG이 반영되지 않는다

GitSync가 pull했는지 확인한다.

# git-sync 컨테이너 로그 확인
kubectl logs <scheduler-pod> -c git-sync -n airflow --tail=50

# emptyDir 볼륨의 실제 파일 확인
kubectl exec <scheduler-pod> -c airflow-scheduler -n airflow -- \
  ls -la /opt/airflow/dags/

# DAG 파일이 심볼릭 링크로 연결됐는지 확인
kubectl exec <scheduler-pod> -c airflow-scheduler -n airflow -- \
  ls -la /git/

Airflow scheduler는 dag_dir_list_interval(기본 300초)마다 dags/ 디렉터리를 스캔한다. GitSync 간격과 scheduler 스캔 간격이 맞지 않으면 반영 시간이 길어진다.

# airflow.cfg
[scheduler]
dag_dir_list_interval = 60   # 기본 300초, 빠른 반영 원하면 줄인다

2. DAG 파싱 에러

scheduler 로그나 Airflow UI의 "DAG Import Errors" 탭에서 확인한다.

# scheduler에서 직접 파싱 테스트
kubectl exec <scheduler-pod> -c airflow-scheduler -n airflow -- \
  python -c "
from airflow.models import DagBag
bag = DagBag('/opt/airflow/dags', include_examples=False)
print('import errors:', bag.import_errors)
print('dags found:', list(bag.dags.keys()))
"

3. 패키지 없음 에러

커스텀 이미지에 패키지가 없으면 DAG 파싱 자체가 실패한다.

# 이미지에서 패키지 확인
kubectl exec <scheduler-pod> -c airflow-scheduler -n airflow -- \
  pip show apache-airflow-providers-google

패키지를 추가해야 한다면 Dockerfile을 수정하고 이미지를 다시 빌드해야 한다. GitSync로는 해결할 수 없다.


배포 요약: DAG vs 패키지 변경 구분

DAG 파일 변경과 패키지 변경은 배포 경로가 다르다. 이를 구분하지 않으면 불필요한 이미지 빌드가 늘거나, 반대로 패키지 변경이 배포되지 않는 상황이 생긴다.

변경 유형배포 방법소요 시간재시작 필요
DAG 파이썬 파일 수정GitSync 자동 반영5~60초불필요
새 DAG 추가GitSync 자동 반영5~60초불필요
provider 패키지 추가/변경이미지 빌드 + Helm upgrade5~20분Pod 재시작
Airflow 버전 업그레이드이미지 빌드 + Helm upgrade + DB migrate30분+DB 마이그레이션 포함
Airflow 설정(airflow.cfg) 변경Helm values 변경 + Pod 재시작5~10분Pod 재시작

GitSync는 DAG 코드 변경만 빠르게 반영하는 경로다. 패키지나 환경 자체가 바뀌면 이미지 빌드 경로를 밟아야 한다. 이 두 경로를 CI/CD 파이프라인에서 명확히 분리해 두면 실수로 잘못된 경로를 밟는 사고를 줄일 수 있다.


References

  • Apache Airflow Docs, "Manage DAG Files (Helm Chart)": https://airflow.apache.org/docs/helm-chart/stable/manage-dag-files.html
  • Apache Airflow Helm Chart, "Parameters Reference": https://airflow.apache.org/docs/helm-chart/stable/parameters-ref.html
  • Medium (Ankercloud Engineering), "Airflow on Argo CD — a Step-by-Step GitOps Guide with git-sync for DAGs": https://medium.com/ankercloud-engineering/airflow-on-argo-cd-a-step-by-step-gitops-guide-with-git-sync-for-dags-464bd0d7e2f5
  • Medium (Apache Airflow), "Streamlining Airflow Deployment: Automating CI/CD with GitHub Actions": https://medium.com/apache-airflow/streamlining-airflow-deployment-automating-ci-cd-with-github-actions-5602f1062783
  • Google Cloud, "Test, synchronize, and deploy your DAGs from GitHub (Cloud Composer)": https://docs.cloud.google.com/composer/docs/composer-2/dag-cicd-github
  • GitHub Discussions apache/airflow, "Efficient CI/CD strategies for Airflow management in production": https://github.com/apache/airflow/discussions/30272
  • Spark Code Hub, "Airflow DAG Versioning Strategies": https://www.sparkcodehub.com/airflow/advanced/dag-versioning