Laptop to Production

Real incidents

Every failure from this course, and what it taught.

Time
45 minutes
Level
Intermediate

Part 1: The problem

QuickCart, the meeting after

After the long rollback, the team met to talk about it. It went badly. Someone asked who had shipped the broken release. Someone else asked why Noor hadn’t called Sam sooner. Noor said very little.

Two weeks later, there was another incident. Afterwards, it turned out that Leo had changed a setting by hand an hour before it started. He hadn’t mentioned it during the incident. After the last meeting, he wasn’t going to be the next person in the spotlight. Nobody learned anything, and the same thing happened again a month later.

Incidents are expensive. The only way to get something back for the cost is to learn from them — which only works if people can say what really happened.

Part 2: The idea

This course’s projects were built for real, and things broke along the way. Every failure below actually happened during those builds. Names are left out; the details are real. Almost none of them were code: they were a missing rule, a port number, a typo. That’s where most outages come from.

What went wrong while building the course

1. New servers never became healthy

StageWhat happened
SymptomThe auto scaling group launched servers, every one failed its health checks, and the group replaced them in a loop
InvestigationSession Manager couldn’t connect either, so there was no way in. The instance’s system log (Actions → Monitor and troubleshoot → Get system log) showed the package update hanging, and the Session Manager agent timing out while connecting on port 443
CauseThe app’s security group had no outbound rules. Replies to incoming requests are allowed automatically, but connections the server starts need an outbound rule — and a booting server starts lots of them
FixAdd an outbound rule. The next servers booted and became healthy
LessonTimed out means the network. When you can’t get in, read the system log: AWS fetches it without any network access to the server
Covered inSecurity groups

2. The same error, two different causes

StageWhat happened
SymptomCustomers got {"error":"database_unavailable"} from the orders page. It happened twice, a day apart
InvestigationThe response was identical both times. Only the structured logs differed: the first time error_type was OperationalError at about 3,000 ms; the second time ProgrammingError in a few ms
CauseFirst, the database’s security group had lost its rule for port 3306. Second, the orders table didn’t exist in the new database
FixPut the rule back; run the missing migration
LessonA friendly error message hides the cause. Log the real error type and timing as separate fields
Covered inLogs and metrics

3. The health check that kept the site up

StageWhat happened
SymptomWith the database unreachable, the orders page returned 500 — but the health check path kept returning 200
InvestigationEvery server stayed in the load balancer, so pages that didn’t need the database still worked
CauseNot a failure: a design decision proving itself. The health check was deliberately shallow: it only checks the app is running, not the database
FixNone needed. A deep health check would have failed every server at once, and turned a database problem into a total outage
LessonHealth checks decide who gets traffic. Don’t let one dependency’s failure remove every server
Covered inLoad balancers

4. The alarm test that proved the whole chain

StageWhat happened
SymptomThe database’s security group rule was removed on purpose, as a test
InvestigationWithin a couple of minutes the error alarm went to ALARM, and the email arrived
CauseNot a failure: this is what an alarm that works looks like. The common failure here is the opposite: an SNS email subscription left Pending confirmation, so the alarm fires and nobody hears
FixNone needed. The subscription had been confirmed and checked
LessonAn alarm you haven’t seen fire is a guess. Test it for real
Covered inAlarms that actually fire

5. Container tasks restarting, “Request timed out”

StageWhat happened
SymptomThe container service never settled: tasks started, failed their health checks and were replaced. The deployment circuit breaker rolled the release back
InvestigationThe app’s own logs showed it started and was listening on 8080. The target health reason was Request timed out — so the traffic never arrived
CauseTwo stacked mistakes. First, the tasks had the load balancer’s security group attached instead of the app’s. After fixing that, the app’s security group rule turned out to allow port 0 instead of 8080
FixAttach the app’s group; allow 8080 from the load balancer’s group. Tasks were healthy in about 20 seconds
LessonAfter each fix, test again: one fix can uncover the next problem
Covered inSecurity groups, ECS on Fargate

6. The load balancer wouldn’t accept the default VPC

StageWhat happened
SymptomCreating an internet-facing load balancer, the console wouldn’t let the default VPC be used
InvestigationThe VPC had no internet gateway attached — it had been removed at some point
CauseAn internet-facing load balancer needs its subnets to route to an internet gateway
FixCreate an internet gateway, attach it, and add a 0.0.0.0/0 route to it
LessonReachable from the internet always comes down to one route
Covered inRoutes, gateways and NAT

7. The image scan said “not found”

StageWhat happened
SymptomAfter pushing v1 to ECR, the repository showed an Image Index with extra untagged rows, and the scan column had no result for the tagged row
InvestigationDocker’s builder had pushed a list — the real image plus build information — rather than one image
CauseThe tagged row was the list, which isn’t scanned. The actual image was one of the untagged rows
FixLook at the untagged image’s scan. Next time, build with --provenance=false so only the image is pushed
LessonRead what the console is actually showing you before deciding something is broken
Covered inStoring images: ECR

8. The load test said every request failed

StageWhat happened
SymptomDuring a release under load, the load-testing script reported every single request as 404
InvestigationA 404 is an instant answer from a healthy server. The script’s own command was checked
CauseA missing space in the command joined two options, so the tool requested a page called /10 instead of the home page
FixAdd the space. The real result was zero failed requests
LessonWhen numbers look wrong, check the test before blaming the system
Covered inReleases without downtime

9. A placeholder in the manifest

StageWhat happened
SymptomA Kubernetes manifest was ready to apply, but still contained the documentation account number 123456789012
InvestigationA search of the file for the placeholder, before applying, found it
CauseThe account box in the guide was empty when the file was copied, so the placeholder was never replaced
FixFill in the value, copy again, and search the file again
LessonCheck a file before applying it. A search takes seconds; a failed deploy takes much longer
Covered inKubernetes and EKS

10. The Kubernetes console said “Unauthorized”

StageWhat happened
SymptomThe EKS console showed Unauthorized when viewing the cluster’s workloads
Investigationkubectl, run as the identity that created the cluster, could see everything
CauseAccess to a cluster’s contents is separate from access to the AWS account. The cluster only trusted the identity that created it
FixUse that identity, or grant the console user access to the cluster
LessonBeing allowed in AWS doesn’t mean being allowed in Kubernetes
Covered inKubernetes and EKS

Writing a blameless postmortem

A postmortem is a short written account of an incident: what happened, why, and what will change. It’s written within a few days, while memories are fresh, and shared widely.

Blameless means it assumes everyone acted sensibly with what they knew at the time, and asks what made the mistake possible. Not because nobody is responsible, but because blame stops people telling you what really happened — as Leo showed.

Key point: Ask what made the mistake easy, not who made it.
BlamingBlameless
Leo forgot the outbound ruleThe security group form allowed a group with no outbound rules, and nothing checked for it
Noor took too long to escalateThe runbook had no escalation time, so there was no signal to call for help
Someone deleted the ruleThe rule was removed at 14:18 by a console session. Changes to the database’s security group weren’t reviewed or alerted on
Be more carefulAdd a check that fails the pipeline when the app’s security group has no outbound rules

The sections of a postmortem

SectionWhat goes in it
SummaryTwo or three sentences a manager could read
ImpactWho was affected, how, and for how long — with numbers
TimelineWhat happened, minute by minute, in UTC: first symptom, alarm, each step of the investigation, fix, recovery
DetectionHow we found out. Could we have found out sooner?
CausesThe direct cause, and the conditions that let it happen. Usually several
What went wellThings to keep doing
What went badlyThings that slowed us down
Where we got luckyThings that could have made it worse
Action itemsSpecific changes, each with an owner and a date

A simple way to get from the direct cause to the conditions behind it is to keep asking why? “The servers couldn’t boot.” Why? “They couldn’t make outbound connections.” Why? “The security group had no outbound rules.” Why? “It was made by hand, and nothing checks it.” Stop when you reach something you can change.

Part 3: See it

Replay three of the incidents above, one event at a time. Watch which phase each event belongs to, and notice the dead ends.

Incident
  1. Detect
  2. Investigate
  3. Cause
  4. Fix
  5. Learn
  1. 6 more events to replay

ReadyReplay New servers never became healthy one event at a time. Choose Next event or Play.

What to notice: every replay has at least one moment where the obvious next step led nowhere. That’s normal, and a good postmortem timeline includes it: the dead ends show where better tools, logs or runbooks would have saved time.

Part 4: Do it

Free — you’ll write a document. Nothing is created in AWS.

