Tired of K8s UI for Jobs? Need More Visibility? Here's an OG Alternative: Apache Airflow

Alright, let's talk Kubernetes. It's powerful, no doubt. For managing containerized applications, scaling services, and ensuring high availability, it's become the de facto standard for many of us. I've used it extensively, and for long-running services, it's a dream. But then come the batch jobs, the cron tasks, the ETL pipelines... and suddenly, the Kubernetes UI and its native Job/CronJob objects can start to feel a bit, well, clunky.

If you're like me, you've probably found yourself wrestling with kubectl commands just to figure out why a job failed, or stringing together complex scripts to manage dependencies between tasks. The default Kubernetes Dashboard, while useful for an overview of cluster resources , often leaves a lot to be desired when it comes to true workflow visibility and management. That's where I hit a wall and started looking for something better, something that could give me the control and insight I needed without sacrificing the power of Kubernetes for execution.

This is where Apache Airflow, an "old good" (OG) but incredibly relevant tool, comes into the picture. This isn't about ditching Kubernetes entirely; it's about leveraging its strengths for execution while using a dedicated orchestrator for the complex dance of your tasks. If you're feeling the pain of managing anything more than a handful of simple, independent K8s CronJobs, this article is for you.

Who Is This Article For? (And Who It Might Not Be For)

I'm writing this for folks who are:

  • Managing multiple Kubernetes Jobs or CronJobs and finding it increasingly complex.

  • Struggling with a lack of visibility into job status, history, and logs directly from a user-friendly UI.

  • Fighting to implement reliable dependency management between jobs (e.g., "Job B only runs if Job A succeeds").

  • Spending too much time manually retrying failed jobs or deciphering cryptic failure reasons from pod logs.

  • Wishing for a more robust alerting mechanism for job failures or delays.

  • Developing data pipelines, ETL/ELT processes, or any batch processing workflows that run on Kubernetes.

  • Comfortable with Python, or willing to learn, as Airflow pipelines are defined in Python.

If you're nodding along to these points, you're in the right place.

However, if you're only running a couple of very simple, independent cron tasks that rarely fail and don't have complex dependencies, then the setup for Airflow might be overkill. Kubernetes CronJobs can handle simple scenarios just fine. But for the rest of us dealing with more intricate workflows, the limitations become apparent quickly.

The Kubernetes Job Struggle: My Common Frustrations

Deploy and Access the Kubernetes Dashboard | Kubernetes

Before we dive into Airflow, let's commiserate a bit. If you've been working with Kubernetes Jobs and CronJobs for anything beyond the most basic tasks, some of these pain points probably sound familiar:

  • The "Black Box" Syndrome: When a Kubernetes Job or a pod spawned by a CronJob misbehaves, figuring out why can feel like detective work. You kubectl get pods, then kubectl describe pod <pod-name>, then kubectl logs <pod-name>... and you're still piecing together the story from disparate outputs. There's often no single place to see the holistic view of a job's execution, its attempts, and its final state in a user-friendly way. Sometimes, pods aren't even created, and the CronJob itself offers few clues.

  • Limited UI for Workflows: The standard Kubernetes Dashboard is a general-purpose tool. It can show you if pods are running, view basic logs, and manage resources. But it's not designed for workflow orchestration. It won't show you a graph of your job dependencies, a history of all runs of a specific workflow, or a Gantt chart of task durations. For complex workflows, you're often left wanting more specialized views.

  • Dependency Hell: What if JobB needs to run only after JobA completes successfully, and JobC needs to run if JobA fails? Natively, Kubernetes Jobs don't have a straightforward mechanism for expressing these kinds of inter-job dependencies. You end up writing custom scripts or controllers to manage this, adding another layer of complexity.

  • Retry Roulette & Failure Handling: Kubernetes Jobs have a spec.backoffLimit which specifies the number of retries before considering a Job as failed. While Kubernetes v1.33 introduced backoffLimitPerIndex for indexed jobs, offering more granular control for parallel tasks , configuring sophisticated retry strategies (e.g., conditional retries, different delays for different types of failures, or triggering specific cleanup actions on failure) is largely a manual implementation effort.

  • Scattered Logs: If a job runs multiple pods, or if a pod is restarted multiple times, aggregating and making sense of logs can be a nightmare. You're often grepping through multiple log streams, trying to correlate timestamps and events.

  • Alerting? Good Luck Setting That Up Manually: Kubernetes itself doesn't provide a built-in, comprehensive alerting system for job failures or SLA misses. You typically need to integrate external monitoring tools like Prometheus and Grafana and configure alerts there, or write custom scripts to check job statuses and send notifications. This adds more operational overhead.

