Skip to content

Reuse async Kubernetes API client across trigger polls - #71349

Open
xvega wants to merge 3 commits into
apache:mainfrom
xvega:fix-async-k8s-client-reuse
Open

Reuse async Kubernetes API client across trigger polls#71349
xvega wants to merge 3 commits into
apache:mainfrom
xvega:fix-async-k8s-client-reuse

Conversation

@xvega

@xvega xvega commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

I've been chasing triggerer issues on our production deployment: sustained triggers.blocked_main_thread bursts whenever a wave of deferrable KubernetesPodOperator/GKEStartPodOperator tasks started or finished, with the async thread blocked 2–10s at a time. #69661 helped the log-parsing path, but the metric barely moved. After reading the source and profiling the TriggerRunner with py-spy during blocking windows, the loop thread showed up inside ssl.create_default_context() reached from AsyncKubernetesHook.get_conn(): every API method builds a fresh ApiClient per call, which parses the CA bundle synchronously on the event loop and opens a new aiohttp session, a full TCP+TLS handshake per status poll (default 2s per trigger) with zero reuse. The GKE hook additionally fetched a new OAuth token and wrote the cluster CA to a temp file each call.

The change itself is simple: instead of building a new API client for every call, the hook now builds one and keeps it for its whole lifetime. For in-cluster and static-auth kubeconfigs Airflow loads the configuration once per hook, and anything that does rotate (the in-cluster service-account token) is read from the configuration per request rather than baked into the client, so one client can serve the hook's lifetime. Exec-based auth (EKS/GKE kubeconfigs) is the exception and keeps the old per-call behavior on purpose, reloading the config on every call is exactly what refreshes its short-lived credentials, so those clients can't be reused. To pair every cached client with an owner, hooks get an idempotent close() and the triggers call it from cleanup(), so the client lives exactly as long as its trigger. The GKE hook gets the same treatment plus token caching: it re-sets the bearer header every time the connection is used, so tokens refresh on schedule instead of being fetched from Google on every call (and since all that was left of its _load_config() was building the client, it's now called _build_client()).

A few things follow from the caching that are worth knowing. If you instantiate these hooks yourself or subclass the triggers and override cleanup(), call hook.close() when you're done, otherwise the client is only cleaned up at garbage collection and aiohttp logs an "unclosed session" warning; calling the hook again after close() just builds a fresh client. mTLS client certs from static kubeconfigs are now read once per hook rather than on every call, which I think is fine for hooks that live only as long as a trigger (if cert rotation mid-trigger ever becomes a real problem, rebuilding the client on auth errors would be the fix). The pre-existing gap left by #65212 the default-kubeconfig path sets _config_loaded without exec-auth detection, is untouched. And one expectation to set: each new trigger still builds its one client on the event loop, so blocked_main_thread should drop dramatically but won't be exactly zero when a batch of triggers starts.

To make sure this was really the problem, I reproduced it locally on a minikube cluster: real pod triggers polling real pods, with Airflow's own block_watchdog running, counting how many SSL contexts and API clients got created. I ran the exact same load twice, once with the old per-call get_conn patched back in as a baseline, once with this change. The baseline created a new client (and SSL context) for every single API call; with this change each trigger creates exactly one and releases it on cleanup. Unit tests in both providers assert the same behavior.

related: #69661

Was generative AI tooling used to co-author this PR?

No

@boring-cyborg boring-cyborg Bot added area:providers provider:cncf-kubernetes Kubernetes (k8s) provider related issues provider:google Google (including GCP) related issues labels Aug 9, 2026
@xvega
xvega force-pushed the fix-async-k8s-client-reuse branch 5 times, most recently from a3d8b31 to e6838f1 Compare August 12, 2026 11:51

@Miretpl Miretpl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you adjust your PR description to match our project guidelines - https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst?

Comment thread providers/google/src/airflow/providers/google/cloud/hooks/kubernetes_engine.py Outdated
@Miretpl

Miretpl commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

I would appreciate a review from a Kubernetes Google maintainer on that part of the code.

@xvega
xvega force-pushed the fix-async-k8s-client-reuse branch from e6838f1 to 0bf66bf Compare August 17, 2026 07:02
@xvega

xvega commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

Could you adjust your PR description to match our project guidelines - https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst?

Besides the AI disclosure block what's missing or not part of the guideline?

@xvega
xvega force-pushed the fix-async-k8s-client-reuse branch from 0bf66bf to 4ec850e Compare August 17, 2026 08:38
@xvega
xvega requested a review from Miretpl August 17, 2026 08:41
@xvega
xvega force-pushed the fix-async-k8s-client-reuse branch from 4ec850e to a708b4b Compare August 17, 2026 17:51
@Miretpl

Miretpl commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Besides the AI disclosure block what's missing or not part of the guideline?

Only this was missing here. Thanks for adding it. I've rerun the whole CI as there were a couple of unrelated failures. Let's see how it will be now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:providers provider:cncf-kubernetes Kubernetes (k8s) provider related issues provider:google Google (including GCP) related issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants