aman
← all writeups

The 17th rule never shipped

Gateway API caps an HTTPRoute at 16 rules. Our 19-rule route failed ArgoCD sync for two days while the app still showed Healthy.

Date
2026-09-02
Type
incident
Duration
~2 days
Impact
A newly onboarded service in the test environment returned a gateway 404 for every request, although its pod, Service and Deployment were all fine.

A new service was deployed but unreachable. Every request through the gateway got a plain page not found. Curl straight to the pod IP returned 405 Method Not Allowed, which is the right answer to a GET on a POST-only endpoint. So the app was fine, and the route was in git. ArgoCD showed the routing app as Healthy.

Timeline

Time (UTC) Event
Aug 31 New /campaignmapping/ rule appended to an existing HTTPRoute and pushed
Aug 31 13:39 ArgoCD sync fails, retries 5 times, gives up. App stays Healthy / OutOfSync
Sep 2 Service reported unreachable. Pod-direct curl works, gateway returns 404
Sep 2 ~12:57 kubectl get httproute has no such rule. Sync error found in app conditions
Sep 2 12:58 Route split into a new HTTPRoute object, committed
Sep 2 12:59 ArgoCD syncs about 51s after the push. New route object created
Sep 2 13:00 Gateway now returns the app’s own auth error. Traffic is reaching the pod

The actual cause

Routes for the test environment live in one file with several numbered HTTPRoute objects: microservices-test-1 through -5. The numbering exists only because of a limit nobody had written down. The Gateway API CRD validates spec.rules to at most 16 items.

Adding the new rule took microservices-test-5 to 19. The API server rejected the whole object:

spec.rules: Too many: 19: must have at most 16 items

This is not a Traefik limit. It’s CRD schema validation, so any Gateway API implementation behaves the same way.

ArgoCD’s reporting made it easy to miss. Health covers resources that exist in the cluster, and those were all fine, because the old version of the route was still applied. The failure only showed up as OutOfSync plus a SyncError in .status.conditions. Nothing alerted on it. For two days git said one thing and the cluster did another. Anything else added to that object in the meantime would have failed the same way.

What changed

  • Moved the last three rules into a new microservices-test-6 HTTPRoute with the same parentRefs and hostnames. -5 now has exactly 16 rules.
  • Onboarding a service now includes counting the rules in the target HTTPRoute. At 16, start a new numbered object.
  • When traffic doesn’t reach a pod, check ArgoCD sync status and conditions before debugging the app, the Service or the Deployment. Healthy alone doesn’t show sync failures.

What didn’t work

We started at the app. Checked the Flask routes, PATH_ALIAS and the probe paths, and went looking for a /ping endpoint that turned out not to exist. The 405 from the pod IP had already ruled out the app. We should have gone straight to the layer between the gateway and the pod.

An app can show Healthy in ArgoCD while its sync keeps failing. If you alert on one, alert on the other too.


← all writeups