If these frustrations resonate, you're not alone. I've been there, and it's what pushed me to explore more robust orchestration solutions that can still play nice with Kubernetes.

So, What's This "Old Good" Apache Airflow?

UI / Screenshots — Airflow Documentation

Enter Apache Airflow. It's an open-source platform to programmatically author, schedule, and monitor workflows. Originally developed at Airbnb, it's now a top-level Apache project with a massive community.

At its core, Airflow lets you define your workflows as Directed Acyclic Graphs (DAGs) using Python. Think of a DAG as a collection of all the tasks you want to run, organized in a way that reflects their relationships and dependencies.

Here are some key concepts:

  • DAGs (Directed Acyclic Graphs): The heart of Airflow. Defined in Python, they describe the "what, when, and how" of your workflow.

  • Operators: These are the building blocks of DAGs. An operator defines a single task in your workflow. Airflow has operators for many common tasks (like BashOperator, PythonOperator), and crucially for us, the KubernetesPodOperator.

  • Tasks: An instance of an operator. It's a node in your DAG.

  • Scheduler: This component monitors all tasks and DAGs, and triggers the task instances whose dependencies have been met.

  • Executor: This defines how your tasks are run. Options include LocalExecutor (for testing), CeleryExecutor (for distributed setups), and importantly, the KubernetesExecutor which runs each task in its own Kubernetes pod.

  • Webserver: This provides a rich user interface to visualize pipelines, monitor progress, and troubleshoot issues.

The beauty of Airflow lies in its ability to provide:

  • Dynamic Pipeline Generation: Since DAGs are Python code, you can generate them dynamically.

  • Extensibility: You can create custom operators, hooks, and plugins.

  • Scalability: With the right executor (like Celery or Kubernetes), Airflow can scale to handle a large number of workflows.

  • Rich UI: For visualization, monitoring, and management.

  • Robust Integrations: Including, as we'll see, deep integration with Kubernetes.

One of the most powerful aspects for those of us already invested in Kubernetes is that Airflow doesn't force you to abandon your containerized tasks. Instead, it can orchestrate them as Kubernetes pods using the KubernetesPodOperator or the KubernetesExecutor. This means you get Airflow's superior orchestration capabilities while your tasks continue to run in your familiar K8s environment, leveraging your existing Docker images and resource management.

A Glimpse into the Airflow UI: Visibility Restored!

One of the first things that will strike you when you move from managing K8s Jobs via kubectl or the basic K8s Dashboard to Airflow is the sheer amount of visibility and control you gain through its UI. The K8s Dashboard is fine for a general overview of cluster resources , but Airflow's UI is purpose-built for workflow orchestration.

Let's look at some key views:

  • The Grid View (formerly Tree View): This is often your landing page for a specific DAG. It shows a historical grid of your DAG runs. Each column is a DAG run, and each cell represents a task instance within that run, color-coded by its status (success, running, failed, upstream_failed, etc.). You can quickly see the status of recent runs and identify any failures.

  • The Graph View: This provides a visual representation of your DAG's structure, showing all the tasks and their dependencies as a directed graph. It's incredibly helpful for understanding the flow of your pipeline and for debugging complex dependencies. You can see the current state of tasks in a running DAG directly on the graph.

  • The Gantt Chart: This view visualizes the duration of each task instance in a DAG run as a Gantt chart. It's excellent for identifying bottlenecks, understanding task overlaps, and seeing how long each part of your workflow takes.

  • Task Logs: For every task instance, Airflow captures its logs and makes them easily accessible through the UI. No more kubectl logs for every attempt of every pod. You can see the logs for each retry, which is invaluable for debugging.

These are just a few highlights. The Airflow UI also provides views for SLA misses, audit logs, trigger history, and much more. This level of built-in, workflow-centric visibility is something you simply don't get out-of-the-box with Kubernetes Jobs and its standard dashboard.

