Page MenuHomePhabricator

Enable Airflow triggerer process for deferrable operators in airflow-ml and airflow-devenv
Closed, ResolvedPublic

Description

We are running a model training DAG using WMFKubernetesPodOperator to launch a GPU-enabled pod. The DAG is currently failing because it requests a GPU, but all available GPUs are in use by other jobs as shown in T406302#11252998.

To address this, we enabled deferrable execution for the training operator so the task can wait efficiently for GPU resources. However, we discovered that the Airflow triggerer process was not running in both airflow-devenv and airflow-ml instances, as shown in T406302#11258029.

Following DPE SRE advice, we manually started the triggerer by execing into the scheduler pod as shown in P83720#336321. This removed the triggerer UI warning, but the DAG task now fails with the error:

Trigger emitted an error event, failing the task: Invalid kube-config file. Expected key current-context in kube-config

see full logs in P83720.

We are requesting DPE SRE to enable the Airflow triggerer process in both airflow-devenv and airflow-ml to support deferrable operators.

Related Objects

Event Timeline

Change #1196028 had a related patch set uploaded (by Brouberol; author: Brouberol):

[operations/deployment-charts@master] airflow: allow the deployment of the triggerer component

https://gerrit.wikimedia.org/r/1196028

Change #1196029 had a related patch set uploaded (by Brouberol; author: Brouberol):

[operations/deployment-charts@master] airflow-ml: enable the triggerer component

https://gerrit.wikimedia.org/r/1196029

Change #1196028 merged by jenkins-bot:

[operations/deployment-charts@master] airflow: allow the deployment of the triggerer component

https://gerrit.wikimedia.org/r/1196028

Change #1196029 merged by Brouberol:

[operations/deployment-charts@master] airflow-ml: enable the triggerer component

https://gerrit.wikimedia.org/r/1196029

Screenshot 2025-10-15 at 12.56.20.png (3,034×2,134 px, 474 KB)
The triggerer is now running in airflow-ml.

Because devenvs are using the same values-production.yaml file than its associated production instance (ie: any devenv using --dags-folder ml will use airflow-ml/values-production.yaml in the values overlay), and we have triggerer.enabled: true in that file, then devenvs based on the ml folder automatically get the triggerer for free:

brouberol@deploy2002:~$ airflow-devenv create --branch main --dags-folder ml
Airflow development environment provisioning parameters:
- instance name: dev-brouberol
- pulled git branch: main
- airflow-dags folder: ml

Installation progress:
- Creating PG database dev_brouberol ✅
- Installing airflow dev environment dev-brouberol ✅
- Waiting for the kerberos pod to start .............. ✅
Password for brouberol@WIKIMEDIA:
- Kerberos credentials successfully setup ✅
- Waiting for the scheduler pod to be ready ✅
- Forcing DAG serialization ✅
- Waiting for the webserver pod to be ready ✅
- Creating admin user (username: admin, password: admin) ✅

Your airflow development environment is fully set up. You can now execute `airflow-devenv expose dev-brouberol` to expose the UI on your local development host.
brouberol@deploy2002:~$ airflow-devenv show
NAME                                                  READY   STATUS    RESTARTS        AGE
airflow-dev-brouberol-envoy-7fdb77b5db-v6cdf          1/1     Running   0               2m58s
airflow-dev-brouberol-gitsync-6d455457b9-6kvrm        1/1     Running   0               2m59s
airflow-dev-brouberol-hadoop-shell-864cfccddb-zkp9c   1/1     Running   0               2m58s
airflow-dev-brouberol-kerberos-6cfd6d7c56-l8grj       1/1     Running   0               2m59s
airflow-dev-brouberol-scheduler-dd4dd4fc5-lq6g7       1/1     Running   0               2m59s
airflow-dev-brouberol-statsd-55678bf84c-d8wgm         1/1     Running   0               2m59s
airflow-dev-brouberol-task-shell-6c55cf6885-fhc7f     1/1     Running   0               2m59s
airflow-dev-brouberol-triggerer-586d695f4d-dz8ln      1/1     Running   0               2m59s  # <---
airflow-dev-brouberol-webserver-86c6dd8489-wgg2p      2/2     Running   0               2m58s

