mirror of
https://github.com/BagelHole/DevOps-Security-Agent-Skills.git
synced 2026-08-22 12:49:53 +02:00
486 lines
13 KiB
Markdown
486 lines
13 KiB
Markdown
---
|
|
name: runbook-creation
|
|
description: Create operational runbooks and standard operating procedures. Document troubleshooting guides and recovery procedures. Use when documenting operational knowledge.
|
|
license: MIT
|
|
metadata:
|
|
author: devops-skills
|
|
version: "1.0"
|
|
---
|
|
|
|
# Runbook Creation
|
|
|
|
Create effective operational runbooks, standard operating procedures, and
|
|
troubleshooting guides that any on-call engineer can follow under pressure.
|
|
|
|
## Runbook Template — Full Structure
|
|
|
|
````markdown
|
|
# Runbook: [Service / Process Name]
|
|
|
|
**Owner:** [Team or individual]
|
|
**Last Reviewed:** YYYY-MM-DD
|
|
**Version:** X.Y
|
|
**Severity if unavailable:** SEV[1-4]
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
Brief description of the service, why this runbook exists, and when to
|
|
use it.
|
|
|
|
## Prerequisites
|
|
|
|
- [ ] Required access / IAM role: [details]
|
|
- [ ] Tools installed: [kubectl, aws-cli, psql, etc.]
|
|
- [ ] VPN connected to [environment]
|
|
- [ ] Communication channel open: [Slack #channel]
|
|
|
|
## Procedure
|
|
|
|
### Step 1 — [Action Name]
|
|
|
|
[Explanation of what this step does and why.]
|
|
|
|
```bash
|
|
# command here
|
|
```
|
|
|
|
**Expected output:** [describe what success looks like]
|
|
|
|
### Step 2 — [Action Name]
|
|
|
|
```bash
|
|
# command here
|
|
```
|
|
|
|
**Expected output:** [description]
|
|
|
|
*(Continue with numbered steps...)*
|
|
|
|
## Verification
|
|
|
|
How to confirm the procedure succeeded:
|
|
|
|
- [ ] [Check 1 — e.g., health endpoint returns 200]
|
|
- [ ] [Check 2 — e.g., no errors in logs for 5 minutes]
|
|
- [ ] [Check 3 — e.g., metrics return to baseline]
|
|
|
|
## Rollback
|
|
|
|
If the procedure fails or causes unexpected issues:
|
|
|
|
### Rollback Step 1
|
|
```bash
|
|
# rollback command
|
|
```
|
|
|
|
### Rollback Step 2
|
|
```bash
|
|
# rollback command
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
| Symptom | Likely Cause | Resolution |
|
|
|---------|-------------|------------|
|
|
| [symptom 1] | [cause] | [fix] |
|
|
| [symptom 2] | [cause] | [fix] |
|
|
|
|
## Escalation
|
|
|
|
If unresolved after [X] minutes:
|
|
- **Primary:** @[team-lead] — [phone/Slack]
|
|
- **Secondary:** @[manager] — [phone/Slack]
|
|
|
|
## Related Runbooks
|
|
|
|
- [Link to related runbook 1]
|
|
- [Link to related runbook 2]
|
|
|
|
## Change Log
|
|
|
|
| Date | Author | Change |
|
|
|------|--------|--------|
|
|
| YYYY-MM-DD | [Name] | Initial version |
|
|
````
|
|
|
|
## Example Runbook — Database Failover
|
|
|
|
````markdown
|
|
# Runbook: PostgreSQL Database Failover
|
|
|
|
**Owner:** Platform / DBA team
|
|
**Last Reviewed:** 2025-06-15
|
|
**Version:** 2.1
|
|
**Severity if unavailable:** SEV1
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
Failover the primary PostgreSQL instance to the synchronous replica when
|
|
the primary is unreachable or degraded. This runbook covers both planned
|
|
(maintenance) and unplanned (emergency) failover.
|
|
|
|
## Prerequisites
|
|
|
|
- [ ] DBA or SRE-level access to primary and replica hosts
|
|
- [ ] `psql` client installed (v14+)
|
|
- [ ] VPN connected to production network
|
|
- [ ] Slack channel #db-ops open
|
|
- [ ] Confirm replica is in sync: replication lag < 1 MB
|
|
|
|
## Procedure
|
|
|
|
### Step 1 — Verify Replica Health
|
|
|
|
```bash
|
|
psql -h replica.db.internal -U dba -d postgres -c \
|
|
"SELECT pg_is_in_recovery(), pg_last_wal_replay_lsn();"
|
|
```
|
|
|
|
**Expected output:** `pg_is_in_recovery = t`, LSN advancing.
|
|
|
|
### Step 2 — Stop Application Writes
|
|
|
|
```bash
|
|
kubectl scale deployment api-server --replicas=0 -n production
|
|
kubectl scale deployment worker --replicas=0 -n production
|
|
```
|
|
|
|
**Expected output:** Deployments scaled to 0 pods.
|
|
|
|
### Step 3 — Confirm Write Quiesce
|
|
|
|
```bash
|
|
psql -h primary.db.internal -U dba -d postgres -c \
|
|
"SELECT count(*) FROM pg_stat_activity WHERE state = 'active' AND query !~ 'pg_stat';"
|
|
```
|
|
|
|
**Expected output:** Count = 0 (no active queries).
|
|
|
|
### Step 4 — Promote Replica
|
|
|
|
```bash
|
|
psql -h replica.db.internal -U dba -d postgres -c "SELECT pg_promote();"
|
|
```
|
|
|
|
Wait up to 30 seconds, then confirm:
|
|
|
|
```bash
|
|
psql -h replica.db.internal -U dba -d postgres -c "SELECT pg_is_in_recovery();"
|
|
```
|
|
|
|
**Expected output:** `pg_is_in_recovery = f` (no longer a replica).
|
|
|
|
### Step 5 — Update DNS
|
|
|
|
```bash
|
|
aws route53 change-resource-record-sets \
|
|
--hosted-zone-id Z1234567890 \
|
|
--change-batch '{
|
|
"Changes": [{
|
|
"Action": "UPSERT",
|
|
"ResourceRecordSet": {
|
|
"Name": "db.internal.example.com",
|
|
"Type": "CNAME",
|
|
"TTL": 60,
|
|
"ResourceRecords": [{"Value": "replica.db.internal"}]
|
|
}
|
|
}]
|
|
}'
|
|
```
|
|
|
|
### Step 6 — Restart Application
|
|
|
|
```bash
|
|
kubectl scale deployment api-server --replicas=6 -n production
|
|
kubectl scale deployment worker --replicas=4 -n production
|
|
```
|
|
|
|
## Verification
|
|
|
|
- [ ] `psql -h db.internal.example.com -c "SELECT 1;"` returns successfully
|
|
- [ ] Application logs show successful DB connections (no errors for 5 min)
|
|
- [ ] Transaction throughput returns to baseline on Grafana dashboard
|
|
- [ ] No replication-lag alerts firing
|
|
|
|
## Rollback
|
|
|
|
If the promoted replica has issues, restore from the most recent backup:
|
|
|
|
```bash
|
|
# Restore latest automated snapshot (RDS example)
|
|
aws rds restore-db-instance-from-db-snapshot \
|
|
--db-instance-identifier prod-db-restored \
|
|
--db-snapshot-identifier prod-db-latest-snapshot
|
|
```
|
|
|
|
## Escalation
|
|
|
|
If unresolved after 15 minutes:
|
|
- **Primary:** @dba-lead — +1-555-0101
|
|
- **Secondary:** @platform-oncall — +1-555-0102
|
|
````
|
|
|
|
## Automation Scripts for Common Operations
|
|
|
|
### Service Health Check
|
|
|
|
```bash
|
|
#!/usr/bin/env bash
|
|
# health-check.sh — Check health of critical services
|
|
set -euo pipefail
|
|
|
|
SERVICES=(
|
|
"https://api.example.com/healthz"
|
|
"https://app.example.com/healthz"
|
|
"https://admin.example.com/healthz"
|
|
)
|
|
|
|
EXIT_CODE=0
|
|
|
|
for url in "${SERVICES[@]}"; do
|
|
HTTP_CODE=$(curl -so /dev/null -w '%{http_code}' --max-time 5 "$url" 2>/dev/null || echo "000")
|
|
if [ "$HTTP_CODE" -eq 200 ]; then
|
|
printf " OK %s\n" "$url"
|
|
else
|
|
printf " FAIL %s (HTTP %s)\n" "$url" "$HTTP_CODE"
|
|
EXIT_CODE=1
|
|
fi
|
|
done
|
|
|
|
exit $EXIT_CODE
|
|
```
|
|
|
|
### Log Collection for Incident Investigation
|
|
|
|
```bash
|
|
#!/usr/bin/env bash
|
|
# collect-logs.sh — Gather logs from multiple sources for incident review
|
|
set -euo pipefail
|
|
|
|
INCIDENT_ID="${1:?Usage: collect-logs.sh <incident-id>}"
|
|
OUTDIR="/tmp/incident-${INCIDENT_ID}"
|
|
mkdir -p "$OUTDIR"
|
|
|
|
echo "Collecting logs for incident $INCIDENT_ID..."
|
|
|
|
# Kubernetes pod logs (last 30 min)
|
|
kubectl logs -l app=api-server -n production --since=30m \
|
|
> "${OUTDIR}/api-server-pods.log" 2>&1
|
|
|
|
# CloudWatch Logs (last 30 min)
|
|
aws logs filter-log-events \
|
|
--log-group-name /ecs/production/api \
|
|
--start-time "$(date -d '30 minutes ago' +%s)000" \
|
|
--output text > "${OUTDIR}/cloudwatch-api.log" 2>&1
|
|
|
|
# Database slow query log
|
|
psql -h db.internal -U dba -d postgres -c \
|
|
"SELECT * FROM pg_stat_activity WHERE state != 'idle' ORDER BY query_start;" \
|
|
> "${OUTDIR}/db-active-queries.log" 2>&1
|
|
|
|
# System resource snapshot
|
|
kubectl top pods -n production > "${OUTDIR}/pod-resources.log" 2>&1
|
|
|
|
echo "Logs saved to $OUTDIR"
|
|
tar czf "${OUTDIR}.tar.gz" -C /tmp "incident-${INCIDENT_ID}"
|
|
echo "Archive: ${OUTDIR}.tar.gz"
|
|
```
|
|
|
|
### Certificate Expiry Check
|
|
|
|
```bash
|
|
#!/usr/bin/env bash
|
|
# cert-check.sh — Warn if TLS certificates expire within 30 days
|
|
set -euo pipefail
|
|
|
|
DOMAINS=(
|
|
"api.example.com"
|
|
"app.example.com"
|
|
"admin.example.com"
|
|
)
|
|
|
|
WARN_DAYS=30
|
|
TODAY=$(date +%s)
|
|
EXIT_CODE=0
|
|
|
|
for domain in "${DOMAINS[@]}"; do
|
|
EXPIRY=$(echo | openssl s_client -servername "$domain" -connect "${domain}:443" 2>/dev/null \
|
|
| openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
|
|
EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s 2>/dev/null || echo 0)
|
|
DAYS_LEFT=$(( (EXPIRY_EPOCH - TODAY) / 86400 ))
|
|
|
|
if [ "$DAYS_LEFT" -lt "$WARN_DAYS" ]; then
|
|
printf " WARN %s expires in %d days (%s)\n" "$domain" "$DAYS_LEFT" "$EXPIRY"
|
|
EXIT_CODE=1
|
|
else
|
|
printf " OK %s — %d days remaining\n" "$domain" "$DAYS_LEFT"
|
|
fi
|
|
done
|
|
|
|
exit $EXIT_CODE
|
|
```
|
|
|
|
### Disk Space Cleanup
|
|
|
|
```bash
|
|
#!/usr/bin/env bash
|
|
# disk-cleanup.sh — Free disk space on a host
|
|
set -euo pipefail
|
|
|
|
echo "=== Disk Usage Before ==="
|
|
df -h /
|
|
|
|
# Remove old journal logs (> 7 days)
|
|
journalctl --vacuum-time=7d 2>/dev/null || true
|
|
|
|
# Clean Docker artifacts
|
|
docker system prune -f --volumes 2>/dev/null || true
|
|
|
|
# Remove old log files
|
|
find /var/log -name "*.gz" -mtime +7 -delete 2>/dev/null || true
|
|
find /tmp -type f -mtime +3 -delete 2>/dev/null || true
|
|
|
|
echo "=== Disk Usage After ==="
|
|
df -h /
|
|
```
|
|
|
|
## Runbook Review Checklist
|
|
|
|
Use this checklist every time a runbook is created or updated.
|
|
|
|
```yaml
|
|
content_review:
|
|
- [ ] Title clearly identifies the service and operation
|
|
- [ ] Overview explains WHEN and WHY to use this runbook
|
|
- [ ] Prerequisites list all required access, tools, and setup
|
|
- [ ] Every step has a concrete command (no vague instructions)
|
|
- [ ] Expected output is documented for each step
|
|
- [ ] Verification section confirms success with specific checks
|
|
- [ ] Rollback section exists and has been tested
|
|
- [ ] Escalation contacts are current (names, phones, Slack handles)
|
|
- [ ] Troubleshooting table covers the top 3-5 known failure modes
|
|
|
|
usability_review:
|
|
- [ ] A new team member can follow the runbook without tribal knowledge
|
|
- [ ] Steps are numbered and sequential (no branching without clear labels)
|
|
- [ ] Commands can be copy-pasted (no placeholder values without explanation)
|
|
- [ ] Time estimates included for long-running steps
|
|
- [ ] No jargon or acronyms used without definition
|
|
|
|
maintenance_review:
|
|
- [ ] Owner and last-reviewed date are set
|
|
- [ ] Version number incremented
|
|
- [ ] Change log entry added
|
|
- [ ] Related runbooks section is up to date
|
|
- [ ] Links to dashboards and docs are valid (not broken)
|
|
```
|
|
|
|
## Runbook Testing Procedures
|
|
|
|
```yaml
|
|
testing_strategy:
|
|
dry_run:
|
|
frequency: "Every time a runbook is created or substantially edited"
|
|
method: "Walk through each step in a staging environment"
|
|
goal: "Verify commands work and output matches documentation"
|
|
|
|
peer_review:
|
|
frequency: "Every edit"
|
|
method: "Another engineer follows the runbook in staging without help"
|
|
goal: "Confirm the runbook is self-contained and unambiguous"
|
|
|
|
scheduled_validation:
|
|
frequency: "Quarterly"
|
|
method: "SRE team picks 5 runbooks at random, executes in staging"
|
|
goal: "Catch runbooks that have drifted from production reality"
|
|
|
|
incident_triggered:
|
|
trigger: "Any time a runbook is used in a real incident"
|
|
method: "Post-mortem includes runbook accuracy assessment"
|
|
goal: "Capture improvements while the experience is fresh"
|
|
|
|
automation_testing:
|
|
method: "CI pipeline validates bash scripts with shellcheck and dry-run"
|
|
example: |
|
|
# .github/workflows/runbook-lint.yml
|
|
name: Lint Runbook Scripts
|
|
on: [pull_request]
|
|
jobs:
|
|
shellcheck:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: ShellCheck
|
|
run: |
|
|
find runbooks/ -name "*.sh" -exec shellcheck {} +
|
|
```
|
|
|
|
## Versioning Strategy
|
|
|
|
```yaml
|
|
versioning:
|
|
storage: "Git repository — one directory per service, one file per runbook"
|
|
naming: "runbooks/<service>/<operation>.md"
|
|
branching: "PRs required for all changes; reviewed by service owner"
|
|
|
|
version_scheme:
|
|
format: "MAJOR.MINOR"
|
|
major_bump: "Procedure changes that alter the steps or their order"
|
|
minor_bump: "Clarifications, typo fixes, updated contact info"
|
|
|
|
directory_layout: |
|
|
runbooks/
|
|
api-server/
|
|
deploy.md
|
|
rollback.md
|
|
scale-up.md
|
|
database/
|
|
failover.md
|
|
backup-restore.md
|
|
vacuum-maintenance.md
|
|
infrastructure/
|
|
dns-update.md
|
|
certificate-renewal.md
|
|
disk-cleanup.md
|
|
|
|
review_requirements:
|
|
- PR must be approved by the service owner
|
|
- CI must pass (shellcheck for scripts, markdown lint)
|
|
- Reviewer confirms they can follow the steps independently
|
|
|
|
retention: "Git history serves as full audit trail — never delete old versions"
|
|
```
|
|
|
|
## Runbook Index Template
|
|
|
|
Keep a top-level index so engineers can find the right runbook quickly.
|
|
|
|
```markdown
|
|
# Runbook Index
|
|
|
|
| Service | Runbook | Severity | Owner | Last Tested |
|
|
|---------|---------|----------|-------|-------------|
|
|
| API Server | [Deploy](api-server/deploy.md) | — | @platform | 2025-05-01 |
|
|
| API Server | [Rollback](api-server/rollback.md) | SEV1 | @platform | 2025-05-01 |
|
|
| Database | [Failover](database/failover.md) | SEV1 | @dba | 2025-04-15 |
|
|
| Database | [Backup Restore](database/backup-restore.md) | SEV2 | @dba | 2025-04-15 |
|
|
| Infra | [DNS Update](infrastructure/dns-update.md) | SEV2 | @sre | 2025-06-01 |
|
|
| Infra | [Cert Renewal](infrastructure/certificate-renewal.md) | SEV3 | @sre | 2025-06-01 |
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
- Write runbooks for the engineer at 3 AM — clear, sequential, copy-pasteable
|
|
- Include expected output so the operator knows if a step succeeded
|
|
- Always provide a rollback path; every action should be reversible
|
|
- Test runbooks in staging before they are needed in production
|
|
- Keep runbooks in version control alongside the code they support
|
|
- Assign an owner to every runbook; ownerless runbooks rot fast
|
|
- After every incident, update the relevant runbook with lessons learned
|
|
- Automate repetitive runbook steps into scripts, but keep the runbook as
|
|
the orchestration guide so operators understand the "why"
|