[한국어] Towards a Unified Airflow: Toss Bank’s PoC for Cluster Consolidation (Part 2)

알리는 말

이 글은 Apache Airflow 공식 블로그에 게시된 Towards a Unified Airflow: Toss Bank’s PoC for Cluster Consolidation — Part 2의 한국어 버전입니다.

2026.02.26 제5회 Apache Airflow 한국인사용자모임 밋업 발표한 내용을 다룹니다.

Recap: 지난 이야기

Part 1에서는 토스뱅크가 6개의 클러스터를 통합하기로 결정한 전략적 이유와, Dag Identification(소속 식별) 및 Task Env Isolation(환경 격리)을 통해 플랫폼과 비즈니스 로직의 환경을 어떻게 분리했는지 살펴보았습니다.

하지만 논리적 격리만으로는 부족합니다. 한 팀의 실수가 클러스터 전체의 물리적 자원 고갈로 이어지는 것을 막고, 유저가 인프라 설정 없이도 안전하게 작업할 수 있는 환경을 어떻게 구축할 수 있을까요? Part 2에서 그 구체적인 PoC 결과를 소개합니다.

Section 3: 통합 Airflow 클러스터의 청사진 (Part 2)

3.3 k8s Namespace & ResourceQuota: 물리적 자원 방화벽

실행 환경을 컨테이너로 격리하더라도, 결국 모든 Pod는 하나의 Kubernetes 클러스터라는 거대한 운동장 위에서 달립니다. 특정 팀이 실수로 수천 개의 태스크를 동시에 밀어 넣는다면, 플랫폼의 심장인 스케줄러와 API 서버까지 멈춰버리는 '공멸’의 상황이 올 수 있습니다.

3.3.1) Core와 Workspace의 분리: “관리자와 유저의 공간 분리” 우리는 네임스페이스(Namespace)를 통해 물리적 경계를 쳤습니다.

  • airflow-Core 네임스페이스: 스케줄러, 웹 서버 등 핵심 컴포넌트만 배치하여 외부 간섭으로부터 보호합니다.

  • airflow-Workspace-{Team} 네임스페이스: 각 팀(DA, DE, ML 등)에게 전용 공간을 할당합니다. 모든 유저 태스크(KPO Pod, Worker Pod)는 오직 본인 팀의 네임스페이스 내에서만 생성되고 소멸됩니다.

이러한 물리적 격리를 자동화하기 위해, 우리는 다음과 같이 task_policy를 활용하여 각 팀의 태스크 워크로드를 전용 네임스페이스로 자동 라우팅하는 로직을 검증했습니다.

# Automated team-based namespace assignment logic verified in PoC
@hookimpl
def task_policy(task: BaseOperator) -> None:
    # 1. Identify the team (extract prefix) based on the Dag file path
    # Note: _get_team_prefix_fileloc is a custom logic defined previously
    prefix = _get_team_prefix_fileloc(task.dag.fileloc)
    
    # 2. Retrieve the dedicated namespace for the team from the mapping table
    target_namespace = TEAM_WORKLOAD_NAMESPACE_MAP.get(prefix, "default")

    # 3. Dynamically inject the appropriate namespace based on the execution environment
    if isinstance(task, KubernetesPodOperator):
        # For KubernetesPodOperator, assign the namespace attribute directly
        task.namespace = target_namespace
    else:
        # For KubernetesExecutor, route the execution to the target namespace via pod_override
        task.executor_config = {
            "pod_override": k8s.V1Pod(
                metadata=k8s.V1ObjectMeta(namespace=target_namespace)
            )
        }

이 코드는 플랫폼 운영자가 개별 Dag를 수정하지 않아도, 실행 시점에 태스크의 목적지를 강제로 변경하는 강력한 도구입니다. 이를 통해 각 팀이 던진 태스크 Pod들이 중앙의 airflow-Core를 침범하지 않고, 본인 팀의 울타리(airflow-Workspace-{Team}) 안에서만 실행되도록 완벽하게 라우팅할 수 있음을 확인했습니다.

3.3.2) ResourceQuota: “팀별 자원 사용의 상한선 설정” 각 팀 네임스페이스에 ResourceQuota를 설정하여 물리적인 한계를 부여했습니다. 특정 팀이 자원을 과도하게 요청하더라도 설정된 한도를 넘으면 K8s 레벨에서 차단되므로, 클러스터 전체의 가용성을 해치지 않는 ‘자원 방화벽’ 역할을 수행함을 확인했습니다.

3.4 Platform Guardrails: 자율성을 보장하는 지능형 안전망

