Schedule icon
Request icon
Script icon
Set icon
If icon
Pause icon
SlackIncomingWebhook icon
Log icon

Resolve a Nomad Deployment Stuck Waiting on Manual Canary Promotion

Detect a Nomad deployment stuck running on a canary awaiting promotion, track it in KV, promote or fail it with approval, verify.

Categories
Infrastructure

A Nomad rolling deployment that uses canaries can get stuck in status "running" indefinitely: canaries can go healthy and simply wait for manual promotion when auto_promote isn't set, or a canary can go unhealthy without auto_revert configured to clean it up. Either way, the deployment sits there with old and new allocations running side by side until a human notices and runs nomad deployment promote or nomad deployment fail by hand. This blueprint watches a job's latest deployment, tracks real elapsed stuck time across runs (not a single snapshot), and resolves it through Nomad's own deployment API once a human has approved the action.

How it works

  1. periodic_deployment_check (io.kestra.plugin.core.trigger.Schedule) runs every 5 minutes, shipped disabled so a manual run can be verified first.
  2. fetch_latest_deployment (io.kestra.plugin.core.http.Request) calls Nomad's GET /v1/job/{job_id}/deployment, confirmed from Nomad's own source to return the flat Deployment object (Status, StatusDescription, TaskGroups), not a wrapped response.
  3. evaluate_deployment_state (io.kestra.plugin.scripts.python.Script) compares the current deployment ID against the previous run's Kestra KV snapshot under nomad_deployment_tracker_{job_id} to compute real elapsed minutes stuck in status "running", classifies canary health from each task group's DesiredCanaries/HealthyAllocs/UnhealthyAllocs, and recommends PROMOTE (all canaries healthy) or FAIL (at least one unhealthy) once stuck_threshold_minutes is exceeded.
  4. save_tracking_state (io.kestra.plugin.core.kv.Set) persists the snapshot, or clears it once the deployment has resolved on its own.
  5. evaluate_action_needed (io.kestra.plugin.core.flow.If) gates the remediation path behind the computed verdict; a clean or not-yet-stuck-long-enough run just logs and exits.
  6. pause_for_operator_approval (io.kestra.plugin.core.flow.Pause) waits indefinitely for a human to resume before anything is mutated.
  7. execute_remediation branches on the recommendation and calls Nomad's real PUT /v1/deployment/promote/{id} (with All: true) or PUT /v1/deployment/fail/{id}.
  8. verify_resolution re-fetches the job's latest deployment and evaluate_verification confirms status is no longer "running" before declaring success; if it's still running, Slack is told explicitly that manual investigation is needed instead of claiming a false success.
  9. The errors block alerts Slack separately if the flow itself fails.

What you get

  • Real elapsed-time tracking of how long a deployment has actually been stuck, computed from a Kestra KV snapshot across runs, not a single poll.
  • An explicit, tool-specific PROMOTE vs FAIL recommendation computed from each task group's real canary health counts, not a flat threshold.
  • A human approval gate between detection and any mutation of live deployment state.
  • Verified resolution: the flow re-checks Nomad's own deployment status after acting instead of trusting the mutation call's HTTP response code alone.

Who it's for

  • Platform engineers running Nomad rolling deployments with canaries who want stuck rollouts surfaced and resolved instead of discovered by a user-facing incident.
  • SREs who are tired of nomad deployment status being the first step of every "is the deploy actually done" investigation.
  • Teams that deliberately run with auto_promote: false for a manual gate, but still want that gate to page someone instead of silently sitting forever.

Why orchestrate this with Kestra

Nomad tracks deployment state but has no built-in alert for "this has been running too long" and no concept of operator approval before a promote or fail call. A cron job hitting the Nomad API ad hoc has no cross-run memory of when a deployment first became stuck, no safe pause-and-resume gate, and no execution history proving what was actually verified after the mutation. Kestra supplies the KV-backed elapsed-time tracking, the approval gate, and a visible, auditable run history.

Prerequisites

  • A Nomad cluster (or nomad agent -dev for local testing) reachable from the Kestra worker, with a job running a rolling deployment with canaries configured.
  • A Nomad ACL token with permission to read and act on deployments for the target namespace, if ACLs are enabled.
  • A Slack incoming webhook for alerts.

Secrets

  • NOMAD_TOKEN: Nomad ACL token sent as the X-Nomad-Token header by every HTTP call in this flow, confirmed from Nomad's own api/api.go client source. Leave the secret empty/unset if ACLs are disabled on the target cluster.
  • SLACK_WEBHOOK_URL: Slack incoming webhook used by the resolution alerts 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') }}.

Inputs

  • nomad_address (STRING, default http://localhost:4646): base URL of the Nomad HTTP API.
  • nomad_namespace (STRING, default default): Nomad namespace the job runs in.
  • job_id (STRING, no default): the job whose latest deployment is audited.
  • stuck_threshold_minutes (INT, default 15): minutes a deployment must stay in status running before this flow offers to act.

Outputs

  • outputs.needs_action / outputs.recommended_action: the headline verdict for every run.
  • outputs.evaluate_deployment_state.vars.minutes_stuck / .reasoning: the full computed detail behind that verdict.

Quick start

  1. Run nomad agent -dev locally, or point nomad_address at a real test cluster.
  2. Deploy a job with update { canary = 1, auto_promote = false } so a rollout will genuinely sit waiting for manual promotion.
  3. Add the NOMAD_TOKEN (if ACLs are enabled) and SLACK_WEBHOOK_URL secrets.
  4. Run the flow manually once with a short stuck_threshold_minutes (e.g. 1) while the deployment is mid-canary; confirm evaluate_deployment_state reports needs_action: true after the second run.
  5. Resume the pause_for_operator_approval step and confirm promote_deployment (or fail_deployment) fires and verify_resolution shows the deployment has left status running.
  6. Enable periodic_deployment_check once you trust the check, and set stuck_threshold_minutes to a realistic value for your rollout SLAs.

How to extend

  • Loop this flow over every job in a namespace (fetch /v1/jobs, filter to those with an active deployment) instead of auditing one job_id at a time.
  • Add a Slack interactive approval (buttons that resume the flow) in place of the bare Pause, so the human decision happens in Slack rather than the Kestra UI.
  • If your org's policy is "always revert, never promote automatically" even for healthy canaries, make recommended_action configurable via an input instead of letting canary health decide it.

Pitfalls

  • This blueprint never disables auto_revert/auto_promote on the job itself. It only acts on deployments that are already stuck because those settings weren't enough (or weren't set) for this particular rollout.
  • The elapsed-stuck-time tracking is keyed by job_id, and only tracks the single latest deployment Nomad reports. If a job is redeployed while already stuck (a new deployment ID appears), the timer resets rather than carrying over, since the new deployment is a genuinely different rollout attempt.
  • FAIL is only a recommendation when at least one canary is unhealthy; it does not itself guarantee a rollback. Whether failing a deployment actually reverts allocations depends entirely on the job's own auto_revert setting, confirmed from Nomad's own DeploymentState.AutoRevert field - this flow does not set that field, it only calls fail.
  • verify_resolution checks that status is no longer "running", not that the outcome was the one requested. A promote call that Nomad itself rejects (for example, a job redeployed mid-flow) could still leave status as "running" or move it to "cancelled" instead of "successful"; the Slack alert names the actual post-verification status for a human to read.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.