NetworkingKUBERNETES-SPECIFICCLOUD-SPECIFIC

Operating the Edge

The ingress is where everyone's traffic meets one shared configuration. It is the component with the widest blast radius per line of config, and the one most often changed by people who own only one route.

The question, the obvious approach, and why it breaks

Every lesson starts where the work starts: an operational problem, a first attempt that is entirely reasonable, and the way production disagrees with it.

The production question

What breaks when the edge is a shared component that every team can change?

The problem

Routing, TLS termination and edge policy for many services live in one place, so a change for one service is applied to a component all of them depend on.

What teams do first

Each team adds its own routing rule. The rules are independent, they only affect their own hostname or path, and the controller applies them.

How it breaks

The rules are not independent once they are rendered into one proxy configuration. A rule that fails validation can block the whole render, so one team's mistake stops everybody's changes from being applied (Reconciliation: The Loop Under Everything).

How it breaks in production
  • The rules are not independent once they are rendered into one proxy configuration. A rule that fails validation can block the whole render, so one team's mistake stops everybody's changes from being applied (Reconciliation: The Loop Under Everything).
  • Path rules interact. Overlapping prefixes, differing match semantics and ordering mean a new rule can capture traffic intended for an existing one, and the existing one's owner finds out from their error rate.
  • The ingress controller is itself a workload with capacity, resource limits and probes. When it is saturated or restarting, every service behind it is affected regardless of its own health (Requests and Limits).
  • It is where certificates live, so an expiry here is an outage for every hostname routed through it (Certificates as an Operational Object).
  • Edge behaviour — timeouts, body size limits, buffering, header handling, rate limits — has defaults that are invisible until a service needs different ones, at which point they present as inexplicable application failures.
  • Errors returned by the edge look like errors from the application. A 502 from the proxy and a 500 from the service tell you completely different things and appear identically to a user (HTTP Debugging: 502, 503 and 504 Are Different Failures in the networking view).
CodeBuildTestArtifactReleaseDeployRunObserveOperateIncidentRecoverLearnImprove

What is actually happening

Underneath the tooling, which is the part that survives a change of tool.

  • The controller watches routing resources, validates them, renders a proxy configuration and reloads the proxy. It is a reconciliation loop like any other, and it fails in the same ways: not seeing a change, rejecting it, or rendering it and failing to reload.
  • Matching is by hostname and path, with the match type deciding the precedence. Two rules that both match a request are resolved by the controller's rules, not by the order the teams wrote them.
  • TLS is terminated here for the hostnames it serves, so this is a certificate custody point and its expiry is a shared event.
  • The edge has its own timeouts and limits that are independent of the application's. A request that exceeds the proxy's timeout is terminated by the proxy, and the application never learns it was abandoned (Timeouts in the backend view).
  • The proxy's status codes are diagnostic: a code indicating no upstream was available is a membership problem, a gateway timeout is an upstream latency problem, and a not-found from the proxy means no rule matched at all — three different investigations.
  • A reload is not free. Under a high rate of configuration change, a proxy can spend meaningful effort reloading, and some reload implementations disturb connections.

A route change is a shared-component change

The pipeline below is what happens to every routing change, and the two steps in the middle are where one team's edit becomes everyone's problem.

From a routing resource to traffic arriving
  1. 1
    Resource applied

    A routing object is created or changed.

    fails by Rejected at admission for a schema or policy violation — the cheap, contained failure.

    evidence The object exists with the expected spec.

  2. 2
    Controller validates

    Checks the rule against the rest of the configuration.

    fails by An invalid or conflicting rule blocks the render, so nobody's changes apply (Reconciliation: The Loop Under Everything).

    evidence The controller reports the configuration as accepted.

  3. 3
    Configuration rendered

    All routing resources become one proxy configuration.

    fails by A rule captures traffic intended for another service, and the render succeeds anyway.

    evidence The applied configuration contains the expected rule with the expected precedence.

  4. 4
    Proxy reloaded

    The proxy adopts the new configuration.

    fails by Reload disturbs long-lived connections, or is deferred under a high rate of change.

    evidence The reload succeeded and connection counts did not drop.

  5. 5
    TLS presented

    The certificate for the hostname is served.

    fails by The hostname is not covered, or the certificate has expired (Certificates as an Operational Object).

    evidence A handshake from outside succeeds and the served certificate covers the name.

  6. 6
    Upstream selected

    The request is forwarded to a ready backend.

    fails by No ready backends → a proxy-generated error that looks like an application failure (Service Discovery in Operation).

    evidence A synthetic request through the public path returns the expected response.

