Skip to content

feat(observability): alert on stale backups, degraded databases and expiring certificates - #426

Merged
devantler merged 2 commits into
mainfrom
claude/observability-platform-health-alert-423
Nov 11, 2026
Merged

devantler merged 2 commits into
mainfrom
claude/observability-platform-health-alert-423

Conversation

@devantler

Copy link
Copy Markdown
Contributor

Why

When the old Prometheus stack was retired, three warnings lost their home: a backup that failed or stopped running, a database left without a recent backup or a working replica, and a certificate about to expire. Today these are found only when someone looks, or when the backup is needed.

What

Production clusters now run a small read-only check every six hours that posts to the existing Slack alerts channel whenever a backup is failed or stale, a database is degraded or unbacked, or a certificate expires within 14 days. Autoscaler failures are deliberately left to the existing observability tool, and the alerting guide now explains all of this. Delivery to Slack on a real production cluster is still to be observed.

Part of #423

🤖 Generated with Claude Code

…xpiring certificates

Retiring kube-prometheus-stack left three signals without an alert. A
production-only CronJob in the observability namespace now lists Velero
schedules and backups, CloudNativePG clusters and backups, and cert-manager
certificates every six hours, and posts one Slack message through the
existing webhook when a backup failed or is stale, a database has no recent
backup, a missing replica or a failing WAL archive, or a certificate
expires within 14 days. Its identity can only list those kinds.

Cluster Autoscaler failures are left to Coroot: KSail owns the autoscaler,
and its effect, pods left Pending, already surfaces as a Coroot incident.

A new test runs the rendered script against recorded API responses, the
observability validator pins the rendered resources and their absence in
local, and the alerting guide lists the new signals.

Part of #423

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Nov 10, 2026 •

Copy link
Copy Markdown
Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

Explain Change →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 13 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 7ade4e26-b393-4282-95af-9115ad57195f
Commits

Reviewing files that changed from the base of the PR and between 42c04cb and efd1010.

Files selected for processing: 2

  • docs/dr/alerting.md
  • scripts/validate-observability-option.sh

No actionable comments were generated in the recent review.

Walkthrough

Adds a production-only, six-hour CronJob to check Velero backups, CloudNativePG health, and certificate expiry. The job uses scoped Kubernetes read permissions and sends findings through a configured webhook. Missing APIs are skipped, while other read and delivery failures fail the job. Adds tests, scaffold validation, a workflow test step, and documentation updates.

Possibly related issues

  • Issue 423 identifies missing alerts for stale or failed backups and expiring certificates. This PR adds production checks and alerts for those conditions.
  • Issue 65 calls for observability alert updates as part of the Coroot migration. This PR adds backup, database, and certificate signals that do not depend on Prometheus.

Possibly related PRs

Merge Risk: Minimal · previous review 42c04

This change adds a production-only six-hour health check that alerts on stale backups, degraded databases and expiring certificates. It has scoped read-only permissions and failure handling covered by tests. No actionable merge-blocking risk remains.

Pre-merge checks: Passed 5

Check name Status Explanation
Title check Passed The title clearly summarizes the main change: alerts for stale backups, degraded databases, and expiring certificates.
Description check Passed The description explains the production health check, the alerts it sends, and the observability documentation changes.
Docstring Coverage Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentat…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 1 functions across 2 files. (10 skipped: 10 unsupported.)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

Recent review info

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: f441b08c-2476-46a3-9ac3-7636e6035a84
Commits

Reviewing files that changed from the base of the PR and between 0f2c89f and 42c04cb.

Files selected for processing: 12

  • .github/workflows/validate-scaffold.yaml
  • docs/TEMPLATING.md
  • docs/dr/alerting.md
  • k8s/providers/hetzner/infrastructure/controllers/kustomization.yaml
  • k8s/providers/hetzner/infrastructure/controllers/platform-health-alert/cluster-role-binding.yaml
  • k8s/providers/hetzner/infrastructure/controllers/platform-health-alert/cluster-role.yaml
  • k8s/providers/hetzner/infrastructure/controllers/platform-health-alert/cron-job.yaml
  • k8s/providers/hetzner/infrastructure/controllers/platform-health-alert/kustomization.yaml
  • k8s/providers/hetzner/infrastructure/controllers/platform-health-alert/secret.yaml
  • k8s/providers/hetzner/infrastructure/controllers/platform-health-alert/service-account.yaml
  • scripts/tests/test-platform-health-alert.sh
  • scripts/validate-observability-option.sh

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Recent review details

