ENGINEERING LABORATORY

BREAK IT.
FIND IT. FIX IT.

Practical infrastructure failure scenarios. No multiple choice. No magic answers. Follow the evidence.

LAB MODE: PRODUCTION
01 / LAB PROTOCOL

Troubleshoot the problem. Don't guess the answer.

01

OBSERVE

Start with the symptoms. What is actually broken?

02

INVESTIGATE

Use commands, logs, metrics and system state to gather evidence.

03

DIAGNOSE

Identify the first broken assumption rather than stopping at the visible error.

04

FIX

Apply the smallest appropriate correction and verify the system.

RULE

If you cannot explain why the system failed, you haven't finished troubleshooting.

LAB / 001

Broken DNS

LEVEL: INTERMEDIATE · SYSTEM: DNS / NETWORKING
SYMPTOM

Users report that the application is unavailable.

The web server responds correctly when accessed by IP address, but the hostname does not resolve.

Start here

ping app.example.com
dig app.example.com
dig @8.8.8.8 app.example.com
cat /etc/resolv.conf
ip route

Evidence

$ dig app.example.com

;; connection timed out; no servers could be reached

$ cat /etc/resolv.conf

nameserver 10.0.0.53

$ ping 10.0.10.20

64 bytes from 10.0.10.20: icmp_seq=1 ttl=64 time=0.4 ms
ROOT CAUSE

The application server can reach the network, but its configured DNS resolver at 10.0.0.53 is unavailable.

Fix

# Verify resolver reachability
ping 10.0.0.53

# Test another resolver
dig @1.1.1.1 app.example.com

# Correct the resolver configuration
# according to the environment's DNS design.

# Verify
dig app.example.com
LESSON

"DNS is broken" is not a diagnosis. Determine whether the problem is the client, resolver, authoritative server, network path, record or delegation.

LAB / 002

Disk at 100%

LEVEL: INTERMEDIATE · SYSTEM: LINUX / STORAGE
SYMPTOM

The application reports write failures.

write error: No space left on device

Start here

df -h
df -i
du -xhd1 /

Evidence

$ df -h /

Filesystem      Size  Used Avail Use% Mounted on
/dev/sda2        50G   50G     0 100% /

$ du -xhd1 /

18G     /var
12G     /usr
8G      /home
2G      /opt
SECONDARY CLUE

The numbers do not account for all of the used space.

Investigate further

lsof +L1

Evidence

$ lsof +L1

COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NLINK NAME
java     2147 app    12w   REG  8,2   8.5G      0  /var/log/app.log (deleted)
ROOT CAUSE

A process still has an open file descriptor to a deleted 8.5 GB log file.

Deleting the file removed the directory entry, but the storage remains allocated until the process closes the file descriptor.

Fix

# Identify the owning process
lsof +L1

# Restart or otherwise safely recycle the process
systemctl restart application.service

# Verify
df -h /
LESSON

"du" shows files visible in the filesystem namespace. "df" shows filesystem allocation. When they disagree, deleted-but-open files are one of the first things to investigate.

LAB / 003

Kubernetes CrashLoopBackOff

LEVEL: INTERMEDIATE · SYSTEM: KUBERNETES
SYMPTOM
$ kubectl get pods

NAME                     READY   STATUS
orders-api-7c8f9c7d8d    0/1     CrashLoopBackOff

Start here

kubectl describe pod orders-api-7c8f9c7d8d
kubectl logs orders-api-7c8f9c7d8d
kubectl logs orders-api-7c8f9c7d8d --previous

Evidence

$ kubectl logs orders-api-7c8f9c7d8d

ERROR: DATABASE_URL is not defined
Application startup failed
ROOT CAUSE

The container starts correctly, but the application exits because the required database configuration is missing.

Investigate the manifest

kubectl get deployment orders-api -o yaml

The deployment references the application image but does not provide the required environment variable or secret.

Fix

kubectl create secret generic orders-db \
  --from-literal=DATABASE_URL='postgresql://...'

# Then reference the secret from the Deployment
# using env.valueFrom.secretKeyRef.

