Schedule icon
List icon
If icon
SlackIncomingWebhook icon
Log icon

Ceph RBD Snapshot Protection Gate

Audit a Ceph RBD image's snapshots for unprotected ones via the Dashboard API and alert Slack, read-only.

Categories
DataInfrastructure

An RBD snapshot without Ceph's own protected flag set is one rbd snap rm away from being gone - no confirmation, no recovery, whether that command runs by a typo, a careless cleanup script, or someone who did not realize that snapshot was the only backup of a volume. This blueprint lists every snapshot on a named RBD image through the Ceph Dashboard API and alerts Slack by name the moment any of them is not protected.

How it works

  1. periodic_protection_check (io.kestra.plugin.core.trigger.Schedule) runs every 6 hours. Shipped disabled so you can validate a manual run first.
  2. list_image_snapshots (io.kestra.plugin.ceph.rbd.snapshots.List, fetchType: FETCH) calls GET /api/block/image/{pool_name}/{image_name} and returns the embedded snapshots array, each with id, name, size, timestamp, and is_protected.
  3. evaluate_snapshot_protection (io.kestra.plugin.core.flow.If) filters snapshots with a jq expression for is_protected == false, branching on whether that count is greater than zero.
    • else (HEALTHY): log_protection_healthy records a clean check.
    • then (AT_RISK): alert_unprotected_snapshots names every unprotected snapshot in the Slack message.
  4. log_audit_complete always runs last, printing the headline count regardless of which branch fired.
  5. The errors block alerts Slack separately if the flow itself fails outright - an unreachable Dashboard host, bad credentials, or a pool_name/image_name that does not exist must not read as "no risk".

What you get

  • A direct read of Ceph's own is_protected flag per snapshot, the same signal rbd snap protect/unprotect controls - not a guess based on snapshot naming or age.
  • The exact snapshot names in the Slack alert, so remediation starts with a list, not a bare count.
  • A read-only design: this flow calls one list endpoint - it cannot create, delete, protect, clone, or roll back any snapshot or image.
  • A final status log every run, gap or not, so the execution history doubles as a protection-compliance trend for that image.

Who it's for

  • Storage and platform teams running Ceph RBD who treat certain snapshots as de facto backups and need to know immediately if one of them is actually deletable by a plain command.
  • SREs auditing snapshot hygiene on volumes backing production data, where an accidental rbd snap rm would be a real incident, not an inconvenience.
  • Anyone evaluating the Ceph plugin who wants a third worked example beyond cluster.GetHealth and pools.List/Update - this one exercises the rbd.snapshots package, the plugin's third and final real feature area, none of which overlap with this repo's other two Ceph blueprints.

Why orchestrate this with Kestra

rbd snap ls run by hand, or the equivalent Dashboard API call, answers one moment in time; it does not decide a cadence, filter for the unprotected ones specifically, or notify anyone. Kestra supplies the schedule, the protection-state filter as a first-class branch, and an execution history that shows exactly when a snapshot went unprotected and for how long before anyone noticed.

Prerequisites

  • A reachable Ceph cluster with the Dashboard Manager module enabled and at least one RBD image with snapshots.
  • A Dashboard account with permission to read /api/block/image/{image_spec}.
  • A Slack incoming webhook for alerts.

Local testing: as with this repo's other Ceph blueprints, Ceph is a multi-daemon distributed system with no official single-container demo image (confirmed against Ceph's own containerized-deployment documentation). Use cephadm bootstrap to stand up a single-node cluster with the Dashboard enabled, create an RBD image, and take a snapshot with rbd snap create (left unprotected on purpose) to exercise this flow. The plugin's own default Dashboard port is 8443, which does not collide with Kestra's own UI on 8080.

Secrets

  • CEPH_USERNAME / CEPH_PASSWORD: Dashboard account credentials used by list_image_snapshots.
  • SLACK_WEBHOOK_URL: Slack incoming webhook used by alert_unprotected_snapshots and the errors block.
  • In Kestra OSS (no Enterprise secrets backend), secrets are supplied as environment variables prefixed SECRET_, base64-encoded, and read back in flows with {{ secret('NAME') }} - for example SECRET_CEPH_PASSWORD=$(echo -n '<your-local-password>' | base64). This keeps credentials out of the flow YAML but, per Kestra's own documentation, offers no encryption at rest or access control beyond the host environment; use the Enterprise secrets backend for stronger guarantees.

Inputs

  • ceph_host (STRING, default localhost): Ceph Dashboard hostname or IP.
  • ceph_port (INT, default 8443): Ceph Dashboard REST API port.
  • pool_name (STRING, default rbd): pool the target image belongs to.
  • image_name (STRING, default data-volume): RBD image audited.

Outputs

  • outputs.list_image_snapshots.snapshots / .total: the full snapshot list and count for the image, every run.

Quick start

  1. Stand up a Ceph cluster with the Dashboard module enabled, for example via cephadm bootstrap.
  2. Create an RBD image and a deliberately unprotected snapshot: rbd create rbd/data-volume --size 1G then rbd snap create rbd/data-volume@test-snap.
  3. Add the CEPH_USERNAME, CEPH_PASSWORD, and SLACK_WEBHOOK_URL secrets.
  4. Run the flow manually with the defaults and confirm alert_unprotected_snapshots fires, naming test-snap.
  5. Run rbd snap protect rbd/data-volume@test-snap and re-run to confirm log_protection_healthy fires instead.
  6. Enable periodic_protection_check once you trust the check.

How to extend

  • Loop over several (pool_name, image_name) pairs with io.kestra.plugin.core.flow.Loop to run the same gate across every image a platform team considers backup-critical.
  • Use io.kestra.plugin.ceph.rbd.images.List first to discover every image in a pool automatically, instead of naming one image_name per flow run.
  • Parse timestamp on a best-effort basis (accepting the plugin's own documented caveat that its format is Ceph-version dependent) to add an age-based signal alongside protection state, clearly flagging it as approximate rather than authoritative.
  • Chain this flow after a backup or clone job (io.kestra.plugin.ceph.rbd.snapshots.Clone) as a verification step, confirming the snapshot it just depended on is still protected before trusting the clone.

Pitfalls

  • Snapshot timestamp is a raw, unparsed string by design. Verified directly from the plugin's own source comment: the format is Ceph-version dependent, so this flow deliberately does not attempt date arithmetic on it - an age-based "stale snapshot" check would need its own, version-aware parsing this blueprint does not attempt.
  • There is no Protect task in this plugin. Verified from the plugin's own source: rbd.snapshots only exposes Clone, Create, Delete, List, Rollback - none of them sets is_protected, which is exactly why alert_unprotected_snapshots only reports, it has nothing safe to call to fix the gap itself.
  • This only audits the one image you name. pool_name/image_name target exactly one RBD image per run - a pool with many images needs a Loop (see How to extend) or one flow execution per image, not a single run covering the whole pool.
  • is_protected guards against deletion, not against the image itself being deleted. A protected snapshot still disappears if the parent image is deleted along with all its snapshots through a sufficiently privileged call - protection only blocks the specific rbd snap rm command on that one snapshot.
  • This blueprint is UNTESTED against a live Ceph cluster. rbd.snapshots.List's properties and SnapshotInfo's fields (id, name, size, timestamp, is_protected) are taken verbatim from the plugin's source on main - but no Docker/Ceph environment or Kestra engine was run to execute this flow end to end.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.