유저에게 "자유롭게 코드를 작성하라"고 권장하는 것은 자율성을 주지만, 동시에 리스크도 따릅니다. 우리는 유저가 실수하더라도 시스템이 스스로를 보호하고, 복잡한 설정은 플랫폼이 알아서 챙겨주는 ‘No-Ops’ 경험을 설계했습니다.

3.4.1) 배포 전 사전 예방 (GitHub PR CI Test)

가장 좋은 장애 대응은 문제 코드가 Dag Processor에 도달하지 못하게 막는 것입니다. CI 단계에서 DagBag Import Error를 체크하거나, 'Dag Processor Killer(Top-level 외부 연결 등)'를 사전에 차단하는 시나리오를 테스트할 수 있습니다. 예를 들어 다음과 같은 코드를 실행하여 배포 전 DagBag Import Error를 체크할 수 있습니다.

import sys
from airflow.models.dagbag import DagBag
...
dagbag = DagBag(dag_folder=REPO_ROOT, include_examples=False)
if dagbag.import_errors:
    sys.exit(1)
sys.exit(0)

3.4.2) Automated Provisioning & Centralized Infrastructure Governance

유저가 인프라의 복잡한 속성을 몰라도 되도록, 식별된 팀 정보를 바탕으로 필수 설정을 자동 주입(Mutation)합니다.

  • Script & Config 자동 주입: 소속에 따라 실행 스크립트가 담긴 볼륨(PVC)과 필수 설정 파일(ConfigMap/Secrets)을 실행 시점에 자동으로 마운트합니다.

  • Dynamic Identity 주입: 보안을 위해 인증을 위한 Keytab 등을 유저가 직접 관리하지 않고, 플랫폼이 소속에 따라 실행 환경에 동적으로 주입합니다. 이를 통해 민감 정보의 노출을 막고 중앙에서 엄격하게 권한을 통제할 수 있음을 확인했습니다.

3.4.3) 클러스터 레벨 안전망 (Cluster Policy - Validation & Skip)

배포 후에도 Airflow 스케줄러가 Dag을 파싱할 때마다 사내 표준 규약을 준수하는지, 현재 클러스터 환경에 적합한지를 실시간으로 검증합니다.

  • AirflowClusterPolicyViolation (강제 규약 적용): 사내 표준에 어긋나는 Dag이 발견되면 의도적으로 Import Error를 발생시켜 등록을 거부합니다.

    • 시나리오 (Email Domain Check): 알람 수신 이메일 도메인이 @toss.im과 같이 인가된 도메인이 아닌 경우, 정책 위반 에러를 던져 잘못된 설정의 Dag 배포를 원천 차단합니다.
  • AirflowClusterPolicySkipDag (환경별 선택적 활성화): 에러를 발생시키지는 않지만, 특정 조건에 따라 파싱 자체를 조용히 스킵합니다.

    • 시나리오 (Environment Selection): only_dev 태그가 붙은 Dag는 개발(Dev) 클러스터에서만 정상 파싱하고, 운영(Prod) 클러스터에서는 Dag Processor가 이를 무시하도록 설정하여 환경 간 배포 실수를 방지합니다.

Section 4: 여전히 불안한 과제 — '공유 자원’의 병목 지점

격리 전략을 세웠음에도 불구하고, 단일 클러스터 모델에는 여전히 해결해야 할 ‘공유 자원의 간섭’ 문제가 남아있습니다. 팀 간의 울타리는 높게 세웠지만, 플랫폼을 지탱하는 핵심 엔진은 결국 모든 팀이 공유하기 때문입니다.

4.1 중앙 제어 시스템의 병목: Celery Worker 트래킹

비즈니스 로직은 팀별 네임스페이스로 격리했지만, 이를 지휘하고 상태를 추적하는 중앙 Celery Worker 그룹은 여전히 전사가 공유하는 자원입니다.

  • 불안 요소: 수천 개의 KPO 작업이 동시에 쏟아질 경우, 실제 로직은 각 팀의 네임스페이스에서 실행되더라도 이를 관리하는 중앙 메시지 큐에 병목이 발생할 수 있습니다. 이는 특정 팀의 작업 폭주가 타 팀 작업의 '지휘’를 늦추는 결과로 이어집니다.

  • 해결 방향: 이를 해결하기 위해 최근 Airflow Helm Chart(1.19.0+)에 도입된 ‘Multiple Celery Worker Sets’ 기능을 검토 중입니다. 팀별 또는 용도별로 전용 워커 그룹을 할당하고 KEDA를 통해 독립적으로 오토스케일링함으로써, 제어부 레벨의 격리까지 달성하고자 합니다.

4.2 파싱 병목과 오픈소스 기여: 단일 Dag Processor의 한계와 극복

