Skip to main content
RunBook Academy

← All checklists in Observability

As neededpost-upgrade

Post-Upgrade Validation

29 items ·25 critical ·4 warn ·0 info

Run this immediately after each component is upgraded, on the canary before the fleet and again on the fleet, and keep the output. It is the companion to the pre-upgrade checklist: that one decides whether to proceed, this one decides whether what happened was what you intended.

The items are grouped by subsystem — the process, the scrape path, the rules, the alerting chain, storage, the query surfaces, and the resources — because those subsystems fail independently. Passing one predicts nothing about the next, which is why the list is long and why stopping halfway through is worse than not starting.

What “up” does not prove

Every component here has a health endpoint, and every one of them answers a narrower question than an operator reads it as answering. Grafana’s checks its own database and nothing else. A Prometheus that has started and is scraping no targets is healthy. A collector with a broken exporter is healthy. A process supervisor sees a running process in every one of those cases and reports success.

The gap between “running” and “correct” is where post-upgrade regressions live, and none of them are loud. A rule that failed to load leaves the other rules evaluating. A datasource that lost its plugin leaves the other panels working. A schema edit that orphaned old chunks leaves new queries fast. Each of these is a specific endpoint away from being obvious and a fortnight away from being discovered by accident.

Baselines are the other half

More than half of these items are comparisons: the same count, the same rate, the same series, the same latency as before. Without a recorded pre-upgrade number they collapse into a judgement about whether the current value looks plausible, which is not evidence. Capture the numbers before the change, and capture them again at the end so the next upgrade has something to compare to.

Substitute your own hostnames, ports and paths throughout — the ones below are the shapes this course uses, not an estate that exists.

Sign-off

  • Validated by: ______________ Date: ___________
  • Change owner: ______________ Date: ___________
  • Rollback still available: yes / no Stated by: ___________

Critical25 items

  1. curl -fsS http://prometheus.internal:9090/api/v1/status/runtimeinfo | jq -r '.data.version'
  2. curl -fsS http://prometheus.internal:9090/api/v1/status/config | jq -r '.data.yaml' | diff -u /etc/prometheus/prometheus.yml - || true
  3. journalctl -u prometheus -u alertmanager --since "-1h" --no-pager | grep -iE "unknown|deprecat|ignor"
  4. curl -fsS http://prometheus.internal:9090/api/v1/targets | jq '{down: [.data.activeTargets[] | select(.health != "up")] | length, total: (.data.activeTargets | length)}'
  5. curl -fsS http://prometheus.internal:9090/api/v1/rules | jq -r '.data.groups[].rules[] | select(.health != "ok") | "\(.name) \(.health) \(.lastError)"'
  6. curl -fsS http://prometheus.internal:9090/api/v1/rules | jq -r '.data.groups[] | "\(.name) \(.lastEvaluation)"' | sort -k2 | head -5
  7. curl -fsS -G http://prometheus.internal:9090/api/v1/query --data-urlencode 'query=count({__name__=~"job:.*"})' | jq -r '.data.result[0].value[1]'
  8. curl -fsS http://prometheus.internal:9090/api/v1/alertmanagers | jq '{active: (.data.activeAlertmanagers | length), dropped: (.data.droppedAlertmanagers | length)}'
  9. curl -fsS http://alertmanager.internal:9093/api/v2/status | jq '.cluster'
  10. amtool --alertmanager.url=http://alertmanager.internal:9093 alert add upgrade_smoke severity=info
  11. curl -fsS http://prometheus.internal:9090/metrics | grep -E '^prometheus_remote_'
  12. curl -fsS http://prometheus.internal:9090/api/v1/status/tsdb | jq '.data.headStats'
  13. curl -fsS -G http://prometheus.internal:9090/api/v1/query_range --data-urlencode 'query=up' --data-urlencode "start=$(date -d '30 days ago' +%s)" --data-urlencode "end=$(date -d '29 days ago' +%s)" --data-urlencode 'step=3600' | jq '.data.result | length'
  14. logcli --addr=http://loki-read:3100 query --since=2160h --limit=5 '{job="varlogs"}'
  15. curl -fsS -G http://meta-prom:9090/api/v1/query --data-urlencode 'query={__name__=~"loki_canary_.*|tempo_canary_.*"}' | jq -r '.data.result[] | "\(.metric.__name__) \(.value[1])"'
  16. curl -fsS http://localhost:8888/metrics | grep -E "otelcol_receiver_accepted|otelcol_exporter_sent"
  17. curl -fsS -u "$GRAFANA_AUTH" http://grafana.internal:3000/api/datasources | jq -r '.[].uid' | while read -r uid; do printf "%s " "$uid"; curl -fsS -u "$GRAFANA_AUTH" "http://grafana.internal:3000/api/datasources/uid/$uid/health" | jq -r '.status'; done
  18. grafana cli plugins ls
  19. journalctl -u grafana-server --since "-1h" --no-pager | grep -iE "ldap|oauth|saml|auth.*(fail|error)"
  20. curl -fsS -u "$GRAFANA_AUTH" http://grafana.internal:3000/api/prometheus/grafana/api/v1/rules | jq -r '.data.groups[].rules[] | "\(.name) \(.health) \(.state)"'
  21. curl -fsS -G http://prometheus.internal:9090/api/v1/query --data-urlencode 'query=prometheus_tsdb_head_series' | jq -r '.data.result[] | "\(.metric.instance) \(.value[1])"'
  22. jq -r '"\(.ulid) v\(.version)"' /var/lib/prometheus/*/meta.json | tail -5

Warning4 items

  1. amtool --alertmanager.url=http://alertmanager.internal:9093 silence query
  2. curl -fsS -u "$GRAFANA_AUTH" http://grafana.internal:3000/api/health | jq
  3. curl -fsS -G http://meta-prom:9090/api/v1/query --data-urlencode 'query=process_resident_memory_bytes' | jq -r '.data.result[] | "\(.metric.job) \(.metric.instance) \(.value[1])"'