Additional context used

Code guidelines: 1

AGENTS.md — auto-discovered
Path-based instructions: 2

Source excerpt: Manifest naming follows the reference platform's conventions (platform#2315), enforced by the shared `devantler-tech/.github/actions/validate-naming` action (a CI gate on manifest, naming-configuration, and naming-workflow c...

CodeRabbit inference engine (AGENTS.md)

Files:

  • k8s/providers/hetzner/infrastructure/controllers/kustomization.yaml
  • k8s/providers/hetzner/infrastructure/controllers/platform-health-alert/kustomization.yaml
Source excerpt: Hierarchical overlays: **base** (`k8s/bases/`) → **provider** (`k8s/providers/`) → **cluster** (`k8s/clusters/`).

CodeRabbit inference engine (AGENTS.md)

Files:

  • k8s/providers/hetzner/infrastructure/controllers/kustomization.yaml
  • k8s/providers/hetzner/infrastructure/controllers/platform-health-alert/service-account.yaml
  • k8s/providers/hetzner/infrastructure/controllers/platform-health-alert/cluster-role-binding.yaml
  • k8s/providers/hetzner/infrastructure/controllers/platform-health-alert/secret.yaml
  • k8s/providers/hetzner/infrastructure/controllers/platform-health-alert/cluster-role.yaml
  • k8s/providers/hetzner/infrastructure/controllers/platform-health-alert/kustomization.yaml
  • k8s/providers/hetzner/infrastructure/controllers/platform-health-alert/cron-job.yaml
Learnings: 1

Common learnings

Learnt from: CR
Repo: devantler-tech/platform-template

Timestamp: 2026-11-10T23:55:50.444Z
Learning: Source excerpt:
# AGENTS.md

## Validation

### File and directory naming conventions

Manifest naming follows the reference platform's conventions (platform#2315),
enforced by the shared `devantler-tech/.github/actions/validate-naming` action (a CI gate
on manifest, naming-configuration, and naming-workflow changes):

- **One Kubernetes resource per file** (vendored upstream operator bundles exempt).
Learnt from: CR
Repo: devantler-tech/platform-template

Timestamp: 2026-11-10T23:55:50.444Z
Learning: Source excerpt:
# AGENTS.md

## Validation

### File and directory naming conventions

Manifest naming follows the reference platform's conventions (platform#2315),
enforced by the shared `devantler-tech/.github/actions/validate-naming` action (a CI gate
on manifest, naming-configuration, and naming-workflow changes):

- **Kebab-case** directories and file stems everywhere under `k8s/` and `talos*/`
  (vendored upstream files inside CR folders — e.g. the testkube CRD dumps —
  keep their upstream names).
Learnt from: CR
Repo: devantler-tech/platform-template

Timestamp: 2026-11-10T23:55:50.444Z
Learning: Source excerpt:
# AGENTS.md

## Validation

### File and directory naming conventions

Manifest naming follows the reference platform's conventions (platform#2315),
enforced by the shared `devantler-tech/.github/actions/validate-naming` action (a CI gate
on manifest, naming-configuration, and naming-workflow changes):

- **Filenames lead with the kebab-cased Kind**: `<kind>.yaml` or
  `<kind>-<purpose>.yaml` (e.g. `cilium-network-policy.yaml`, `http-route.yaml`,
  `config-map-corefile.yaml`).
Checkov (3.3.30)

k8s/providers/hetzner/infrastructure/controllers/platform-health-alert/cron-job.yaml

[low] 21-228: Ensure that Service Account Tokens are only mounted where necessary

(CKV_K8S_38)

Trivy (0.76.0)

