Skip to content

Defaulting Webhook

Reference documentation for the mutating admission webhook that sets sensible defaults on Memcached custom resources when fields are omitted.

Source: api/v1beta1/memcached_webhook.go

Overview

The Memcached operator includes a mutating admission webhook that intercepts CREATE and UPDATE requests for Memcached resources and populates omitted fields with sensible default values. This allows cluster operators to create a Memcached CR with only metadata and have all core spec fields populated automatically.

The webhook uses the controller-runtime CustomDefaulter interface (admission.Defaulter[*Memcached]). It is registered with the manager via SetupMemcachedWebhookWithManager and served at the conventional Kubebuilder webhook path.

Defaulting Precedence

Values are resolved in the following order (highest priority first):

  1. User-specified values — explicitly set fields in the CR spec are never overwritten by the webhook.
  2. Webhook defaults — applied at admission time for omitted fields (this webhook).
  3. CRD schema defaults — applied by the API server for fields with +kubebuilder:default markers, before the webhook runs.

Webhook Path

text
/mutate-memcached-c5c3-io-v1beta1-memcached

Admission type: Mutating (mutating=true) Failure policy: Fail — rejects the request if the webhook is unavailable. Side effects: None


Defaulted Fields

The table below lists every field that the webhook defaults, its default value, and the condition under which the default is applied.

Core Fields (Always Defaulted)

These fields are defaulted on every Memcached resource, regardless of which optional sections are present.

FieldTypeDefaultCondition
spec.replicas*int321When nil and autoscaling is not enabled
spec.image*stringmemcached:1.6When nil (pointer)
spec.memcached.maxMemoryMBint3264When 0 (struct initialized if nil)
spec.memcached.maxConnectionsint321024When 0 (struct initialized if nil)
spec.memcached.threadsint324When 0 (struct initialized if nil)
spec.memcached.maxItemSizestring1mWhen empty string
spec.memcached.verbosityint320Go zero value — no action needed

The spec.memcached struct is always initialized (created if nil) because its fields are core operational parameters required by every Memcached deployment.

Monitoring Fields (Opt-In)

These fields are only defaulted when spec.monitoring is already non-nil. If the monitoring section is omitted entirely, it remains nil — the webhook does not force-initialize optional sections.

FieldTypeDefaultCondition
spec.monitoring.exporterImage*stringprom/memcached-exporter:v0.15.4When nil and monitoring section exists
spec.monitoring.serviceMonitor.intervalstring30sWhen empty and serviceMonitor section exists
spec.monitoring.serviceMonitor.scrapeTimeoutstring10sWhen empty and serviceMonitor section exists

High Availability Fields (Opt-In)

These fields are only defaulted when spec.highAvailability is already non-nil.

FieldTypeDefaultCondition
spec.highAvailability.antiAffinityPreset*AntiAffinityPresetsoftWhen nil and highAvailability section exists

Autoscaling Fields (Opt-In, Requires Enabled)

These fields are only defaulted when spec.autoscaling is non-nil andspec.autoscaling.enabled is true. If the autoscaling section is omitted or disabled, no defaults are injected.

FieldTypeDefaultCondition
spec.autoscaling.metrics[]autoscalingv2.MetricSpecCPU utilization metric targeting 80% averageWhen empty and autoscaling is enabled
spec.autoscaling.behavior*autoscalingv2.HorizontalPodAutoscalerBehaviorscaleDown stabilization window of 300 secondsWhen nil and autoscaling is enabled

The default CPU utilization metric is equivalent to:

yaml
metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 80

The default behavior is equivalent to:

yaml
behavior:
  scaleDown:
    stabilizationWindowSeconds: 300

The 300-second stabilization window prevents rapid scale-down that could cause cache stampedes when load decreases temporarily.


CR Examples

Minimal CR (Empty Spec)

yaml
apiVersion: memcached.c5c3.io/v1beta1
kind: Memcached
metadata:
  name: my-cache
  namespace: default
spec: {}

After the webhook applies defaults:

yaml
spec:
  replicas: 1
  image: "memcached:1.6"
  memcached:
    maxMemoryMB: 64
    maxConnections: 1024
    threads: 4
    maxItemSize: "1m"
    verbosity: 0

Partially Specified (User Values Preserved)

yaml
apiVersion: memcached.c5c3.io/v1beta1
kind: Memcached
metadata:
  name: my-cache
spec:
  replicas: 3
  image: "memcached:1.6.28"
  memcached:
    maxMemoryMB: 256

After the webhook:

yaml
spec:
  replicas: 3                    # preserved
  image: "memcached:1.6.28"     # preserved
  memcached:
    maxMemoryMB: 256             # preserved
    maxConnections: 1024         # defaulted
    threads: 4                   # defaulted
    maxItemSize: "1m"            # defaulted
    verbosity: 0                 # zero value

With Monitoring Enabled

yaml
apiVersion: memcached.c5c3.io/v1beta1
kind: Memcached
metadata:
  name: my-cache
spec:
  monitoring:
    serviceMonitor: {}

