← 학습 카테고리

Learn

Airflow

151개 모듈 · 현재 77번째

Airflow 모듈 77/151 airflow-learn-77

Plugins — External View/React App/FastAPI 확장과 스코핑

Apache Airflow Official Documentation (in-repo snapshot) — Apache Software Foundation administration-and-deployment/plugins.rst (전체)

이 모듈을 다 읽으면

  • AirflowPlugin이 등록할 수 있는 빌딩 블록 종류와 각각의 용도를 설명할 수 있다
  • 플러그인의 lazy loading 특성과, 워커 포킹 방식이 플러그인 코드 변경 반영에 미치는 영향을 설명할 수 있다
  • applies_to 스코핑 규칙(키 내부 OR, 키 간 AND, 평가 불가 기준의 스킵)을 판단하고, 이것이 인가(authorization) 경계가 아니라 표시 편의 기능일 뿐이라는 함의를 설명할 수 있다
  • Airflow 3에서 Flask AppBuilder 기반 플러그인이 어떤 새 인터페이스로 대체되었는지 설명할 수 있다

Airflow 플러그인은 $AIRFLOW_HOME/plugins 폴더에 파일을 두는 것만으로 외부 기능을 코어에 통합하는 메커니즘이다. Airflow 3.1부터 External View·FastAPI 앱/미들웨어에 더해 React 앱까지 등록할 수 있게 되었고, applies_to로 특정 Dag·태스크에만 뷰를 노출하도록 스코핑할 수 있다. 다만 이 스코핑은 UI 표시 편의일 뿐 인가 경계가 아니라는 점, 그리고 플러그인은 기본적으로 lazy loading되어 변경 후 재시작이 필요하다는 점이 실무에서 자주 놓치는 함정이다.

플러그인의 목적과 등록 가능한 빌딩 블록

Airflow는 ``$AIRFLOW_HOME/plugins`` 폴더에 파일을 두는 것만으로 외부 기능을 코어에 통합할 수 있는 간단한 내장 플러그인 매니저를 갖고 있다. Airflow 3.1부터는 React 앱, FastAPI 엔드포인트, 미들웨어 같은 새로운 기능도 플러그인 시스템으로 지원되어 커스텀 통합을 더 쉽게 만들 수 있다. plugins 폴더의 파이썬 모듈은 임포트되고, 매크로와 웹 뷰가 Airflow의 주요 컬렉션에 통합되어 사용 가능해진다. 문제를 진단할 때는 로드된 플러그인 정보를 덤프하는 ``airflow plugins`` CLI 명령을 쓸 수 있다.

플러그인은 조직마다 다른 스택·요구에 맞춰 Airflow 설치를 커스터마이즈하는 방법이자, 새 기능 세트를 작성·공유·활성화하는 쉬운 방법이며, Hive 로그를 파싱해 메타데이터를 노출하는 도구, 이상 탐지 프레임워크, 감사(auditing) 도구, 설정 기반 SLA 모니터링 도구 같은 더 복잡한 애플리케이션을 만드는 데도 쓰인다. Airflow 위에 이런 것을 만드는 이유는 Airflow가 이미 뷰를 렌더링할 웹서버, 모델을 저장할 메타데이터 DB, DB 연결 지식, 워크로드를 보낼 워커 배열, 배포 로직, 기본 차팅 기능까지 재사용 가능한 여러 컴포넌트를 갖고 있기 때문이다.

플러그인이 등록할 수 있는 빌딩 블록은: External Views(UI에 새 페이지로 연결되는 버튼/탭 추가), React Apps(UI에 커스텀 React 앱 임베드, 3.1 신규), FastAPI Apps(커스텀 API 엔드포인트 추가), FastAPI Middlewares(API 요청/응답 가로채기·수정), Macros(Dag 템플릿에서 쓸 재사용 함수), Operator Extra Links(태스크 상세 화면에 커스텀 버튼 추가), Timetables & Listeners(커스텀 스케줄링 로직·이벤트 훅 구현), Deadline References(커스텀 Deadline Alert 참조 클래스 등록)이다.

