Blog
airflowmonitoringobservabilityprometheusgrafana

Airflow 3 모니터링 & 운영 — 로그·메트릭·알림 한 번에 잡기

Airflow 3의 로그 중앙화(S3/GCS), StatsD·OpenTelemetry → Prometheus·Grafana 메트릭, on_failure_callback 알림, SLA를 대체한 Deadline, 컴포넌트별 헬스체크까지 운영 관점에서 정리합니다.

Data Dynamics2026年7月4日17 min read
This post is not yet translated. The original Korean version is shown below.

이 글은 "Airflow 3 실전 연재"의 10편 — 모니터링 & 운영입니다. 직전 편에서는 REST API와 원격 스케줄 변경을 다뤘고, 다음 편에서는 테스트·CI/CD·보안으로 이어집니다.

파이프라인이 한두 개일 때는 "UI에서 빨간색이 보이면 고친다"로 충분합니다. 하지만 DAG가 수백 개로 늘고, 스케줄러·triggerer·DAG processor가 각자 다른 노드에서 돌기 시작하면 이야기가 달라집니다. 어디가 느린지, 무엇이 쌓이는지, 누가 실패했는지를 사람이 눈으로 보기 전에 시스템이 먼저 알려줘야 합니다.

이 글은 Airflow 3 환경에서 세 가지 축 — 로그, 메트릭, 알림 — 을 어떻게 엮어 운영 가시성을 만드는지, 그리고 Airflow 3에서 바뀐 부분(SLA 제거, Task Execution API 환경에서의 로그 처리)을 어떻게 다뤄야 하는지 풀어 봅니다.

이 글에서 배우는 것

  • 태스크 로그를 원격 스토리지(S3/GCS)로 중앙화하는 이유와 방법
  • StatsD·OpenTelemetry로 메트릭을 빼서 Prometheus·Grafana로 보는 파이프라인
  • 꼭 봐야 하는 핵심 지표와 그 의미
  • on_failure_callback 알림, 그리고 SLA를 대체한 Deadline 개념
  • 컴포넌트별 헬스체크 엔드포인트

1. 로그: 일단 한곳으로 모은다

Airflow의 태스크 로그는 기본적으로 태스크를 실행한 워커의 로컬 디스크에 쌓입니다. 워커가 한 대일 때는 문제없지만, 워커가 여러 대거나 Kubernetes에서 Pod가 끝나면 사라지는 환경에서는 "실패한 태스크 로그를 보려는데 워커가 이미 없다"는 상황이 생깁니다.

해법은 단순합니다. 로그를 오브젝트 스토리지로 중앙화하는 것입니다. Airflow는 원격 로깅(remote logging)을 기본 지원해서, 태스크가 끝나면 로컬 로그를 S3·GCS·Azure Blob 같은 백엔드로 업로드하고, UI는 그곳에서 로그를 읽어 옵니다.

# airflow.cfg의 [logging] 섹션 (또는 AIRFLOW__LOGGING__* 환경변수)
[logging]
remote_logging = True
remote_base_log_folder = s3://my-airflow-logs/logs
remote_log_conn_id = aws_logs

핵심은 remote_log_conn_id가 가리키는 Connection입니다. 이 연결에 스토리지 접근 권한(IAM 역할이나 키)을 담아 두면, Airflow가 그 권한으로 로그를 올리고 내려받습니다. 워커가 죽어도 로그는 버킷에 남고, UI에서는 평소처럼 조회됩니다.

Task Execution API 환경에서의 로그

Airflow 3에서 워커(태스크)는 더 이상 메타데이터 DB에 직접 붙지 않고, API server의 Task Execution Interface를 통해 상태를 주고받습니다(아키텍처 편 참고). 이 변화가 로그에 주는 의미는 이렇습니다.

  • 태스크 로그 자체는 여전히 실행 주체(워커/Pod/엣지 워커) 쪽에서 생성됩니다.
  • 하지만 실행 환경이 메타데이터 DB와 분리되어 있으므로(특히 EdgeExecutor로 원격·엣지에서 도는 태스크), 로그가 머무는 로컬 디스크에 운영자가 SSH로 들어가 보는 방식은 더 이상 통하지 않습니다.
  • 따라서 Airflow 3에서는 원격 로깅이 "있으면 좋은 것"이 아니라 사실상 기본 전제가 됩니다. 실행이 어디서 돌든 로그는 공통 버킷으로 모이고, API server가 제공하는 UI/REST API를 통해 일관되게 조회됩니다.

분산·원격 실행이 늘수록 "로그가 어디 있나"를 묻지 않게 만드는 것이 운영의 절반이다. 답은 항상 "버킷"이어야 한다.


2. 메트릭: StatsD·OpenTelemetry에서 Prometheus·Grafana로

로그가 "무슨 일이 있었나"라면, 메트릭은 "지금 시스템이 어떤 상태인가"입니다. Airflow는 내부적으로 스케줄러 지연, 큐 길이, 태스크 성공/실패 횟수 같은 수치를 메트릭으로 내보낼 수 있습니다. 내보내는 방식은 크게 두 가지입니다.

  • StatsD: 오래 쓰여 온 방식. Airflow가 StatsD 프로토콜로 수치를 쏘면, statsd_exporter가 받아 Prometheus가 긁어갈 수 있는 형태로 변환합니다.
  • OpenTelemetry(OTel): 표준화된 관측 가능성(observability) 프레임워크. 메트릭·트레이스를 OTel Collector로 보내면, Collector가 Prometheus를 비롯한 여러 백엔드로 라우팅합니다. 새로 시작한다면 생태계가 넓은 OTel 쪽이 장기적으로 유리합니다.

어느 쪽이든 최종 그림은 비슷합니다. 메트릭은 Prometheus에 쌓이고, Grafana로 본다. 아래는 로그와 메트릭이 어떻게 외부 시스템으로 흘러가는지 보여 줍니다.

Loading diagram…

설정은 [metrics] 섹션에서 합니다. 예를 들어 StatsD를 켜는 경우는 다음과 같습니다.

[metrics]
statsd_on = True
statsd_host = statsd-exporter
statsd_port = 8125
statsd_prefix = airflow

OpenTelemetry를 쓴다면 [metrics]의 OTel 관련 옵션(예: otel_on, OTel Collector 엔드포인트)을 활성화하고, 나머지 라우팅은 Collector 설정에서 처리합니다. 구체적인 옵션 이름과 지원 범위는 운영 중인 Airflow 버전의 공식 문서를 확인하는 편이 안전합니다.

꼭 봐야 하는 핵심 지표

대시보드에 수십 개 패널을 욱여넣는 것보다, 다음 몇 가지를 확실히 보는 것이 훨씬 낫습니다. 아래 수치는 의미를 설명하기 위한 예시입니다.

지표무엇을 말해 주나위험 신호(예시)
스케줄러 heartbeat / 지연스케줄러가 살아서 제때 도는가heartbeat 끊김, 스케줄 지연이 점점 증가
큐 적체(queued tasks)실행을 기다리는 태스크가 쌓이나queued 수가 줄지 않고 계속 상승
태스크 실패율실패가 평소보다 늘었나특정 DAG/시간대에 실패율 급증
DAG 파싱 시간DAG processor가 무거운 DAG에 끌려가나파싱 시간이 길어지며 신규 DAG 반영 지연
풀(Pool) 사용률자원 격리용 풀이 꽉 찼나특정 풀이 항상 100%, 대기 누적

이 다섯 가지는 환경설정 & 최적화 편에서 다룬 동시성 3계층(parallelism·max_active_tasks_per_dag·max_active_runs_per_dag)과 Pool 설정이 실제로 잘 동작하는지를 사후가 아니라 실시간으로 확인하게 해 줍니다. 큐가 계속 쌓인다면 parallelism이 너무 낮거나 워커가 부족하다는 신호고, 특정 풀만 항상 가득 차 있다면 풀 크기를 다시 산정해야 한다는 뜻입니다.


3. 알림: 실패를 사람에게 전달하는 길

지표가 빨개졌어도 아무도 보지 않으면 의미가 없습니다. 알림은 "이상이 생겼을 때 사람에게 닿게" 만드는 마지막 고리입니다. Airflow 3에서 알림은 두 갈래로 생각하면 깔끔합니다.

(1) 태스크/DAG 콜백 — on_failure_callback

가장 직접적인 방법은 태스크나 DAG에 콜백 함수를 다는 것입니다. 태스크가 실패하면 Airflow가 그 함수를 호출하고, 함수 안에서 Slack 메시지나 이메일을 보냅니다.

from airflow.sdk import dag, task
 
def notify_slack(context):
    ti = context["task_instance"]
    msg = f":red_circle: 실패: {ti.dag_id}.{ti.task_id} (run {context['run_id']})"
    # 여기서 Slack/Webhook 등으로 전송
    send_to_slack(msg)
 
@dag(
    schedule="@daily",
    catchup=False,                 # Airflow 3에서 기본값이 False
    default_args={"on_failure_callback": notify_slack},
)
def sales_pipeline():
    @task
    def extract():
        ...
    extract()
 
sales_pipeline()

콜백은 context로 어떤 태스크가, 어떤 run에서, 언제 실패했는지를 받습니다. Slack용 알림은 직접 webhook을 쏘거나 커뮤니티 provider 패키지를 쓰면 되고, 이메일 알림은 클러스터 구성 편에서 다룬 SMTP 설정과 연동됩니다. 임포트 경로가 airflow.sdk인 점에 주의하세요 — Airflow 3의 Task SDK 경로입니다.