kubectl rollout restart deployment orders-api

kubectl get pods
LESSON

CrashLoopBackOff is not the root cause. It is Kubernetes telling you that the container repeatedly starts and terminates. Always inspect the container logs and previous container logs.

LAB / 004

Broken systemd Service

LEVEL: BEGINNER / INTERMEDIATE · SYSTEM: LINUX / SYSTEMD
SYMPTOM
$ systemctl status api.service

Active: failed

Start here

systemctl status api.service
journalctl -u api.service -n 100
systemctl cat api.service

Evidence

$ systemctl status api.service

Process: 1821 ExecStart=/opt/api/start.sh
(code=exited, status=203/EXEC)
ROOT CAUSE

systemd cannot execute the configured /opt/api/start.sh.

The script exists, but its executable permission was removed.

Verify

ls -l /opt/api/start.sh

-rw-r--r-- 1 root root 412 start.sh

Fix

chmod 755 /opt/api/start.sh

systemctl daemon-reload
systemctl restart api.service

systemctl status api.service
LESSON

systemd errors often tell you exactly where to look. Read the exit status before changing random configuration.

LAB / 005

TCP Connection Failure

LEVEL: INTERMEDIATE · SYSTEM: NETWORKING
SYMPTOM

The application reports:

connection refused: database:5432

Start here

ping database
nc -vz database 5432
ss -lntp
ip route

Evidence

$ nc -vz database 5432

Connection refused

$ ping database

64 bytes from 10.0.20.15

$ ss -lntp

LISTEN 0 128 127.0.0.1:5432
ROOT CAUSE

PostgreSQL is listening only on the loopback interface. The network path works, but the service is not accepting remote connections.

Fix

# PostgreSQL configuration must listen
# on the required interface.

listen_addresses = '*'

# Then restart PostgreSQL
systemctl restart postgresql

# Verify
ss -lntp | grep 5432
LESSON

Connection refused is different from connection timeout. A refusal usually means the destination was reachable but nothing accepted the connection on that address/port.

LAB / 006

Terraform Drift

LEVEL: INTERMEDIATE · SYSTEM: IaC / CLOUD
SYMPTOM

The infrastructure repository has not changed, but Terraform reports changes during planning.

terraform plan

Evidence

~ aws_instance.web

  instance_type = "t3.small" -> "t3.medium"

Investigate

terraform state show aws_instance.web

aws ec2 describe-instances ...
ROOT CAUSE

Someone changed the instance type directly in the cloud console. The real infrastructure no longer matches the declared configuration.

Decision

There are two valid directions.

  • If the change was intentional, update the Terraform code and review it through version control.
  • If the change was accidental, allow Terraform to reconcile the infrastructure back to the declared state.

Fix

# Review the proposed change
terraform plan

# Apply only after verifying the desired state
terraform apply
LESSON

Terraform is not merely a provisioning tool. The value comes from maintaining a reliable relationship between declared state and real infrastructure.

LAB / 007

Kubernetes Node NotReady

LEVEL: ADVANCED · SYSTEM: KUBERNETES / LINUX
SYMPTOM
$ kubectl get nodes

NAME       STATUS
worker-01  Ready
worker-02  NotReady
worker-03  Ready

Pods scheduled on worker-02 are failing.

Start here

kubectl describe node worker-02
kubectl get pods -A -o wide
kubectl get events -A --sort-by=.lastTimestamp

Evidence

Conditions:

Ready       False
MemoryPressure False
DiskPressure True

Reason:
KubeletHasDiskPressure

Move to the node

df -h
df -i
du -xhd1 /var
journalctl -u kubelet
ROOT CAUSE

The node's filesystem used by the container runtime has reached the configured disk pressure threshold.

Fix

# Identify container/image/log consumption
du -xhd1 /var/lib/containerd
du -xhd1 /var/log

# Remove unnecessary data using the runtime's
# supported garbage-collection mechanisms.

# Verify
df -h

kubectl get node worker-02
LESSON

A Kubernetes problem can still be a Linux problem. Kubernetes does not remove the operating system underneath it.

LAB / 008

Certificate Expired