가장 민감한 공유 자원은 바로 Dag Parsing 영역입니다. 현재 Airflow 구조에서는 하나의 중앙 Dag Processor가 등록된 모든 팀의 코드(Bundle)를 순차적으로 돌아가며 파싱합니다.

  • 문제의 본질: 특정 팀이 파싱 부하가 높은 코드를 배포하거나 에러가 발생하면, 전체 프로세서 루프가 지연됩니다. 이는 곧 전사적인 Dag 업데이트 주기 지연이라는 '운영 간섭’으로 직결됩니다.

  • 오픈소스 생태계와 함께 해결하기: 저는 이 문제를 근본적으로 해결하기 위해 직접 오픈소스 아키텍처 개선에 참여하고 있습니다.

    • Proposal (Issue #61037): 번들(Bundle) 단위로 독립적인 Dag Processor 배포를 허용하여 리소스와 장애 전파 범위를 물리적으로 격리하는 아키텍처 개선안을 제안했습니다.

    • PR (PR #61039): 이를 실현하기 위해 Airflow Helm Chart에서 번들별로 독립적인 프로세서 Pod를 생성할 수 있도록 하는 deployPerBundle 기능을 구현하여 코드를 제출하였으며, 현재 리뷰가 진행 중입니다.

Conclusion: 미래를 준비하는 통합, 신뢰를 향한 여정

우리의 이번 PoC 여정은 단순히 과거의 문제를 해결하는 데 그치지 않고, 현재 데이터 오케스트레이션의 표준이 된 Airflow 3의 핵심 철학과 깊게 맞닿아 있습니다. 우리는 차세대 Airflow가 지향하는 표준 아키텍처를 선제적으로 검토하고 토스뱅크의 환경에 녹여내며 다음과 같은 로드맵을 현실화하고 있습니다.

  • AIP-67 (Multi-team deployment of Airflow components): 단일 클러스터를 여러 팀에게 안전하게 제공하기 위한 구조적 해답입니다. 팀별 독립적인 Dag Processor 할당과 태스크 라우팅 내재화는 우리가 추구해온 '팀별 식별 및 격리’의 글로벌 표준이 되었습니다.

  • AIP-69 (Edge Executor): 단일 클러스터의 물리적·논리적 한계를 넘어 환경 분리를 극대화하는 멀티 클러스터 오케스트레이션의 기반입니다. Task SDK와 결합하여 서로 다른 네트워크 환경에서도 유연하게 작업을 실행할 수 있는 확장성을 확보해 나가고 있습니다.

  • AIP-72 (Task Execution Interface - Task SDK): 유저의 실행 환경을 Airflow Core로부터 완벽히 분리(Decoupling)하는 핵심 기술입니다. 이를 통해 플랫폼 팀은 Core 업데이트에 집중하고, 유저는 본인의 비즈니스 로직에만 전념할 수 있는 '진정한 의존성 해방’을 경험하고 있습니다.

6개의 클러스터에서 단일 플랫폼으로 가는 길은 단순히 기술적인 마이그레이션이 아닌, Airflow의 다음 세대를 미리 준비하는 과정이었습니다.

우리는 이번 PoC를 통해 식별, 격리, 보호라는 원칙이 Airflow 3의 핵심 로드맵과 어떻게 맞물리는지 확인했습니다. 비록 지금은 모든 과정이 완벽하지 않고 해결해야 할 과제들이 남아있지만, 오픈소스 생태계와 AIP의 방향성을 나침반 삼아 함께 정답을 찾아가고 있습니다.

토스뱅크 데이터 플랫폼 팀은 앞으로도 Airflow와 함께 더 견고하고 투명한 데이터 생태계를 만들어갈 것이며, 저희의 고민이 대규모 환경에서 Airflow를 운영하는 많은 동료 엔지니어분께 실질적인 영감이 되기를 바랍니다.

3개의 좋아요

저는 아직 멀티팀 환경을 직접 경험해보진 못했는데,
최근에 멀티팀 관련해서 빠르게 발전하는 것 같더라고요!
어느 정도 규모가 있어서 팀 단위로 나뉘어 운영되는 환경에서는 이런 방식이 미래가 되겠다는 생각이 드네요.
공유 감사합니다 :slight_smile:

1개의 좋아요

맞습니다! Airflow 3.2부터 멀티팀을 고려한 기능이 실험적으로 포함되어 배포되었더라고요.

토스뱅크에서는 추후 Upstream에서 공식 지원될 기능들을 기대하며, 그 방향성에 맞춰 환경을 구축해 나가고 있습니다. 공식 지원이 안정화되면 지금보다 훨씬 편하게 멀티팀 운영이 가능해지지 않을까 기대하고 있습니다