New to Kestra?
Use blueprints to kickstart your first workflows.
Audit a Flink job for FAILED, FAILING, or RESTARTING state, alert on breach, and optionally stop it cleanly with TriggerSavepoint.
A Flink job that keeps failing and auto-restarting does not go quiet - it keeps
consuming TaskManager slots and CPU on every retry, often for hours, while producing
nothing useful. Flink's own fault-tolerance is deliberately persistent: that is exactly
what makes a genuinely broken job look, from the outside, identical to a job that is
about to recover on its own. This blueprint polls a job's state through Flink's own
JobManager REST API, flags one stuck FAILED, FAILING, or RESTARTING, alerts Slack,
and can stop it cleanly with a savepoint-and-cancel - preserving its state for a later
resume instead of leaving it to retry indefinitely.
flink_job_health_schedule (io.kestra.plugin.core.trigger.Schedule) polls every 15 minutes, always forcing auto_remediate: "false" and dry_run: "true" through its own inputs: override regardless of this flow's defaults. Shipped disabled so you can validate a manual run first.probe_job_status (io.kestra.plugin.core.http.Request) calls GET /jobs/:jobid on Flink's own JobManager REST API with options.allowFailed: true, so a missing job or unreachable cluster does not fail the task outright.classify_job_health (io.kestra.plugin.core.debug.Return) classifies the result as UNREACHABLE (no HTTP 200), AT_RISK (state is FAILED, FAILING, or RESTARTING), or HEALTHY.route_by_health (io.kestra.plugin.core.flow.Switch) branches on that classification:HEALTHY: log_healthy records a clean check.AT_RISK: a nested route_remediation (Switch on auto_remediate) either considers remediation ("true", gated again by dry_run through check_dry_run) or only logs that remediation is disabled ("false"). When both gates allow it, trigger_savepoint_and_cancel (io.kestra.plugin.flink.TriggerSavepoint, cancelJob: true) stops the job with a savepoint. alert_at_risk always fires to Slack regardless of which path ran.UNREACHABLE: alert_unreachable pages Slack directly - a broken audit must never read as "job is fine".log_audit_status always runs last, printing the classification regardless of which branch fired.errors block alerts Slack separately if the flow itself fails outside these handled branches.auto_remediate, dry_run) between "stuck job detected" and "job actually stopped".HEALTHY/AT_RISK/UNREACHABLE) and a final status log every run, so the execution history doubles as a job-health trend.TriggerSavepoint used as a genuine operational safeguard rather than a routine checkpoint.Flink's own REST API answers one request at a time and takes no action on its own; stopping a stuck job by hand means someone noticing, logging in, and running the stop command themselves. Kestra supplies the schedule, the three-way risk classification as a first-class branch, two independent authorization gates before anything is stopped, and an execution history that shows exactly when a job first got stuck and whether it was cleanly halted.
Local testing:
docker run -d --name flink-restart-sentinel -p 8082:8081 apache/flink:latest jobmanager
docker network connect YOUR_KESTRA_NETWORK flink-restart-sentinel (only needed if the Kestra Worker runs in a separate Docker network than this container; set flink_rest_url to http://flink-restart-sentinel:8081 from inside that network, or http://localhost:8082 from the host). A JobManager alone has no TaskManager to run jobs on; submit a TaskManager container and a sample job per Flink's own standalone-cluster quickstart before exercising AT_RISK end to end.
SLACK_WEBHOOK_URL: Slack incoming webhook used by alert_at_risk, alert_unreachable, and the errors block.flink_rest_url carries no embedded credentials, matching Flink's own unauthenticated-by-default REST API. For a cluster behind an authentication proxy, add the required headers to probe_job_status and trigger_savepoint_and_cancel from {{ secret(...) }} values rather than hardcoding them.SECRET_, base64-encoded, and read back in flows with {{ secret('NAME') }} - for example SECRET_SLACK_WEBHOOK_URL=$(echo -n 'https://hooks.slack.com/...' | 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.flink_rest_url (STRING, default http://flink-jobmanager:8081): JobManager REST base URL.job_id (STRING, no default): the job audited.savepoint_target_directory (STRING, default file:///tmp/flink-savepoints): where TriggerSavepoint writes the savepoint when remediation runs.auto_remediate (SELECT: "false", "true"; default "false"): must be "true" for the savepoint-and-cancel to even be considered.dry_run (SELECT: "true", "false"; default "true"): must be explicitly "false", together with auto_remediate: "true", for the job to actually be stopped.outputs.probe_job_status.code / .body: the raw HTTP status and JSON body for every run.outputs.classify_job_health.value: HEALTHY, AT_RISK, or UNREACHABLE for every run.flink_rest_url/job_id at an existing cluster and job.SLACK_WEBHOOK_URL secret.RUNNING job and confirm log_healthy fires.AT_RISK, submit a job that intentionally throws on startup (or stop its sole TaskManager while it runs) so it enters FAILED/RESTARTING, then re-run and confirm alert_at_risk fires.auto_remediate: "true" and dry_run: "true" against that stuck job and confirm log_dry_run_remediation describes the savepoint-and-cancel without running it.auto_remediate: "true" and dry_run: "false" and confirm the job transitions to CANCELED and a savepoint appears under savepoint_target_directory.flink_job_health_schedule once you trust the check; it always runs in the safe auto_remediate: "false" / dry_run: "true" mode regardless of what you leave the flow's own defaults set to.job_id values with io.kestra.plugin.core.flow.Loop (ForEach is deprecated) to sweep every running job on a cluster in one run, after first listing jobs via GET /jobs the same way probe_job_status queries a single one.Schedule with the plugin's own io.kestra.plugin.flink.MonitorJob trigger, which fires directly on Flink job state changes instead of polling.restart-strategy-reported restart count (also available from GET /jobs/:jobid) to distinguish a job that has restarted once from one restarting in a tight loop, before escalating to remediation.Pause approval task instead of (or alongside) the static auto_remediate input, for a human-reviewed stop on a production job.Submit, SubmitSql, CancelJob, TriggerSavepoint, plus the MonitorJob trigger - none of them read and return a job's current state as a plain value. This blueprint's probe calls Flink's own GET /jobs/:jobid REST endpoint directly through the core io.kestra.plugin.core.http.Request task instead.options.allowFailed: true, not allowFailed: true at the task level. Verified from two existing in-repo flows (aikido-reachability-gated-triage.yaml, api-health-monitor.yaml); the flat task-level property does not exist on io.kestra.plugin.core.http.Request.file:// path that only exists on the JobManager's local disk will fail for jobs with tasks running on other nodes in a real multi-node cluster.cancelJob: true stops the job - it does not restart it. TriggerSavepoint with this flag is Flink's own documented pattern for a clean, stateful stop, not an in-place recovery; resuming the job from the written savepoint is a separate, deliberate Submit with restoreFromSavepoint set, not something this blueprint does automatically.auto_remediate and dry_run are independent gates, not a single boolean. Both must be "true"/"false" respectively for TriggerSavepoint to run; setting only one leaves the other still blocking.Switch case keys are quoted strings matching the classifier's exact output. "HEALTHY", "AT_RISK", and "UNREACHABLE" must match classify_job_health.value verbatim; the defaults: block exists specifically to catch any value that does not match one of those three cases.