OutputValues icon
ConnectorList icon
If icon
Set icon
Log icon
SlackIncomingWebhook icon
Pause icon
Loop icon
ConnectorResume icon
Sleep icon
Fail icon
Schedule icon

Kafka Connect Paused Connector Resume Guard

Detect Kafka Connect connectors stuck PAUSED or STOPPED, resume them with ConnectorResume behind an approval gate, then re-read and fail if one is not RUNNING.

Categories
DataInfrastructure

A paused Kafka Connect connector is not a failed one, which is exactly what makes it dangerous. Every self-healing monitor people write looks for FAILED connectors or FAILED tasks and restarts them. A PAUSED connector sails straight past all of them while moving zero records. The sink stops writing. The source stops reading. Nothing errors, so nothing alerts, so the pipeline looks healthy right up to the point somebody asks why yesterday is missing from the warehouse.

They get left behind for ordinary reasons. A release runbook pauses the sinks before a schema change and the step that resumes them is the step that gets skipped when the release runs late. A STOPPED connector from a planned cutover never gets started again. An operator pauses one connector to debug it on a Friday. A worker rolling restart leaves a connector reading RUNNING while its tasks are still PAUSED, which no connector-level check catches at all.

This blueprint finds them and puts them back, with the brake that the problem demands. Some connectors are paused on purpose, so resuming one of those restarts data movement a human deliberately stopped. That is why the allowlist is the safety model here rather than a convenience: a name on expected_paused is reported on every run but can never reach the resume step. On top of that, a connector has to have been silent for a whole grace window, measured from the first run that saw it, so a connector paused thirty seconds ago by a maintenance job that is still running is left alone. Resuming still needs auto_remediate true, dry_run false and a human resuming a paused execution. Afterwards it re-reads the Connect REST API and fails the run if a target is not RUNNING with every task RUNNING.

This blueprint was created by zkasuran.

Safety

  • Read-only by default. With the shipped inputs it lists, reports and alerts. It changes nothing.
  • A resume needs three things at once: auto_remediate true, dry_run false and a human resuming the Pause with approved=true. An unanswered resume reads as not approved.
  • A connector named in expected_paused is never a target. It is split out before the age check, so no combination of inputs can route it into the resume loop.
  • A connector has to hold its silent state for the whole min_paused_minutes window before it is eligible. The window is measured from the first run that saw that exact state, so a connector that moved from PAUSED to STOPPED starts again.
  • The KV first-seen record carries no ttl, so the window cannot quietly expire and hand a freshly paused connector an old date. After a confirmed resume the names are pruned, so one paused again later gets its own window.
  • FAILED connectors are counted in the report but never touched. Restarting a failed connector is a different operation with different risks, so it belongs to a different flow.
  • The re-read after the resume is the real gate. A connector that stayed paused, came back with no tasks, came back with a paused task or vanished from the worker fails the run and alerts instead of reporting success.
  • The resume loop runs one connector at a time, so a worker that refuses one resume does not take the rest of the batch with it.

How it works

  1. freeze captures one timestamp for the run, so the detect, the cursor and the age check all agree on now.
  2. list_connectors (io.kestra.plugin.kafka.ConnectorList with expandStatus: true, read-only) returns every connector on the worker with its connector state and the state of each of its tasks.
  3. analyze marks a connector silent when its own state is PAUSED or STOPPED, as well as when it reads something else while one of its tasks is PAUSED. It records a reason per connector and splits the allowlisted names out immediately.
  4. route (io.kestra.plugin.core.flow.If) branches. With nothing silent off the allowlist it clears the cursor and logs the inventory it checked.
  5. cursor_merge keeps the first time each candidate was seen in its current state, carrying the old date forward only when the state signature matches. save_cursor (io.kestra.plugin.core.kv.Set) stores it with no ttl.
  6. split divides candidates into targets past min_paused_minutes and held ones that are still too recent.
  7. log_findings writes the whole picture to the execution log. alert posts the same thing to Slack, because a connector that is silent without being failed is worth knowing about whether or not this run is allowed to fix it.
  8. remediate_gate runs only when auto_remediate is true, dry_run is false and there is at least one target. Otherwise report_only logs what would have been resumed.
  9. approve (io.kestra.plugin.core.flow.Pause) holds the execution for a human. do_resume reads onResume.approved ?? false, so an unanswered resume does not count as approval.
  10. remediate_resume (io.kestra.plugin.core.flow.Loop, concurrencyLimit: 1) calls io.kestra.plugin.kafka.ConnectorResume once per target.
  11. settle waits verify_delay, then reverify re-reads the Connect REST API and verify_eval checks every target for RUNNING with every task RUNNING.
  12. verify_result fails the run through io.kestra.plugin.core.execution.Fail when any target is still wrong, listing exactly which. On a clean read back it prunes those names from the cursor and logs the result.