k8s/providers/hetzner/infrastructure/controllers/platform-health-alert/cron-job.yaml

[warning] 57-219: Restrict container images to trusted registries

Container check in cronjob platform-health-alert (namespace: observability) uses an image from an untrusted registry.

Rule: KSV-0125

Learn more

(IaC/Kubernetes)

Additional comments: 12

k8s/providers/hetzner/infrastructure/controllers/platform-health-alert/service-account.yaml: 1

1-6: LGTM!

k8s/providers/hetzner/infrastructure/controllers/platform-health-alert/cluster-role.yaml: 1

1-16: LGTM!

k8s/providers/hetzner/infrastructure/controllers/platform-health-alert/cluster-role-binding.yaml: 1

1-13: LGTM!

k8s/providers/hetzner/infrastructure/controllers/platform-health-alert/secret.yaml: 1

1-13: LGTM!

k8s/providers/hetzner/infrastructure/controllers/platform-health-alert/kustomization.yaml: 1

1-15: LGTM!

k8s/providers/hetzner/infrastructure/controllers/kustomization.yaml: 1

44-45: LGTM!

k8s/providers/hetzner/infrastructure/controllers/platform-health-alert/cron-job.yaml: 1

1-228: LGTM!

scripts/tests/test-platform-health-alert.sh: 1

1-174: LGTM!

scripts/validate-observability-option.sh: 1

533-572: LGTM!

Also applies to: 948-953, 1193-1194, 1229-1232

.github/workflows/validate-scaffold.yaml: 1

66-71: LGTM!

docs/dr/alerting.md: 1

7-7: LGTM!

Also applies to: 15-18, 30-42, 106-151

docs/TEMPLATING.md: 1

147-149: LGTM!

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer (interactive Claude Code session)

User trial.

  • Rendered the production controller layer and ran the health-check script exactly as rendered (with sh, and again with dash) against recorded Kubernetes API responses. A healthy cluster posts nothing. A failed and a stale Velero schedule, a database with no backup, a database short of replicas past the grace period, a failing WAL archive and a certificate 10 days from expiry each produce one line in a single Slack message; a database still inside its grace period and a certificate 16 days out produce none.
  • The local placeholder webhook logs the findings and does not deliver. Clusters without these APIs are skipped. An API error, a malformed list, a failed delivery, a plain-http webhook and a webhook with a newline each fail the job.
  • The observability validator passes, rejects a widened ClusterRole and a CronJob on the wrong service account, and confirms local renders none of it.

Not yet proven (why this says Part of #423): the first two criteria need a Slack notification from a real production cluster, which this host cannot run. The autoscaler decision and the guide update are done here.

🤖 Generated with Claude Code

@devantler
devantler marked this pull request as ready for review November 11, 2026 00:04
@devantler
devantler requested a review from a team as a code owner November 11, 2026 00:04
The platform-health-alert pod has no policy of its own: the observability
namespace's allow-coroot policy selects every pod but the heartbeat and
allows the API server, DNS and, in production, hooks.slack.com:443. The
validator now requires the pod to stay outside the heartbeat carve-out
and allow-coroot to keep those three egress rules, so narrowing either
fails CI instead of silently stopping the alerts.

Part of #423

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-11T00:47:01.562000Z efd1010 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

Reviewed commit: efd10106ca

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer (interactive Claude Code session)

User trial at efd1010. Everything in the earlier trial still holds at this head. New here: in the rendered production controller layer, the namespace's existing allow-coroot network policy selects the check's pod (every pod but the heartbeat) and allows the Kubernetes API, DNS and hooks.slack.com:443, so the job can read and deliver without a policy of its own. The validator now requires that, and fails when the pod gains the heartbeat label, when the Slack destination is removed, or when the API rule is narrowed to a port.

Still Part of #423: a Slack notification from a real production cluster is the one thing this host cannot show.

🤖 Generated with Claude Code

@devantler
devantler merged commit 1d95655 into main Nov 11, 2026
19 checks passed
@devantler
devantler deleted the claude/observability-platform-health-alert-423 branch November 11, 2026 00:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant