Apache Airflow — ML Orchestration Skeleton
Interview Reference: ZenML-to-Airflow Translation
Goal: Demonstrate conceptual fluency in Airflow architecture and ML pipeline design. Sushant’s production stack is ZenML — use this document as the translation layer.
1. What Airflow Is (and Isn’t)
What Airflow is: Apache Airflow is an open-source workflow orchestration platform that lets you author, schedule, and monitor multi-step pipelines as DAGs (Directed Acyclic Graphs) of tasks — in pure Python. It does not move data or run compute; it tells other systems to do work and records what happened.
Airflow vs ZenML — analogous but not equivalent:
| Airflow | ZenML | |
|---|---|---|
| Scope | General-purpose (ETL, ML, data, any workflow) | ML-first (training, evaluation, deployment) |
| Scheduling | Built-in cron scheduler | External trigger (CI/CD, API, manual) |
| Artifact handling | Manual — XCom metadata + external storage paths | Native — auto-versioned typed artifacts per step |
| ML tooling | Requires manual MLflow / registry integration | Native integrations (MLflow, KServe, ONNX) |
| Abstraction | DAG of operators / tasks | Pipeline of typed steps |
Are they analogous? At the structural level — yes. Both represent workflows as a directed graph of discrete units with dependency edges. At the purpose level — no. Airflow is a general orchestrator that happens to run ML pipelines; ZenML is an ML platform that happens to orchestrate. ZenML can even use Airflow as its backend execution engine.
What it solves: Multi-step ML workflows (load → clean → train → evaluate → promote) that need scheduling, dependency management, retry logic, and observability — things cron jobs and shell scripts cannot provide cleanly at scale.
2. Architecture — The Four Components
| |
| Component | Role |
|---|---|
| Scheduler | Parses DAG files, marks tasks ready, sends to Executor |
| Executor | Dispatches tasks to workers (Local / Celery / Kubernetes) |
| Worker | Runs the actual Python/Bash code |
| Metadata DB | Stores all run history, task states, XCom values — source of truth |
| Web Server | UI + REST API; reads from metadata DB |
Key insight: The Scheduler never runs your code. Workers do. The Scheduler only decides when and in what order.
Kubernetes Primer — What an Interviewer Expects You to Know
Kubernetes (K8s) is a container orchestration platform — it manages the deployment, scaling, and lifecycle of containerised applications across a cluster of machines. Think of it as an operating system for a fleet of servers.
Core concepts:
| Concept | What it is |
|---|---|
| Container | A lightweight, isolated process bundling code + dependencies (via Docker). Runs the same everywhere. |
| Pod | The smallest deployable unit in Kubernetes. Wraps one or more containers that share networking and storage. One pod = one task in KubernetesExecutor. |
| Node | A physical or virtual machine in the cluster that runs pods. Nodes have CPU/RAM that pods consume. |
| Cluster | The full set of nodes managed together by Kubernetes. One control plane (master) + many worker nodes. |
| Namespace | Logical isolation within a cluster — e.g., airflow, ml-training, monitoring namespaces share hardware but are isolated in access and quotas. |
| Deployment | A spec declaring desired state — “run 3 replicas of this container image.” Kubernetes ensures this is always true. |
| Service | A stable network endpoint (IP + DNS name) for a set of pods. Pods come and go; the Service address doesn’t change. |
| ConfigMap / Secret | Inject configuration or credentials into pods at runtime without hardcoding in the image. |
| Resource Requests/Limits | Each pod declares how much CPU/RAM it needs (requests) and the maximum it can use (limits). Kubernetes uses this to schedule pods onto nodes that have capacity. |
Where Helm Charts come in:
Deploying Airflow (or any complex app) onto Kubernetes means writing dozens of YAML manifests — Deployments, Services, ConfigMaps, Secrets, PersistentVolumes. This is tedious and error-prone to manage manually.
Helm is the package manager for Kubernetes. A Helm Chart is a pre-packaged, parameterisable bundle of all the Kubernetes manifests needed to deploy an application.
| Concept | What it is |
|---|---|
| Chart | A directory of templated Kubernetes manifests for one application (e.g., the official apache-airflow chart) |
| values.yaml | The configuration file where you override defaults — number of workers, executor type, image tag, resource limits |
| Release | One deployed instance of a chart. You can have airflow-dev and airflow-prod as two releases of the same chart |
helm install | Deploys the chart to the cluster, creating all K8s resources at once |
helm upgrade | Updates a release — e.g., bumps Airflow version or changes executor config without rewriting manifests |
helm rollback | Reverts a release to a previous version — one command undoes a bad deploy |
Where Helm fits in the deployment chain:
In practice: the official Apache Airflow Helm chart packages the Scheduler, Webserver, Workers, and PostgreSQL metadata DB as one deployable unit. Your team only needs to override values.yaml — executor type, image tag, resource requests — and helm upgrade handles the rest. No manual pod management.
How Kubernetes relates to Airflow:
With KubernetesExecutor, every Airflow task runs in its own dedicated pod — created when the task starts, deleted when it finishes. This means:
- A heavy training task gets a 16GB RAM pod; a lightweight logging task gets 512MB — no contention.
- Failed pods don’t affect other tasks — full isolation.
- The cluster auto-scales: if 20 tasks are queued, Kubernetes spins up 20 pods in parallel (subject to node capacity).
The interview framing:
“Kubernetes is the infrastructure layer beneath the orchestration layer. Airflow decides what to run and when; Kubernetes decides where to run it and ensures it gets the right resources. In a production ML platform, Airflow’s KubernetesExecutor bridges the two — each task becomes a pod spec, and Kubernetes handles placement, resource allocation, and cleanup.”
3. DAG — The Core Concept
A DAG is a Python file that defines a directed, acyclic graph of tasks. No loops — tasks flow forward only.
| |
Three parameters that matter most
catchup=False
If you deploy a DAG with start_date 30 days ago and catchup=True (default), Airflow queues 30 simultaneous backfill runs — flooding your workers. Always set catchup=False unless backfilling is intentional.
max_active_runs=1
Prevents a second scheduled run from starting while the previous one is still running. Essential for training pipelines — you never want two jobs simultaneously writing to the same model registry entry.
schedule and logical date
Airflow runs at the end of an interval, not the start. A daily DAG with schedule="@daily" and start_date=2024-01-01 triggers its first run on 2024-01-02 for the logical date 2024-01-01. Use {{ ds }} in templates to get data_interval_start as YYYY-MM-DD.
4. Tasks — The Unit of Work
A task = one step in the pipeline. Three ways to define one:
TaskFlow API (preferred — Airflow 2.x)
| |
Classic Operator (for non-Python work)
Sensor (waits for external condition)
| |
Wiring dependencies
5. XCom — Passing Data Between Tasks
XCom (Cross-Communication) stores values in the metadata DB. It is for metadata, not data.
| |
| |
Why: XCom is stored in PostgreSQL. A 100MB DataFrame serialised there kills your metadata DB. The pattern is: write large data to S3/GCS, XCom the path, downstream task reads from the path.
6. Complete ML Pipeline Skeleton
| |
What this demonstrates:
- Sensor for upstream data dependency
- Quality gate with early abort
- Isolated steps (tune failure doesn’t lose cleaned data)
- XCom by path
- Always-on failure alerting via
trigger_rule - Implicit dependencies from TaskFlow call order
7. Key Patterns to Know
Branching
| |
Trigger Rules
| Rule | When it runs |
|---|---|
all_success (default) | All upstream succeeded |
one_failed | At least one upstream failed — use for alerts |
none_failed_min_one_success | None failed, one succeeded — use after branching |
all_done | All upstream finished regardless of state |
Parallelism via expand (Airflow 2.3+)
Executors — One-line summary each
| Executor | Use when |
|---|---|
LocalExecutor | Single machine, moderate load |
CeleryExecutor | Multi-worker, large-scale, many small tasks |
KubernetesExecutor | Cloud-native; each task gets a dedicated pod with its own resource spec |
KubernetesExecutor is the right answer for ML: heavy training tasks get 16GB RAM pods; lightweight monitoring tasks get 512MB pods — no resource contention.
8. Airflow vs ZenML — Your Translation Layer
| Concept | ZenML (your stack) | Airflow equivalent |
|---|---|---|
| Workflow | @pipeline decorated function | DAG (Python file) |
| Unit of work | @step function | @task / Operator |
| Step ordering | Implicit from function calls | >> operator or TaskFlow call order |
| Data passing | Typed Artifacts (versioned, S3-backed) | XCom (metadata only) + external storage |
| Scheduling | CI/CD trigger (GitHub Actions) | Built-in Scheduler with cron |
| Config | YAML → pipeline.with_options(config_path=...) | Variables, op_kwargs, Connections |
| Experiment tracking | Native MLflow integration | Manual via MLflow hook in task |
| Model promotion | promote_model step → MLflow registry | Task calling MLflow / custom registry API |
| Conditional execution | Not native | BranchPythonOperator, ShortCircuitOperator |
| Parallelism | Parallel branches in pipeline graph | Fan-out tasks, expand() |
| Backfill | Manual pipeline re-run | airflow dags backfill -s DATE -e DATE |
The key difference to articulate
ZenML auto-versions every step’s input and output as a named artifact — you can reproduce any past run by loading the exact artifact versions. Airflow doesn’t version artifacts natively; you manage this by embedding dates in S3 paths (model_2024_01_01.pkl). MLflow or DVC fills that gap when using Airflow for ML.
9. Interview Talking Points
“What is Airflow and how does it work?”
“Airflow is a metadata-driven workflow orchestrator. The Scheduler parses DAG files and marks tasks ready when their dependencies are met. The Executor dispatches those tasks to Workers, which run the actual code. Everything is recorded in a PostgreSQL metadata database. Critically, Airflow doesn’t move data — it tells other systems to do work. That separation is what makes it composable: the same Airflow DAG can orchestrate Spark jobs, Python scripts, and dbt runs within one dependency graph.”
“Walk me through how you’d design a daily retraining pipeline.”
“I’d structure it as a DAG with
catchup=Falseandmax_active_runs=1so concurrent runs can’t race on the model registry. First, an S3 sensor inreschedulemode waits for the upstream data snapshot — this decouples the training DAG from the data pipeline DAG. Then a validation task that aborts early if row count is below threshold. Then separate tune and train tasks — keeping them separate means a training failure doesn’t force hyperparameter search to re-run. Artifacts flow between tasks as S3 paths via XCom, never as DataFrames. A final alert task withtrigger_rule='one_failed'fires PagerDuty if anything breaks.”
“How do you pass data between tasks?”
“Via XCom, but only for small metadata. XCom is backed by the metadata database — you don’t want a DataFrame going in there. The production pattern is: write data to S3, XCom the path, downstream task reads from S3. This also means you can restart a failed task from the middle of the pipeline without re-running the data loading step — the path is already in XCom.”
“How does this relate to your ZenML experience?”
“The mental model is identical — a directed graph of typed steps with dependency edges. The differences are in scheduling and artifact management. ZenML doesn’t have a built-in scheduler; we trigger via GitHub Actions CI. Airflow has a mature scheduler with cron support and backfill capability, which is a real advantage for time-series workflows. ZenML auto-versions artifacts; in Airflow you manage versioning yourself through path conventions or MLflow. If I were porting our KPI model training system to Airflow, it would be a DAG with task groups per KPI,
expand()for parallelism across eight models, and the same MLflow logging calls we already use — the Python logic is identical, just the orchestration wrapper changes.”
“When would you use KubernetesExecutor?”
“Any ML platform where tasks have very different resource profiles. A data cleaning task needs 2 cores and 2GB RAM; a hyperparameter search needs 8 cores and 32GB. With CeleryExecutor, workers are sized for the heaviest task — most workers are wasteful most of the time. KubernetesExecutor creates a pod per task with its own resource spec, so resources are allocated exactly to what each task needs. The tradeoff is pod startup latency — 30-60 seconds — so for DAGs with many fast tasks, Celery is faster.”