Show Me the Code: K8s Job YAML vs. Airflow DAG

Let's make this more concrete. Imagine you have a Python script, process_data.py, that you've containerized into an image called my-python-processor:latest. You want to run this as a job.

1. The Kubernetes Job Way (YAML)

You'd typically define a Kubernetes Job using YAML like this :

# my_k8s_job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: my-processing-job
spec:
  template:
    spec:
      containers:
      - name: data-processor
        image: my-python-processor:latest
        command: ["python", "process_data.py"]
        # You might add resource requests/limits here
        # resources:
        #   requests:
        #     memory: "64Mi"
        #     cpu: "250m"
        #   limits:
        #     memory: "128Mi"
        #     cpu: "500m"
      restartPolicy: OnFailure # Or Never
  backoffLimit: 2 # Number of retries before marking the Job as failed
  # For CronJobs, you'd wrap this in a CronJob spec with a schedule

Explanation:

  • apiVersion: batch/v1, kind: Job: Standard K8s object definition.

  • metadata.name: Name of your job.

  • spec.template.spec.containers: Defines the container(s) to run.

    • image: Your Docker image.

    • command: The command to execute in the container.

  • restartPolicy: Defines if/how K8s should restart failed containers within the pod. OnFailure restarts the container if it exits with an error. Never means it won't.

  • backoffLimit: How many times the Job controller will retry creating a new pod if the previous one fails before marking the entire Job as failed.

To run this, you'd use kubectl apply -f my_k8s_job.yaml.

2. The Airflow Way (Python DAG with KubernetesPodOperator)

Now, let's see how you'd achieve the same thing using Airflow, specifically with the KubernetesPodOperator. This operator allows Airflow to launch a Kubernetes pod to execute your task.

# my_airflow_dag.py
from airflow import DAG
from airflow.providers.cncf.kubernetes.operators.kubernetes_pod import KubernetesPodOperator
from datetime import datetime

with DAG(
    dag_id='my_k8s_processing_dag',
    start_date=datetime(2023, 1, 1),
    schedule_interval='@daily', # Or your cron expression
    catchup=False,
    tags=['example', 'k8s'],
    default_args={
        'owner': 'airflow',
        'retries': 2, # Airflow-level retries for the task
        # 'retry_delay': timedelta(minutes=5), # Delay between retries
    }
) as dag:
    run_my_processor = KubernetesPodOperator(
        task_id='run_data_processor_pod',
        name='data-processor-pod', # Name for the K8s pod itself
        namespace='default',      # Or your target K8s namespace
        image='my-python-processor:latest',
        cmds=["python"],
        arguments=["process_data.py"],
        is_delete_operator_pod=True, # Delete pod after task completion (success or failure)
        get_logs=True,               # Fetch logs from the pod and make them available in Airflow UI
        log_events_on_failure=True,  # Log K8s events on failure
        # Define resource requests/limits for the pod if needed
        # resources={'request_memory': '64Mi', 'request_cpu': '250m',
        #            'limit_memory': '128Mi', 'limit_cpu': '500m'},
        # You can pass secrets, configmaps, volumes, etc.
        # secrets=[k8s_secret_file_object, k8s_secret_env_object],
        # configmaps=['my-configmap'],
    )

    # If you had another task, say 'cleanup_task', that runs after:
    # cleanup_task = BashOperator(task_id='cleanup', bash_command='echo "cleaning up..."')
    # run_my_processor >> cleanup_task

Explanation:

  • DAG Definition: Standard Airflow DAG setup (ID, start date, schedule). default_args can specify retries at the Airflow task level.

  • KubernetesPodOperator: This is the magic.

    • task_id: Unique ID for this task within the Airflow DAG.

    • name: The name that will be given to the Kubernetes pod when it's launched.

    • namespace: The K8s namespace where the pod will be created.

    • image, cmds, arguments: Similar to the K8s Job spec, defining what the pod runs.

    • is_delete_operator_pod=True: This is great for cleanup. Airflow will delete the pod from K8s once the task finishes (successfully or not).

    • get_logs=True: Airflow will pull the logs from the K8s pod and store them, making them accessible directly in the Airflow UI for that task instance.

    • resources: You can specify K8s resource requests and limits directly.

