Repository navigation
feat(BA-8444): export ingresses from the coordinator to Traefik etcd - #15893
Draft
jopemachine wants to merge 35 commits into
Draft
jopemachine wants to merge 35 commits into
jopemachine wants to merge 35 commits into
Conversation
jopemachine
added a commit
that referenced
this pull request
Oct 20, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 tasks done
jopemachine
force-pushed
the
feat/BA-8443-coordinator-ingress-push
branch
from
October 20, 2026 05:30
96cfd2b to
23c7172
Compare
jopemachine
added a commit
that referenced
this pull request
Oct 20, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jopemachine
force-pushed
the
feat/BA-8444-traefik-ingress-export
branch
from
October 20, 2026 05:31
1863133 to
812ac34
Compare
jopemachine
force-pushed
the
feat/BA-8443-coordinator-ingress-push
branch
from
October 20, 2026 05:34
23c7172 to
5db6471
Compare
jopemachine
added a commit
that referenced
this pull request
Oct 20, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jopemachine
force-pushed
the
feat/BA-8444-traefik-ingress-export
branch
from
October 20, 2026 05:34
812ac34 to
70a4a7f
Compare
jopemachine
force-pushed
the
feat/BA-8443-coordinator-ingress-push
branch
from
October 20, 2026 05:44
5db6471 to
be45681
Compare
jopemachine
added a commit
that referenced
this pull request
Oct 20, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jopemachine
force-pushed
the
feat/BA-8444-traefik-ingress-export
branch
from
October 20, 2026 05:45
70a4a7f to
3272c7c
Compare
jopemachine
force-pushed
the
feat/BA-8443-coordinator-ingress-push
branch
from
October 20, 2026 05:47
be45681 to
bacd9c7
Compare
jopemachine
force-pushed
the
feat/BA-8444-traefik-ingress-export
branch
from
October 20, 2026 05:47
3272c7c to
fa67cfa
Compare
jopemachine
added a commit
that referenced
this pull request
Oct 20, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jopemachine
force-pushed
the
feat/BA-8443-coordinator-ingress-push
branch
from
October 20, 2026 05:49
bacd9c7 to
ada3d50
Compare
jopemachine
added a commit
that referenced
this pull request
Oct 20, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jopemachine
force-pushed
the
feat/BA-8444-traefik-ingress-export
branch
from
October 20, 2026 05:49
33a9c3e to
e986316
Compare
jopemachine
force-pushed
the
feat/BA-8443-coordinator-ingress-push
branch
from
October 20, 2026 05:52
ada3d50 to
3b02de2
Compare
jopemachine
added a commit
that referenced
this pull request
Oct 20, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jopemachine
force-pushed
the
feat/BA-8444-traefik-ingress-export
branch
from
October 20, 2026 05:52
e986316 to
55346e3
Compare
2 tasks done
jopemachine
force-pushed
the
feat/BA-8443-coordinator-ingress-push
branch
from
October 20, 2026 05:58
3b02de2 to
d22905f
Compare
`_revision_prefix` and `_revision_key` become methods of `TraefikWorkerClient`, and the older-revision check is written where the held revision is read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`_Names` becomes `_IngressTraefikObjectNames` with `router_name`, `service_name`, `ip_allowlist_middleware_name` and `auth_plugin_middleware_name`, `_ingress_prefixes` becomes `_ingress_etcd_prefixes`, and the locals spell out the held revision, the ingress key, the protocol etcd prefix, the router rule and the worker clients by authority. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`_write` makes one guarded replacement. When another write to the same frontend commits between the revision check and the write, it raises `TraefikIngressConcurrentWriteError` (409) instead of re-reading and retrying; resending the ingress applies it or reports a newer revision. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nfigFactory `etcd_prefix` becomes `router_etcd_key`, `service_etcd_key` and `http_middleware_etcd_key`, which return the whole key of one object, so `TraefikWorkerClient` no longer joins a protocol prefix with `routers`/`services`/`middlewares` itself. `_ingress_etcd_prefixes` becomes `_ingress_object_etcd_keys`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…onfigFactory The factory builds the object names, etcd keys and configs of an ingress from the ingress itself: `object_etcd_keys`, `router_etcd_key`, `service_etcd_key`, `http_router_config(ingress, worker)`, `tcp_router_config(ingress)` and `http_middleware_configs(ingress)`, which also decides whether the ipAllowList middleware is written. This drops `_IngressTraefikObjectNames` and `_ingress_object_etcd_keys` from `TraefikWorkerClient`, which now keeps only the etcd transactions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…se them The object name, etcd key, rule, entrypoint and target helpers and the CORS middleware name become private members of `TraefikRoutingConfigFactory`, and `_REVISION_ROOT` a private constant of `TraefikWorkerClient`, since nothing outside those classes uses them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Traefik v3 rule matchers take one value each, so a rule such as ClientIP(`a`, `b`) is rejected and the router is disabled. An ingress with several allowed client IPs now gets ClientIP(`a`) || ClientIP(`b`). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A worker registers its hostname, wildcard domain and tls_listen as service discovery labels next to its authority, and `TraefikWorkerClient` reads them from the appproxy-worker group instead of the coordinator `workers` table, so the Traefik ingress export does not depend on the coordinator DB. `WorkerRepository` is removed; a worker registered without these labels is reported as `ObjectNotFound` as a missing row was. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e factory `TraefikRoutingConfigFactory` keeps two public methods, `object_etcd_keys` and `object_configs_by_etcd_key`, which returns the config of every Traefik object an ingress uses by its etcd key. The etcd key, router, service and middleware builders become private, and `TraefikWorkerClient._generate_ingress_subtree` only clears the object keys and converts the configs for etcd. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The coordinator holds a core and a worker service discovery, and `TraefikWorkerClient` and `IngressService` read workers from the latter, so their field, argument and test fixture names say `worker_service_discovery`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uting configs "Object" was a term made up for them; Traefik calls them its routing configuration, as the factory name does. `object_etcd_keys` becomes `routing_config_etcd_keys` and `object_configs_by_etcd_key` becomes `routing_configs_by_etcd_key`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`_CORS_HEADERS_MIDDLEWARE_NAME` and `_REVISION_ROOT` move out of `TraefikRoutingConfigFactory` and `TraefikWorkerClient` to their modules. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…omponents composer `ComponentsComposer` provides the api-delivery worker `ClientPool` and the Traefik worker client (or None without Traefik etcd) through `WorkerClientPoolProvider` and `TraefikWorkerClientProvider`. The server enters it in `components_ctx`, which replaces `worker_client_pool_ctx`, and `ingress_service_ctx` only assembles `IngressService` from the results. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`TraefikWorkerClient` raises the new `WorkerNotFound` (404) with the authority when service discovery holds no worker of it, in place of the generic `ObjectNotFound`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 tasks done
The factory keeps the `TraefikConfig` it is given and reads `client_ip_strategy` from it when building an ipAllowList middleware, instead of keeping only the `ClientIPStrategyConfig`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`TraefikWorker` is the value the Traefik worker client builds from service discovery and the routing config factory reads, so it lives in its own module next to both instead of in the factory. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It holds the worker's frontend settings a Traefik router needs (hostname, wildcard domain, tls_listen), not a worker, so the type and the arguments carrying it say so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…listen separately `WorkerFrontendSettings` grouped two worker API settings with the Traefik wildcard domain under a made-up name. The factory now takes the three values as keyword arguments and hands each private builder only the ones it uses, and `clients/worker/types.py` is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tingConfigFactory The factory receives the Traefik, secret and permit hash config sections as one `TraefikRoutingConfigFactoryArgs` and reads the auth plugin secrets from them, instead of taking the two secret strings separately. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fig_etcd_keys It returns every routing config key an ingress can own, whether or not the ingress uses it now, for clearing the ones it no longer has. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The router, service and middleware configs the factory returns are `TypedDict`s (`TraefikHttpRouterConfig`, `TraefikTcpServiceConfig`, `TraefikIpAllowListMiddlewareConfig`, ...) instead of `dict[str, Any]`, with the keys Traefik reads. The written etcd keys and values are unchanged. The tests compare each config as a whole. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every TypedDict in traefik_routing_config describes a Traefik config, so the module name already carries the prefix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… into a traefik package Move clients/worker/traefik.py to clients/worker/traefik/client.py and the routing config module to clients/worker/traefik/routing_config.py with its TypedDicts in clients/worker/traefik/types.py, matching #16018. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ances Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ingress-export # Conflicts: # src/ai/backend/appproxy/coordinator/errors/ingress.py
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolves #15540 (BA-8444)
Summary
enable_traefik, the coordinator/v2/ingressAPI sends every authority's ingresses toTraefikWorkerClient, which writes them to Traefik etcd, instead of pushing to workers. Without it, the push path from feat(BA-8443): push ingresses from the coordinator to api-delivery workers #15833 is unchanged.TraefikWorkerClientandAppProxyWorkerClientshareBaseWorkerClient, soIngressServicepicks an authority's clients in one place and waits for every client before reporting the failed ones inIngressPushFailedError(renamed fromApiDeliveryWorkerPushFailedError).worker_{authority}/{http|tcp}/:routers/bai_ingress_{frontend_key}_router,services/..._service, and for HTTPmiddlewares/..._ipallowlistand..._auth.ingress_revisions/worker_{authority}/{frontend_key}/revision, outside every Traefik root key. The replacement commits only if that key still holds the revision read before it (compare_and_replace_prefixes, feat(BA-8643): add compare_and_replace_prefixes to AbstractKVStore #15988).TraefikIngressRevisionConflictError), and rewrites one of the same revision.TraefikIngressConcurrentWriteErrorinstead of retrying; resending the ingress applies it or reports a newer revision. An api-delivery worker serializes the check and the write under a lock, so it never fails this way.appproxy-traefik-pluginmiddleware receives the ingress JSON asingressin place ofcircuit.TraefikWorkerClienttakesTraefikRoutingConfigFactory(merged in refactor(BA-8675): addTraefikRoutingConfigFactoryfor ingresses #16018) throughTraefikWorkerClientArgs, clears every key fromall_routing_config_etcd_keysin the same transaction as writing the configs fromrouting_configs_by_etcd_key(or on remove), and keeps only the etcd transactions.hostname,wildcard_domain,tls_listen) come from service discovery: a worker now registers them ashostname,wildcard-domainandtls-listenlabels next toauthority, so the Traefik export does not read the coordinator DB. An authority with no worker registered with them is reported as 404WorkerNotFound.ClientPooland the Traefik worker client in a new components stage (dependencies/components/,ComponentsComposerwithWorkerClientPoolProviderandTraefikWorkerClientProvider). The server enters it incomponents_ctx, which replacesworker_client_pool_ctx, andingress_service_ctxonly assemblesIngressService.bai_service_{uuid}keys, so it never deletes ingress keys.Not in this PR
ingressinstead ofcircuit.appproxy-traefik-plugin-go) for ingresses; it takes session ids, which an ingress does not carry (BA-8321 records usage by target id).Test plan
pants check/pants testontests/unit/appproxy::clients/worker/traefik/test_client.py: writes when the held revision is absent, older or the same; rejects a newer one; writes none of a batch holding an older ingressservices/test_ingress.py: a failed Traefik write is reported after every write finishes🤖 Generated with Claude Code