New to Kestra?
Use blueprints to kickstart your first workflows.
Savepoint a running Flink job, stop and relaunch it from that savepoint, then re-verify it resumed from the savepoint and is still consuming.
A long running Flink streaming job holds state in memory and in its checkpoints. Restarting it for a config change, a version bump or a node drain means stopping it and starting it again. A plain stop and start throws that state away. The job then reprocesses from the start or from the latest offsets with empty operator state, which breaks exactly the aggregations and the deduplication the job exists for. The safe path is a savepoint: snapshot the state, stop from that snapshot, start the new run from it.
This blueprint runs that savepoint and restart as a drill against a real Flink cluster. It finds the
running job through the REST API, takes a savepoint and in remediate mode stops the job with a
savepoint then relaunches it from that savepoint with the io.kestra.plugin.flink.Submit task. It
then reads the cluster again and fails the run unless a job is running from the savepoint and its
input record count is still climbing. A savepoint only dry run proves the state can be captured
without touching the live job.
This blueprint was created by zkasuran.
The restart is destructive: it stops a live streaming job. It runs only when auto_remediate is
true and dry_run is false, then only after a human approves the approve_restart pause. The
default inputs and the schedule leave the flow in savepoint only mode.
The savepoint and the stop with savepoint are driven through the Flink REST API with
io.kestra.plugin.core.http.Request. The dedicated TriggerSavepoint and CancelJob tasks in the
bundled plugin build their status poller with a retry interval equal to its own ceiling, which Kestra
rejects before the first poll, so they cannot complete a savepoint on this version. The relaunch uses
the Submit task, which works. The REST calls hit the same endpoints those tasks would.
detect (io.kestra.plugin.core.http.Request) reads GET /jobs/overview from the Flink REST API.assess (io.kestra.plugin.core.output.OutputValues) keeps the RUNNING jobs, applies the
job_name filter and picks the target. findings_alert posts to Slack naming the job the drill
will touch. drill (io.kestra.plugin.core.flow.If) runs the rest only when a target exists.describe and baseline read the target job and sum read-records across its vertices as the
before number.trigger_savepoint posts POST /jobs/{id}/savepoints with cancel-job false. wait_savepoint
(io.kestra.plugin.core.flow.LoopUntil) polls the request until it is COMPLETED. read_savepoint
reads the savepoint path. savepoint_guard fails the run if the savepoint did not complete or the
job did not stay RUNNING, so a dry run that disturbed the job is caught.remediate_gate requires auto_remediate true and dry_run false. approve_restart
(io.kestra.plugin.core.flow.Pause) holds for a human.stop_with_savepoint posts POST /jobs/{id}/stop, wait_stop polls it to
COMPLETED and read_stop_savepoint reads the new savepoint path. choose_savepoint uses
savepoint_path_override when set, otherwise that path.download_jar (io.kestra.plugin.core.http.Download) fetches the job JAR into Kestra storage.
restore (io.kestra.plugin.flink.Submit) relaunches the job with restoreFromSavepoint set to
the chosen path.verify_restart samples the relaunched job twice, reads GET /jobs/{id}/checkpoints for the
restore record, then verify fails the run through io.kestra.plugin.core.execution.Fail unless
the job is RUNNING, its latest.restored.external_path is the savepoint it was told to use and
its read-records advanced by at least min_records_delta. A relaunch that returned no job id
fails too.errors alerts Slack when the flow fails, for example when the REST API is unreachable.A safe restart is several ordered REST calls with waits in between: trigger a savepoint, poll it to
completion, stop with a savepoint, poll again, relaunch from the exact path, then read the cluster
back. Kestra adds the approval gate before anything stops, the record count comparison that decides
whether the run passes or fails, the Slack alerts and an execution history that shows every drill and
the savepoint path it used. The relaunch goes through the typed io.kestra.plugin.flink.Submit task.
On Kestra 2.0.5 OSS against a real Flink 1.20.5 standalone cluster (one JobManager, one TaskManager)
on the same Docker network, with a local HTTP sink standing in for Slack. The JobManager and the
TaskManager shared one savepoint and checkpoint volume, with execution.checkpointing.interval set
to 10s. The job under drill was the Flink StateMachineExample, an unbounded keyed streaming job that
reads a generated event stream. Every number below was confirmed against the Flink REST API directly
(job state, the savepoint path on the shared volume and the read-records metric), not only through
Kestra's log.
| Run | Inputs | Result |
|---|---|---|
| detect, no target | defaults, no job running | execution 7KODBYMFuON8426yK1NxBQ succeeded with state no target. Nothing was touched |
| dry run savepoint | dry_run true against a running StateMachineExample |
execution 6RuNAVKNCuP1jpSSx7CWy9 took savepoint savepoint-2e2b34-d07eb4f31d58, its _metadata file was present on the shared volume and the job stayed RUNNING |
| restart | auto_remediate true, dry_run false, approved |
execution 7bEi59j7wjGR4IayiGAn4c stopped the job with savepoint savepoint-2e2b34-9144bca657b7 then relaunched job a9b023fcddc920f55565a4ff65fc5baa. The relaunched job reported latest.restored.external_path equal to that savepoint with is_savepoint true, so it resumed from the savepoint. Its read-records went from 0 to 24413 across the two in run samples. A later REST sample read 39522014 then 39580463 over 14s, so it kept consuming |
| failure, bad savepoint | savepoint_path_override set to a path that does not exist, approved |
execution 3QgQHKQJwsczE1NcMUH7Nz stopped the job with savepoint savepoint-a9b023-ba802c3b6e04, the relaunch returned HTTP 400 with no job id, verify failed the run and the errors block posted to Slack. No job was left running, which is the loss the drill is built to catch |
| REST API down | rest_url at a host that does not resolve |
execution 6claRATZ8ZZF2Sto5h8DfZ failed at detect and the errors block posted to Slack |
The savepoint paths and the record counts above were read from the Flink REST API and the shared
volume directly, not only from Kestra's log. The relaunched job restored keyed state from the
savepoint while its read-records counter started again at 0, which is correct: the counter is per
run while the state is what the savepoint carries. The restore record is the proof it resumed.
rest_url.s3://). A savepoint written on one node and read on another fails when the
path is local to a single container.jar_url reachable from Kestra and its entry_class. The flow
downloads the JAR into Kestra storage, because the Submit task reads the JAR from Kestra storage
rather than an arbitrary URL.SLACK_WEBHOOK_URL: used by findings_alert and the errors block. In Kestra OSS, provide it as
the base64 encoded environment variable SECRET_SLACK_WEBHOOK_URL.| Input | Default | Purpose |
|---|---|---|
rest_url |
http://flink-jobmanager:8081 |
Flink JobManager REST base URL |
job_name |
empty | Name filter, empty picks the first running job |
jar_url |
empty | Job JAR URL, fetched into Kestra storage for the relaunch |
entry_class |
empty | Fully qualified main class used on the relaunch |
parallelism |
2 |
Parallelism for the relaunched job |
savepoint_dir |
file:///flink-savepoints |
Shared directory where savepoints are written |
drain |
false |
Drain streaming input before the stop with savepoint |
allow_non_restored_state |
false |
Skip savepoint state that no longer maps to the job graph |
savepoint_path_override |
empty | Relaunch from this exact path instead of the stop savepoint |
min_records_delta |
1 |
Records the relaunched job must gain between the two samples |
poll_seconds |
180 |
Timeout for a savepoint or a stop with savepoint |
sample_seconds |
12 |
Seconds between the two record count samples |
auto_remediate |
false |
Must be true before a restart |
dry_run |
true |
Must be false before a restart |
SLACK_WEBHOOK_URL secret.rest_url to your Flink JobManager REST URL and run with the defaults. The flow takes a
savepoint of the running job and leaves it running. Read the savepoint path in the logs.job_name if more than one job runs and you want a specific one.jar_url and entry_class for the job, set auto_remediate to true
and dry_run to false, run it and approve the paused execution. The flow stops the job with a
savepoint, relaunches it from that savepoint and fails if it did not come back consuming.daily trigger once the inputs match your cluster. It stays in savepoint only mode.savepoint_path_override.savepoint_dir at object storage so the savepoint survives the loss of any single node.allow_non_restored_state true,
to turn the drill into an upgrade.read-records against the before number over a longer window to assert throughput, not
just that the count moved.