Part IV · Chapter 6 · Several Services · lesson 3
Your diagram has four dependencies. You have nineteen
12 min
Builds on You promised three nines. That is nine hours a year
Part IV · Chapter 6 · Several Services · lesson 3
12 min
Builds on You promised three nines. That is nine hours a year
The diagram on the wiki has four boxes: the service, Postgres, Redis, and the payments provider. Last Tuesday the service would not start on new nodes. The cause was an expired intermediate certificate in the container registry's trust chain, which no running pod cared about and every new pod did. The registry is not on the diagram. Neither are the fourteen other things a pod touches before it answers a request.
Predict
Each of these produces a list from something the system already does, rather than from somebody remembering. That is the only property that matters: a list assembled from memory omits exactly the dependencies nobody thinks about.
One: the outbound connection census. Watch every remote endpoint one
instance talks to, for a full hour, so that periodic work is included. On a
node, ss -tnp on a loop into a file, deduplicated on the remote address, is
crude and sufficient. In a cluster with a mesh, the Envoy sidecar's access log
already has it and you can read it off. On AWS, VPC Flow Logs for the pod's
network interface give the same answer without touching the host. Every distinct
remote endpoint is a dependency, including the ones your code never mentions —
the telemetry collector, the token endpoint, the clock.
Two: the start-up census. Everything consumed between process launch and the
readiness probe passing. For a Kubernetes workload this is readable off the pod
spec rather than measured: every imagePullSecrets entry, every init container,
every envFrom and every volume mounted from a Secret or ConfigMap, plus
whatever the entrypoint fetches before it binds a port. These are the dependencies
that are invisible while the fleet is stable and mandatory the moment it is not.
Three: the deploy-time census. Every step that must succeed for a new version of the code to exist and reach a node. The CI runner, the registry, the artefact signing step, the Terraform state bucket, the admission webhook that mutates the pod. These do not break serving. They break your ability to change anything, which is a problem precisely when you most want to change something.
Four on the diagram, fifteen not. Each line is the dependency, the named thing that currently plays that role, and when it is needed.
That is fifteen, and it is a floor rather than a count. It leaves out the CNI plugin that allocates the pod an address, the clock the token validator trusts, the node's local image cache, the load balancer in front of the gateway, and the package index your build pulls from. Every one of those has taken somebody's service down.
The useful classification is not critical versus non-critical. It is when the dependency is needed, because that decides what its failure looks like and therefore what the mitigation is.
Needed per request. Failure is an outage now. These are the ones worth a fallback: a cached key set for the identity provider, last-known-good configuration on local disk, a resolver cache with a sane negative TTL.
Needed at start-up. Failure is a latent outage. Nothing happens, then a node is replaced or traffic rises, and the fleet cannot grow back. This class is the most dangerous because it hides: the registry broke at noon and the incident starts at the evening peak, by which time the two events look unrelated. Pin images by digest, keep an image cache on the node, and have one instance restart on purpose every day so that a broken start path fails while someone is awake.
Needed neither per request nor at start. Telemetry, mostly. The correct treatment is to make sure it cannot take you down: a log shipper that blocks the application when its backend is unreachable has converted an observability problem into an outage, and this is a configuration choice in Fluent Bit and in most collectors. Check which way yours is set before you need to know.
What would you do
Check
Run the outbound connection census and you may get a short list, because a sidecar proxy is terminating everything and the host only ever sees connections to the sidecar. That is not an absence of dependencies; it is the proxy doing its job.
In that case the census moves: the sidecar's own configuration is the list. For
Envoy, the set of configured clusters is the set of places it will send traffic,
and curl localhost:15000/clusters on a pod running Istio's sidecar dumps it.
This is a better list than the connection census produces, because it includes
destinations that happen to have had no traffic in your observation hour.
The general shape of the problem: every layer that abstracts the network also hides the dependency list, and the list then lives in that layer's configuration. Service discovery, a mesh, a connection pooler, an egress gateway. Find the layer, read its config, and the hour of packet watching becomes a cross-check rather than the method.
The classification above has a fourth case the three censuses handle badly, and it is the one this lesson stops being useful for.
A dependency that is needed neither per request nor at start-up but only to produce a new version — the CI runner, the artefact signing key, the Terraform state bucket, the registry you push to rather than pull from — will not appear in an hour of connection watching, because nothing in production talks to it. It will not appear in the pod spec either. Its failure mode is not an outage: the fleet keeps serving, indefinitely, exactly the version it has.
That makes it both less urgent and worse to discover, because you find out during an unrelated incident, at the moment you want to ship a fix. So the mitigation is a different kind: a documented path to produce and deploy an image without the usual pipeline, tested at least once, by somebody who was not the person who built it. Not a fallback, not a cache. A break-glass procedure, and a date on the last time it was exercised.