After the webhook:

yaml
spec:
  replicas: 1
  image: "memcached:1.6"
  memcached:
    maxMemoryMB: 64
    maxConnections: 1024
    threads: 4
    maxItemSize: "1m"
  monitoring:
    exporterImage: "prom/memcached-exporter:v0.15.4"
    serviceMonitor:
      interval: "30s"
      scrapeTimeout: "10s"

With High Availability Enabled

yaml
apiVersion: memcached.c5c3.io/v1beta1
kind: Memcached
metadata:
  name: my-cache
spec:
  replicas: 3
  highAvailability: {}

After the webhook:

yaml
spec:
  replicas: 3
  image: "memcached:1.6"
  memcached:
    maxMemoryMB: 64
    maxConnections: 1024
    threads: 4
    maxItemSize: "1m"
  highAvailability:
    antiAffinityPreset: soft

With Autoscaling Enabled

yaml
apiVersion: memcached.c5c3.io/v1beta1
kind: Memcached
metadata:
  name: my-cache
spec:
  autoscaling:
    enabled: true
    minReplicas: 2
    maxReplicas: 10

After the webhook:

yaml
spec:
  # replicas is cleared (mutually exclusive with autoscaling.enabled)
  image: "memcached:1.6"
  memcached:
    maxMemoryMB: 64
    maxConnections: 1024
    threads: 4
    maxItemSize: "1m"
  autoscaling:
    enabled: true
    minReplicas: 2
    maxReplicas: 10
    metrics:
      - type: Resource
        resource:
          name: cpu
          target:
            type: Utilization
            averageUtilization: 80
    behavior:
      scaleDown:
        stabilizationWindowSeconds: 300

Fully Specified CR (No Changes)

A CR with all fields explicitly set passes through the webhook unchanged:

yaml
apiVersion: memcached.c5c3.io/v1beta1
kind: Memcached
metadata:
  name: my-cache
spec:
  replicas: 5
  image: "memcached:1.6.28"
  memcached:
    maxMemoryMB: 512
    maxConnections: 2048
    threads: 8
    maxItemSize: "2m"
    verbosity: 1
  monitoring:
    exporterImage: "prom/memcached-exporter:v0.14.0"
    serviceMonitor:
      interval: "15s"
      scrapeTimeout: "5s"
  highAvailability:
    antiAffinityPreset: hard

Nil Struct Initialization

The webhook handles nil nested structs carefully to respect the opt-in nature of optional sections:

SectionNil Behavior
spec.memcachedAlways initialized — created and populated with defaults because memcached config is required for every deployment
spec.monitoringNot initialized — remains nil; sub-field defaults only apply when the section already exists
spec.highAvailabilityNot initialized — remains nil; sub-field defaults only apply when the section already exists
spec.autoscalingNot initialized — remains nil; sub-field defaults only apply when the section exists and enabled is true

This design means:

  • Omitting spec.monitoring entirely produces a deployment with no exporter sidecar — the webhook does not opt users into monitoring.
  • Omitting spec.highAvailability entirely produces a deployment with no anti-affinity rules — the webhook does not force HA settings.
  • Omitting spec.memcached still results in a fully configured deployment because the webhook creates and populates the struct.
  • Omitting spec.autoscaling or setting enabled: false results in no autoscaling defaults — the webhook does not inject metrics or behavior unless autoscaling is explicitly enabled.

Runtime Behavior

ActionResult
Create CR with empty specCore fields defaulted; optional sections remain nil
Create CR with explicit valuesUser values preserved; only omitted fields defaulted
Create CR with monitoring sectionMonitoring sub-fields defaulted; core fields defaulted
Create CR with HA sectionHA sub-fields defaulted; core fields defaulted
Create CR with autoscaling enabledMetrics and behavior defaulted; replicas cleared; core fields defaulted
Update CR clearing a field to nil/zeroWebhook re-applies the default for that field
Webhook unavailableRequest rejected (failurePolicy=Fail)

Implementation

The MemcachedCustomDefaulter struct in api/v1beta1/memcached_webhook.go implements admission.Defaulter[*Memcached]:

go
type MemcachedCustomDefaulter struct{}

func (d *MemcachedCustomDefaulter) Default(ctx context.Context, mc *Memcached) error

Registration is handled by SetupMemcachedWebhookWithManager:

go
func SetupMemcachedWebhookWithManager(mgr ctrl.Manager) error {
    return ctrl.NewWebhookManagedBy(mgr, &Memcached{}).
        WithDefaulter(&MemcachedCustomDefaulter{}).
        Complete()
}

This function is called from cmd/main.go during operator startup.

Webhook Configuration

The kubebuilder marker on SetupMemcachedWebhookWithManager generates the webhook manifest in config/webhook/. The marker defines:

  • Path: /mutate-memcached-c5c3-io-v1beta1-memcached
  • Mutating: true
  • Failure policy: Fail
  • Side effects: None
  • Verbs: create, update
  • API group: memcached.c5c3.io
  • API version: v1beta1