Steps two and three are the shared ones. Everything else fails for one hostname; those two fail for everybody.

Edge errors are not application errors

The proxy generates its own responses when it cannot get a usable one from upstream, and each of them is a precise statement about which part of the path failed. Reading them correctly is the difference between investigating the edge and investigating a service that is fine.

Who produced this error?
TriggerSymptomCauseResponse
No rule matchedA not-found response with the proxy's own error bodyHostname or path does not match any rule, or the rule is in another namespaceCompare the request's host and path to the rendered configuration, not to the manifest
No ready upstreamA bad-gateway response, immediatelyThe service has no ready endpoints, or the connection was refusedCheck readiness and endpoint membership first (Probes: Readiness, Liveness and Startup)
Upstream too slowA gateway-timeout response after a fixed delayThe proxy's timeout elapsed; the application is still working on itCompare proxy and application timeouts — the shorter one wins and the other never finds out
Body too largeUploads rejected at a size the application supportsAn edge body-size limit the service owner never sawMake edge limits explicit per route (File Uploads Through the Backend in the backend view)
Rate limit at the edgeRejections invisible in application metricsTraffic never reached the serviceExport edge rejection metrics into the service's own dashboard
Overlapping path rulesOne service's request rate drops with no errors anywhereA new rule captured traffic by precedenceAlert on request-rate drops, not only on error rates (Alert on Symptoms, Not on Causes)
Controller not convergingRouting changes have no effect; existing traffic is fineA blocked render or a stalled controllerAlert on configuration convergence, not just on traffic
Controller saturatedLatency added to every service behind the edgeThe controller is a workload and it is out of resourcesGive it limits, headroom and replicas suited to a tier-one dependency (Requests and Limits)

One edge or several?

ORG-SPECIFICThe right answer depends on how many teams share the platform, whether there is a team that genuinely owns the edge, and what your isolation requirements actually are rather than what they are assumed to be.

The blast radius of the edge is decided by how many of them you run. This is a platform-level choice with a real cost either way, and it is usually made once by accident.

How many ingress points does the platform run?

Several teams need public routes. What is the topology?

One shared ingress

when Small number of teams, uniform requirements, and a platform team that owns it properly.

cost One blast radius for everyone: one render, one certificate set, one saturation point (Blast Radius: If This Is Wrong, How Much Does It Affect?).

One per tier or criticality

when You can name which services must not share a failure with which others.

cost More certificates, addresses and configurations, and a boundary that must be maintained as services change tier.

One per team

when Strong isolation requirements, or teams with genuinely divergent edge policy needs.

cost Multiplied operational surface and a real risk that each one is operated slightly differently (Environment Drift has the same shape).

Cloud load balancer per service

when Few services, or a strong preference for provider-managed components.

cost Direct cost per balancer and per certificate, and edge policy has to be reimplemented per service (Cost Drivers).

How to do it properly

Most important first.

  • Treat the ingress as a shared production component with an owner, a capacity plan, alerting and a change process — not as a config file several teams happen to edit (The Ownership Record).
  • Validate routing changes before they reach the controller. A rejected render blocks everyone, so this is a place where a pipeline check earns its cost immediately (Policy as Code).
  • Distinguish edge errors from application errors on the dashboard. Proxy-generated status codes and upstream-generated ones should be separate series, or every incident starts with the wrong hypothesis (Dashboards an Operator Can Act On).
  • Give the controller resources, probes and disruption budgets appropriate to a tier-one dependency, and more than one replica.
  • Make the edge's timeouts, body limits and buffering explicit and documented, because their defaults will otherwise be discovered during an incident (Runbooks).
  • Keep routing configuration in version control alongside the service it routes to, so a route change is reviewed with the change that needs it (Infrastructure as Code).
  • Know the failure mode you want when the edge is degraded: fail closed, serve a static page, or shed selectively (Load Shedding).

How much can this affect

Every production change has a blast radius. Stated as a scale so it is comparable between changes rather than adjectival — and paired with what actually contains it, because a wide scope with a real containment mechanism is a different situation from a wide scope with none.

Blast radius if this is wrongEveryone
One testEveryone
What contains it

The edge is the one component every request passes through: a bad render, an expired certificate or a saturated controller affects every service behind it at once. The partial containment is that the last good configuration keeps serving while a broken one fails to apply — traffic continues, changes stop. Real containment means separate ingresses per blast-radius boundary, which costs certificates, addresses and operational surface.

