← 학습 카테고리

Learn

Airflow

151개 모듈 · 현재 96번째

Airflow 모듈 96/151 airflow-learn-96

웹 스택: API 서버 설정과 운영

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

이 모듈을 다 읽으면

  • URL prefix 배포 시 함께 바꿔야 하는 두 설정을 설명할 수 있다
  • Core API Server와 Execution API Server를 분리 실행하는 방법과 이유를 설명할 수 있다
  • uvicorn과 gunicorn 서버 타입의 차이와 각각을 선택해야 하는 상황을 판단할 수 있다

Airflow의 API 서버는 URL prefix 배포, Core/Execution API 분리 실행, uvicorn/gunicorn 두 서버 타입 선택, 그리고 쿠버네티스에서 uvicorn을 운영할 때의 메모리 관리 이슈까지 다양한 운영 판단이 필요한 컴포넌트다.

URL prefix 배포와 API 서버 분리

백엔드와 프론트엔드를 가변 URL 경로 prefix 뒤에 배포하고 싶을 때가 있다. 이 경우 ``api__base_url`` 설정(예: ``http://localhost:28080/d12345``)을 구성하면, 프론트엔드를 다시 빌드하지 않고도 모든 API 라우트가 그 추가 ``d12345`` prefix를 통해 제공되며 XHR 요청과 정적 파일 요청도 그 prefix가 붙은 URL로 자동으로 향하게 된다. 태스크가 새 prefix로 API에 도달할 수 있도록 실행 API 서버 URL(``core__execution_api_server_url``)도 함께 갱신해야 한다.

기본적으로 Core API Server와 Execution API Server는 함께 서빙된다(``airflow api-server``는 ``--apps all`` 또는 ``--apps core,execution``과 동일). 독립적으로 확장하거나 서로 다른 머신에 배포하고 싶다면 ``--apps core`` 또는 ``--apps execution``으로 분리해서 실행할 수 있다.

핵심 포인트

  • URL prefix 배포 시 api__base_url뿐 아니라 core__execution_api_server_url도 함께 갱신해야 태스크가 API에 도달할 수 있다
  • 기본은 Core API Server + Execution API Server가 함께 서빙되며, --apps core/--apps execution으로 독립 실행/확장할 수 있다

알려진 이슈: PYTHONASYNCIODEBUG/PYTHONDEVMODE 비호환

Python 3.12 이상에서 ``PYTHONASYNCIODEBUG=1``이 설정되어 있거나 ``PYTHONDEVMODE``로 실행 중일 때 API 서버가 세그멘테이션 폴트로 크래시할 수 있다. 이는 Uvicorn이 성능 향상을 위해 사용하는 ``uvloop``과 파이썬의 asyncio 디버그 모드 사이의 비호환성 때문이다. asyncio 이슈를 디버그해야 한다면 애플리케이션 레벨에서 디버깅하거나 uvloop이 설치되지 않은 개발 환경을 구성하는 것이 대안이다.

핵심 포인트

  • Python 3.12+에서 PYTHONASYNCIODEBUG=1 또는 PYTHONDEVMODE는 uvloop과의 비호환으로 API 서버 세그폴트를 유발할 수 있다
  • 대안은 애플리케이션 레벨 디버깅이나 uvloop 없는 개발 환경 구성이다

서버 타입: uvicorn과 gunicorn

API 서버는 ``uvicorn``(기본값)과 ``gunicorn`` 두 서버 타입을 지원한다.

Uvicorn은 설정이 간단하고 Windows를 포함한 모든 플랫폼에서 동작한다.

Gunicorn은 프로덕션 배포를 위한 추가 기능을 제공한다: fork 이후 copy-on-write로 워커 간 메모리를 공유해 총 메모리 사용량을 줄이는 메모리 공유, 워커를 순차적으로 재활용해 메모리 누적을 막는 롤링 워커 재시작, SIGTTOU로 가장 오래된 워커부터(FIFO) 종료시켜 진짜 롤링 재시작을 가능케 하는 적절한 시그널 처리다. Gunicorn은 ``gunicorn`` extra 설치가 필요하고(``pip install 'apache-airflow-core[gunicorn]'``), Unix 전용이라 Windows에서는 동작하지 않는다. ``AIRFLOW__API__SERVER_TYPE=gunicorn`` 환경변수로 활성화한다.

핵심 포인트

  • uvicorn(기본)은 간단하고 Windows 포함 전 플랫폼에서 동작한다
  • gunicorn은 메모리 공유, 롤링 워커 재시작, SIGTTOU 기반 FIFO 종료를 제공하지만 Unix 전용이며 별도 extra 설치가 필요하다

롤링 워커 재시작(gunicorn)과 설정 옵션

장시간 실행되는 프로세스의 메모리 누적을 막기 위한 주기적 워커 재활용은 ``AIRFLOW__API__WORKER_REFRESH_INTERVAL``(예: 43200초 = 12시간마다 재시작)과 ``AIRFLOW__API__WORKER_REFRESH_BATCH_SIZE``(예: 1 = 한 번에 하나씩 재시작)로 구성한다. 롤링 재시작 과정은 다음과 같다: 기존 워커를 죽이기 전에 새 워커를 먼저 띄우고(무중단), 새 워커가 준비될 때까지 기다린 뒤(프로세스 타이틀 확인), HTTP 헬스체크로 워커가 실제로 요청을 처리할 수 있는지 검증하고, 가장 오래된 워커부터 죽인 뒤, 원래 워커가 모두 교체될 때까지 반복한다.

``[api]`` 섹션의 설정 옵션: ``server_type``(uvicorn 기본 또는 gunicorn), ``worker_refresh_interval``(재활용 주기 초, 0=비활성 기본값), ``worker_refresh_batch_size``(주기당 재활용할 워커 수, 기본 1), ``dag_cache_size``(API 서버에 캐시할 SerializedDAG 버전 최대 개수, 기본 64, 0=크기 제한 없음), ``dag_cache_ttl``(캐시된 Dag의 TTL 초, 기본 3600, 0=TTL 없음; 둘 다 0이면 제거 없음).

핵심 포인트

  • 롤링 재시작은 신규 워커 기동 → 준비 확인 → 헬스체크 → 가장 오래된 워커부터 종료 → 반복의 순서로 무중단 재활용을 수행한다
  • worker_refresh_interval/worker_refresh_batch_size로 재활용 주기와 배치 크기를 조절한다
  • dag_cache_size/dag_cache_ttl로 캐시된 SerializedDAG 개수와 TTL을 조절한다(0/0이면 제거 없음)

언제 어떤 서버 타입을 쓸지, 그리고 쿠버네티스에서의 uvicorn 운영

gunicorn을 쓰는 것이 좋은 경우: 메모리 누적이 우려되는 장시간 실행 API 서버 프로세스, 메모리 공유가 중요한 다중 워커 배포, 무중단 워커 재활용이 필요한 프로덕션 환경. 기본 uvicorn을 쓰는 것이 좋은 경우: Windows 환경, 개발/테스트 환경, 짧게 살고 사라지는 컨테이너(예: 어차피 재활용되는 쿠버네티스 파드).

``server_type = uvicorn``으로 쿠버네티스에서 API 서버를 실행할 때는, API 서버가 파드당 하나의 장시간 실행 프로세스로 동작하며 gunicorn 같은 롤링 워커 재시작을 지원하지 않는다. 장시간 실행되는 쿠버네티스 배포에서는 이로 인해 점진적인 메모리 증가나 오래된 내부 상태가 누적될 수 있어, uvicorn을 쓸 때는 주기적으로 API 서버 파드를 재시작하는 것이 권장된다: API 서버 Deployment의 쿠버네티스 롤링 재시작, Helm 업그레이드 중 rollout 트리거나 재시작 어노테이션 변경 같은 Helm 기반 재시작, 또는 uvicorn을 장기간 운영할 때의 클러스터 레벨(예: 스케줄된 재시작) 메커니즘.

API 서버는 서버 타입과 무관하게 ``dag_cache_size``와 ``dag_cache_ttl``로 캐시된 SerializedDAG 객체를 제거해 Dag 버전 누적으로 인한 메모리 증가를 줄인다. 다만 메모리를 확실히 상한선 이하로 묶어주는 것은 ``dag_cache_size``뿐이다 — 캐시 항목의 TTL은 ``core__min_serialized_dag_update_interval`` 이후 그 항목이 DB와 대조될 때만 갱신되고, 매 요청마다 갱신되지 않는다. 만약 TTL이 그 간격보다 짧다면, 자주 요청되는 항목이라도 확인 사이에 만료되어 다시 로드될 수 있다.

많은 쿠버네티스 환경에서는 쿠버네티스의 OOM kill이나 크래시 재시작에만 의존하는 것은 권장되지 않는다 — 메모리 증가가 항상 OOM 이벤트를 유발하는 것은 아니기 때문이다. 파드 재시작 없이 자동 워커 재활용이 필요한 프로덕션 배포라면 ``server_type = gunicorn``을 대신 고려해야 한다.

핵심 포인트

  • gunicorn은 장시간 실행/다중 워커/무중단 재활용이 필요할 때, uvicorn은 Windows/개발/짧게 사는 컨테이너에 적합하다
  • 쿠버네티스에서 uvicorn은 파드당 단일 장시간 프로세스라 롤링 워커 재시작이 없어 점진적 메모리 증가에 대비해 주기적 파드 재시작이 권장된다
  • dag_cache_size만이 메모리를 확실히 상한 짓고, dag_cache_ttl은 min_serialized_dag_update_interval 이후 DB 대조 시에만 갱신되어 매 요청 갱신이 아니다
  • 쿠버네티스 OOM kill/크래시 재시작에만 의존하지 말고, 자동 워커 재활용이 필요하면 gunicorn을 고려해야 한다