콜백은 "이미 일어난 실패"를 알리는 데 강하다. 반면 "일어났어야 할 일이 안 일어난" 상황은 다른 도구가 필요하다.

(2) SLA는 사라졌다 — Deadline로 대체

Airflow 2.x에는 SLA(Service Level Agreement) 기능이 있었습니다. "이 태스크는 N분 안에 끝나야 한다, 안 그러면 SLA 미스로 알림"이라는 개념이었죠. 그런데 Airflow 3에서 기존 SLA 기능은 제거되었습니다. 동작이 직관적이지 않고(특히 logical_date 기준 계산), 혼란을 많이 일으켰기 때문입니다.

그 자리를 Deadline(데드라인) / deadline alerting 개념이 대체합니다. 핵심 발상은 "특정 시점까지 완료되지 않으면 알린다"로 SLA와 비슷하지만, 기준 시점과 동작을 더 명시적으로 정의한다는 점이 다릅니다. SLA가 "이 태스크의 logical_date로부터 N분"처럼 암묵적이었다면, Deadline은 "이 기준 시각 + 여유시간을 넘기면 이 콜백을 실행"처럼 더 또렷하게 의도를 적습니다.

SLA에서 Deadline으로의 전환은 단순한 이름 변경이 아니라 "언제를 기준으로 늦음을 판단하는가"를 분명히 하라는 요구다.

SLA 기반 알림을 쓰던 2.x DAG를 마이그레이션한다면, 해당 로직을 Deadline 기반으로 다시 설계해야 합니다. 정확한 API 형태와 옵션은 사용 중인 3.x 마이너 버전의 공식 문서(airflow.apache.org)를 기준으로 확인하세요 — 이 영역은 버전별로 다듬어지는 중입니다.


4. 헬스체크: 컴포넌트별로 살아 있는지 본다

메트릭과 알림이 "동작의 질"을 본다면, 헬스체크는 "살아 있는가"라는 가장 기본적인 질문에 답합니다. Airflow 3은 컴포넌트가 아키텍처 편에서 본 것처럼 여러 프로세스로 나뉘어 있으므로, 컴포넌트별로 건강 상태를 확인해야 합니다.

Loading diagram…

운영에서 쓰는 방식은 보통 이렇습니다.

  • API server의 /health 엔드포인트를 헬스 프로브로 사용합니다. 이 응답에는 메타데이터 DB 연결 상태와 함께, 스케줄러·triggerer·DAG processor 같은 컴포넌트의 최근 heartbeat 상태가 포함됩니다. heartbeat가 너무 오래됐으면 해당 컴포넌트가 멎은 것으로 봅니다.
  • Kubernetes에 배포한다면 각 컴포넌트의 liveness/readiness probe로 이 헬스 정보를 연결해, 멎은 Pod를 자동 재기동하게 만듭니다.
  • 메트릭(2장)과 결합하면 더 강력합니다. "스케줄러 heartbeat 지연" 지표가 임계치를 넘으면 Alertmanager가 알림을 쏘도록 규칙을 걸어 두면, 헬스 엔드포인트를 사람이 새로고침하지 않아도 됩니다.

엔드포인트 경로와 응답 스키마 세부는 버전에 따라 조금씩 다를 수 있으니, 프로브를 거는 경로는 운영 버전 문서로 확정하세요.


5. 정리 — 세 축을 하나의 운영 루프로

운영 가시성은 따로 노는 세 도구의 합이 아니라, 하나의 루프입니다.

  1. 로그는 원격 스토리지로 중앙화한다 — 워커가 죽어도, 엣지에서 돌아도 답은 항상 "버킷".
  2. 메트릭은 StatsD/OTel로 빼서 Prometheus·Grafana로 본다 — heartbeat·큐 적체·실패율·파싱 시간·풀 사용률 다섯 가지를 핵심으로.
  3. 알림은 on_failure_callback으로 실패를 즉시 전달하고, SLA를 대체한 Deadline로 "늦음"을 다룬다.
  4. 헬스체크/health로 컴포넌트별 생존을 확인하고, 메트릭 알림과 묶어 자동화한다.

이 루프가 갖춰지면, 다음 단계는 "장애가 나기 전에 막는" 쪽입니다. 다음 편 테스트·CI/CD·보안에서는 DAG가 프로덕션에 올라가기 전에 검증하고, 배포 파이프라인과 접근 제어로 안정성을 끌어올리는 방법을 다룹니다.

좋은 모니터링은 대시보드 개수가 아니라, "이상이 생겼을 때 사람이 보기 전에 알림이 먼저 도착하는가"로 평가된다.