Custom Resource Schemas
CRD structure and validation rules for site recovery resources
This page documents the Custom Resource Definition (CRD) schemas for Trilio Site Recovery. Each CRD represents a discrete piece of DR state—protection configuration, replication policy, failover intent, or health observation—and is the primary way you interact with the operator. Understanding the schema of each resource, including its required fields, valid values, and validation rules, lets you author manifests confidently, write GitOps policies, and diagnose status conditions without guesswork. All resources share the API group siterecovery.trilio.io/v1alpha1 unless noted.
Before working with these schemas you need:
- OpenShift 4.14 or later
- OpenShift Virtualization (CNV) 1.0 or later
- Trilio Site Recovery installed:
site-recovery-quorum-control-planeHelm chart deployed to the quorum cluster,site-recovery-workload-control-planechart deployed to the primary and DR clusters site-recovery-protectionzone-controllerchart deployed to the quorum cluster before the quorum control plane chartkubectlorocCLI with kubeconfig contexts for all clusters- Helm 3.0 or later (for installation; not required for day-to-day CRD operations)
- DRBD kernel module 9.x on all worker nodes
- The DRBD Operator installed on primary and DR clusters (DRBD Operator deployment model)
The CRD manifests are bundled with the Helm charts and applied automatically during chart installation. You do not need to install CRDs manually in normal deployments. If you need to apply or refresh CRD manifests independently—for example, after a partial installation or during an upgrade—use kubectl apply directly.
Step 1 — Install the ProtectionZone controller (quorum cluster first)
This chart must be installed before the quorum control plane because it registers the ProtectionZone admission webhook and CRDs that the quorum control plane depends on.
helm upgrade --install site-recovery-protectionzone-controller \
trilio/site-recovery-protectionzone-controller \
--namespace dr-system \
--create-namespace \
--kubeconfig ~/.kube/config-quorum
Step 2 — Install the quorum control plane (quorum cluster)
helm upgrade --install site-recovery-quorum-control-plane \
trilio/site-recovery-quorum-control-plane \
--namespace dr-system \
--kubeconfig ~/.kube/config-quorum
This deploys the failover-controller, protection-controller, pg-sync-controller, and test-failover-controller, and registers the FailoverRequest, ProtectionRequest, TestFailover, RPOEvent, ReplicationGroupStatus, and ProtectionGroup CRDs on the quorum cluster.
Step 3 — Install the workload control plane (primary cluster)
helm upgrade --install site-recovery-workload-control-plane \
trilio/site-recovery-workload-control-plane \
--namespace dr-system \
--create-namespace \
--kubeconfig ~/.kube/config-primary
Step 4 — Install the workload control plane (DR cluster)
helm upgrade --install site-recovery-workload-control-plane \
trilio/site-recovery-workload-control-plane \
--namespace dr-system \
--create-namespace \
--kubeconfig ~/.kube/config-dr
Steps 3 and 4 install the drbd-node-agent DaemonSet, admission webhooks, and the DRBDReplicationPolicy and DRBDResource CRDs on the workload clusters.
Step 5 — Verify CRD registration
# Quorum cluster
kubectl get crds --kubeconfig ~/.kube/config-quorum | grep siterecovery
# Primary cluster
kubectl get crds --kubeconfig ~/.kube/config-primary | grep siterecovery
All siterecovery.trilio.io CRDs should appear in the output before you proceed to create resources.
Each CRD has its own spec fields. The tables below list every field defined in the authoritative code surface, its type, whether it is required, and its effect on operator behavior.
ProtectionZone
Defines a named DR deployment that links a primary cluster, a DR cluster, and the quorum cluster under a single management namespace (dr-<name>). Install one ProtectionZone per DR deployment before creating any other resources.
| Field | Type | Required | Description |
|---|---|---|---|
spec.displayName | string | Yes | Human-readable name shown in the TSR web console. |
spec.clusters | array | Yes | List of cluster references (primary, DR, quorum) that form this deployment. |
spec.storageBackendMode | string | Yes | Storage model. Accepted values: drbd-operator. Controls which protection and replication CRDs the operator expects. |
spec.description | string | No | Free-text description of this DR deployment. |
spec.logging | object | No | Log level and output configuration for controllers scoped to this zone. |
ProtectionGroup
Groups one or more VMs so they fail over together as a coordinated unit. The admission webhook enforces that all member VMs are in the same namespace (see Troubleshooting for the multi-namespace rejection behavior).
| Field | Type | Required | Description |
|---|---|---|---|
spec.virtualMachines | array | Yes | List of VM name references to include in this group. All VMs must exist in the same namespace as the ProtectionGroup. |
spec.desiredState | string | No | Operator-managed field. Values: running, stopped. The protection-group-controller reconciles VM power state to match. Do not set this directly in normal operations; it is written by the failover-controller during failover. |
spec.sourceCluster | string | No | Name of the cluster context that is currently the active (primary) side for this group. |
spec.resourceGroupName | string | No | Maps this group to a named resource group for storage inventory tracking. |
spec.sla | object | No | Service level parameters, including RPO targets, that the replication monitor evaluates against. |
Status fields (read-only)
The protection-group-controller writes the following to .status:
state:Active,DegradedreplicationHealth:Healthy,Degraded,CriticalcurrentState:running,stopped,mixedprotectedVMs: per-VM replication state array
ProtectionRequest
Requests block-level replication protection for a single VM in the DRBD Operator deployment model. Creating this resource causes the protection-controller to validate the VM, create a DRBDResource pair, and switch the VM to DRBD-backed frontend PVCs.
| Field | Type | Required | Description |
|---|---|---|---|
spec.virtualMachine | object | Yes | Reference to the KubeVirt VirtualMachine to protect (name and namespace). |
spec.sourceCluster | string | Yes | Name of the cluster context where the VM is currently running. |
spec.replicationConfig | object | No | Overrides for replication behavior (protocol, sync mode) for this specific VM, taking precedence over the DRBDReplicationPolicy defaults. |
Status phases: Pending → Validating → ProvisioningDRBDResource → SwitchingPVCs → Protected (or Failed)
DRBDReplicationPolicy
Defines how volumes replicate between the primary and DR clusters: which storage classes to map, the replication endpoints, and whether to use synchronous (Protocol C) or asynchronous (Protocol A) replication. One policy can be marked as the default for a ProtectionZone.
| Field | Type | Required | Description |
|---|---|---|---|
spec.clusters | array | Yes | Two-element array identifying the primary-side and DR-side cluster contexts. Order matters; the first entry is the replication source. |
spec.replicationMode | string | No | synchronous (Protocol C, RPO=0, requires <50ms RTT) or asynchronous (Protocol A, seconds RPO, unlimited distance). Defaults to asynchronous if omitted. |
spec.drbdProtocol | string | No | DRBD wire protocol letter. C for synchronous, A for asynchronous. Must be consistent with replicationMode. |
spec.diskConfig | object | No | Storage class mappings between primary and DR: for each source storage class, the equivalent class to provision on the DR side. |
spec.networkConfig | object | No | DRBD replication network settings: interface selection, port ranges (TCP 7000–7999). |
spec.resyncConfig | object | No | Controls resync rate limits and priority to prevent replication I/O from saturating production workload I/O. |
spec.rpo | object | No | RPO objective in seconds. The replication monitor compares observed lag against this value and emits RPOEvent resources when violated. |
spec.isDefault | boolean | No | When true, this policy applies to all ProtectionRequests in the zone that do not specify an explicit replicationConfig. Only one policy per ProtectionZone should have isDefault: true. |
DRBDResource
Represents all disks of a single VM as one replicated DRBD resource. Created automatically by the protection-controller when processing a ProtectionRequest; you do not normally create these manually.
| Field | Type | Required | Description |
|---|---|---|---|
spec.role | string | Yes | DRBD role for this side: Primary or Secondary. |
spec.side | string | Yes | Which cluster side this CR represents: source or target. |
spec.port | integer | Yes | TCP port (7000–7999) assigned to this DRBD resource for replication traffic. Must be unique per resource per node. |
spec.volumes | array | Yes | List of PVC references that make up this resource. One entry per VM disk. |
spec.endpoints | array | No | Node IP addresses or hostnames for the DRBD replication link. Populated automatically from node topology if omitted. |
spec.nodeName | string | No | Worker node where this resource's Primary role is pinned. |
spec.replication | object | No | Per-resource protocol and sync overrides. |
spec.replicationIntent | string | No | Desired replication state: Replicated, Standalone. |
spec.paused | boolean | No | When true, the drbd-node-agent suspends replication for this resource without deleting it. Useful during maintenance. Default: false. |
FailoverRequest
Triggers a planned or unplanned failover for a ProtectionGroup. Creating this resource causes the failover-controller to stop VMs on the source cluster, promote DRBD volumes on the target cluster, and start VMs there.
| Field | Type | Required | Description |
|---|---|---|---|
spec.protectionGroupRef | object | Yes | Name and namespace of the ProtectionGroup to fail over. |
spec.targetCluster | string | Yes | Name of the cluster context to fail over to. |
spec.failoverType | string | No | planned (graceful: VMs shut down before volume promotion, zero data loss) or unplanned (emergency: volumes force-promoted without waiting for source shutdown). Defaults to planned. |
spec.drainTimeoutSeconds | integer | No | How long the failover-controller waits for VMs on the source cluster to reach a stopped state before treating the failover as unplanned. |
spec.batchBootTimeoutSeconds | integer | No | How long the failover-controller waits for each batch of VMs to reach a running state on the target cluster before marking the operation Failed. |
Status phases: Pending → InProgress → Completed (or Failed)
Internal sub-phases (written to .status.phase):
StoppingOnSource → WaitingForDRBD → StartingOnTarget → Completed
TestFailover
Triggers a non-disruptive DR validation using volume snapshots. The test-failover-controller creates snapshot-based copies of VMs in an isolated namespace on the DR cluster, runs verification checks, and cleans up—without affecting production workloads.
| Field | Type | Required | Description |
|---|---|---|---|
spec.protectionGroupRef | object | Yes | Name and namespace of the ProtectionGroup to test. |
spec.cleanupPolicy | string | No | Automatic (default): resources are deleted after the test completes or fails. Manual: resources are retained for inspection until you delete the TestFailover CR. |
spec.retentionTime | string | No | Duration string (e.g. 1h, 30m) controlling how long test resources are kept before automatic cleanup. Requires cleanupPolicy: Automatic. |
spec.timeout | string | No | Maximum wall-clock duration for the entire test operation before the controller marks it Failed. |
spec.batchBootTimeoutSeconds | integer | No | Per-batch timeout for test VMs reaching a running state. |
spec.verification | object | No | Structured verification checks to run inside test VMs (e.g., filesystem consistency, application health probes). |
Status phases: CreatingSnapshots → ProvisioningTestPVCs → StartingTestVMs → VerifyingData → Succeeded (or Failed) → CleaningUp
ReplicationGroupStatus
Aggregates replication health for a ProtectionGroup into a single resource. The replication-monitor reconciler creates and updates this resource automatically; you create the CR to register a group for monitoring.
| Field | Type | Required | Description |
|---|---|---|---|
spec.protectionGroupRef | object | Yes | Name and namespace of the ProtectionGroup to monitor. |
spec.replicationProtocol | string | No | Expected protocol (A or C). Used to validate that the live DRBD configuration matches intent. |
spec.rpoObjectiveSeconds | integer | No | RPO threshold in seconds. Violations generate RPOEvent resources. |
spec.pollingIntervalSeconds | integer | No | How often the replication-monitor queries DRBD state. Lower values give faster alerting at the cost of more API calls. |
Status fields (read-only): health (Healthy, Degraded, Critical), lastSyncTimestamp, per-volume sync detail array.
RPOEvent
Records a single replication lag violation for audit and alerting. Created automatically by the replication-monitor when observed lag exceeds the rpoObjectiveSeconds configured in the associated ReplicationGroupStatus.
| Field | Type | Required | Description |
|---|---|---|---|
spec.protectionGroupRef | object | Yes | ProtectionGroup that experienced the violation. |
spec.eventType | string | Yes | Classification: ViolationStart, ViolationEnd, VolumeDegraded. |
spec.timestamp | string | Yes | RFC 3339 timestamp when the event was observed. |
spec.rpoAtEvent | integer | No | Observed replication lag in seconds at the time of the event. |
spec.violationReason | string | No | Human-readable explanation (e.g., NetworkPartition, HighIOLoad). |
spec.affectedVolumes | array | No | List of PVC names whose replication was lagging. |
spec.outOfSyncBytesAtEvent | integer | No | Number of bytes not yet replicated to the DR side at event time. |
spec.connectionDetails | object | No | DRBD connection state snapshot at event time. |
spec.previousRPOStatus | string | No | Health classification before this event. |
spec.newRPOStatus | string | No | Health classification after this event. |
spec.secondsSinceLastEvent | integer | No | Interval since the previous RPOEvent for this group. |
spec.recoveryEstimate | object | No | Estimated time to resync based on current throughput. |
spec.message | string | No | Free-text summary for display in the TSR web console. |
All day-to-day DR operations are performed by creating or patching Custom Resource manifests with kubectl or oc. The operator controllers continuously reconcile the desired state expressed in these resources against the actual state of VMs, DRBD volumes, and cluster connectivity.
Common patterns
1. Establish a DR deployment
Create a ProtectionZone on the quorum cluster to register the primary and DR cluster pair under a management namespace:
kubectl apply -f protectionzone.yaml --kubeconfig ~/.kube/config-quorum
2. Define a replication policy
Create a DRBDReplicationPolicy on the primary cluster to specify storage class mappings and the replication protocol:
kubectl apply -f drbd-replication-policy.yaml --kubeconfig ~/.kube/config-primary
3. Protect a VM
Create a ProtectionRequest on the quorum cluster. The protection-controller validates the VM, provisions the DRBDResource pair, and switches the VM to DRBD-backed frontend PVCs:
kubectl apply -f protection-request.yaml --kubeconfig ~/.kube/config-quorum
# Watch protection phases
kubectl get protectionrequest <name> -n <namespace> -w --kubeconfig ~/.kube/config-quorum
4. Group VMs for coordinated failover
Create a ProtectionGroup that references the protected VMs. All VMs must be in the same namespace:
kubectl apply -f protection-group.yaml --kubeconfig ~/.kube/config-primary
5. Monitor replication health
Create a ReplicationGroupStatus to register the group for continuous health monitoring:
kubectl apply -f replication-group-status.yaml --kubeconfig ~/.kube/config-quorum
# Check aggregated health
kubectl get replicationgroupstatus <name> -n <namespace> -o jsonpath='{.status.health}' --kubeconfig ~/.kube/config-quorum
6. Run a non-disruptive test failover
Create a TestFailover on the quorum cluster. This does not affect production VMs:
kubectl apply -f test-failover.yaml --kubeconfig ~/.kube/config-quorum
kubectl get testfailover <name> -n <namespace> -w --kubeconfig ~/.kube/config-quorum
7. Execute a planned failover
Create a FailoverRequest on the quorum cluster with failoverType: planned:
kubectl apply -f failover-request.yaml --kubeconfig ~/.kube/config-quorum
kubectl get failoverrequest <name> -n <namespace> -w --kubeconfig ~/.kube/config-quorum
8. Execute an unplanned failover
Create a FailoverRequest with failoverType: unplanned. The failover-controller force-promotes DRBD volumes on the DR cluster without waiting for the primary cluster to respond:
kubectl apply -f unplanned-failover-request.yaml --kubeconfig ~/.kube/config-quorum
9. Audit RPO compliance
List all RPOEvents for a namespace to review historical replication lag violations:
kubectl get rpoevents -n <namespace> --sort-by='.spec.timestamp' --kubeconfig ~/.kube/config-quorum
Each example below is a complete, self-contained manifest. Replace placeholder values (angle brackets) with values from your environment.
Example 1 — ProtectionZone: register a DR deployment
Create this on the quorum cluster before any other resources. The namespace dr-production is created by the ProtectionZone controller.
apiVersion: siterecovery.trilio.io/v1alpha1
kind: ProtectionZone
metadata:
name: production
namespace: dr-production
spec:
displayName: "Production DR Zone"
description: "Primary-to-DR replication for production VMs"
storageBackendMode: drbd-operator
clusters:
- name: primary-cluster
role: primary
kubeConfigSecret: primary-cluster-kubeconfig
- name: dr-cluster
role: dr
kubeConfigSecret: dr-cluster-kubeconfig
Apply and verify:
kubectl apply -f protectionzone.yaml --kubeconfig ~/.kube/config-quorum
kubectl get protectionzone production -n dr-production --kubeconfig ~/.kube/config-quorum
Example 2 — DRBDReplicationPolicy: synchronous replication
Create on the primary cluster. This policy uses Protocol C (RPO=0) and is the default for all ProtectionRequests in the zone.
apiVersion: siterecovery.trilio.io/v1alpha1
kind: DRBDReplicationPolicy
metadata:
name: sync-policy
namespace: dr-production
spec:
clusters:
- name: primary-cluster
role: source
- name: dr-cluster
role: target
replicationMode: synchronous
drbdProtocol: C
isDefault: true
diskConfig:
storageClassMappings:
- sourceStorageClass: standard-rwo
targetStorageClass: standard-rwo-dr
networkConfig:
portRangeStart: 7000
portRangeEnd: 7999
rpo:
objectiveSeconds: 0
Note:
drbdProtocol: Crequires round-trip latency below 50ms between primary and DR worker nodes on TCP ports 7000–7999.
Example 3 — ProtectionRequest: protect a single VM
Create on the quorum cluster. The protection-controller provisions a DRBDResource pair and switches the VM to DRBD-backed frontend PVCs.
apiVersion: siterecovery.trilio.io/v1alpha1
kind: ProtectionRequest
metadata:
name: protect-web-vm-01
namespace: dr-production
spec:
sourceCluster: primary-cluster
virtualMachine:
name: web-vm-01
namespace: production
Watch the protection phases:
kubectl get protectionrequest protect-web-vm-01 -n dr-production -w \
--kubeconfig ~/.kube/config-quorum
Expected output (phases progress automatically):
NAME PHASE AGE
protect-web-vm-01 Pending 2s
protect-web-vm-01 Validating 5s
protect-web-vm-01 ProvisioningDRBDResource 12s
protect-web-vm-01 SwitchingPVCs 28s
protect-web-vm-01 Protected 45s
Example 4 — ProtectionGroup: group VMs for coordinated failover
Create on the primary cluster. All VMs must be in the production namespace.
apiVersion: siterecovery.trilio.io/v1alpha1
kind: ProtectionGroup
metadata:
name: production-group
namespace: production
spec:
sourceCluster: primary-cluster
virtualMachines:
- name: web-vm-01
- name: web-vm-02
- name: db-vm-01
sla:
rpoObjectiveSeconds: 0
Verify replication health:
kubectl get protectiongroup production-group -n production \
-o jsonpath='{.status.replicationHealth}' \
--kubeconfig ~/.kube/config-primary
Expected output:
Healthy
Example 5 — TestFailover: non-disruptive DR validation
Create on the quorum cluster. Test VMs are started in an isolated namespace and cleaned up automatically after one hour.
apiVersion: siterecovery.trilio.io/v1alpha1
kind: TestFailover
metadata:
name: quarterly-dr-test
namespace: dr-production
spec:
protectionGroupRef:
name: production-group
namespace: production
cleanupPolicy: Automatic
retentionTime: 1h
timeout: 30m
batchBootTimeoutSeconds: 300
verification:
runHealthProbes: true
Watch phases:
kubectl get testfailover quarterly-dr-test -n dr-production -w \
--kubeconfig ~/.kube/config-quorum
Expected output:
NAME PHASE AGE
quarterly-dr-test CreatingSnapshots 5s
quarterly-dr-test ProvisioningTestPVCs 18s
quarterly-dr-test StartingTestVMs 35s
quarterly-dr-test VerifyingData 2m10s
quarterly-dr-test Succeeded 4m02s
quarterly-dr-test CleaningUp 4m05s
Example 6 — FailoverRequest: planned failover
Create on the quorum cluster. The failover-controller gracefully shuts down VMs on the primary cluster, promotes DRBD volumes on the DR cluster, and starts VMs there.
apiVersion: siterecovery.trilio.io/v1alpha1
kind: FailoverRequest
metadata:
name: planned-failover-20240315
namespace: dr-production
spec:
protectionGroupRef:
name: production-group
namespace: production
targetCluster: dr-cluster
failoverType: planned
drainTimeoutSeconds: 120
batchBootTimeoutSeconds: 300
Watch the failover progress:
kubectl get failoverrequest planned-failover-20240315 -n dr-production -w \
--kubeconfig ~/.kube/config-quorum
Expected output:
NAME PHASE AGE
planned-failover-20240315 Pending 2s
planned-failover-20240315 StoppingOnSource 8s
planned-failover-20240315 WaitingForDRBD 42s
planned-failover-20240315 StartingOnTarget 55s
planned-failover-20240315 Completed 3m22s
Example 7 — FailoverRequest: unplanned (emergency) failover
Use when the primary cluster is unreachable. DRBD volumes are force-promoted on the DR cluster.
apiVersion: siterecovery.trilio.io/v1alpha1
kind: FailoverRequest
metadata:
name: emergency-failover-20240315
namespace: dr-production
spec:
protectionGroupRef:
name: production-group
namespace: production
targetCluster: dr-cluster
failoverType: unplanned
batchBootTimeoutSeconds: 300
Warning: With
failoverType: unplanned, if the DRBDReplicationPolicy uses Protocol A (asynchronous), VMs on the DR cluster will start from the last replicated state, which may be seconds behind the primary. Protocol C (synchronous) deployments retain RPO=0 even in unplanned failovers.
Example 8 — ReplicationGroupStatus: register a group for health monitoring
apiVersion: siterecovery.trilio.io/v1alpha1
kind: ReplicationGroupStatus
metadata:
name: production-group-health
namespace: dr-production
spec:
protectionGroupRef:
name: production-group
namespace: production
replicationProtocol: C
rpoObjectiveSeconds: 0
pollingIntervalSeconds: 30
Query aggregated health:
kubectl get replicationgroupstatus production-group-health -n dr-production \
-o jsonpath='{.status}' \
--kubeconfig ~/.kube/config-quorum | jq .
Expected output:
{
"health": "Healthy",
"lastSyncTimestamp": "2024-03-15T14:23:01Z",
"volumes": [
{ "pvcName": "web-vm-01-disk", "syncState": "UpToDate" },
{ "pvcName": "db-vm-01-disk", "syncState": "UpToDate" }
]
}
Use the following patterns to diagnose common failures. For any issue that requires log inspection across clusters, run tsr-gather against the quorum cluster to collect a unified diagnostic bundle before opening a support case.
# Collect a full diagnostic bundle
oc adm must-gather --image=<tsr-gather-image> --kubeconfig ~/.kube/config-quorum
Issue: ProtectionRequest stuck in Validating phase
Symptom: A ProtectionRequest remains in the Validating phase for more than two minutes and does not progress to ProvisioningDRBDResource.
Likely causes:
- The referenced VirtualMachine does not exist or is in a different namespace than stated in
spec.virtualMachine.namespace. - The VM's PVCs use a storage class that is not mapped in any DRBDReplicationPolicy.
- No DRBDReplicationPolicy exists for this ProtectionZone, or none has
isDefault: true.
Fix:
# Check protection-controller logs on the quorum cluster
kubectl logs -n dr-system -l app=site-recovery-quorum-control-plane \
--kubeconfig ~/.kube/config-quorum | grep -i "protect-request-name"
# Verify the VM exists on the primary cluster
kubectl get vm <vm-name> -n <namespace> --kubeconfig ~/.kube/config-primary
# Verify a default DRBDReplicationPolicy exists
kubectl get drbdreplicationpolicy -n dr-production \
-o jsonpath='{range .items[*]}{.metadata.name}{" isDefault="}{.spec.isDefault}{"\n"}{end}' \
--kubeconfig ~/.kube/config-primary
Issue: ProtectionGroup admission webhook rejects a create or update with a namespace error
Symptom: kubectl apply returns an error similar to: admission webhook denied: all VMs in a ProtectionGroup must be in the same namespace.
Likely cause: You referenced VMs from more than one namespace in spec.virtualMachines. The validating webhook enforces single-namespace membership per group (tracked as TSR-162; multi-namespace support is tracked by TSR-157).
Fix: Split VMs into separate ProtectionGroups, one per namespace. You cannot work around this restriction with --force or by pre-creating the resource; the webhook rejects any create or update that would leave members spanning namespaces.
# Identify which VMs span namespaces
kubectl get protectiongroup <name> -n <namespace> \
-o jsonpath='{.spec.virtualMachines}' \
--kubeconfig ~/.kube/config-primary | jq .
Issue: FailoverRequest stuck in StoppingOnSource phase
Symptom: A FailoverRequest has been in the StoppingOnSource phase longer than the configured drainTimeoutSeconds.
Likely causes:
- The failover-controller cannot reach the primary cluster API server (port 6443 from quorum cluster blocked).
- A VM on the primary cluster is stuck in a shutdown loop due to a guest OS issue.
- The ProtectionGroup on the primary cluster is in a
Degradedstate and the protection-group-controller is not reconciling.
Fix:
# Check failover-controller logs
kubectl logs -n dr-system -l app=site-recovery-quorum-control-plane \
--kubeconfig ~/.kube/config-quorum | grep -i "failover"
# Check connectivity from quorum cluster to primary API
kubectl get nodes --kubeconfig ~/.kube/config-primary
# Check ProtectionGroup status on primary
kubectl get protectiongroup <pg-name> -n <namespace> \
-o jsonpath='{.status}' \
--kubeconfig ~/.kube/config-primary | jq .
# If primary is truly unreachable, re-create the FailoverRequest with unplanned type
kubectl delete failoverrequest <name> -n dr-production --kubeconfig ~/.kube/config-quorum
# Then apply an updated manifest with failoverType: unplanned
Issue: TestFailover fails at VerifyingData phase
Symptom: A TestFailover reaches VerifyingData and then transitions to Failed. The test VMs started successfully.
Likely causes:
- A verification probe defined in
spec.verificationfailed (application did not respond within the timeout). - The snapshot taken at
CreatingSnapshotscaptured a partially written state that caused a filesystem inconsistency.
Fix:
# Inspect the TestFailover status for the specific failure reason
kubectl get testfailover <name> -n dr-production \
-o jsonpath='{.status}' \
--kubeconfig ~/.kube/config-quorum | jq .
# If cleanupPolicy is Automatic, check logs before resources are deleted
kubectl logs -n dr-system -l app=site-recovery-quorum-control-plane \
--kubeconfig ~/.kube/config-quorum | grep -i "testfailover"
# For deeper inspection, re-run with cleanupPolicy: Manual
# to retain test VMs and PVCs for examination
Issue: RPOEvent resources are being created continuously
Symptom: The quorum cluster accumulates a growing number of RPOEvent resources with eventType: ViolationStart and no corresponding ViolationEnd events.
Likely causes:
- Network bandwidth between primary and DR worker nodes (TCP 7000–7999) is saturated, causing DRBD replication lag to consistently exceed
rpoObjectiveSeconds. - The DRBDReplicationPolicy uses Protocol C (
synchronous) but the round-trip latency between clusters has exceeded 50ms, stalling writes. - A drbd-node-agent pod on a worker node has crashed and is not reporting replication state.
Fix:
# Check current replication group health
kubectl get replicationgroupstatus -n dr-production \
-o jsonpath='{range .items[*]}{.metadata.name}{" health="}{.status.health}{"\n"}{end}' \
--kubeconfig ~/.kube/config-quorum
# List recent RPO events sorted by timestamp
kubectl get rpoevents -n dr-production \
--sort-by='.spec.timestamp' \
--kubeconfig ~/.kube/config-quorum
# Check drbd-node-agent DaemonSet health on the primary cluster
kubectl get daemonset -n dr-system --kubeconfig ~/.kube/config-primary
kubectl get pods -n dr-system -l app=drbd-node-agent \
--kubeconfig ~/.kube/config-primary
# Review individual agent logs for connection errors
kubectl logs -n dr-system -l app=drbd-node-agent \
--kubeconfig ~/.kube/config-primary | grep -i "error\|disconnect"
If the replication protocol is Protocol C and latency has risen above 50ms, consider switching to Protocol A in the DRBDReplicationPolicy (replicationMode: asynchronous, drbdProtocol: A) and accepting a seconds-scale RPO until the network issue is resolved.
Issue: DRBDResource stuck with role: Secondary on both sides
Symptom: After a failover or protection operation, both the primary-side and DR-side DRBDResource CRs show role: Secondary, and VMs will not start.
Likely cause: A split-brain condition or an interrupted failover left both DRBD resource sides in Secondary role. The failover-controller's promote step did not complete.
Fix:
# Check the FailoverRequest status for the last operation
kubectl get failoverrequest -n dr-production \
--kubeconfig ~/.kube/config-quorum
# Check drbd-node-agent logs for promotion errors
kubectl logs -n dr-system -l app=drbd-node-agent \
--kubeconfig ~/.kube/config-dr | grep -i "promote\|primary\|split-brain"
# If the intended primary side is known and data is consistent,
# delete the stale FailoverRequest and create a new one.
# The failover-controller will re-run the promotion sequence.
kubectl delete failoverrequest <stale-name> -n dr-production \
--kubeconfig ~/.kube/config-quorum
# Then re-apply your FailoverRequest manifest
If the issue persists after re-running the FailoverRequest, collect a tsr-gather bundle and contact Trilio support before manually modifying DRBDResource role fields.