Schedule icon
Status icon
Return icon
Switch icon
Log icon
SlackIncomingWebhook icon

Argo CD Application Drift Sentinel

Check an Argo CD application's sync and health status on a schedule, change nothing on the cluster, and alert Slack on drift or degraded health.

Categories
CloudInfrastructure

A GitOps application can drift out of sync with its source of truth, or quietly degrade on the cluster, long after the pipeline that deployed it has finished and stopped watching. This blueprint turns a scheduled, read-only argocd app get call into a Day 2 health gate: io.kestra.plugin.argocd.apps.Status reports the application's current sync and health state without changing anything on the cluster, a single Pebble expression classifies the combination, and the result routes to a quiet log, an informational Slack note, or a Slack page depending on severity.

How it works

  1. check_app_status (io.kestra.plugin.argocd.apps.Status) reads argocd_application's sync status (Synced/OutOfSync) and health status (Healthy/Progressing/Degraded/Suspended/Missing/Unknown) from the Argo CD server, along with its conditions and managed resources. Setting refresh to "true" forces a live re-check instead of Argo CD's cached state, at the cost of an extra API call. allowFailure: true keeps a server or application lookup failure from aborting the flow outright.
  2. classify_app_drift (io.kestra.plugin.core.debug.Return) evaluates a single Pebble expression against both fields together: missing either entirely becomes UNREACHABLE; a health status of Degraded, Missing, or Unknown becomes CRITICAL regardless of sync state, since a broken application matters more than whether Git and the cluster currently agree; an out-of-sync application or a transitional health status (Progressing, Suspended) becomes WARNING; otherwise HEALTHY.
  3. route_by_drift (io.kestra.plugin.core.flow.Switch) branches on that classification: HEALTHY logs a quiet confirmation; WARNING posts an informational Slack note; CRITICAL and UNREACHABLE each page Slack with the reported sync and health status; a defaults fallback logs the raw value for any classification outside the four known cases.
  4. log_audit_result (io.kestra.plugin.core.log.Log) writes one audit line per run, independent of which Switch branch fired, so the check's own history stays queryable even if Slack delivery fails.
  5. The flow-level errors block pages Slack separately if the flow fails outright, so a broken sentinel is never mistaken for a healthy application.
  6. The app_drift_schedule trigger checks every 6 hours and ships disabled: true, with explicit inputs: overrides for argocd_application and force_refresh so the scheduled run's target and cache behavior are pinned regardless of this flow's own defaults.

What you get

  • A scheduled, read-only drift and health check for any Argo CD application with zero custom argocd CLI scripting.
  • A single status output (HEALTHY / WARNING / CRITICAL / UNREACHABLE) other flows can gate on.
  • Three distinct Slack messages, one per severity, each carrying the reported sync and health status.
  • An audit log line on every run, separate from Slack, so the check's own history survives a webhook outage.

Who it's for

  • Platform teams running GitOps with Argo CD who have no check for drift or degraded health once a deploy pipeline finishes.
  • On-call engineers who want a signal that a manual cluster change drifted from Git, or that a rollout never actually settled, before it is discovered by accident.
  • Teams that already verify status immediately after a sync (as in a deploy pipeline) and want a complementary, continuous signal that is not tied to any specific deploy.

Why orchestrate this with Kestra

Argo CD's own UI shows drift and health in real time, but nothing pages anyone when it happens unless someone is watching the dashboard. Kestra's scheduled, read-only Status check gives that page automatically: the schedule, the classification, and the Slack call are all one auditable execution history, with the Switch branch ready to grow into real remediation (triggering the existing Sync task on drift, opening a ticket) without rewriting the check itself.

Prerequisites

  • An Argo CD server reachable at the ARGOCD_SERVER secret, with an existing Application named argocd_application.
  • An Argo CD auth token with read access to that application.
  • A Slack incoming webhook for the WARNING, CRITICAL, UNREACHABLE, and flow-failure alerts.

Secrets

  • ARGOCD_SERVER: Argo CD server address, e.g. argocd.example.com or argocd-server.argocd.svc.cluster.local:443.
  • ARGOCD_TOKEN: Argo CD API token with at least read access to the application.
  • SLACK_WEBHOOK: Slack incoming webhook used for every alert branch and the flow-failure alert.

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_SLACK_WEBHOOK=$(echo -n 'https://hooks.slack.com/services/<placeholder>' | 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.

Quick start

  1. Start a local single-node Kubernetes cluster in Docker, on its own API server port well away from Kestra's own UI: docker run -d --name k3s-server --privileged -p 6443:6443 rancher/k3s:latest server --tls-san=localhost
  2. If Kestra is also running in Docker, check docker network ls for Kestra's network and attach the cluster to it: docker network connect YOUR_KESTRA_NETWORK k3s-server
  3. Install Argo CD into the cluster and port-forward its API server to a port that does not collide with Kestra's own 8080: docker exec k3s-server kubectl create namespace argocd docker exec k3s-server kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml docker exec k3s-server kubectl port-forward svc/argocd-server -n argocd 8443:443
  4. Retrieve the initial admin password and generate an API token: docker exec k3s-server kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" argocd login localhost:8443 --username admin argocd account generate-token
  5. Add ARGOCD_SERVER (localhost:8443), ARGOCD_TOKEN, and SLACK_WEBHOOK as secrets, create a sample Application, and run the flow once manually to confirm status reads HEALTHY.
  6. Enable app_drift_schedule once pointed at a real server and application.

How to extend

  • Chain a WARNING branch into the existing io.kestra.plugin.argocd.apps.Sync task for automatic drift correction instead of a Slack note alone.
  • Loop this flow's argocd_application input over every application a team owns using io.kestra.plugin.core.flow.Loop, instead of checking one application per execution.
  • Parse outputs.check_app_status.conditions or outputs.check_app_status.resources to name the specific resource causing a Degraded health status in the Slack alert.
  • Push status into a KV store and alert only on state transitions instead of every run.

Inputs

  • argocd_application: the Argo CD Application to check (type STRING, default my-argocd-app).
  • force_refresh: type SELECT, values ["true", "false"], default "false"; forces a live cluster re-check instead of Argo CD's cached state.

Outputs

  • status: one of HEALTHY, WARNING, CRITICAL, UNREACHABLE.
  • Example path: {{ outputs.classify_app_drift.value }}, also exposed as the flow output {{ outputs.status }}.

Links

Pitfalls

  • Without refresh: "true", Argo CD can serve a cached sync/health status for a short window after a cluster-side change; set it to "true" if the check needs to see drift the moment it happens, at the cost of one extra API call per run.
  • Progressing health is classified WARNING, not CRITICAL, because it is normal during any rollout; a WARNING that persists across several scheduled checks in a row is the real signal of a stuck rollout, not a single occurrence.
  • This flow only ever calls argocd app get; it never calls argocd app sync, argocd app rollback, or argocd app delete, so there is nothing destructive to default to dry-run for.
  • ARGOCD_TOKEN only needs read access to the target application; scope it narrowly rather than reusing an administrative token for a monitoring check.
See How

New to Kestra?

Use blueprints to kickstart your first workflows.