Brief Comparison:

  • Structure: YAML for K8s is declarative, defining the desired state of a resource. Python for Airflow is imperative, defining a sequence of operations and logic.

  • Management: The K8s Job is a standalone resource you manage with kubectl. The Airflow task is part of a versionable, monitorable, and manageable DAG within the Airflow ecosystem.

  • Flexibility: Python in Airflow gives you immense power. You can dynamically generate tasks, use loops, conditionals, pass data between tasks (via XComs, though be mindful with large data ), and integrate with a vast ecosystem of providers for different services.

The critical takeaway here is that the KubernetesPodOperator (and the more encompassing KubernetesExecutor) acts as a bridge. It allows you to retain your investment in containerization and Kubernetes resource management while gaining Airflow's superior orchestration, visibility, and management features. You're not choosing between Airflow and Kubernetes for execution; you're using Airflow to better manage tasks that run on Kubernetes. This shift from simple K8s Job YAMLs to Python-defined Airflow DAGs opens up a world of possibilities for building robust, maintainable, and observable data pipelines and batch workflows.

Airflow to the Rescue: Solving Your K8s Nightmares, Point by Point

Now, let's revisit those frustrations with Kubernetes Jobs and see how Airflow steps in to save the day:

  • "Black Box" Syndrome Solved: Airflow's UI is all about visibility. The Grid View shows you the status of every task in every DAG run at a glance. The Graph View visualizes dependencies and progress. The Gantt chart shows durations. If a task fails, it's immediately obvious, and you can drill down into its logs with a click. No more guessing or extensive kubectl spelunking.

  • Superior UI for Workflows: As highlighted, Airflow's UI is purpose-built for workflow orchestration. It provides specialized views like the Graph, Grid, and Gantt charts that are essential for understanding and managing complex pipelines. This is a world away from the general-purpose Kubernetes Dashboard, which is more focused on cluster resource status.

  • Robust Dependency Management: This is a core strength of Airflow. In your DAG file, you explicitly define dependencies between tasks using simple Python syntax like task_A >> task_B or task_A >>. Airflow's scheduler ensures tasks are executed in the correct order, only starting a task when all its upstream dependencies have succeeded.

  • Advanced Retry & Failure Handling: Airflow offers fine-grained control over retries. You can configure the number of retries, the delay between retries (retry_delay), and even exponential backoff directly in your task definition or DAG defaults. Furthermore, you can define on_failure_callback functions that get executed when a task fails. This allows you to implement custom alerting (e.g., send a Slack message, create a JIRA ticket) or cleanup actions. Airflow also supports SLAs (Service Level Agreements) per task, and the UI has a dedicated view for SLA misses, helping you track if tasks are meeting their expected completion times.

  • Centralized & Task-Aware Logging: When using operators like KubernetesPodOperator with get_logs=True, Airflow automatically fetches the logs from the Kubernetes pod for each task attempt and stores them. These logs are then readily available in the Airflow UI, associated with the specific task instance and attempt. This is a massive improvement over manually collecting logs from potentially multiple K8s pods.

  • Built-in and Extensible Alerting: Beyond on_failure_callback, Airflow can be configured to send email alerts on task failures or retries out of the box. Through community providers, you can easily integrate with other alerting systems like Slack, PagerDuty, etc.. This means you get timely notifications without having to build a separate alerting infrastructure for your jobs.

By addressing these common pain points, Airflow empowers you to build more resilient, observable, and manageable workflows on Kubernetes. The developer experience improves significantly when you can define your pipelines in Python, leverage your existing IDEs for development and testing, and rely on Airflow for the heavy lifting of orchestration.

Getting Your Feet Wet: Basic Requirements for Using Airflow

