$crond.io
integrations / kubernetes

Kubernetes CronJob Monitoring

Monitor your Kubernetes CronJobs without rebuilding your job images. A Helm chart ships your ping keys as a Secret and provides a template macro you add to each CronJob spec — an init container injects the agent, and your job runs through it. No operator, no CRD, no changes to your job code.

Why this is hard by default

Kubernetes' CronJob controller does the bare minimum: it tries to create a Job at the scheduled time and records what happened in the resource's status. If the cluster is under pressure, scheduled runs can be missed entirely — and unless someone watches kubectl get cj, the failure is invisible.

Bolting heartbeat monitoring onto existing CronJobs usually means editing the workload image to call a ping URL on exit. crond avoids the rebuild: the agent is injected at runtime through an init container, so you wrap the CronJob spec with a one-line Helm macro instead of touching the image.

Kubernetes CronJob monitoring is available on the Pro and Enterprise plans — the Kubernetes monitor source is gated server-side.

1. Create the monitor and install the chart

In the dashboard, click New monitor and pick the Kubernetes CronJob tile to get a ping-key UUID. Then install the chart with that key:

# Install with your monitor's ping key
helm install crond-agent \
oci://ghcr.io/platops-security/crond-agent/charts/crond-agent \
--version 0.2.0 \
--namespace my-jobs --create-namespace \
--set pingKeys.PING_KEY_BACKUP=<uuid-from-dashboard>

Each entry under pingKeys becomes one key in the chart-owned Secret. Install per-namespace and pass multiple keys to monitor multiple CronJobs.

2. Wrap your CronJob

The chart provides a Helm template macro you call from inside your own CronJob spec — most teams embed it in an umbrella chart that depends on crond-agent:

apiVersion: batch/v1
kind: CronJob
metadata:
name: nightly-backup
namespace: my-jobs
spec:
schedule: "0 2 * * *" # match your crond.io monitor
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
{{- include "crond-agent.wrap" (dict
"context" $ "envKey" "PING_KEY_BACKUP"
"image" "myco/backup:1.0"
"command" (list "/opt/backup.sh")) | nindent 10 }}

The macro expands to:

  • An emptyDir volume shared between containers
  • An init container that copies the agent binary in via its own install subcommand
  • A job container running crond-agent exec around your original command

Stdout still flows through to kubectl logs.

What gets reported

  • Start ping when the job container begins
  • Success ping with exit code 0 and execution duration
  • Failure ping with exit code, duration, and captured stderr
  • Missed runs detected by crond.io after the schedule plus your grace period

Alternative: pre-baked image

If you control your job image, bake the agent in and skip the init container — your CronJob then invokes crond-agent exec directly, and the chart still owns the ping-key Secret:

FROM ghcr.io/platops-security/crond-agent:0.2.0 AS agent
FROM alpine:3.20
COPY --from=agent /crond-agent /usr/local/bin/crond-agent
COPY backup.sh /opt/backup.sh
ENTRYPOINT ["/opt/backup.sh"]

Good to know

  • You wrap each CronJob explicitly, or enable the opt-in mutating admission webhook (V2, injector.enabled) to auto-inject any CronJob labeled crond.io/inject. Auto-discovery of existing CronJobs is still tracked for a future release.
  • The CronJob schedule and the crond.io monitor schedule are independent — set both to the same expression and grace window.
  • Kubernetes 1.25+, batch/v1 CronJob (not the deprecated v1beta1)
  • Agent image is multi-arch (amd64 + arm64) and cosign-signed; air-gapped clusters can point image.registry at an internal mirror