Docs
Additional Features
Alerts
CRDs

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: headers

Spec fields

Exactly one of http, slackWebhook, or slackWebApi must be present. Setting zero or more than one reports ValidationFailed in .status.conditions.

FieldTypeRequiredDefaultDescription
httpobjectConditionalHTTP channel variant. Mutually exclusive with slackWebhook and slackWebApi.
slackWebhookobjectConditionalSlack Incoming Webhook variant. Mutually exclusive with http and slackWebApi.
slackWebApiobjectConditionalSlack Web API variant. Mutually exclusive with http and slackWebhook.

http fields

FieldTypeRequiredDefaultDescription
http.urlSecretRefobjectYesReference to the Secret key whose value is the destination URL.
http.urlSecretRef.namestringYesName of the Secret in the wave-autoscale namespace.
http.urlSecretRef.keystringYesKey within the Secret whose value is the URL.
http.methodstringYes (runtime)HTTP method. Valid values: POST, PUT, PATCH, DELETE.
http.headersSecretRefobjectNoReference 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.namestringYes (if set)Name of the Secret.
http.headersSecretRef.keystringYes (if set)Key within the Secret.
http.proxySecretRefobjectNoReference to a Secret key whose value is an HTTPS proxy URL.
http.proxySecretRef.namestringYes (if set)Name of the Secret.
http.proxySecretRef.keystringYes (if set)Key within the Secret.

slackWebhook fields

FieldTypeRequiredDefaultDescription
slackWebhook.webhookUrlSecretRefobjectYesReference to the Secret key whose value is the Slack Webhook URL.
slackWebhook.webhookUrlSecretRef.namestringYesName of the Secret in the wave-autoscale namespace.
slackWebhook.webhookUrlSecretRef.keystringYesKey within the Secret.
slackWebhook.proxySecretRefobjectNoReference to a Secret key whose value is an HTTPS proxy URL.
slackWebhook.proxySecretRef.namestringYes (if set)Name of the Secret.
slackWebhook.proxySecretRef.keystringYes (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: url

slackWebApi fields

FieldTypeRequiredDefaultDescription
slackWebApi.tokenSecretRefobjectYesReference to the Secret key whose value is the Slack Bot token (e.g. xoxb-...).
slackWebApi.tokenSecretRef.namestringYesName of the Secret in the wave-autoscale namespace.
slackWebApi.tokenSecretRef.keystringYesKey within the Secret.
slackWebApi.channelstringYesTarget Slack channel — a channel ID or #channel-name.
slackWebApi.proxySecretRefobjectNoReference to a Secret key whose value is an HTTPS proxy URL.
slackWebApi.proxySecretRef.namestringYes (if set)Name of the Secret.
slackWebApi.proxySecretRef.keystringYes (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.namespace required.
  • Exactly one variant (http / slackWebhook / slackWebApi) must be set. This is validated by Wave at reconcile time and surfaces in .status.conditions as ValidationFailed — not rejected at kubectl apply.
  • All credentials, URLs, headers, and proxy settings are Secret references. Wave reads Secret values from the wave-autoscale namespace at reconcile time and never writes them into the CR. A missing Secret or key reports SecretNotFound in .status.conditions and self-heals when the Secret appears.
  • The headersSecretRef value must be a JSON object string. A malformed value is currently treated as SecretNotFound.
  • Channels are referenced by name from WaveAlert.spec.alertMessages[].channelRef. Deleting a WaveAlertChannel CR is never blocked by Kubernetes; while existing WaveAlerts still reference it, Wave keeps the channel configuration active and reports RefInUse in .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

FieldTypeRequiredDefaultDescription
titlestringYesHuman-readable name for this alert rule, shown in the Wave console.
eventTypestringYesEvent 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.
eventTargetsobjectYesScopes 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.
eventRuleExpressionstringYesJavaScript 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.
eventRuleCheckIntervalMinintegerNo1How 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.
alertMessagesarrayNo[]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[].channelRefstringYes (per item)Name of a WaveAlertChannel CR. A missing channel reports RefNotFound in .status.conditions and self-heals when the channel CR appears.
alertMessages[].messagestringYes (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: []
FieldTypeRequiredDescription
all.resourceTypestringYes (for all)Resource type to monitor. Deployment, StatefulSet, DaemonSet, or Pvc — which values are meaningful depends on eventType (see the table below).
specific[].resourceTypestringYes (per item)Resource type. Same accepted values as all.resourceType.
specific[].namespacestringNoNamespace of the target resource. Omit to match the resource across all namespaces.
specific[].namestringNoName 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

eventTypeSupported resourceType values
deployment_workload_metricsDeployment
deployment_scheduling_phasenone — targetless, use specific: []
autopilot_logs_missingDeployment
pending_pod_durationnone — targetless, use specific: []
pod_container_failurenone — targetless, use specific: []
pv_usagePvc
smart_sizing_recommendationDeployment, StatefulSet, DaemonSet
oom_kill_risknone — 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.

eventTypeExpression variablesAdditional message-only variables
deployment_workload_metricsevaluation_period_minutes, cpu_utilization_arr, memory_utilization_arr — array variables support max() / min() / avg() helpers${namespace}, ${workload_name}, ${alert_title}, ${alert_time}
deployment_scheduling_phasephase ("START" or "END"), deployment_count${scheduling_title}, ${occurred_at}, ${alert_title}, ${alert_time}
autopilot_logs_missingmissing_duration_minutes, error_duration_minutes${namespace}, ${workload_name}, ${alert_title}, ${alert_time}
pending_pod_durationpending_duration_minutes, reason_category${namespace}, ${pod_name}, ${workload_name}, ${alert_title}, ${alert_time}
pod_container_failuretransition_type, workload_kind${namespace}, ${pod_name}, ${container_name}, ${workload_name}, ${alert_title}, ${alert_time}
pv_usageusage_percent, used_bytes, capacity_bytes, available_bytes, minutes_since_last_alert${namespace}, ${pvc_name}, ${alert_title}, ${alert_time}
smart_sizing_recommendationcpu_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_riskutilization, 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_recommendationSmart 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.namespace required.
  • eventType is validated by Wave at reconcile time. An unrecognized value reports ValidationFailed in .status.conditions and the alert is not activated.
  • eventRuleExpression is validated by Wave. An expression referencing variables not available for the configured eventType (or that is otherwise malformed) reports ValidationFailed and the alert is not activated. The rule self-heals when the expression is corrected.
  • alertMessages is optional. A WaveAlert with 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 channelRef must match the metadata.name of an existing WaveAlertChannel CR. A missing channel reports RefNotFound in .status.conditions and converges automatically when the channel CR appears. The entire alert is skipped (not just the missing entry) until all channel references resolve.
  • eventTargets is 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 at kubectl apply — they surface as ValidationFailed via Wave's reconciler.
  • Deleting a WaveAlertChannel that is still referenced by a WaveAlert is never blocked by Kubernetes — Wave keeps the channel configuration active and reports RefInUse in .status.conditions until the referencing alert CRs are removed, at which point it is cleaned up.