Convinced that Airflow might be the solution to your K8s job woes? Great! Here's a rundown of what you'll generally need to get started:

  1. Python Knowledge: Airflow DAGs are written in Python, so a decent understanding of Python is essential. You don't need to be a Python guru, but you should be comfortable with its syntax, data structures, and basic programming concepts.

  2. Understanding of DAG Concepts: You'll need to grasp the core ideas of Directed Acyclic Graphs, operators, tasks, dependencies, and the general lifecycle of an Airflow workflow. The official Airflow documentation is a good place to start.

  3. Airflow Installation & Setup:

    • Metadata Database: Airflow needs a database to store its state (DAG runs, task instances, connections, etc.). PostgreSQL or MySQL are commonly used in production. SQLite can be used for local development but isn't recommended for production.

    • Executor: You'll need to choose an executor. For local development and testing, LocalExecutor is fine. For production, especially if you're already on Kubernetes, the KubernetesExecutor (runs each Airflow task in a new pod) or using the KubernetesPodOperator with another executor like CeleryExecutor are strong choices.

    • Core Components: You'll run the Airflow Webserver (for the UI) and the Airflow Scheduler (to monitor and trigger tasks).

  4. If using KubernetesExecutor or KubernetesPodOperator (highly recommended for this audience):

    • A Running Kubernetes Cluster: Obviously, you need a K8s cluster where Airflow can launch pods.

    • kubectl Configured: Your kubectl command-line tool should be configured to interact with your cluster.

    • Helm (Recommended for Deployment): The official Apache Airflow Helm chart is the recommended way to deploy Airflow on Kubernetes. It simplifies the configuration and management of Airflow components.

    • Docker Images for Your Tasks: If your tasks are to run as Kubernetes pods (which is the idea here), they need to be containerized into Docker images that your K8s cluster can pull.

It's worth noting that managed Airflow services (like Amazon Managed Workflows for Apache Airflow - MWAA, Google Cloud Composer, or Astronomer) can abstract away much of the Airflow infrastructure setup and maintenance, letting you focus more on writing DAGs. However, understanding these basic requirements is still beneficial.

There's an initial learning curve and setup effort, especially when integrating with Kubernetes. But for teams wrestling with the limitations of native K8s Job orchestration for complex workflows, the investment often pays off handsomely in terms of improved visibility, reliability, and developer productivity.

Conclusion: Is It Time to Embrace the Airflow Advantage?

So, let's bring it all home. If you started reading this because you're tired of the limitations of the Kubernetes UI for your jobs, if you're struggling with visibility, dependency management, and the overall clunkiness of orchestrating complex workflows solely with kubectl and YAML, then Apache Airflow offers a compelling, battle-tested alternative.

It's not about abandoning Kubernetes; it's about making Kubernetes even better for your batch workloads by layering a powerful, workflow-aware orchestrator on top. For teams managing data pipelines, ETL processes, machine learning workflows, or any series of interconnected batch tasks on Kubernetes, the benefits are clear:

  • Unparalleled visibility into every step of your workflow.

  • Robust dependency management defined cleanly in Python.

  • Sophisticated retry and failure handling mechanisms.

  • Centralized logging and alerting.

  • The power of Python for dynamic and complex pipeline definition.

  • Seamless integration with Kubernetes via the KubernetesPodOperator or KubernetesExecutor.

Yes, Airflow has its own learning curve, and setting it up (especially on Kubernetes) requires some effort. But if the pain points I've described resonate deeply, the long-term gains in control, efficiency, and sanity can be well worth the initial investment. You're trading the ongoing frustration of managing K8s jobs with limited tools for the upfront effort of learning and implementing a system designed specifically for workflow orchestration.

If you're feeling that Kubernetes, while great for services, is letting you down on the job orchestration front, I strongly encourage you to give Apache Airflow a serious look. Spin up a local instance, try out the KubernetesPodOperator with one of your existing containerized tasks, and explore the UI. You might just find that it brings the clarity, control, and "old good" robustness you've been missing in your Kubernetes job orchestration.

0
Subscribe to my newsletter

Read articles from Vladyslav Kotliarenko directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Vladyslav Kotliarenko
Vladyslav Kotliarenko

I'm a backend and infrastructure engineer specializing in Go, Kubernetes, GitOps, and cloud-native platforms (GCP, k8s, RKE2). I build scalable systems, debug production like a surgeon, and automate everything from deployments to disaster recovery. Prefer minimalism over overengineering. ⚙️ Tools of the trade: Go, Terraform, Helm, ArgoCD, Prometheus, PostgreSQL, Kafka, Docker, CI/CD 🧠 Interests: distributed systems, edge computing, LLM integrations, tech strategy, overemployment, tax-optimized engineering careers. 💬 Here I share practical insights, deep dives, and postmortems from the trenches of infrastructure and backend development.