핵심 포인트

  • 플러그인은 plugins 폴더에 파일을 두는 것만으로 동작하며, airflow plugins CLI로 로드된 플러그인 정보를 확인할 수 있다
  • 3.1부터 React Apps·FastAPI Apps·FastAPI Middlewares가 새 빌딩 블록으로 추가되어 External Views·Macros·Operator Extra Links 등 기존 항목과 함께 쓸 수 있다

로딩 시점과 인터페이스

플러그인은 기본적으로 지연 로드(lazy load)되고, 한번 로드되면 다시 로드되지 않는다(단 UI 플러그인은 Webserver에서 자동으로 로드된다). 매 Airflow 프로세스 시작 시점에 로드하고 싶다면 ``airflow.cfg``에서 ``[core] lazy_load_plugins = False``로 설정한다. 즉 플러그인 코드를 바꾸고 그 새 코드를 Webserver나 Scheduler가 쓰게 하려면 그 프로세스들을 재시작해야 하며, 재시작해도 새로 실행 중인 태스크에는 스케줄러가 부팅된 이후에야 반영된다.

기본적으로 태스크 실행은 포킹(forking)을 쓴다 — 새 파이썬 인터프리터를 만들고 Airflow의 코드·시작 루틴 전체를 다시 파싱하는 오버헤드를 피하기 위함으로, 특히 짧은 태스크에 유리하다. 이는 태스크 안에서 플러그인을 쓰고 그 갱신을 반영하려면 워커(CeleryExecutor 사용 시)나 스케줄러(LocalExecutor 사용 시)를 재시작해야 한다는 뜻이다. 다른 옵션은 시작 시 속도 저하를 감수하고 ``core.execute_tasks_new_python_interpreter``를 True로 설정해 태스크마다 완전히 새 파이썬 인터프리터를 띄우는 것이다. (반면 Dag 파일에서만 임포트되는 모듈은 이 문제가 없다 — Dag 파일은 어떤 장기 실행 Airflow 프로세스에서도 로드/파싱되지 않기 때문이다.)

플러그인을 만들려면 ``airflow.plugins_manager.AirflowPlugin`` 클래스를 상속하고, 참조하고 싶은 객체들을 넣으면 된다. 주요 속성은: ``name``(필수, 플러그인 이름), ``macros``, ``fastapi_apps``, ``fastapi_root_middlewares``, ``external_views``, ``react_apps``(3.1+, 실험적), ``on_load(*args, **kwargs)`` 콜백(향후 함수 시그니처에 추가 파라미터가 주입되어도 안전하도록 반드시 ``*args``·``**kwargs``를 받아야 함), ``global_operator_extra_links``, ``operator_extra_links``, ``timetables``, ``deadline_references``, ``listeners``다. 이 속성들은 클래스 속성으로도, 추가 초기화가 필요하면 프로퍼티로도 정의할 수 있다. 플러그인 변경 후에는 반드시 Webserver와 Scheduler를 재시작해야 적용된다. Airflow 3.1은 Admin -> Plugins 메뉴에 설치된 플러그인을 볼 수 있는 Plugin Management Interface도 도입했다.

