fix(deploy): let the cluster own the Gateway, keep only the route#518
Merged
Conversation
This repo cannot know the cluster it lands in, and gateway-listeners.yaml was
trying to. Between them, the ListenerSet and its routes hardcoded four facts
about cfp-live-cluster — the shared Gateway's name and namespace, the
ClusterIssuer, the TLS Secret name, and the ListenerSet API itself — and three
were wrong there:
- cert-manager.io/cluster-issuer: letsencrypt-prod is dead. It solves ACME
over ingress-nginx, which requires PROXY protocol on every connection and
is therefore unreachable from inside the cluster. No certificate has issued
through it since May.
- certificateRefs: balancer-tls is the legacy Ingress Secret. Gateway certs
are <app>-gw-tls.
- ListenerSet is not reconciled by Envoy Gateway v1.7.3. It watches
XListenerSet and logs "XListenerSet CRD not found, skipping XListenerSet
watch". The object applies cleanly and is then ignored — the app just has
no listener, with nothing in any log to say why.
None of that is a mistake anyone could have avoided from this repo. It isn't
knowable here. So stop trying to know it.
Split on what each side actually knows:
this repo -> HTTPRoute: paths, backend Services, ports. App facts. They
change when the app changes, in the same commit.
cluster -> Gateway: listeners, hostnames, TLS, issuers. Cluster facts. They
differ per environment and are shared with every other app.
The route now attaches to a Gateway the cluster provides, named after the app,
and declares no hostnames — a route that omits hostnames inherits them from the
listeners it attaches to. So one file serves balancerproject.org in live and
balancer.sandbox.k8s.phl.io in sandbox, with no patches and no placeholders.
The HOSTNAME_PLACEHOLDER substitutions and the overlay routing patches are gone.
Drops balancer-http-redirect too: the cluster already redirects every hostname
reaching it on port 80. Worse than redundant — a route claiming an exact
hostname on the shared HTTP listener outranks the catch-all redirect and can
shadow cert-manager's ACME solver route, which is what produced the apex 404
that #517 set out to fix. That 404 was a stuck certificate, and it cleared the
moment the cert issued.
Adds a README stating the contract, so the next person doesn't have to infer it.
Refs CodeForPhilly/cfp-live-cluster#166, #168
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.
Draft — one assumption to verify against a real cluster first (see the end).
Follow-up to #517. It was chasing a real bug, but the fix pointed at the wrong layer, and the layer it pointed at can't run on our cluster.
The problem
This repo cannot know the cluster it lands in, and
gateway-listeners.yamlwas trying to. Between the ListenerSet and its routes, it hardcoded four facts about cfp-live-cluster — the shared Gateway's name and namespace, the ClusterIssuer, the TLS Secret name, and the ListenerSet API itself. Three of them are wrong there:cert-manager.io/cluster-issuer: letsencrypt-prodis dead. It solves ACME through ingress-nginx, which requires PROXY protocol on every connection and is therefore unreachable from inside the cluster. No certificate has issued through it since May — that's what expired nine certs on 2026-07-12 (cfp-live-cluster#144).certificateRefs: balancer-tlsis the legacy Ingress Secret. Gateway certs are<app>-gw-tls.ListenerSetis not reconciled by Envoy Gateway v1.7.3. It watchesXListenerSet:The object applies cleanly to the API server and is then silently ignored.
balancerproject.orgwould simply have no listener, with nothing in any log to say why. (It lands in EG v1.8.)None of that is a mistake anyone could have avoided from this repo — it isn't knowable here. So the fix is to stop trying to know it.
The split
HTTPRoute— paths, backend Services, portsGateway— listeners, hostnames, TLSGatewayClassPaths and ports are app facts: they change when the app changes, and they should land in the same commit as that change. (Case in point — v1.1.6 moved the Service from
8000→80, and the cluster's copy of the route still says8000. Under this split that mismatch cannot happen.)Hostnames, TLS, and issuers are cluster facts: they differ per environment and are negotiated with every other app sharing the ingress.
The contract
The cluster provides a
Gatewaynamed after the app, in the app's namespace. That's the entire interface.A route that omits
hostnamesinherits them from the listeners it attaches to. One file servesbalancerproject.orgin live andbalancer.sandbox.k8s.phl.ioin sandbox — no patches, no placeholders, no environment-specific routing. TheHOSTNAME_PLACEHOLDERsubstitutions and the overlay routing patches are all gone.HOSTNAMEstays in the ConfigMap: the application legitimately needs its own public name for DjangoALLOWED_HOSTS/CSRF_TRUSTED_ORIGINS. That's app config, not routing.Also drops
balancer-http-redirectThe cluster already redirects every hostname reaching it on port 80. An app-level redirect is worse than redundant: a route claiming an exact hostname on the shared HTTP listener outranks the catch-all redirect under Gateway API precedence, and can shadow cert-manager's ACME solver route.
That's what produced the apex 404 #517 set out to fix. The 404 was a stuck certificate — cert-manager's solver route had been sitting on the hostname for 57 days because DNS hadn't cut over. It cleared the instant the cert issued, and
balancerproject.orgnow returns301 → httpswith no code change at all.Changes
base/gateway-listeners.yamlbase/httproute.yaml— one route,parentRefsby convention, no hostnames, no redirect routebase/kustomization.yaml— drop the ListenerSetoverlays/{production,sandbox}— drop all routing patchesdeploy/manifests/balancer/README.mdstating the contractAll three kustomizations build clean (
base,overlays/production,overlays/sandbox).Before merging — verify the inheritance
The design rests on a route with no
hostnamesinheriting its Gateway listener's hostname. That's what the Gateway API spec says, and Envoy Gateway should implement it — but today's whole lesson is that a manifest can look correct and be silently ignored, so I don't want to assert it. Worth confirming against a real Gateway (sandbox'secho-httpis a zero-stakes probe) before this merges.If inheritance doesn't behave, the fallback is one explicit hostname patch per overlay — still no Gateway, no issuer, no TLS in this repo.
Unrelated, but noticed
overlays/sandboxsetsHOSTNAME=sandbox.balancerproject.org, but the sandbox cluster actually servesbalancer.sandbox.k8s.phl.io. Left alone here — flagging it in case it's stale.Refs CodeForPhilly/cfp-live-cluster#166, CodeForPhilly/cfp-live-cluster#168