Webhook Issues
Admission webhook failures and validation errors on recovery resource creation
This page explains how to diagnose and resolve admission webhook failures that occur when creating or updating Site Recovery custom resources such as ProtectionGroup, ProtectionRequest, FailoverRequest, TestFailover, and DRBDReplicationPolicy. Admission webhooks enforce validation rules before any DR resource reaches the cluster API—if a webhook is unavailable or a resource fails validation, the operation is rejected immediately with an error. Understanding these failure modes is critical because a misconfigured or unavailable webhook can block VM protection, failover triggers, and test failover operations entirely.
Before troubleshooting webhook issues, confirm the following:
- OpenShift 4.14 or later
- KubeVirt or OpenShift Virtualization (CNV) 1.0 or later
kubectlorocCLI available and configured with kubeconfig files for the quorum cluster, primary cluster, and DR cluster- Helm 3.0 or later (if you need to reinstall or upgrade webhook components)
- The
site-recovery-protectionzone-controllerchart was installed on the quorum cluster before thesite-recovery-quorum-control-planechart—the ProtectionZone admission webhook and its CRDs must be present before the quorum control plane initializes - Network connectivity: TCP port 6443 open from the quorum cluster to the primary and DR cluster API servers
- Sufficient RBAC permissions to inspect
ValidatingWebhookConfiguration,MutatingWebhookConfiguration, pods, and events in the DR namespace (dr-<name>) on the quorum cluster
Webhook components are installed as part of the Site Recovery Helm charts. If a webhook is missing or broken, reinstall the relevant chart.
Step 1: Verify the ProtectionZone webhook is installed first
The site-recovery-protectionzone-controller chart must be installed before the quorum control plane chart. Check whether it is present:
helm list -n dr-<name> --kubeconfig ~/.kube/config-quorum
Expected output includes both site-recovery-protectionzone-controller and site-recovery-quorum-control-plane entries. If the ProtectionZone chart is missing, install it first:
helm install site-recovery-protectionzone-controller \
trilio/site-recovery-protectionzone-controller \
-n dr-<name> \
--kubeconfig ~/.kube/config-quorum
Step 2: Install the quorum control plane chart
If the quorum control plane is missing or was installed before the ProtectionZone chart:
helm install site-recovery-quorum-control-plane \
trilio/site-recovery-quorum-control-plane \
-n dr-<name> \
--kubeconfig ~/.kube/config-quorum
Step 3: Install the workload control plane on primary and DR clusters
Admission webhooks on the workload side are installed by the site-recovery-workload-control-plane chart. Run this against both clusters:
# Primary cluster
helm install site-recovery-workload-control-plane \
trilio/site-recovery-workload-control-plane \
-n dr-<name> \
--kubeconfig ~/.kube/config-primary
# DR cluster
helm install site-recovery-workload-control-plane \
trilio/site-recovery-workload-control-plane \
-n dr-<name> \
--kubeconfig ~/.kube/config-dr
Step 4: Confirm webhook configurations are registered
# Quorum cluster — ProtectionZone and quorum control plane webhooks
oc get validatingwebhookconfiguration,mutatingwebhookconfiguration \
--kubeconfig ~/.kube/config-quorum
# Primary cluster — workload-side webhooks
oc get validatingwebhookconfiguration,mutatingwebhookconfiguration \
--kubeconfig ~/.kube/config-primary
If expected webhook configurations are absent, the relevant Helm chart did not install successfully. Check Helm release status:
helm status site-recovery-protectionzone-controller \
-n dr-<name> \
--kubeconfig ~/.kube/config-quorum
Webhook behavior is determined by the Helm chart values used at install time and by the Site Recovery CRDs themselves. The following describes the key areas that affect webhook validation.
ProtectionZone admission webhook
The site-recovery-protectionzone-controller chart installs the webhook that validates ProtectionZone CRs before they are admitted. A ProtectionZone requires:
spec.displayName(required string)spec.clusters(required array — must list valid cluster references)spec.storageBackendMode(required string — controls whether DRBD Operator mode is used)
If any required field is absent or invalid, the webhook rejects the create or update with a descriptive error.
ProtectionGroup namespace restriction
The validating webhook enforces that all VMs in a ProtectionGroup belong to the same namespace. Any create or update that would result in member VMs spanning more than one namespace is rejected. This restriction applies even to ProtectionGroup objects that existed before the rule was introduced. If you have a multi-namespace ProtectionGroup, you must delete it and recreate it as separate single-namespace groups.
FailoverRequest required fields
The webhook validates FailoverRequest resources before admission. The following fields are required and must be present and well-formed:
spec.protectionGroupRef(required object — must reference an existingProtectionGroup)spec.targetCluster(required string)
Optional fields such as spec.failoverType, spec.drainTimeoutSeconds, and spec.batchBootTimeoutSeconds are validated for type correctness if provided.
TestFailover required fields
The webhook validates TestFailover resources. The only required field is:
spec.protectionGroupRef(required object)
Optional fields (spec.cleanupPolicy, spec.retentionTime, spec.timeout, spec.verification, spec.batchBootTimeoutSeconds) are type-checked if present.
DRBDReplicationPolicy required fields
For DRBD Operator deployments, the webhook validates DRBDReplicationPolicy resources. Required:
spec.clusters(required array)
Optional fields such as spec.drbdProtocol, spec.replicationMode, spec.rpo, spec.networkConfig, spec.diskConfig, and spec.resyncConfig are type-checked if provided.
Webhook failure policy
By default, the Site Recovery webhooks use a Fail failure policy, meaning that if the webhook service is unreachable, all resource create and update operations for Site Recovery CRDs are blocked. This is intentional to prevent invalid DR state from being admitted to the cluster. If a webhook pod is down, restore it before attempting any DR operations.
Use the following patterns to investigate and resolve webhook issues in day-to-day operations.
Check whether a webhook rejection is a validation error or a connectivity error
When you run oc apply or kubectl apply and the operation is rejected, the error message will indicate which:
- A validation error contains a human-readable message from the webhook controller, such as
"all VMs in a ProtectionGroup must be in the same namespace"or"spec.protectionGroupRef is required". - A connectivity error will reference
connection refused,timeout,no endpoints available, or a TLS handshake failure rather than a policy message.
Inspect webhook pods on the quorum cluster
The site-recovery-quorum-control-plane deployment on the quorum cluster hosts the admission webhook reconcilers. Check whether its pods are running:
oc get pods -n dr-<name> \
-l app.kubernetes.io/name=site-recovery-quorum-control-plane \
--kubeconfig ~/.kube/config-quorum
If pods are in CrashLoopBackOff, Pending, or Error state, retrieve logs:
oc logs -n dr-<name> \
deployment/site-recovery-quorum-control-plane \
--kubeconfig ~/.kube/config-quorum
Inspect the ProtectionZone controller pod
oc get pods -n dr-<name> \
-l app.kubernetes.io/name=site-recovery-protectionzone-controller \
--kubeconfig ~/.kube/config-quorum
oc logs -n dr-<name> \
deployment/site-recovery-protectionzone-controller \
--kubeconfig ~/.kube/config-quorum
Inspect workload-side webhook pods
For rejections that occur on the primary or DR cluster when applying ProtectionGroup or DRBDReplicationPolicy resources:
oc get pods -n dr-<name> \
-l app.kubernetes.io/name=site-recovery-workload-control-plane \
--kubeconfig ~/.kube/config-primary
oc logs -n dr-<name> \
deployment/site-recovery-workload-control-plane \
--kubeconfig ~/.kube/config-primary
Collect a full diagnostic bundle
If you cannot identify the root cause from logs alone, use tsr-gather to collect logs, CRD state, and configuration from all clusters:
oc adm must-gather \
--image=<tsr-gather-image> \
--kubeconfig ~/.kube/config-quorum
This produces a single bundle containing webhook configurations, controller logs, and CRD states across the DR estate without capturing secrets.
Example 1: ProtectionGroup rejected due to multi-namespace VMs
Attempting to add a VM from a different namespace to an existing ProtectionGroup:
oc patch protectiongroup production-pg \
-n workloads \
--type merge \
--kubeconfig ~/.kube/config-primary \
-p '{"spec":{"virtualMachines":[{"name":"vm-a"},{"name":"vm-b"}]}}'
Expected rejection output:
Error from server: admission webhook "protectiongroup.siterecovery.trilio.io" denied the request:
all VMs in a ProtectionGroup must be in the same namespace; vm-b is in namespace "other-ns", expected "workloads"
Resolution: Ensure all VMs listed in spec.virtualMachines exist in the same namespace as the ProtectionGroup. If you need to protect VMs from multiple namespaces, create separate ProtectionGroup objects per namespace.
Example 2: FailoverRequest rejected due to missing required field
Applying a FailoverRequest without a targetCluster:
apiVersion: siterecovery.trilio.io/v1alpha1
kind: FailoverRequest
metadata:
name: failover-production
namespace: dr-production
spec:
protectionGroupRef:
name: production-pg
namespace: workloads
failoverType: planned
oc apply -f failover-production.yaml \
--kubeconfig ~/.kube/config-quorum
Expected rejection output:
Error from server: admission webhook "failoverrequest.siterecovery.trilio.io" denied the request:
spec.targetCluster is required
Resolution: Add the spec.targetCluster field:
spec:
protectionGroupRef:
name: production-pg
namespace: workloads
targetCluster: dr-cluster
failoverType: planned
Example 3: Webhook connectivity failure blocking all DR resource operations
The site-recovery-protectionzone-controller pod is down. Any attempt to create or modify a ProtectionZone is rejected:
oc apply -f protection-zone.yaml \
--kubeconfig ~/.kube/config-quorum
Expected error:
Error from server: Internal error occurred: failed calling webhook
"protectionzone.siterecovery.trilio.io": Post "https://site-recovery-protectionzone-controller.dr-production.svc:443/validate":
connect: connection refused
Check pod status:
oc get pods -n dr-production \
-l app.kubernetes.io/name=site-recovery-protectionzone-controller \
--kubeconfig ~/.kube/config-quorum
Expected output showing the problem:
NAME READY STATUS RESTARTS
site-recovery-protectionzone-controller-7d9f6b-xk2jp 0/1 CrashLoopBackOff 5
Retrieve logs to identify the root cause:
oc logs -n dr-production \
deployment/site-recovery-protectionzone-controller \
--kubeconfig ~/.kube/config-quorum
Example 4: ProtectionZone rejected due to missing required fields
apiVersion: siterecovery.trilio.io/v1alpha1
kind: ProtectionZone
metadata:
name: zone-east
namespace: dr-production
spec:
clusters:
- name: primary-cluster
- name: dr-cluster
description: "East region DR zone"
oc apply -f zone-east.yaml \
--kubeconfig ~/.kube/config-quorum
Expected rejection:
Error from server: admission webhook "protectionzone.siterecovery.trilio.io" denied the request:
spec.displayName is required; spec.storageBackendMode is required
Resolution: Add the missing required fields:
spec:
displayName: "East Region DR Zone"
storageBackendMode: drbd-operator
clusters:
- name: primary-cluster
- name: dr-cluster
description: "East region DR zone"
Use the consistent format below—Symptom → Likely Cause → Fix—for each issue.
Issue: connection refused or timeout when applying any Site Recovery CR
Symptom: Every oc apply for a ProtectionGroup, FailoverRequest, TestFailover, ProtectionRequest, DRBDReplicationPolicy, or ProtectionZone returns an error containing connection refused, i/o timeout, or no endpoints available for service.
Likely cause: The webhook pod (site-recovery-quorum-control-plane, site-recovery-protectionzone-controller, or site-recovery-workload-control-plane) is not running or its service has no ready endpoints. Because the webhook failure policy is Fail, all admission calls block until the webhook pod is healthy.
Fix:
- Identify the affected cluster and namespace from the error message.
- Check pod status:
oc get pods -n dr-<name> --kubeconfig ~/.kube/config-<cluster> - If pods are in
CrashLoopBackOfforPending, retrieve logs:oc logs -n dr-<name> deployment/<component> --kubeconfig ~/.kube/config-<cluster> - Address the root cause (image pull failure, OOMKilled, missing secret) and wait for the pod to reach
Runningstate. - Retry your
oc apply.
Issue: ProtectionGroup create or update rejected with a namespace mismatch error
Symptom: admission webhook denied the request: all VMs in a ProtectionGroup must be in the same namespace
Likely cause: You are attempting to add a VM that lives in a different namespace from the other members of the ProtectionGroup. The validating webhook enforces single-namespace membership. This restriction also applies to ProtectionGroup objects created before the restriction was introduced—any spec change on a pre-existing multi-namespace group is rejected.
Fix:
- Verify the namespace of each VM listed in
spec.virtualMachines:oc get vm <name> -n <namespace> --kubeconfig ~/.kube/config-primary - Move all VMs to the same namespace, or create separate
ProtectionGroupobjects—one per namespace. - If an existing
ProtectionGrouphas members in multiple namespaces and cannot be edited, delete it and recreate it as single-namespace groups.
Issue: FailoverRequest or TestFailover rejected with a missing required field error
Symptom: admission webhook denied the request: spec.targetCluster is required or spec.protectionGroupRef is required
Likely cause: The submitted manifest is missing one or more fields that the webhook requires. FailoverRequest requires both spec.protectionGroupRef and spec.targetCluster. TestFailover requires spec.protectionGroupRef.
Fix:
- Review the error message to identify which field is missing.
- Add the missing field to your manifest. For
FailoverRequest:spec: protectionGroupRef: name: <protection-group-name> namespace: <namespace> targetCluster: <dr-cluster-name> - Reapply the manifest.
Issue: ProtectionZone rejected with missing displayName or storageBackendMode
Symptom: admission webhook denied the request: spec.displayName is required or spec.storageBackendMode is required
Likely cause: The ProtectionZone manifest omits one or both required fields. The site-recovery-protectionzone-controller webhook enforces these fields at admission time.
Fix:
- Add
spec.displayName(a human-readable string) andspec.storageBackendModeto yourProtectionZonemanifest. - Reapply:
oc apply -f <protectionzone-manifest>.yaml --kubeconfig ~/.kube/config-quorum
Issue: site-recovery-protectionzone-controller was installed after site-recovery-quorum-control-plane, causing CRD or webhook initialization errors
Symptom: The quorum control plane pod logs contain errors referencing missing CRDs or webhook configurations immediately after installation. ProtectionZone resources cannot be created.
Likely cause: The site-recovery-protectionzone-controller chart installs the ProtectionZone CRD and its admission webhook. If this chart is installed after site-recovery-quorum-control-plane, the quorum control plane initializes before the webhook and CRD exist.
Fix:
- Uninstall both charts:
helm uninstall site-recovery-quorum-control-plane -n dr-<name> --kubeconfig ~/.kube/config-quorumandhelm uninstall site-recovery-protectionzone-controller -n dr-<name> --kubeconfig ~/.kube/config-quorum - Reinstall in the correct order—ProtectionZone controller first, then quorum control plane:
helm install site-recovery-protectionzone-controller \ trilio/site-recovery-protectionzone-controller \ -n dr-<name> --kubeconfig ~/.kube/config-quorum helm install site-recovery-quorum-control-plane \ trilio/site-recovery-quorum-control-plane \ -n dr-<name> --kubeconfig ~/.kube/config-quorum
Issue: Webhook TLS certificate error
Symptom: Error contains x509: certificate signed by unknown authority or TLS handshake error.
Likely cause: The webhook service certificate has expired, or the caBundle in the ValidatingWebhookConfiguration or MutatingWebhookConfiguration does not match the certificate currently served by the webhook pod.
Fix:
- Check webhook configuration:
oc get validatingwebhookconfiguration -o yaml --kubeconfig ~/.kube/config-<cluster> - Verify the
caBundlefield is populated and current. - Reinstall the relevant Helm chart with
helm upgrade --installto regenerate certificates and update the webhook configuration:helm upgrade --install site-recovery-quorum-control-plane \ trilio/site-recovery-quorum-control-plane \ -n dr-<name> --kubeconfig ~/.kube/config-quorum
Issue: Cannot determine root cause from pod logs alone
Symptom: Pod logs are insufficient, truncated, or the problem is intermittent.
Likely cause: Multi-cluster issues, transient API server connectivity problems, or a configuration state that is not visible from a single cluster.
Fix:
- Run
tsr-gatheragainst the quorum cluster to collect logs, CRD state, and webhook configurations from all clusters in the DR estate:oc adm must-gather \ --image=<tsr-gather-image> \ --kubeconfig ~/.kube/config-quorum - Attach the resulting bundle when opening a support case.