Skip to main content
RunBook Academy

← All break/fix scenarios in Observability

advancedobservability-of-observability~45 min

Break/Fix: Monitoring System Fails During Business Outage

Reported symptoms

  • ●Customers cannot complete checkout from 21:14 on a Friday; a Sev1 is declared at 21:19
  • ●The pinned Sev1 dashboard will not load; Grafana returns 502 from every on-call laptop
  • ●The documented fallback, a replica Prometheus, times out from the same laptops
  • ●The pre-exported dashboard JSON lives on the team file share, and the share will not mount either
  • ●Pages are arriving normally throughout, so the alerting path is demonstrably healthy
  • ●Two engineers who happen to be on the office network can reach Grafana; three on home broadband cannot
  • ●Blackbox probes for the checkout endpoint report success for the entire incident
  • ●The checkout service reports healthy from inside the cluster, with normal request rate, error rate and latency
  • ●A checkout deploy from 20:52 is rolled back at 21:56 and nothing changes

Evidence

  • · A curl to the primary Prometheus API times out from an on-call laptop and returns 200 from a jump host inside the perimeter
  • · Grafana returns 502 from outside and 200 from inside, for the same URL path
  • · The checkout health endpoint returns 200 from inside the cluster and up{job="checkout"} is 1 for every instance
  • · probe_success for the checkout blackbox job is 1 throughout, and the blackbox exporter runs inside the perimeter
  • · The replica Prometheus resolves to an address behind the same edge tier as the primary
  • · The team file share is published through the same edge tier
  • · A change to the shared edge tier was applied at 21:09, five minutes before the first customer report
  • · The paper runbook in the on-call bag carries a print date eleven months old, and two of its three contact numbers are wrong
  • · No meta alert fired about the platform, because meta alerts route through the production Alertmanager
Diagnosis and resolutionclick to reveal

Root cause

There is one fault, not two. A single shared edge tier fronts both the customer path to checkout and every human entry point into the observability platform, and a change applied to it at 21:09 broke inbound routing. Checkout itself was healthy for the whole incident and so was the observability stack; what failed was the path from a person on the outside to either of them. The reason this took ninety minutes rather than ten is that the response was organised to treat the platform outage as an obstacle rather than as evidence. Two services with no code in common, no shared database and no shared team failed in the same minute, and the only thing they share is the edge. That co-occurrence was the strongest signal available and it was in the room from 21:19. Every documented fallback failed for the same reason it was supposed to protect against: the replica Prometheus sits behind the same edge, the exported dashboard JSON sits on a file share published through the same edge, the direct API path terminates at the same edge, and the paper runbook was eleven months stale. The fallback pack was assembled as a list of artefacts rather than as a set of independent paths, and it had never been rehearsed, so nobody discovered the shared dependency until it mattered. The blackbox probes made it worse by being confidently wrong: they run inside the perimeter, so they measured a path no customer uses and reported success throughout, which is what gave the team the confidence to spend forty minutes rolling back an unrelated deploy.

Remediation

The first move is to name the meta-incident out loud and give it a separate owner, because the alternative is the whole response team debugging Grafana while nobody works the customer problem. The incident commander keeps the business incident; one named person owns restoring a read path and reports back on a fixed cadence. The second move is to stop treating the two outages as unrelated and ask the question their co-occurrence poses: what do a payment service and a dashboard server have in common. On this platform the answer is one hop, and that answer arrives faster than any dashboard would have. Meanwhile get a read path that does not cross the broken hop rather than working through the fallback list in order, since every item on that list crosses it. A jump host inside the perimeter gives direct access to the Prometheus and Alertmanager APIs, which is enough to answer the questions the dashboard would have answered, and the answers arrive as text rather than as panels. Appoint a scribe and keep the evidence ledger by hand: the usual tooling for it is behind the same edge, and a timeline reconstructed afterwards from memory is not a timeline. Resist rolling back application deploys to see if it helps. That is a change made without a hypothesis during an incident where the observable signals are already unreliable, and its main effect is to add a variable. If restoring the edge requires a change window that is not open, hold deliberately: name an owner and an end time, state that customers cannot reach checkout although checkout is healthy, and say which signals the team is currently unable to see.

Verification

