Skip to content

ConversionWebhookServer

A ConversionWebhookServer is a deployable, independently scalable instance of the shared conversion webhook runtime — the thing that actually receives ConversionReview requests from the apiserver and converts objects. The Helm chart creates exactly one, named default and marked spec.default: true; create more directly as CRs for scale-out or tenant isolation.

It's cluster-scoped, but its owned resources (Deployment, Service, Certificate, HPA, PDB) live in a real namespace given by spec.namespace (defaulting to the operator's own install namespace). The Deployment itself is built by the operator from this CR — not from Helm templates — so pod knobs live here, not under a Helm Deployment for webhook-server pods. The chart's conversionWebhookServer.* values are passed through onto the default instance's spec.

Spec

apiVersion: terasky.com/v1alpha1
kind: ConversionWebhookServer
metadata:
  name: default
spec:
  default: true
  replicas: 2
  extraArgs:
    - --cert-reload-interval=1m
  extraEnv:
    - name: TENANT
      value: a
  podLabels:
    tenant: a
  cacheSelector:
    matchLabels:
      tenant: a
  certificate:
    issuerRef:
      name: declarative-conversion-operator-selfsigned-issuer
      kind: ClusterIssuer
  podDisruptionBudget:
    minAvailable: 1
Field Description
default Marks this instance as the fallback target for XRDConversionConfigs that don't set spec.webhookServerRef. At most one instance may be default at a time — the admission webhook rejects creating a second one.
namespace Where this instance's owned resources live. Defaults to the operator's install namespace.
replicas Fixed replica count. Mutually exclusive with autoscaling — once autoscaling is set, the HPA owns the replica count and this controller stops driving it directly.
autoscaling.{minReplicas,maxReplicas,targetCPUUtilizationPercentage} Creates a HorizontalPodAutoscaler for this instance instead of a fixed count.
image.{repository,tag,digest,pullPolicy} Overrides the webhook-server image for this instance. Omit to use the operator's own default (set via Helm image.webhookServer.* / a manager flag). When digest is set it takes precedence over tag (repository@digest).
resources, nodeSelector, tolerations, affinity, priorityClassName, topologySpreadConstraints, serviceAccountName Standard Kubernetes pod-scheduling knobs, applied to this instance's Deployment.
podLabels Merged onto the pod template. Keys the controller uses for the Deployment selector (app.kubernetes.io/name, instance, managed-by) are ignored so a mis-set label cannot break rolling updates.
podAnnotations Set on the webhook-server pod template.
extraArgs Additional container arguments appended after operator-managed flags (--webhook-server-name, --tls-cert-dir, bind addresses, feature toggles, --cache-label-selector). For optional webhook-server flags (e.g. --cert-reload-interval, zap options). Admission and reconcile reject ExtraArgs that name those managed flags.
extraEnv, extraVolumes, extraVolumeMounts Appended after the operator-managed environment / tls+tmp volumes. Use for custom CA bundles, proxies, or tenant env.
cacheSelector Optional metav1.LabelSelector. When set, webhook-server replicas watch only matching XRDConversionConfig / CRDConversionConfig objects. Unset (the default) watches every config.
certificate.issuerRef The cert-manager Issuer/ClusterIssuer for this instance's webhook TLS certificate. certificate.dnsNames, .duration, .renewBefore are also available.
service.{type,port,annotations} The Service fronting this instance's pods.
podDisruptionBudget.{minAvailable,maxUnavailable} Creates a PodDisruptionBudget for this instance.

Status

status:
  observedGeneration: 2
  replicas: 2
  readyReplicas: 2
  endpoint: https://default-webhook-server.declarative-conversion-system.svc:443
  conditions:
    - type: Available
      status: "True"
      reason: DeploymentAvailable
    - type: ServiceReady
      status: "True"
    - type: CertificateReady
      status: "True"
      reason: CertificateReady
  assignedConfigs:
    - name: xwidgets-conversion
      xrdName: xwidgets.example.org
      phase: Applied

status.assignedConfigs reflects the desired assignment as computed by the shared resolver every reconcile — not proof that every replica has actually loaded that config. Per-pod actual state (what's really compiled and serving right now) is deliberately kept out of this status field, to avoid the operator's own reconcile loop depending on a network call to the webhook-server pods; check each pod's own /debug/registry endpoint or its metrics for that.

Conditions

Condition Meaning when True
Available The owned Deployment reports Available.
ServiceReady The owned Service has ready endpoints.
CertificateReady The owned Certificate's Secret contains a valid TLS keypair.
DefaultConflict More than one instance is marked default — shouldn't happen if the admission webhook works, but direct edits/restores can still produce it. The reconciler flags this loudly and does not auto-fix it.
DeletionBlocked Deletion is being held by the finalizer — see Deletion safety.

Multiple instances

Create additional instances for scale-out or tenancy, then point specific configs at them:

apiVersion: terasky.com/v1alpha1
kind: ConversionWebhookServer
metadata:
  name: tenant-a-webhook
spec:
  replicas: 3
  namespace: tenant-a
  certificate:
    issuerRef:
      name: tenant-a-issuer
      kind: ClusterIssuer
---
apiVersion: terasky.com/v1alpha1
kind: XRDConversionConfig
metadata:
  name: xdatabases-conversion
spec:
  targetXRD:
    name: xdatabases.tenant-a.example.org
  webhookServerRef:
    name: tenant-a-webhook
  # ...

Every replica of every instance is symmetric and self-sufficient: each runs its own lightweight controller-runtime manager watching XRDConversionConfig, ConversionWebhookServer, and the relevant XRDs directly — there's no push mechanism from the main operator, and no leader election, since there's no shared state to coordinate. A single config's compile failure only affects that config: the pod keeps serving whatever was last good for every other XRD, and never crash-loops or de-readies over one bad config.

Deletion safety

Deleting a ConversionWebhookServer runs the same finalizer-gated safety check: the operator lists every XRDConversionConfig, resolves its assignment, and blocks deletion (DeletionBlocked condition, listing the dependent configs by name) if any of them resolve to this instance — explicitly via webhookServerRef, or implicitly as the fallback default. The break-glass override is the same pattern as XRDConversionConfig:

kubectl annotate conversionwebhookserver default \
  conversion.terasky.com/allow-force-delete=true
kubectl delete conversionwebhookserver default

With the annotation present (checked live at the moment of the delete reconcile), the finalizer is removed and the owned Deployment/Service/Certificate/HPA/PDB garbage-collect via their owner references.