@brouberol, thank you for enabling the triggerer process in Airflow. I tested it using this DAG, and the WMFKubernetesPodOperator deferred the training task, launched a GPU-enabled pod, and the pod ran and completed successfully.

However, the task failed to resume after pod completion, with the following error:

kubernetes_asyncio.config.config_exception.ConfigException: Invalid kube-config file. Expected key current-context in kube-config

Full logs are available in P84073.

Hmm, it appears that we're affected by https://github.com/apache/airflow/issues/34644, which has no resolution or suggested bug fix. I'll try to dig.

Can you try to pass the in_cluster=True argument to the WMFKubernetesPodOperator that also has deferrable=True ?

Can you try to pass the in_cluster=True argument to the WMFKubernetesPodOperator that also has deferrable=True ?

I have passed the in_cluster argument to the deferrable operator, but the DAG task failed with error:

aiohttp.client_exceptions.ClientConnectorError: Cannot connect to host 10.67.32.1:443 ssl:default [Connect call failed ('10.67.32.1', 443)]

see full logs in P84087.

Ah, that's progress! That seems to be the pod trying to call the kubernetes API, which is prevented by a Network Policy.

brouberol@deploy2002:~$ k exec -it airflow-scheduler-6b8df7cfcb-9jd29 -- bash
pairflow@airflow-scheduler-6b8df7cfcb-9jd29:/opt/airflow$ python3
Python 3.10.15 (main, Jun 23 2025, 12:27:25) [GCC 10.2.1 20210110] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import socket
>>> socket.gethostbyaddr('10.67.32.1')
('kubernetes.default.svc.cluster.local', [], ['10.67.32.1'])
>>>

We see that the created pod has the following labels:

brouberol@deploy2002:~$ k get pod -oyaml train-model-lanjnya
apiVersion: v1
kind: Pod
metadata:
  annotations:
    cni.projectcalico.org/containerID: 4e6a6c543d01cfd525c6990b9ce244fe1faafe4c7114b6be518bdb6a441be2f6
    cni.projectcalico.org/podIP: ""
    cni.projectcalico.org/podIPs: ""
    container.seccomp.security.alpha.kubernetes.io/base: runtime/default
  creationTimestamp: "2025-10-20T07:58:05Z"
  labels:
    airflow_kpo_in_cluster: "True"
    airflow_version: 2.10.5
    app: airflow
    component: task-pod
    dag_id: example_ml_training_pipeline
    kubernetes_pod_operator: "True"
    release: dev-kevinbazira
    routed_via: dev-kevinbazira
    run_id: manual__2025-10-20T073328.0310750000-7cfb204d7
    task_id: train_model
    try_number: "3"