핵심 포인트

  • lazy_load_plugins(기본 True)는 프로세스당 한 번만 플러그인을 로드하므로, 플러그인 코드 변경은 Webserver·Scheduler 재시작 없이는 반영되지 않는다
  • 태스크는 포킹으로 실행되어 플러그인 갱신 반영에도 워커/스케줄러 재시작이 필요하며, execute_tasks_new_python_interpreter=True로 매 태스크 새 인터프리터를 쓰면 이 문제를 피하되 시작 속도를 희생한다
  • AirflowPlugin의 on_load 콜백은 향후 파라미터 추가에 대비해 반드시 *args/**kwargs를 받도록 정의해야 한다

External View·React App 예시와 스코핑(applies_to)

External View는 ``url_route`` 값을 지정하면 iframe으로 UI 안에 인라인 렌더링할 수도 있다(그렇지 않으면 새 브라우저 탭에서 열리는 외부 링크로 취급된다). ``external_view_with_metadata`` 딕셔너리는 ``name``, 컨텍스트 변수({DAG_ID}/{RUN_ID}/{TASK_ID}/{MAP_INDEX}/{ASSET_ID}/{ASSET_URI} 중 렌더링 위치에 따른 일부)를 템플릿으로 쓸 수 있는 ``href``, 뷰가 표시될 위치를 정하는 ``destination``(nav/dag/dag_run/task/task_instance/asset/base 지원, 기본 nav), ``icon``/``icon_dark_mode``, iframe 렌더링을 위한 상대 경로 ``url_route``(nav 전용 ``category``는 browse/docs/admin/user 기존 메뉴에 매칭되거나 새 메뉴를 만듦), 내비게이션 바에 항상 직접 노출할지 정하는 ``nav_top_level`` 같은 필드를 갖는다. React App(``react_app_with_metadata``)도 유사한 필드에 더해 앱이 서빙되는 ``bundle_url``, ``dashboard``/``dag_overview``/``task_overview`` 같은 기존 페이지 내부 위치(CSS ``order``로 배치 순서 결정)와 툴바 스트립에 마운트하는 ``base`` destination을 지원한다.

applies_to로 뷰·앱을 특정 Dag·태스크로 스코핑할 수 있다: ``dag_tags``(Dag가 나열된 태그 중 하나라도 가지면 매칭), ``dag_ids``(정확한 dag_id), ``task_ids``(정확한 task_id), ``operators``(오퍼레이터 클래스명), ``operator_names``(UI에 표시되는 이름, 즉 ``custom_operator_name`` — 일반 오퍼레이터는 클래스명과 같지만, ``@task.bash``처럼 표시 이름은 ``@task.bash``이면서 실제 클래스는 ``_BashDecoratedOperator``인 데코레이터 기반 태스크는 둘이 다르므로 이런 경우 ``operator_names``를 써야 한다). 기준 결합 방식은 쿠버네티스 레이블 셀렉터와 같다 — 같은 키 안에서는 OR, 서로 다른 키 사이에서는 AND. 다만 이 AND는 '현재 페이지가 평가할 수 있는 기준들 사이에서만' 적용된다 — 예를 들어 ``task_ids``는 Dag 레벨 페이지에서는 평가할 수 없으므로 매치 실패로 처리되지 않고 그냥 건너뛴다. 이 덕분에 하나의 applies_to 블록을 플러그인의 Dag 레벨·태스크 레벨 destination 양쪽에 공유할 수 있다. destination별로 평가 가능한 기준이 다른데, ``dag``/``dag_run``/``dag_overview``는 dag_tags·dag_ids만 평가하고 task_ids·operators·operator_names는 건너뛰며, ``task``/``task_overview``/``task_instance``는 둘 다 평가하고, ``nav``/``base``/``dashboard``/``asset``는 둘 다 건너뛴다(즉 이 destination에서는 설정된 기준이 있어도 평가되지 않는다). 주어진 페이지에서 설정된 기준을 하나도 평가할 수 없다면 뷰는 표시된다. 태스크 그룹 페이지에서는 태스크 레벨 기준이 건너뛰어진다(그룹은 태스크가 아니므로).

잘못된 applies_to(딕셔너리가 아니거나, 모르는 기준 이름이거나, 문자열 리스트가 아닌 값을 준 경우)는 플러그인 로드 시 경고로 보고되고 무시되어 뷰는 스코핑되지 않은 채로 로드된다. 어떤 destination이 평가할 수 없는 기준을 설정한 경우(예: ``dag`` 뷰에 ``task_ids``)도 효과가 없다는 경고가 남는다. 뷰가 예상과 다르게 스코핑된다면 API 서버 로그에서 이 경고들을 확인해야 한다.

중요한 점: applies_to는 표시 편의 기능이지 인가 경계가 아니다. UI가 탭을 제공할지만 제어할 뿐, 그 뷰 자체에 접근 가능한지를 제어하지 않는다 — ``url_route``를 아는 사용자는 직접 그 URL로 이동할 수 있다. 플러그인 데이터를 볼 수 있는 사람을 실제로 제한하려면 접근 제어(access control)를 써야 한다.

핵심 포인트

  • operators는 오퍼레이터 클래스명, operator_names는 UI 표시 이름(custom_operator_name)이며, 데코레이터 기반 태스크(@task.bash 등)는 표시 이름과 클래스명이 달라 operator_names를 써야 매칭된다
  • applies_to 기준 결합은 K8s 레이블 셀렉터처럼 키 내부 OR·키 간 AND이지만, AND는 현재 페이지가 평가 가능한 기준에 한해서만 적용되고 평가 불가 기준은 매치 실패가 아니라 스킵된다
  • nav/base/dashboard/asset destination은 dag_tags·task_ids 등 모든 스코핑 기준을 평가하지 않으므로 사실상 스코핑이 적용되지 않는다
  • applies_to는 UI 표시 여부만 제어하는 편의 기능일 뿐 인가 경계가 아니며, url_route를 아는 사용자는 스코핑과 무관하게 뷰에 직접 접근할 수 있다

React 앱 context props, CSRF 예외, 패키지 배포, Airflow 3 전환

React 앱(실험적 기능)은 External View가 ``bundle_url``의 ``{DAG_ID}``류 토큰으로만 컨텍스트를 받는 것과 달리, 컴포넌트로 렌더링되어 props로 직접 컨텍스트를 받는다. 마운트 위치(``destination``·라우트)에 따라 ``dagId``/``runId``/``taskId``/``mapIndex``/``assetId``(문자열, 존재할 때) 같은 현재 라우트 식별자, ``assetUri``(asset 라우트일 때), 그리고 REST API 응답 스키마(``DAGDetailsResponse``, ``DAGRunResponse``, ``TaskInstanceResponse``, ``AssetResponse``)와 일치하는 ``dag``/``dagRun``/``taskInstance``/``asset`` 전체 레코드를 받을 수 있다 — 이 객체들은 그것이 의존하는 식별자가 라우트에 존재할 때만 제공되며, 상세 페이지가 이미 채워둔 UI 쿼리 캐시에서 서빙되어 추가 요청이 발생하지 않는다. 해당 식별자가 없는 라우트나 destination(``nav``, ``base``, ``dashboard`` 등)에서는 이 객체들이 ``undefined``다.

모든 뷰는 CSRF로 보호하는 것이 강력히 권장되지만, 필요하다면 ``airflow.www.app``의 ``csrf`` 객체가 제공하는 ``@csrf.exempt`` 데코레이터로 특정 뷰를 예외 처리할 수 있다.

플러그인은 setuptools entrypoint(그룹 ``airflow.plugins``)로도 로드할 수 있다 — 패키지가 설치되어 있으면 Airflow가 entrypoint 목록에서 자동으로 플러그인을 로드한다. entrypoint 이름도, 플러그인 클래스의 이름도 모듈·클래스 이름 자체에는 영향을 주지 않는다.

Airflow 2는 플러그인에서 Flask AppBuilder 뷰(``appbuilder_views``), 메뉴 아이템(``appbuilder_menu_items``), Flask Blueprint(``flask_blueprints``)를 지원했다. Airflow 3에서는 이들이 External Views(``external_views``), FastAPI Apps(``fastapi_apps``), FastAPI Middlewares(``fastapi_root_middlewares``), React Apps(``react_apps``)로 대체(superseded)되어 더 확장된 기능과 UI 통합을 제공한다. 새로 만드는 모든 플러그인은 새 인터페이스를 써야 한다. 다만 전환을 돕기 위해 Flask/FAB 플러그인용 호환 레이어가 제공되므로, FAB 프로바이더를 설치하고 Airflow 3 마이그레이션 가이드에 따라 코드를 손보면 기존 Flask AppBuilder 뷰·Blueprint·메뉴 아이템을 계속 쓸 수 있다.

문제를 진단할 때는 Flask CLI를 쓸 수 있다 — ``FLASK_APP`` 환경 변수를 ``airflow.www.app:create_app``으로 설정하면, 예를 들어 ``flask routes``로 전체 라우트를 출력해볼 수 있다.

핵심 포인트

  • React 앱의 dag/dagRun/taskInstance/asset props는 UI가 이미 채운 쿼리 캐시에서 제공되어 추가 API 요청 없이 REST 응답 스키마 그대로 전달된다
  • Airflow 3에서 Flask AppBuilder 뷰·Blueprint·메뉴 아이템은 External Views/FastAPI Apps/FastAPI Middlewares/React Apps로 대체되었고, FAB 프로바이더 설치로 호환 레이어를 유지할 수 있다
  • FLASK_APP=airflow.www.app:create_app 뒤 flask routes로 등록된 전체 라우트를 확인해 플러그인 문제를 진단할 수 있다