You’ll write a blameless postmortem for incident 1, new servers never became healthy, using a template. About 30 minutes.

  1. Copy the template

    Create postmortems/servers-never-healthy.md and copy this in:

    postmortems/servers-never-healthy.md
    # Postmortem: <title>
    
    Status: draft
    Date of incident: <date>
    Author: <you>
    
    ## Summary
    <Two or three sentences: what broke, for how long, and what fixed it.>
    
    ## Impact
    <Who was affected, how, and for how long. Use numbers.>
    
    ## Timeline (UTC)
    | Time | What happened |
    |---|---|
    | <hh:mm> | <first symptom> |
    | <hh:mm> | <alarm or report> |
    | <hh:mm> | <each investigation step, including dead ends> |
    | <hh:mm> | <cause found> |
    | <hh:mm> | <fix applied> |
    | <hh:mm> | <recovery confirmed> |
    
    ## Detection
    <How did we find out? How could we have found out sooner?>
    
    ## Causes
    Direct cause: <the one thing that broke>
    Contributing conditions:
    - <what made it possible>
    - <what made it slow to find>
    
    ## What went well
    - <keep doing>
    
    ## What went badly
    - <slowed us down>
    
    ## Where we got lucky
    - <could have been worse>
    
    ## Action items
    | Action | Owner | Due |
    |---|---|---|
    | <specific change to the system> | <name> | <date> |
  2. Fill in the timeline

    Use the replay above: choose Servers never healthy and step through it. Copy each event into the timeline, including the dead end.

    You should see

    Six rows, from the first failed health check to the servers becoming healthy, with the Session Manager dead end in the middle.

  3. Write the summary and impact

    For impact, imagine this happened in production at 10:00: say what customers experienced and for how long. If there were other healthy servers still serving, say so — “no customer impact, because the existing servers kept serving” is a real and useful impact statement.

  4. Find the causes, with five whys

    Start from “the new servers never became healthy” and ask why? until you reach something you can change. Write the direct cause, then at least two contributing conditions.

    You should see

    Something like: the direct cause is the missing outbound rule. Contributing conditions: the security group was made by hand with nothing to check it; Session Manager depends on the same outbound access, so the usual way in failed too; nobody on the team knew about the system log.

  5. Write action items that change the system

    Write at least three. Each must be specific, have an owner and a due date, and change the system rather than asking people to be more careful. Some ideas:

    ActionWhy it helps
    Define the security group in Terraform, reviewed in a pull requestA missing rule is visible in review
    Add “Get system log” to the unhealthy-hosts runbook, as step 2The next person knows where to look
    Alarm when the auto scaling group launches more than N servers in 15 minutesA replacement loop gets noticed quickly
  6. Do a blameless pass

    Read the whole document looking for any sentence that names a person as the cause. Rewrite each one to say what made the mistake possible, as in the table in the idea. Change Status to ready for review.

    You should see

    No sentence says who got it wrong. Every cause points at something that can be changed.

  7. Clean-up step: Clean up

    Nothing was created in AWS, so there’s nothing to delete. Keep the postmortem in your repository, next to your runbooks. A folder of postmortems is how a team remembers what it has learned.

    You should see

    One postmortem, ready for review, with at least three action items that each have an owner and a date.

Part 5: Check yourself

What does "blameless" mean in a blameless postmortem?

New servers kept failing health checks, and Session Manager couldn't connect either. What was the cause?

Two incidents both showed customers database_unavailable. What told them apart?

A load test reported every request as 404. What is the lesson?

Which is a good postmortem action item?

Part 6: Cheat sheet

On one card
Pattern or practiceIn one line
Most outagesSmall settings — a rule, a port, a route, a typo — not code
No outbound ruleReplies are allowed; connections the server starts are not
Same message, different causesLog error_type and latency_ms as fields
Shallow health checkKeeps servers in service when a dependency fails
Two stacked causesAfter each fix, test again
Numbers look wrongCheck the test before the system
PostmortemSummary, impact, timeline, detection, causes, went well, went badly, lucky, actions
BlamelessWhat made the mistake possible — not who made it
Five whysKeep asking why until you reach something you can change
Action itemSpecific, changes the system, has an owner and a date

Finished the build and the quiz? Mark it done to fill in its stop on the route map.