What can go wrong

Failure modes, including of the mitigation
  • One invalid rule blocking the render, so every team's routing changes silently stop being applied while the existing configuration keeps serving.
  • An overlapping path rule capturing another service's traffic, presenting to that service as a sudden drop in request rate rather than as an error.
  • Certificate expiry at the edge taking down every hostname simultaneously (Renewal: Automating the Thing That Expires).
  • The controller under-resourced and throttled or OOM-killed, so configuration stops converging and nobody connects the two (CPU Throttling: The Latency With No Error).
  • A proxy timeout shorter than a legitimate long request, terminating it at the edge while the application continues working on it obliviously.
  • A body size limit rejecting uploads that the application supports, reported to users as a generic error.
  • A reload disturbing long-lived connections, so frequent configuration changes produce a steady trickle of disconnections.
  • Rate limits configured at the edge with no visibility, so rejected traffic never appears in application metrics at all (Rate Limiting in the backend view).
Misreads this invites
  • "My rule only affects my service." It is rendered into a shared configuration and can block, capture or reorder other people's traffic.
  • "A 502 means the application is broken." It means the proxy could not get a usable response from an upstream — no ready backend, a refused connection, or a response it could not parse (Service Discovery in Operation).
  • "The ingress is infrastructure, so it is stable." It is a workload with limits, probes and a rollout, and it fails like one (Reading a Broken Workload).
  • "Rate limiting at the edge protects the service." It protects it from volume and hides that volume from the service's own metrics unless you export it deliberately.

Operating it

Evidence is the signal, not the intention. Rollback is sometimes 'you cannot, and that is the point'.

How you know it worked
  • Status codes split by whether the edge or the upstream produced them, which is the single most useful edge dashboard there is.
  • The controller's own health: replica count, restarts, resource usage and configuration-reload success.
  • Configuration convergence: the applied configuration matches the desired routing resources, and a failed render alerts rather than sitting in a log.
  • A synthetic request per hostname, from outside, exercising the full path including TLS (A Successful Deploy Is Not Evidence of a Healthy System).
  • Certificate expiry per hostname served by the edge, checked from outside (Certificates as an Operational Object).
How you get back
  • Routing resources are declarative, so reverting the resource reverts the route — as long as the controller is able to render, which is exactly what is in doubt when a bad rule is the problem.
  • When a render is blocked, the currently serving configuration keeps working. That is a genuinely helpful failure mode: traffic continues on the last good configuration while changes queue up unapplied.
  • Rolling back the ingress controller itself is a change to a shared component and affects every service at once — it deserves the caution of a platform change rather than the speed of a service one (Change Management).
What to automate, and what stays human
  • Automate validation of routing changes in the pipeline, including conflict detection against existing rules. This is the highest-value automation at the edge because it prevents the shared-failure case (Required Checks).
  • Automate certificate issuance and renewal for every hostname the edge serves (Renewal: Automating the Thing That Expires).
  • Keep changes to shared edge policy — global timeouts, rate limits, default behaviours — human and reviewed, because they alter behaviour for services whose owners are not in the room (Guardrails, Not Gates).
What this costs
  • One shared ingress is cheap, uniform and a single blast radius. Several ingresses isolate teams and multiply the certificates, addresses and configurations to operate.
  • Rich edge policy — retries, rate limits, header manipulation, authentication — centralises useful behaviour and moves logic away from the service that owns it, where it is harder to test and to find.
  • A cloud-managed edge removes operational work and reduces what you can inspect and change during an incident (Managed vs Self-Hosted has the same shape in the cloud view).

Where this applies

This domain is unusually tool- and organisation-dependent. These labels say what each claim is specific to, and what a different platform, provider or organisation does instead.

  • KUBERNETES-SPECIFICA controller reconciling routing resources into a proxy configuration is Kubernetes. On a VM fleet the equivalent is a load balancer's listener and rule set, changed through the cloud API — the same shared blast radius with a different change mechanism and usually stricter per-rule validation, since rules are applied individually rather than rendered together. A PaaS gives each service its own hostname and no shared edge configuration at all, which removes this failure class and most of the flexibility with it.
  • CLOUD-SPECIFICWhether the edge is a cloud load balancer, a self-operated proxy, or both in series changes who owns the certificate, where the timeouts live and what you can see. Two clusters with identical routing resources can behave differently because of what is in front of them.

Where the depth lives

This domain teaches delivery and operation, and hands the mechanism off to the domain that owns it.