Verify from where the customer is, because every check that ran inside the perimeter was green throughout this incident and none of them was about the customer. Confirm checkout completes end to end from an external vantage point, on a network that is not the office network, before declaring the business incident resolved. Confirm the platform separately and from the same outside position: Grafana loads, the primary Prometheus API answers, the replica answers, and the file share mounts. Then verify the thing the incident actually exposed, which is the fallback pack. Take each of the four documented fallbacks in turn and establish which network path it depends on; a fallback that shares a hop with the primary is not a fallback and should be struck from the document rather than left in it. Confirm the blackbox probe now runs from outside the perimeter by breaking the edge in a staging environment and watching probe_success go to zero, since a probe that cannot fail has never been verified. Confirm a meta alert reaches the on-call rotation with the production Alertmanager deliberately stopped. And close the loop on the response itself: the incident record should show the time the meta-incident was named and the time a read path was restored, because those two numbers are what the next drill is trying to reduce.

Prevention

Put the observability platform on a different path from the product it watches. Sharing an edge tier is the cheapest possible way to guarantee that the platform is unavailable exactly when it is needed, and no amount of redundancy inside the platform compensates for a single shared hop in front of it. Design the fallback pack as independent paths rather than as a list of artefacts, and record next to each one which path it uses: a replica on a separate network, a dashboard snapshot on the device the engineer is physically holding rather than on a file share, an API token that works against something reachable, and a printed runbook with a print date on the inside cover. Rehearse it quarterly as a game day, because an unrehearsed fallback is a document rather than a capability, and the rehearsal is the only thing that finds a shared dependency before an incident does. Probe from outside the perimeter; an internal blackbox probe measures a path no customer uses and will report success through a total customer outage. Route meta alerts through a separate Alertmanager on a separate channel, so a platform outage can page even when the production alerting path is part of the outage. And write down the reasoning this incident should have used: when two unrelated services fail in the same minute, enumerate what they share before investigating either one.

Reported symptoms

21:14 on a Friday. Customer support takes three calls in four minutes: checkout fails at the payment step. The checkout error-rate alert pages at 21:16. A Sev1 is declared at 21:19 and an incident commander is appointed.

The IC opens the pinned Sev1 dashboard. It does not load. Grafana returns 502.

The fallbacks do not work either. The runbook lists four, and the response team works through them in order over the next eleven minutes. The replica Prometheus, which exists precisely for this, times out. The pre-exported dashboard JSON is on the team file share, and the share will not mount. The read-only API token is valid, and a curl straight at the primary Prometheus times out too. The paper runbook is in the on-call bag; its print date is eleven months ago and two of its three contact numbers ring out.

The alerting path is fine. Pages keep arriving throughout, on time, correctly grouped. Whatever is wrong, it is not Alertmanager, and that fact makes the platform outage feel local and unimportant.

Some people can see, and some cannot. By 21:30 somebody notices that the two engineers who happen to be at the office can load Grafana. The three at home cannot. This is filed as a VPN problem.

Everything inside says checkout is healthy. An engineer with cluster access checks: the checkout service returns 200 on its health endpoint, up is 1 for every instance, and its request rate, error rate and latency are all normal. The blackbox probes for the checkout endpoint report success for the entire incident.

At 21:56, in the absence of anything else to try, the checkout deploy from 20:52 is rolled back. Customers still cannot check out. At 22:04 the team begins looking at the payment provider.

Evidence provided

The first useful measurement is the same request made from two places. Everything the team had tried so far was from one side of the perimeter.

Read-only / Safesame service, same path, two vantage points
$ curl -s -o /dev/null -w 'laptop grafana %{http_code} in %{time_total}s\n' \
--max-time 10 https://grafana.example.com/api/health
ssh jump-01.internal.example.com \
"curl -s -o /dev/null -w 'jumphost grafana %{http_code} in %{time_total}s\n' \
 --max-time 10 http://grafana.internal.example.com:3000/api/health"
laptop grafana 502 in 0.312s
jumphost grafana 200 in 0.041s

Illustrative output

The primary Prometheus behaves identically, and so does the replica that was supposed to be the answer when the primary does not.

Read-only / Safethe fallback resolves to the same address as the primary
$ for host in prometheus.example.com prometheus-replica.example.com; do
printf '%s ' "$host"
curl -s -o /dev/null -w '%{http_code}\n' --max-time 10 \
  "https://$host/api/v1/query?query=up" || echo timeout
done
dig +short prometheus.example.com prometheus-replica.example.com
prometheus.example.com 000
prometheus-replica.example.com 000
203.0.113.40
203.0.113.40

Illustrative output

From inside, the application is untouched. This is the evidence that made the team look at the payment provider, and read correctly it points the other way.

Read-only / Safecheckout is serving normally to everyone who reaches it
$ ssh jump-01.internal.example.com "curl -sG \
http://prometheus.internal.example.com:9090/api/v1/query \
--data-urlencode 'query=sum by (code) (rate(http_requests_total{job=\"checkout\"}[5m]))' \
| jq -r '.data.result[] | .metric.code + \" \" + .value[1]'"
200 41.882
500 0.017

Illustrative output

And the synthetic check that exists to catch exactly this situation reported success throughout.

Read-only / Safethe probe passes, and it runs inside the perimeter
$ ssh jump-01.internal.example.com "curl -sG \
http://prometheus.internal.example.com:9090/api/v1/query \
--data-urlencode 'query=probe_success{job=\"blackbox_checkout\"}' \
| jq -r '.data.result[] | .metric.instance + \" \" + .value[1]'"
kubectl -n monitoring get pods -l app=blackbox-exporter -o wide
https://checkout.example.com/health 1
NAME                       READY   STATUS    NODE
blackbox-exporter-0        1/1     Running   node-04.internal

Illustrative output

Two facts complete the picture. The team file share is published through the same edge tier as Grafana and the Prometheus endpoints. And the change calendar records a change to that edge tier applied at 21:09, five minutes before the first customer call.

Work the evidence before reading on

Two services are broken. They share no code, no database and no team.

  1. Every check that runs from inside the perimeter is green, and every check that runs from outside it fails. Before naming a component, what is the smallest statement that covers both results?
  2. The replica Prometheus was written into the runbook as the fallback for the primary being unavailable. It resolves to the same address. What was the fallback actually protecting against, and what did the author believe it protected against?
  3. probe_success is 1 for the checkout endpoint while no customer can complete a checkout. The probe is not lying. What is it measuring, and what would have had to be true about the probe for its answer to be about customers?
  4. Pages arrived normally all evening. Which direction does a notification travel, and which direction does a dashboard request travel?

Before continuing: checkout and the observability platform failed within the same minute. List everything they have in common. If that list has one entry, what is the correct next action, and why is it not opening a dashboard?

Root cause

1. One fault, two outages, one shared hop

Checkout was healthy for the entire incident. So was Grafana, so was Prometheus, so was the replica. Nothing in either system malfunctioned.

A single shared edge tier fronts the customer path to checkout and every human entry point into the observability platform: Grafana, the primary Prometheus, the replica Prometheus and the team file share. The change applied to that tier at 21:09 broke inbound routing through it. From outside, everything behind it became unreachable. From inside, nothing changed at all.

That is the whole fault, and it explains every symptom without needing a second cause. The office engineers could load Grafana because their traffic reaches the internal network without crossing the edge. Pages continued to arrive because notifications travel outbound and never traverse the inbound path. The application metrics were normal because the application was normal. Rolling back the 20:52 deploy changed nothing because that deploy was never involved.

2. The co-occurrence was the diagnosis and it was treated as an obstacle

Two services with nothing in common failed in the same minute. On this platform they share exactly one thing.

That inference was available at 21:19, from information already in the room, and it requires no telemetry whatsoever. It was not made because of how the failure was framed: the platform being down registered as an impediment to investigating checkout rather than as a fact about checkout. The response spent its first forty minutes trying to restore a view of a service that was fine, and its next twenty rolling back a deploy chosen because it was the most recent change anyone could see — while the change that mattered was in a calendar nobody thought to open, because the edge tier was not on anyone’s mental list of checkout dependencies.

This is the specific failure of judgement the scenario exists to train. When the monitoring platform fails at the same moment as the thing it monitors, that is not bad luck. It is a measurement, and it is usually the most precise one available, because shared failure implies shared dependency and the list of shared dependencies is short enough to enumerate on a whiteboard.

3. Every fallback shared the dependency it was meant to survive

The runbook documented four fallbacks and all four failed for the same reason.

The replica Prometheus was built to answer when the primary does not. It sits behind the same edge and resolves to the same address, so it can only answer when the primary is broken and the edge is not — which is a much narrower class of failure than the document implies, and not the class that occurred.

The exported dashboard JSON was on the team file share, published through the same edge. The direct API path terminated at the same edge. The paper runbook, the one fallback with no network dependency at all, was eleven months old and had drifted: two of three contact numbers were wrong.

None of this was ever tested. The pack was assembled as a list of artefacts — a replica exists, a snapshot exists, a token exists, a printout exists — rather than as a set of paths, and nobody had ever asked which network each one crosses. A fallback that has never been exercised is a design intention, and this incident is the first and only test it was given.

Resolution

  1. Name the meta-incident explicitly and give it a separate owner. The IC keeps the business incident; one named person owns restoring a read path and reports on a fixed cadence. Without that split, the entire response team debugs Grafana and nobody works the customer problem.
  2. Ask what the two failing services share, before opening anything else. On this platform the list has one entry, and reaching it takes a minute. Do this even when the dashboard is available, because it is faster than a dashboard when it works.
  3. Get a read path that avoids the broken hop rather than working down the fallback list in order. A jump host inside the perimeter reaches the Prometheus and Alertmanager APIs directly, and text answers to the right questions beat panels you cannot load.
  4. Establish the direction of the fault from two vantage points. The same request from inside and from outside, against the application and against the platform, converts a confused incident into a one-sentence statement about a hop.
  5. Take the edge change from 21:09 to the team that owns that tier, with the two-vantage-point evidence attached. Diagnosing what the change did is their work, not the response team work, and handing it over with evidence is faster than investigating it without access.
  6. Appoint a scribe and keep the evidence ledger by hand. The usual tooling for it is behind the same edge, and a timeline reconstructed from memory afterwards is not a timeline.
  7. Do not roll back application deploys speculatively. It is a change without a hypothesis in a system you cannot currently observe, and its main effect is to add a variable.
  8. Tell customer support and the status page what is true: the service is healthy and the path to it is broken. That is a different message, a different expected duration, and a different set of questions from the ones support are currently answering.

Verification

  1. Checkout completes end to end from an external vantage point, on a network that is not the office network. Every internal check was green throughout this incident and not one of them was about a customer.
  2. The platform is reachable from that same outside position: Grafana loads, the primary Prometheus answers, the replica answers, and the file share mounts. Verify these separately from the business path — they failed together, but they are not guaranteed to recover together.
  3. Each of the four documented fallbacks has a named network path written next to it. Any fallback that shares a hop with the primary is struck from the document rather than left in it with a caveat.
  4. The blackbox probe now runs from outside the perimeter, and it can fail. Break the edge in staging and confirm probe_success goes to zero. A probe that has only ever returned 1 has never been tested.
  5. A meta alert reaches the on-call rotation with the production Alertmanager deliberately stopped. If it does not, the meta alert path shares fate with the thing it is meant to report on.
  6. The paper runbook has a print date inside the cover within the last quarter, and every contact number in it has been dialled.
  7. The incident record shows two timestamps: when the meta-incident was named, and when a read path was restored. Those are the numbers the next drill is trying to reduce, and they cannot be recovered later.

Prevention

  • Put the observability platform on a different path from the product it watches. A shared edge tier is the cheapest possible guarantee that the platform will be unavailable exactly when it is needed, and no amount of redundancy behind that tier compensates for one shared hop in front of it.

  • Design the fallback pack as independent paths, not as a list of artefacts, and record the path next to each entry. A replica on a separate network. A dashboard snapshot on the device the engineer is physically holding rather than on a file share. A token that works against something reachable. A printed runbook with a print date on the inside cover.

  • Rehearse it quarterly. The rehearsal is the only thing that finds a shared dependency before an incident does, and it is cheap: block the edge in a drill and time how long the team takes to reach a read path. An unrehearsed fallback is a document, not a capability.

  • Probe from outside the perimeter, from more than one network. An internal probe measures a route no customer uses and will report success through a complete customer outage, which is worse than having no probe at all because it is trusted.

  • Route meta alerts through a separate Alertmanager on a separate channel. A platform outage has to be able to page even when the production alerting path is part of the outage, and the two paths should meet only at the notification provider.

  • Write the reasoning into the incident process itself: when two unrelated services fail in the same minute, enumerate what they share before investigating either one. It costs a minute, it needs no tooling, and on this evening it was worth ninety.