What you get

  • A detector for the one broken state that FAILED-based monitoring cannot see.
  • Task-level reading, so a connector that reads RUNNING over paused tasks is caught too.
  • An allowlist that a deliberately paused connector can sit on safely.
  • An age window held in Kestra KV, so a pause that is part of a running maintenance job is never yanked back.
  • A resume that is read-only by default, gated on two inputs and a human approval.
  • A re-read of the Connect REST API that fails the run if the resume did not land, with the state and task counts of every target in the failure message.
  • A Slack alert on detection plus an errors: alert on failure.

Who it's for

Platform teams running Kafka Connect in production. If your release runbook has a step that pauses connectors before a schema change, somebody has already shipped a day where the resume step got skipped. Data engineers who have debugged a silent pipeline where every health check was green will recognise the state this looks for.

Why orchestrate this with Kestra

The detect, decide, fix, re-verify loop needs state that outlives one run, a human in the middle of it and a hard stop when the fix does not work. Kestra KV holds the first-seen dates so the age window survives restarts. Pause puts a person in front of the one irreversible-feeling action without anybody watching a terminal. If keeps the write behind two inputs. Fail turns a failed re-verify into a failed execution with an alert rather than a log line nobody reads. A shell script doing the same thing would need its own state store, its own approval channel and its own alerting.

Tested end to end

Run on Kestra 2.0.5 against Kafka 3.9.1 in KRaft mode with a Kafka Connect 7.9.0 worker, using the bundled FileStreamSource connector so the rig needs no external system.

  • Healthy worker, every connector running: the detector reported zero candidates and took the clean branch.
  • One connector paused through PUT /connectors/{name}/pause: detected with reason connector PAUSED, with the running connector beside it untouched.
  • dry_run=true: the target was listed with the resume it would have made. A GET /connectors/{name}/status straight after still read PAUSED.
  • auto_remediate=true, dry_run=false, approved: ConnectorResume ran, the re-read returned RUNNING with every task RUNNING. The source file offset started advancing again after sitting frozen through the pause.
  • Pause resumed without approval: nothing was resumed and the connector was still PAUSED afterwards.
  • A paused connector on expected_paused: reported as expected paused, never a target, still PAUSED at the end of the run.
  • A connector paused inside the age window: held as too recent, never a target.
  • A connector whose config points at a source that no longer exists: resumed, came back not running, so the run FAILED on the re-read rather than reporting success.
  • Connect REST endpoint unreachable: the task failed and the errors: block posted to Slack.

Prerequisites

A Kafka Connect worker whose REST API this Kestra instance can reach, plus a Slack incoming webhook.

Secrets

  • KAFKA_CONNECT_URL: base URL of the Connect worker REST API, for example http://connect:8083.
  • SLACK_WEBHOOK_URL: Slack incoming webhook for the findings alert plus the failure alert.

If your Connect worker sits behind basic auth, add username and password to each Kafka task from secrets of your own.

Inputs

  • connect_url: Connect REST endpoint. Defaults to the secret above.
  • expected_paused: connector names allowed to stay paused. Never resumed.
  • min_paused_minutes: minutes a connector must stay silent before it is eligible. Defaults to 30.
  • verify_delay: wait before the re-read. Defaults to PT10S.
  • auto_remediate: defaults to false.
  • dry_run: defaults to true.

Quick start

  1. Add the two secrets.
  2. Run it as shipped. It reports what is silent on the worker without changing anything.
  3. Put any connector that is paused on purpose on expected_paused and run it again. Those names move to the expected-paused line of the report.
  4. Check the target list in the log. When it holds only connectors that should be running, run with auto_remediate=true and dry_run=false, read the pause description, then resume the execution with approved=true.
  5. Enable the hourly_scan trigger once you are happy with the report. It runs in safe mode, so it reports without changing anything until you give it different inputs.

How to extend

  • Lower min_paused_minutes to minutes for a cluster where no legitimate pause lasts long. Raise it to days where migrations run slow.
  • Replace the allowlist with a config lookup if you already track planned maintenance somewhere, by pointing expected_paused at a task that reads it.
  • Add io.kestra.plugin.kafka.ConnectorGetOffsets before the resume to record where each connector stood, which makes it easy to show how far behind it fell.
  • Route the Slack alert to a different channel per connector prefix with a Switch on the target names.
  • Pair it with a FAILED-connector restart monitor. This one deliberately leaves failed connectors alone, so the two cover the whole space without overlapping.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.