Capstone Part 3 — Write the On-Call Runbook
This is the third of six parts building one continuous project: checkout-api, the same 99.9%-availability service from Part 1 and Part 2, now gets somewhere real for its pages to go. Part 2 left both severity tiers — page and ticket — routed to a Python http.server stand-in on your own laptop, and explicitly promised that this page would replace it. Today you register checkout-api as a real PagerDuty service, close the single point of failure a lone escalation rung always has, swap the stand-in webhooks for a tested paging path, and write the runbook a bleary responder actually reads in the first five minutes: what to do before diagnosing anything, and documented steps for the two or three dependencies most likely to be the actual root cause when CheckoutErrorBudgetBurnFast fires.
Part 2 built a smoke detector and proved with your own eyes that it actually beeps. A smoke detector beeping into an empty hallway at 3 a.m. doesn't save anyone — someone has to be woken up, have a backup in case they don't wake up, and know the difference between "the toast burned" and "the wiring's on fire." Today you wire the detector to an actual phone with a real backup number, not just one name at the end of the chain; you write down what a groggy responder should do in the first five minutes before touching anything; and — since checkout-api only really has three ways to catch fire, the inventory service, the payment processor, or its own database — you write down what each of those three fires actually looks like and how to put it out.
Arriving: checkout running in Part 2's docker compose stack, four burn-rate alerts evaluating correctly against real scraped metrics, and both severities routed to a Python http.server stand-in on your own laptop. Leaving this page: checkout-api registered as a real PagerDuty service behind a tested three-level escalation policy — a primary schedule, a genuinely offset secondary schedule, and a named manager backstop — Alertmanager's page-oncall receiver pointed at a real routing key instead of localhost:5001, a written first-five-minutes checklist, three documented runbook entries for checkout-api's three dependencies, and every one of Part 2's four alert rules carrying a runbook_url annotation that actually resolves to a section on this page. Part 4 picks up exactly here and spends real minutes of the budget Part 1 calculated, on purpose, against the exact runbook you write today.
Where Part 3 picks up
☺ Like you're 10: Two severities that currently page and ticket into an empty laptop terminal — today they get a real destination, and the destination gets a name, a backup, and a script.
Nothing below invents new facts about checkout-api — it's entirely built from what Parts 1 and 2 already established. Keep this table in your head; every section on this page cites one row of it by name.
| Established fact | From | What Part 3 does with it |
|---|---|---|
| Availability SLO: 99.9% over a rolling 30-day window, 43.2-minute / 5,000-request error budget | Part 1 | Quoted, not re-derived — the runbook's escalation authority below is Part 1's policy verbatim |
| Three hard dependencies: the inventory service, the external payment processor, the order database | Part 1 | Each gets its own runbook entry — the three most likely reasons the alert below actually fired |
CheckoutErrorBudgetBurnFast / Moderate — severity: page | Part 2 | Routed to a real, tested PagerDuty escalation policy instead of localhost:5001 |
CheckoutErrorBudgetBurnSlow / Slowest — severity: ticket | Part 2 | Routed into #checkout-reliability instead of localhost:5002 — the same channel Part 1's early-warning trigger already posts to |
| Written error-budget policy: two triggers, a freeze/redirect/resume cycle, a named exception path | Part 1 | Its named approver (on-call IC or EM) becomes a concrete person — the escalation policy's own Level 3, built below |
Registering checkout-api as a PagerDuty service — and naming what's still missing
☺ Like you're 10: One name, one policy — but today's policy has only a single rung at the end before it gives up, and that's the exact gap this page exists to close.
This course's own PagerDuty page already walked through registering a checkout-api service — reuse its pagerduty_user "asha", its pagerduty_schedule "checkout_primary" (named checkout-primary-oncall, weekly rotation, America/New_York), its pagerduty_service "checkout" (PagerDuty display name checkout-api), and its pagerduty_service_integration against the Prometheus vendor, exactly as written there. One small reconciliation worth stating out loud: Part 2's container, metric labels, and alert names all say checkout; PagerDuty's own service record says checkout-api. That's not a naming inconsistency to fix — it's completely normal for a short container name and a formal service-catalog entry to differ, and the Terraform resource address (pagerduty_service.checkout) versus its name attribute ("checkout-api") is exactly where that gap is supposed to live.
What that page's escalation policy doesn't have is the real problem. Its two rules were: Level 1, the checkout_primary schedule, 15-minute delay; Level 2, a single user_reference called team_lead, full stop. That single named person at Level 2 — no schedule, no rotation, no backup of their own — is precisely the failure mode a production readiness review flags as blocking: a lone name at the end of a chain is a single point of failure wearing a policy's clothing. Closing it is this section's whole job.
Closing the gap: a genuine secondary schedule and a named backstop
☺ Like you're 10: Same two engineers, same seven-day rotation — just started a week later, so whoever's "secondary" this week is never the same tired person who's already "primary."
Add a second schedule built from the same two-person pool, with its rotation start offset by exactly one turn. With only two people cycling weekly, that single offset guarantees the secondary is always the person not currently primary — never the same human getting called twice:
# NEW — closes "no secondary rotation staffed": a real second schedule,
# not just a lone name sitting at the end of the chain.
resource "pagerduty_schedule" "checkout_secondary" {
name = "checkout-secondary-oncall"
time_zone = "America/New_York"
layer {
name = "Weekly rotation, offset one turn from primary"
start = "2026-01-12T09:00:00-05:00" # exactly 7 days after checkout_primary's start
rotation_virtual_start = "2026-01-12T09:00:00-05:00"
rotation_turn_length_seconds = 604800 # same 7-day cadence, same two people
users = [
pagerduty_user.asha.id,
pagerduty_user.marco.id,
]
}
}
resource "pagerduty_user" "priya" {
name = "Priya Shah"
email = "priya@acme.io"
role = "manager"
}
# Replaces the two-rule policy from the PagerDuty tool page wholesale —
# same resource address, three real rungs instead of two.
resource "pagerduty_escalation_policy" "checkout" {
name = "checkout-primary-ep"
num_loops = 1 # one real backstop beats two loops through a policy that used to dead-end
rule {
escalation_delay_in_minutes = 15
target {
type = "schedule_reference"
id = pagerduty_schedule.checkout_primary.id
}
}
rule {
escalation_delay_in_minutes = 15
target {
type = "schedule_reference"
id = pagerduty_schedule.checkout_secondary.id
}
}
rule {
escalation_delay_in_minutes = 5 # inert once num_loops = 1 — nothing follows this rule
target {
type = "user_reference"
id = pagerduty_user.priya.id
}
}
}num_loops drops from 2 to 1 on purpose: once the chain ends in a real human with manager authority instead of dead-ending, looping the whole three-rung chain a second time before anyone with actual decision power gets involved just delays real escalation by another half hour for no benefit. One clean pass through three real rungs beats two passes through a policy that used to have only two.
Swapping the stand-in webhooks for the real routing key
☺ Like you're 10: Only the receiver's address changes — the severity label that decides page versus ticket doesn't move at all, exactly like Part 2 promised it wouldn't.
The pagerduty_service_integration resource from the PagerDuty tool page mints a routing key the moment it's applied. Alertmanager's own YAML has no built-in environment-variable interpolation, and pasting a live routing key straight into a config file checked into Git is a bad habit worth not starting — so pull it into a file Alertmanager reads at startup instead, the same pattern the _file variants exist for on every Alertmanager receiver type that takes a secret:
mkdir -p alertmanager/secrets
terraform apply
terraform output -raw checkout_routing_key > alertmanager/secrets/checkout-routing-key
chmod 600 alertmanager/secrets/checkout-routing-keyMount that directory into the container — one line added to Part 2's docker-compose.yml, nothing else in that file changes:
alertmanager:
image: prom/alertmanager:v0.27.0
volumes:
- ./alertmanager:/etc/alertmanager
- ./alertmanager/secrets:/etc/alertmanager/secrets:ro # NEWThen replace both receivers in alertmanager/alertmanager.yml — page-oncall now points at PagerDuty for real, and file-ticket lands in the same #checkout-reliability Slack channel Part 1's error-budget policy already uses for its early-warning trigger, so the team has one place to watch every tier of degradation instead of two:
receivers:
- name: default
- name: page-oncall
pagerduty_configs:
- routing_key_file: /etc/alertmanager/secrets/checkout-routing-key
severity: critical
description: '{{ .CommonAnnotations.summary }}'
details:
alertname: '{{ .CommonLabels.alertname }}'
runbook: '{{ .CommonAnnotations.runbook_url }}'
- name: file-ticket
slack_configs:
- channel: "#checkout-reliability"
api_url_file: /etc/alertmanager/secrets/slack-webhook-url # your own incoming webhook, saved the same way
title: '{{ .CommonAnnotations.summary }}'
text: "ticket-tier burn — reviewed at next standup, not paged"curl -s -X POST http://localhost:9093/-/reload
# no restart needed — Alertmanager hot-reloads its own config fileVerify with a real drill, the same Events API v2 shape the PagerDuty page already showed, now pointed at your own service instead of a hypothetical one. Fire it, then deliberately don't acknowledge, so you actually watch the fifteen-minute fall-through instead of taking it on faith:
ROUTING_KEY=$(terraform output -raw checkout_routing_key)
curl -X POST https://events.pagerduty.com/v2/enqueue \
-H 'Content-Type: application/json' \
-d '{
"routing_key": "'"$ROUTING_KEY"'",
"event_action": "trigger",
"dedup_key": "checkout-api-runbook-drill",
"payload": {
"summary": "DRILL — verifying the escalation policy actually rings a phone",
"source": "manual-runbook-drill",
"severity": "critical"
}
}'
date -u # note when you fired it
# ...wait, without acknowledging, and confirm the fall-through:
curl -s "https://api.pagerduty.com/incidents?statuses[]=triggered" \
-H "Authorization: Token token=$PD_API_KEY" \
-H 'Accept: application/vnd.pagerduty+json;version=2' \
| jq '.incidents[] | {id, status, last_status_change_by}'
# resolve the drill once you've watched it fall through once
curl -X POST https://events.pagerduty.com/v2/enqueue \
-H 'Content-Type: application/json' \
-d '{"routing_key":"'"$ROUTING_KEY"'","event_action":"resolve","dedup_key":"checkout-api-runbook-drill"}'It's tempting to declare this section done the moment terraform apply succeeds — the plan looked clean, the resources exist, ship it. That proves the YAML is syntactically valid; it proves nothing about whether Asha's phone actually rings, whether the fifteen-minute timer actually fires, or whether Priya is really reachable as Level 3. Send the drill above and watch it fall through at least once, on purpose, before this policy goes anywhere near the real checkout-api production service — the exact gotcha the PagerDuty page warns about: an escalation policy is not configuration you set once and trust, it's the thing standing between an alert and a human, every single time.
The first five minutes
☺ Like you're 10: Stop the clock before you start diagnosing — acknowledging takes five seconds and buys you the entire fifteen-minute window to actually think instead of racing it.
This is the section every runbook_url below points to. Whatever fired, whatever the eventual root cause turns out to be, these steps come first, in this order, every time:
| When | Action | Why |
|---|---|---|
| T+0:00 | Acknowledge the page in the PagerDuty app | Stops the 15-minute escalation clock. Do this before opening any dashboard — an unacknowledged page keeps racing toward Marco whether you're looking at it or not |
| T+0:30 | Open the Grafana dashboard from Part 2: error ratio (all 7 windows), burn-rate gauge, budget-remaining stat, alert-state table | Sizes the blast radius in one glance — which alert is actually firing, how far over threshold, and how much of the 30-day budget is already spent |
| T+1:30 | Check whether checkout-api or any of its three dependencies had a deploy or config change in the last 60 minutes | A recent change correlating with the alert's onset is the single highest-value clue you have — check it before you check anything else |
| T+2:30 | Work the triage steps below to identify which of the three dependencies is involved | The alert itself can't tell you which of the three broke — see the next section for exactly how to find out |
| T+3:30 | Decide severity: full outage or critical-path failure (checkout itself down for most users) is SEV1 — declare an incident channel and hand off Incident Commander per incident management & on-call; a partial degradation with the SLO burning but checkout still working for most users is SEV2 | The severity matrix decides who gets pulled in and how loudly, before anyone has to argue about it mid-incident |
| T+5:00 | If none of the three known failure modes match and the budget is still burning, escalate on purpose rather than continuing to dig alone | Don't wait for the 15-minute auto-escalation clock to do this for you once you already know you need help — that's fifteen minutes of budget you didn't have to spend |
Triage: which of the three dependencies is actually on fire
☺ Like you're 10: checkout-api only calls three things — the alert that pages you can't tell you which one broke, so the runbook has to.
CheckoutErrorBudgetBurnFast and Moderate are deliberately symptom-based — they fire on checkout-api's own overall error ratio, exactly the way monitoring & observability says a page-worthy alert should, and exactly the way Part 2 built them: one number, agnostic to cause. That's correct design, but it means the alert itself never tells you which of the three dependencies from Part 1's schematic is actually responsible. A production checkout-api would label its outbound-call metric by dependency (http_client_requests_total{dependency="payment-processor",code=~"5.."}, and so on) precisely so this triage step is a single query instead of a guessing game — if your own deployment hasn't added that label yet, add it before you need it at 3 a.m., not during the incident. With it, isolating the culprit is one comparison:
sum by (dependency) (
rate(http_client_requests_total{service="checkout-api", code=~"5.."}[5m])
)
/
sum by (dependency) (
rate(http_client_requests_total{service="checkout-api"}[5m])
)
# whichever "dependency" label has the highest error ratio is where you start —
# if none of the three are elevated, the fault is likely inside checkout-api itselfWhichever dependency's ratio is elevated, jump straight to its entry below — don't read all three in order under pressure.
Runbook entry — the external payment processor
Symptom: error ratio elevated specifically on the payment-processor dependency label; POST /v1/checkout requests hang until timeout or come back 5xx right after the card-authorization call.
Diagnose:
# p99 latency to the payment processor specifically
curl -s "http://localhost:9090/api/v1/query?query=histogram_quantile(0.99,sum(rate(http_client_request_duration_seconds_bucket{service=%22checkout-api%22,dependency=%22payment-processor%22}[5m]))by(le))" | jq '.data.result[0].value[1]'
# check the payment processor's own published status page first —
# this is an external vendor; you cannot fix their outage from here
# pull recent traces to confirm the failure is isolated to this one call
curl -s "localhost:16686/api/traces?service=checkout-api&tags=%7B%22error%22%3A%22true%22%7D&minDuration=2s" | jq .Mitigate: if the vendor's status page confirms a full outage, this is SEV1 — checkout has no fallback path for an authorization call, so declare the incident and communicate rather than continuing to dig. If it's degraded rather than fully down (elevated latency, intermittent timeouts), do not blindly retry failed authorization attempts — a retry that lands after the original actually succeeded is exactly the duplicate-charge scenario Part 1's zero-tolerance invariant covers, and that invariant overrides this runbook's normal escalation entirely, regardless of remaining error budget.
Escalate: loop in whoever owns the vendor relationship for a formal status inquiry if the outage passes 10 minutes — this is outside the team's direct control, so pulling in help earlier than usual is the right call, not a sign you're stuck.
Runbook entry — the inventory service
Symptom: error ratio elevated on the inventory-service dependency label; failures cluster on cart validation, before the payment call is ever reached.
Diagnose:
curl -s "http://localhost:9090/api/v1/query?query=sum(rate(http_client_requests_total{service=%22checkout-api%22,dependency=%22inventory-service%22,code=~%225..%22}[5m]))" | jq .
# check the inventory service's own dashboard / on-call channel —
# is this checkout-api-specific, or is inventory-service down for everyone?Mitigate: unlike the payment processor, skipping the inventory check is technically possible — but it trades a hard failure for a soft one: overselling a sold-out item. That trade is a business decision, not an engineering one, and this runbook does not authorize it unilaterally. Flag it to the Incident Commander (or Priya, if the incident has reached Level 3) and get explicit sign-off before bypassing inventory validation, the same named-approver discipline Part 1's exception path already requires for anything shipped during a freeze.
Escalate: page inventory-service's own on-call rotation directly rather than waiting for them to notice their own dashboards — a dependency failing silently for hours because nobody told the owning team is a preventable delay, not bad luck.
Runbook entry — checkout-api's own order database
Symptom: error ratio elevated with no dependency label showing a spike — both the payment processor and inventory service read clean. This is the internal case: the fault is inside checkout-api or its own datastore.
Diagnose:
kubectl -n checkout exec -it deploy/checkout-api -- \
psql "$DATABASE_URL" -c "SELECT count(*) FROM pg_stat_activity WHERE state != 'idle';"
# find the specific query holding connections open
kubectl -n checkout exec -it deploy/checkout-api -- \
psql "$DATABASE_URL" -c "SELECT pid, now() - query_start AS duration, state, query FROM pg_stat_activity ORDER BY duration DESC LIMIT 5;"Mitigate, in order of speed: (1) if the deploy check from the first-five-minutes table found a recent change, roll it back first — a bad query shipped an hour ago is faster to undo than to fix live; (2) if a single long-running query is holding the pool, terminate it: SELECT pg_terminate_backend(pid); for the offending pid from the query above; (3) if the pool is simply undersized for current traffic with no single culprit query, bump the pool size — treat this as an emergency change requiring the same sign-off as any other production change, not a quiet edit.
Escalate: pull in whoever owns the order database if none of the above recovers it within about 10 minutes — a saturated connection pool with no obvious single cause is exactly the kind of problem that benefits from a second set of eyes on replication lag, disk I/O, or a query plan regression you haven't thought to check.
Escalation authority: Part 1's error-budget policy, quoted verbatim
☺ Like you're 10: This runbook doesn't get to invent its own rules about when to freeze shipping or who's allowed to override that — Part 1 already wrote that rule down, before anyone was under pressure, and this page just quotes it.
Per Part 1's explicit promise — "Part 3 quotes this page's policy trigger and exception path verbatim as the runbook's escalation section" — here it is, unedited:
TRIGGER 1 — early warning
≤25% of budget remains (≤10.8 min / ≤1,250 failed requests left)
with more than 5 days remaining in the window.
-> Post a heads-up in #checkout-reliability. No freeze yet.
TRIGGER 2 — freeze
Budget fully consumed (0 minutes / 0 requests remaining)
before the 30-day window closes.
-> FREEZE: no new checkout-api feature launches, no non-critical
dependency upgrades, no checkout-flow experiments.
-> REDIRECT: the owning team's next sprint goes to reliability
work tied to whatever actually burned the budget — not
general cleanup chosen after the fact.
-> RESUME: automatically, once the rolling 30-day window has
aged the bad minutes out and the trailing budget is > 0 again.
EXCEPTION PATH
A change may still ship during a freeze only with written
sign-off from the on-call incident commander or the team's EM,
logged in the incident channel, and limited to: security
patches, active-incident mitigations, legally mandated fixes.That exception path names a role, not a person — "the on-call incident commander or the team's EM." This page is what makes that role concrete: whoever holds Level 3 of the escalation policy above is the EM this policy refers to. Priya Shah isn't just the final rung a page falls through to when nobody else answers — she's the specific, named human this course's own error-budget policy already designated as the exception path's approver. The org chart and the PagerDuty config agree with each other on purpose, not by coincidence.
Wiring runbook_url into Part 2's alert rules
☺ Like you're 10: An alert that pages you without telling you where the instructions live just traded one problem for a worse one — a phone ringing with no next step attached.
Add one annotation line to each of Part 2's four alert rules in prometheus/checkout-burn-rate.rules.yml — the query, the for:, and the severity label don't change at all:
- alert: CheckoutErrorBudgetBurnFast
expr: |
sre:checkout_requests:error_ratio1h > (14.4 * 0.001)
and
sre:checkout_requests:error_ratio5m > (14.4 * 0.001)
for: 2m
labels: { severity: page }
annotations:
summary: "checkout burning error budget at 14.4x — 2% of the 30-day budget in 1h"
runbook_url: "https://runbooks.acme.io/checkout-api#first-five-minutes" # NEWThe same runbook_url goes on all four alerts, page and ticket tier alike, and it deliberately points at the top-level first-five-minutes section rather than any single dependency entry — the alert can't tell you which of the three dependencies is involved, so its runbook_url shouldn't pretend to guess either. The triage step you just read is what routes a responder to the payment-processor entry, the inventory entry, or the database entry from there. Reload the same way Part 2 already showed:
curl -s -X POST http://localhost:9090/-/reload
curl -s http://localhost:9090/api/v1/rules | jq '.data.groups[].rules[] | select(.type=="alerting") | {name, annotations}'
# every one of the four alerts should now show a runbook_url in its annotationsThis is also the concrete artifact behind one line of the on-call readiness checklist: "every alert has been tested for actionability: it links to a runbook, and a recent audit confirms it isn't firing without a required response." checkout-api can now check that box honestly, with a URL that resolves to real content instead of a promise to write one someday.
What "done" looks like for Part 3
☺ Like you're 10: A phone that actually rings, a backup that actually answers if it doesn't, and a page that finally tells the person holding it what to do next.
At the end of this part: checkout-api is a real PagerDuty service behind a three-level escalation policy you have personally watched fall through from Asha to Marco under a real drill event; Alertmanager's page-oncall and file-ticket receivers point at a real routing key and a real Slack channel instead of two localhost stand-ins; a first-five-minutes checklist exists that starts with "acknowledge" and ends with "escalate on purpose if the clock is about to do it for you"; three runbook entries document the payment processor, the inventory service, and the order database — the three dependencies Part 1 named as where Part 4's incident and Part 5's capacity plan will both trace back to; and every one of Part 2's four alert rules carries a runbook_url that resolves to real content. Nothing here gets thrown away:
| Part | What it does with today's artifacts |
|---|---|
| 4 — Run the Incident & Write the Postmortem | Runs a real incident against one of today's three runbook entries and grades the response against the first-five-minutes checklist above |
| 5 — Capacity Plan & Load Test | Load-tests the order database specifically, the dependency today's third runbook entry already flags as an internal saturation risk |
| 6 — Chaos Engineer It | Deliberately breaks one of the three dependencies documented today and checks whether the escalation policy and runbook entry actually hold up live |
Timmy the Turtle: Before this policy goes anywhere near production — has anyone actually watched it fall through to the second name, or are we trusting the YAML?
Pip the Hummingbird: Fired a real drill ten minutes ago. Rang Asha first — she didn't touch it, on purpose — and it reached Marco on the secondary schedule at fifteen minutes flat.
Foxy: And if Marco doesn't answer either?
Pip the Hummingbird: Priya's next — and last. One clean pass this time, with a real person with real authority at the end of it, instead of the chain just... stopping.
Timmy the Turtle: Good. A policy nobody's tested is just an opinion with YAML syntax.
Professor Owl: And the three entries underneath it — payment processor, inventory, database — are what turn "someone's phone is ringing" into "someone knows exactly what to do next." The policy gets you a human. The runbook is what that human reads the moment they pick up.
1. Why does the new three-level escalation policy set num_loops to 1 instead of the 2 used in the earlier two-rule version, and what specifically closes the single point of failure the old version had? 2. What's the very first action a responder should take once a page fires, before opening any dashboard, and why does it matter given the policy's 15-minute delay between rungs? 3. Name checkout-api's three dependencies from this page's triage section, and for one of them, state the specific manual mitigation the matching runbook entry prescribes. 4. Where does Part 1's error-budget policy show up on this page, and who does its exception path's named approver turn out to be, concretely?
Check your answers
- The old two-rule policy dead-ended at a single named person with no schedule of their own — a lone rung is a single point of failure by definition. The new version adds a genuine second schedule (offset one turn from primary, so it's never the same person as primary that week) as Level 2, with a real manager as Level 3.
num_loopsdrops to 1 because looping a chain that now ends in a real human with authority a second time before reaching them just delays real escalation for no benefit. - Acknowledge the page immediately, before doing anything else. The escalation policy's 15-minute delay keeps running whether or not you're actively looking at it — acknowledging is what stops the clock from paging the secondary schedule while you're still just opening a dashboard.
- The external payment processor, the inventory service, and checkout-api's own order database. For the payment processor: since it's an external vendor with no fallback path, do not blindly retry failed authorization attempts, because a retry landing after the original actually succeeded risks a duplicate charge — Part 1's zero-tolerance invariant, which overrides this runbook's normal handling entirely.
- Quoted verbatim in the "Escalation authority" section, unedited from Part 1. Its exception-path approver — "the on-call incident commander or the team's EM" — is concretely Priya Shah, the same person sitting at Level 3 of the escalation policy built earlier on this page; the org chart and the PagerDuty config were deliberately made to agree.
Part 3 gave checkout-api's page somewhere real to go: a tested escalation policy with a genuine backup, a first-five-minutes checklist, and three runbook entries that turn "the phone is ringing" into "here's exactly what to check first." Continue to Capstone Part 4 — Run the Incident & Write the Postmortem, where this exact runbook gets used for real against a scenario that spends real minutes of Part 1's budget. Or step back to Run a Reliable Service — start here for the full six-part map, revisit incident management & on-call and PagerDuty for the concepts behind what you built today, or get a faster, standalone rep of the escalation-design instinct with Drill — Incident Response Tabletop.