To be able to reach out to the kubernetes API, it needs to have the kube_api_enabled: True label. See for comparison the pod associated with the Kubernetes Executor (don't mind the casing that is caused by what I think is a bug in airflow):

Screenshot 2025-10-20 at 10.04.28.png (802×664 px, 115 KB)

I don't 100% understand why the pod would need to talk to the k8s API, but let's roll with it for now.

To fix this, pass the labels={"kubeapi_enabled": "True"} argument to the operator. If that works, we could improve the common tooling to abstract this away down the road.

Also note that now that we have fine-grained labels on GPU models and capabilities, you can drop the affinity=V1Affinity(node_anti_affinity_for_hostnames(["dse-k8s-worker1001.eqiad.wmnet"])), argument.

Ah wait, I think I read the error wrong!

Trigger emitted an error event, failing the task: Cannot connect to host 10.67.32.1:443 ssl:default [Connect call failed ('10.67.32.1', 443)]

I think the triggerer needs to have access to the kubernetes API!

I'm going to manually edit the labels selector of the networkpolicy to rig it into a working state.

brouberol@deploy2002:~$ kubectl edit networkpolicies.crd.projectcalico.org  airflow-dev-kevinbazira-to-kubeapi -oyaml
apiVersion: crd.projectcalico.org/v1
kind: NetworkPolicy
metadata:
  annotations:
    meta.helm.sh/release-name: dev-kevinbazira
    meta.helm.sh/release-namespace: airflow-dev
  creationTimestamp: "2025-10-20T08:27:29Z"
  generation: 2
  labels:
    app.kubernetes.io/managed-by: Helm
  name: airflow-dev-kevinbazira-to-kubeapi
  namespace: airflow-dev
  resourceVersion: "846888402"
  uid: 49fafc51-c433-4dba-8dd4-9b8ae1b7ca45
spec:
  egress:
  - action: Allow
    destination:
      services:
        name: kubernetes
        namespace: default
  selector: app == 'airflow' && release == 'dev-kevinbazira' && component in { 'scheduler', 'webserver', 'triggerer' }
  types:
  - Egress

I now see that the triggerer is allowed to hit the kubernetes API by the network policy, but is denied permission. Because I didn't anticipated that it would need k8s API access, I didn't give it the appropriate airflow service account. Let me fix that.

brouberol@deploy2002:~$ k get pod -l release=dev-kevinbazira,component=triggerer -o json | jq '.items[0].spec.serviceAccount'
"airflow-dev-kevinbazira"

That seems to have done the trick!

[2025-10-20T08:48:29.716+0000] {triggerer_job_runner.py:338} INFO - Starting the triggerer
[2025-10-20T08:49:29.765+0000] {triggerer_job_runner.py:510} INFO - 0 triggers currently running
[2025-10-20T08:49:43.777+0000] {triggerer_job_runner.py:627} INFO - trigger example_ml_training_pipeline/manual__2025-10-20T08:31:55.034415+00:00/train_model/-1/3 (ID 3) starting
[2025-10-20T08:49:43.777+0000] {pod.py:146} INFO - Checking pod 'train-model-tq54uwn' in namespace 'airflow-dev'.
[2025-10-20T08:49:43.781+0000] {crypto.py:82} WARNING - empty cryptography key - values will not be stored encrypted.
[2025-10-20T08:49:43.812+0000] {pod.py:221} INFO - Still waiting for pod to start. The pod state is Pending
[2025-10-20T08:49:52.869+0000] {triggerer_job_runner.py:631} INFO - Trigger example_ml_training_pipeline/manual__2025-10-20T08:31:55.034415+00:00/train_model/-1/3 (ID 3) fired: TriggerEvent<{'status': 'success', 'namespace': 'airflow-dev', 'name': 'train-model-tq54uwn', 'last_log_time': None}>
[2025-10-20T08:49:52.869+0000] {triggerer_job_runner.py:652} INFO - trigger example_ml_training_pipeline/manual__2025-10-20T08:31:55.034415+00:00/train_model/-1/3 (ID 3) completed

I'll make a couple of changes to the chart and will assign you as reviewer./

Super! The DAG task with a deferrable operator has now succeeded in the airflow-devenv.

Screenshot from 2025-10-20 11-52-47.png (1,846×837 px, 270 KB)

Change #1197207 had a related patch set uploaded (by Brouberol; author: Brouberol):

[operations/deployment-charts@master] airflow: enable the triggerer to hit the Kubernetes API servers with appropriate permissions

https://gerrit.wikimedia.org/r/1197207

Change #1197207 merged by Brouberol:

[operations/deployment-charts@master] airflow: enable the triggerer to hit the Kubernetes API servers with appropriate permissions

https://gerrit.wikimedia.org/r/1197207

Feel free to destroy/redeploy your whole devenv in about ~5 minutes, and check whether things work OOTB now!

Feel free to destroy/redeploy your whole devenv in about ~5 minutes, and check whether things work OOTB now!

Works like a charm! Thanks a lot for your help, @brouberol.