LEVEL: INTERMEDIATE · SYSTEM: TLS / NETWORKING
SYMPTOM

Users suddenly receive a browser certificate warning.

NET::ERR_CERT_DATE_INVALID

Start here

openssl s_client \
  -connect app.example.com:443 \
  -servername app.example.com

date

Evidence

notBefore=Jun 01 00:00:00 2026 GMT
notAfter=Sep 01 23:59:59 2026 GMT
ROOT CAUSE

The certificate expired and was not renewed.

Investigate the renewal path

certbot certificates
systemctl list-timers
journalctl -u certbot

Fix

# Renew using the environment's configured
# ACME / Certbot method.

certbot renew

# Verify
openssl s_client \
  -connect app.example.com:443 \
  -servername app.example.com
LESSON

Certificate expiry is rarely a certificate problem. It is usually a lifecycle automation problem.

LAB / 009

Runaway Process

LEVEL: INTERMEDIATE · SYSTEM: LINUX / PERFORMANCE
SYMPTOM

The server is slow. CPU utilisation is close to 100%.

Start here

uptime
top
ps aux --sort=-%cpu | head
pidstat 1

Evidence

PID   USER   %CPU   COMMAND
4812  app    399.8  /opt/app/worker

Investigate

ps -fp 4812
ls -l /proc/4812/exe
cat /proc/4812/cmdline
strace -p 4812
ROOT CAUSE

The worker has entered a tight retry loop after losing its downstream dependency.

Fix

# Correct the underlying application/configuration issue.

# Then safely restart the worker.
systemctl restart application-worker.service

# Verify CPU
top
LESSON

High CPU is a symptom. Killing the process may restore capacity temporarily, but it does not explain why the process consumed the CPU.

LAB / 010

Container Cannot Reach Database

LEVEL: ADVANCED · SYSTEM: CONTAINERS / NETWORKING
SYMPTOM

The database is healthy. The application container cannot connect.

connection timed out: postgres:5432

Start inside the container

getent hosts postgres
nc -vz postgres 5432
ip addr
ip route

Evidence

$ getent hosts postgres

10.20.0.15 postgres

$ nc -vz postgres 5432

Connection timed out

Check the destination

ss -lntp | grep 5432

LISTEN 0 128 127.0.0.1:5432
ROOT CAUSE

The database hostname resolves correctly and the network path exists, but PostgreSQL is listening only on localhost.

Fix

# Configure PostgreSQL to listen on the
# required network interface.

listen_addresses = '*'

# Configure pg_hba.conf appropriately.

# Restart PostgreSQL.

systemctl restart postgresql

# Verify from the application container.
nc -vz postgres 5432
LESSON

Separate the layers:

DNS → ROUTING → FIREWALL → LISTENER → APPLICATION

Test each layer instead of assuming "the database is down."

03 / TROUBLESHOOTING MATRIX

Start at the layer.

Layer Question Useful tools
Process Is the process running? ps · top · systemctl
Memory Is the system under memory pressure? free · vmstat · top
Storage Is space or inode capacity exhausted? df · du · lsof
Network Can the destination be reached? ping · ip · ss · nc · traceroute
DNS Does the name resolve correctly? dig · host · getent
TLS Is the certificate and handshake valid? openssl · curl
Container Does the workload start and remain healthy? docker · crictl · logs
Kubernetes Is desired state converging? kubectl · events · logs
IaC Does declared state match reality? terraform plan · state
04 / THE GOLDEN RULE

Don't fix the symptom. Find the broken assumption.

USER REPORT
  ↓
SYMPTOM
  ↓
OBSERVATION
  ↓
EVIDENCE
  ↓
HYPOTHESIS
  ↓
TEST
  ↓
ROOT CAUSE
  ↓
FIX
  ↓
VERIFY
  ↓
PREVENT
05 / LAB COMPLETE

Production rewards engineers who follow the evidence.

The commands change. The platforms change. The infrastructure changes.

The troubleshooting process remains remarkably consistent.

OBSERVE.
MEASURE.
QUESTION.
TEST.
FIX.
VERIFY.