Alert CRDs
These kinds configure Alerts in CRD Mode. See the Alerts overview for an introduction to channels and rules. WaveAlertChannel defines where notifications go; WaveAlert defines what to monitor and when to fire.
WaveAlertChannel
WaveAlertChannel defines a notification destination — an HTTP endpoint, a Slack Webhook, or a Slack Web API channel. Alert rules reference channels by name. All credentials, URLs, headers, and proxy settings are stored in Kubernetes Secrets and never written into the CR itself.
Scope: cluster-scoped — no metadata.namespace required.
First, create the Secrets in the wave-autoscale namespace:
apiVersion: v1
kind: Secret
metadata:
name: http-endpoint
namespace: wave-autoscale
type: Opaque
stringData:
url: "https://hooks.example.com/webhook"
---
apiVersion: v1
kind: Secret
metadata:
name: http-headers
namespace: wave-autoscale
type: Opaque
stringData:
# Must be a JSON object string — key/value pairs of HTTP headers.
headers: '{"Authorization":"Bearer <token>","Content-Type":"application/json"}'Then create the channel CR:
apiVersion: wavek8s.com/v1alpha1
kind: WaveAlertChannel
metadata:
name: oncall-http
spec:
http:
urlSecretRef:
name: http-endpoint
key: url
method: POST
headersSecretRef:
name: http-headers
key: headersSpec fields
Exactly one of http, slackWebhook, or slackWebApi must be present. Setting zero or more than one reports ValidationFailed in .status.conditions.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
http | object | Conditional | — | HTTP channel variant. Mutually exclusive with slackWebhook and slackWebApi. |
slackWebhook | object | Conditional | — | Slack Incoming Webhook variant. Mutually exclusive with http and slackWebApi. |
slackWebApi | object | Conditional | — | Slack Web API variant. Mutually exclusive with http and slackWebhook. |
http fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
http.urlSecretRef | object | Yes | — | Reference to the Secret key whose value is the destination URL. |
http.urlSecretRef.name | string | Yes | — | Name of the Secret in the wave-autoscale namespace. |
http.urlSecretRef.key | string | Yes | — | Key within the Secret whose value is the URL. |
http.method | string | Yes (runtime) | — | HTTP method. Valid values: POST, PUT, PATCH, DELETE. |
http.headersSecretRef | object | No | — | Reference to a Secret key whose value is a JSON object of HTTP header key/value pairs (e.g. {"Authorization":"Bearer ...","Content-Type":"application/json"}). |
http.headersSecretRef.name | string | Yes (if set) | — | Name of the Secret. |
http.headersSecretRef.key | string | Yes (if set) | — | Key within the Secret. |
http.proxySecretRef | object | No | — | Reference to a Secret key whose value is an HTTPS proxy URL. |
http.proxySecretRef.name | string | Yes (if set) | — | Name of the Secret. |
http.proxySecretRef.key | string | Yes (if set) | — | Key within the Secret. |
slackWebhook fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
slackWebhook.webhookUrlSecretRef | object | Yes | — | Reference to the Secret key whose value is the Slack Webhook URL. |
slackWebhook.webhookUrlSecretRef.name | string | Yes | — | Name of the Secret in the wave-autoscale namespace. |
slackWebhook.webhookUrlSecretRef.key | string | Yes | — | Key within the Secret. |
slackWebhook.proxySecretRef | object | No | — | Reference to a Secret key whose value is an HTTPS proxy URL. |
slackWebhook.proxySecretRef.name | string | Yes (if set) | — | Name of the Secret. |
slackWebhook.proxySecretRef.key | string | Yes (if set) | — | Key within the Secret. |
Example:
apiVersion: v1
kind: Secret
metadata:
name: slack-webhook-url
namespace: wave-autoscale
type: Opaque
stringData:
url: "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXX"
---
apiVersion: wavek8s.com/v1alpha1
kind: WaveAlertChannel
metadata:
name: oncall-slack-webhook
spec:
slackWebhook:
webhookUrlSecretRef:
name: slack-webhook-url
key: urlslackWebApi fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
slackWebApi.tokenSecretRef | object | Yes | — | Reference to the Secret key whose value is the Slack Bot token (e.g. xoxb-...). |
slackWebApi.tokenSecretRef.name | string | Yes | — | Name of the Secret in the wave-autoscale namespace. |
slackWebApi.tokenSecretRef.key | string | Yes | — | Key within the Secret. |
slackWebApi.channel | string | Yes | — | Target Slack channel — a channel ID or #channel-name. |
slackWebApi.proxySecretRef | object | No | — | Reference to a Secret key whose value is an HTTPS proxy URL. |
slackWebApi.proxySecretRef.name | string | Yes (if set) | — | Name of the Secret. |
slackWebApi.proxySecretRef.key | string | Yes (if set) | — | Key within the Secret. |
Example:
apiVersion: v1
kind: Secret
metadata:
name: slack-bot-token
namespace: wave-autoscale
type: Opaque
stringData:
token: "xoxb-your-bot-token-here"
---
apiVersion: wavek8s.com/v1alpha1
kind: WaveAlertChannel
metadata:
name: oncall-slack-webapi
spec:
slackWebApi:
tokenSecretRef:
name: slack-bot-token
key: token
channel: "#alerts"http.method is required — omitting it reports ValidationFailed in .status.conditions and the channel fails. Valid values are POST, PUT, PATCH, DELETE. An unrecognized value (including GET) is not rejected — it is silently treated as POST. Double-check the spelling of your method value.
Notes
- Cluster-scoped — no
metadata.namespacerequired. - Exactly one variant (
http/slackWebhook/slackWebApi) must be set. This is validated by Wave at reconcile time and surfaces in.status.conditionsasValidationFailed— not rejected atkubectl apply. - All credentials, URLs, headers, and proxy settings are Secret references. Wave reads Secret values from the
wave-autoscalenamespace at reconcile time and never writes them into the CR. A missing Secret or key reportsSecretNotFoundin.status.conditionsand self-heals when the Secret appears. - The
headersSecretRefvalue must be a JSON object string. A malformed value is currently treated asSecretNotFound. - Channels are referenced by name from
WaveAlert.spec.alertMessages[].channelRef. Deleting aWaveAlertChannelCR is never blocked by Kubernetes; while existingWaveAlerts still reference it, Wave keeps the channel configuration active and reportsRefInUsein.status.conditions— it is cleaned up once the referencing alerts are gone.
WaveAlert
WaveAlert defines an alert rule: which event type to evaluate, what expression determines whether the alert fires, which targets to scope it to, and which channels to deliver notifications to.
Scope: cluster-scoped — no metadata.namespace required.
apiVersion: wavek8s.com/v1alpha1
kind: WaveAlert
metadata:
name: high-cpu-prod-api
spec:
title: High CPU/memory on prod/api
eventType: deployment_workload_metrics
eventTargets:
specific:
- resourceType: Deployment
namespace: prod
name: api
eventRuleExpression: "evaluation_period_minutes >= 1 && ( max(cpu_utilization_arr) >= 80 || avg(memory_utilization_arr) >= 80 )"
eventRuleCheckIntervalMin: 5
alertMessages:
- channelRef: oncall-http
message: "${namespace}/${workload_name} CPU/memory exceeded 80% over the evaluation window"Spec fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
title | string | Yes | — | Human-readable name for this alert rule, shown in the Wave console. |
eventType | string | Yes | — | Event type to evaluate. One of deployment_workload_metrics, deployment_scheduling_phase, autopilot_logs_missing, pending_pod_duration, pod_container_failure, pv_usage, smart_sizing_recommendation, oom_kill_risk. An unrecognized value reports ValidationFailed. |
eventTargets | object | Yes | — | Scopes the rule to specific resources or all resources of a type. See below. Free-form at the CRD layer (x-kubernetes-preserve-unknown-fields: true); structural errors surface via Wave rather than at kubectl apply. |
eventRuleExpression | string | Yes | — | JavaScript expression evaluated against the event's variables. The alert fires when the expression evaluates to true. An expression referencing undefined variables reports ValidationFailed and the alert is not activated. |
eventRuleCheckIntervalMin | integer | No | 1 | How often (in minutes) Wave evaluates the rule. Clamped to a 1-minute floor — 0 or a negative value is treated as 1 rather than spinning the evaluator. |
alertMessages | array | No | [] | Channels and message templates to fan out to when the rule fires. May be omitted — the rule is still evaluated but delivered to no channel until at least one entry is added. |
alertMessages[].channelRef | string | Yes (per item) | — | Name of a WaveAlertChannel CR. A missing channel reports RefNotFound in .status.conditions and self-heals when the channel CR appears. |
alertMessages[].message | string | Yes (per item) | — | Message template rendered as a JavaScript template literal. Interpolate context variables using ${varName} syntax. Available variables depend on eventType — see the table below. |
eventTargets shape
eventTargets takes exactly one of two forms:
# Target all resources of a given type:
eventTargets:
all:
resourceType: Deployment
# Target specific named resources:
eventTargets:
specific:
- resourceType: Deployment
namespace: prod
name: api
# Targetless event type (cluster-wide) — use an empty specific list:
eventTargets:
specific: []| Field | Type | Required | Description |
|---|---|---|---|
all.resourceType | string | Yes (for all) | Resource type to monitor. Deployment, StatefulSet, DaemonSet, or Pvc — which values are meaningful depends on eventType (see the table below). |
specific[].resourceType | string | Yes (per item) | Resource type. Same accepted values as all.resourceType. |
specific[].namespace | string | No | Namespace of the target resource. Omit to match the resource across all namespaces. |
specific[].name | string | No | Name of the target resource. Omit to match by type (and namespace if set) only. |
Targetless event types. pending_pod_duration, pod_container_failure, oom_kill_risk, and deployment_scheduling_phase are cluster-wide — they are not scoped to a named workload. eventTargets is still a required field for them, so set specific: []. The workers ignore the target list and fire on any matching cluster event.
Supported target types by eventType
eventType | Supported resourceType values |
|---|---|
deployment_workload_metrics | Deployment |
deployment_scheduling_phase | none — targetless, use specific: [] |
autopilot_logs_missing | Deployment |
pending_pod_duration | none — targetless, use specific: [] |
pod_container_failure | none — targetless, use specific: [] |
pv_usage | Pvc |
smart_sizing_recommendation | Deployment, StatefulSet, DaemonSet |
oom_kill_risk | none — targetless, use specific: [] |
Rule expression variables by eventType
Each event type exposes a different set of variables. eventRuleExpression uses bare variable names — no ${} wrapper. alertMessages[].message uses ${varName} JavaScript template-literal syntax.
eventType | Expression variables | Additional message-only variables |
|---|---|---|
deployment_workload_metrics | evaluation_period_minutes, cpu_utilization_arr, memory_utilization_arr — array variables support max() / min() / avg() helpers | ${namespace}, ${workload_name}, ${alert_title}, ${alert_time} |
deployment_scheduling_phase | phase ("START" or "END"), deployment_count | ${scheduling_title}, ${occurred_at}, ${alert_title}, ${alert_time} |
autopilot_logs_missing | missing_duration_minutes, error_duration_minutes | ${namespace}, ${workload_name}, ${alert_title}, ${alert_time} |
pending_pod_duration | pending_duration_minutes, reason_category | ${namespace}, ${pod_name}, ${workload_name}, ${alert_title}, ${alert_time} |
pod_container_failure | transition_type, workload_kind | ${namespace}, ${pod_name}, ${container_name}, ${workload_name}, ${alert_title}, ${alert_time} |
pv_usage | usage_percent, used_bytes, capacity_bytes, available_bytes, minutes_since_last_alert | ${namespace}, ${pvc_name}, ${alert_title}, ${alert_time} |
smart_sizing_recommendation | cpu_request_delta_abs_percent, memory_request_delta_abs_percent, recommended_cpu_request, recommended_memory_request, current_cpu_request, current_memory_request | ${workload_type}, ${namespace}, ${workload_name}, ${container_name}, ${alert_title}, ${alert_time} |
oom_kill_risk | utilization, minutes_since_last_alert | ${namespace}, ${workload_type}, ${workload_name}, ${pod_name}, ${container_name}, ${alert_title}, ${alert_time} |
The five event types below autopilot_logs_missing were added in Wave 3.4.0. On an older Core they report ValidationFailed in .status.conditions.
Event type reference
pending_pod_duration — a Pod has been stuck in Pending longer than the threshold. reason_category groups the scheduler's reason (for example insufficient resources versus an unbound volume), so you can route capacity problems separately from storage problems.
apiVersion: wavek8s.com/v1alpha1
kind: WaveAlert
metadata:
name: pods-stuck-pending
spec:
title: Pods pending over 5 minutes
eventType: pending_pod_duration
eventTargets:
specific: []
eventRuleExpression: "pending_duration_minutes >= 5"
alertMessages:
- channelRef: oncall-slack-webhook
message: "Pod ${namespace}/${pod_name} has been pending for ${pending_duration_minutes} minutes"pod_container_failure — a container entered a failure state. transition_type names the transition (for example CrashLoopBackOff, OOMKilled, ImagePullBackOff) and workload_kind is Deployment, StatefulSet, or DaemonSet. The default expression is true — it fires on every failure incident, and you narrow it with a transition_type condition. Workloads listed in WaveDiagnosisConfig.spec.podContainerFailures.excludedWorkloads never produce events here.
apiVersion: wavek8s.com/v1alpha1
kind: WaveAlert
metadata:
name: crashloop-only
spec:
title: CrashLoopBackOff
eventType: pod_container_failure
eventTargets:
specific: []
eventRuleExpression: "transition_type == 'CrashLoopBackOff'"
alertMessages:
- channelRef: oncall-slack-webhook
message: "Container ${container_name} in pod ${namespace}/${pod_name} failed: ${transition_type}"pv_usage — a PersistentVolumeClaim crossed a usage threshold. Detection runs every minute, so pair the threshold with a minutes_since_last_alert clause to act as a re-notify cooldown — without it a PVC that stays over threshold re-alerts every minute.
apiVersion: wavek8s.com/v1alpha1
kind: WaveAlert
metadata:
name: pvc-nearly-full
spec:
title: PVC over 80%
eventType: pv_usage
eventTargets:
all:
resourceType: Pvc
eventRuleExpression: "usage_percent >= 80 && minutes_since_last_alert >= 30"
alertMessages:
- channelRef: oncall-slack-webhook
message: "PVC ${namespace}/${pvc_name} usage: ${usage_percent}%"smart_sizing_recommendation — Smart Sizing produced a recommendation that differs materially from the container's current request. The *_delta_abs_percent variables are absolute percentage gaps, so a single threshold catches both over- and under-provisioning. CPU values are in cores, memory values in MiB.
apiVersion: wavek8s.com/v1alpha1
kind: WaveAlert
metadata:
name: sizing-drift
spec:
title: Smart Sizing drift over 20%
eventType: smart_sizing_recommendation
eventTargets:
all:
resourceType: Deployment
eventRuleExpression: "cpu_request_delta_abs_percent >= 20 || memory_request_delta_abs_percent >= 20"
alertMessages:
- channelRef: oncall-slack-webhook
message: "Smart Sizing: ${namespace}/${workload_name}/${container_name} CPU ${current_cpu_request} -> ${recommended_cpu_request} cores, Memory ${current_memory_request} -> ${recommended_memory_request} MiB"oom_kill_risk — a container's memory utilization reached the OOM-kill threshold, in real time, before the kill happens. The threshold and the per-container cooldown are set on WaveDiagnosisConfig.spec.memoryAnomaly (oomkillUtilization, default 95%; oomkillCooldownMinutes, default 10). Because that cooldown already suppresses repeats at the source, the rule expression usually only needs a minutes_since_last_alert guard.
apiVersion: wavek8s.com/v1alpha1
kind: WaveAlert
metadata:
name: oom-kill-risk
spec:
title: OOM-kill risk
eventType: oom_kill_risk
eventTargets:
specific: []
eventRuleExpression: "minutes_since_last_alert >= 10"
alertMessages:
- channelRef: oncall-slack-webhook
message: "Container ${container_name} in pod ${namespace}/${pod_name} at ${utilization}% memory — OOM-kill risk"Notes
- Cluster-scoped — no
metadata.namespacerequired. eventTypeis validated by Wave at reconcile time. An unrecognized value reportsValidationFailedin.status.conditionsand the alert is not activated.eventRuleExpressionis validated by Wave. An expression referencing variables not available for the configuredeventType(or that is otherwise malformed) reportsValidationFailedand the alert is not activated. The rule self-heals when the expression is corrected.alertMessagesis optional. AWaveAlertwith no messages is valid and its rule is still evaluated — it just delivers to no channel. This is useful for testing expressions before wiring up channels.- Message templates use JavaScript template-literal interpolation (
${varName}). Go-style{{.var}}is not interpolated and emits verbatim. - Each
channelRefmust match themetadata.nameof an existingWaveAlertChannelCR. A missing channel reportsRefNotFoundin.status.conditionsand converges automatically when the channel CR appears. The entire alert is skipped (not just the missing entry) until all channel references resolve. eventTargetsis a free-form object at the CRD layer (x-kubernetes-preserve-unknown-fields: true). Structural errors such as typos in field names are not caught atkubectl apply— they surface asValidationFailedvia Wave's reconciler.- Deleting a
WaveAlertChannelthat is still referenced by aWaveAlertis never blocked by Kubernetes — Wave keeps the channel configuration active and reportsRefInUsein.status.conditionsuntil the referencing alert CRs are removed, at which point it is cleaned up.