Installation
Installing CRDs and deploying the site recovery operator
This page walks you through installing Trilio Site Recovery on OpenShift clusters using the DRBD Operator deployment model. By the end of these steps, you will have the quorum control plane running on your management cluster, the workload control plane running on your primary and DR clusters, and all required CRDs in place—ready for you to create your first ProtectionZone and begin protecting virtual machine workloads. Installing in the correct order matters: the ProtectionZone webhook must be present before the quorum control plane chart, and the workload control plane must be healthy on both workload clusters before you apply any protection CRDs.
Before you begin, confirm the following are in place across all three clusters.
OpenShift clusters
- OpenShift ≥ 4.14 on each cluster (quorum, primary, DR)
kubectlorocCLI configured and able to reach all three cluster API servers- Helm ≥ 3.0 installed on your workstation
- A valid kubeconfig for each cluster
Virtualization
- OpenShift Virtualization (CNV) ≥ 1.0 installed on the primary and DR clusters
- KubeVirt VMs you intend to protect must be running before protection CRDs are applied
Storage and networking
- DRBD Operator installed on both the primary and DR clusters
- DRBD kernel module ≥ 9.x loaded on every worker node of the primary and DR clusters
- TCP ports 7000–7999 open between all primary and DR worker nodes (DRBD replication)
- TCP port 6443 open from the quorum cluster to the primary and DR API servers
- For synchronous replication (Protocol C, RPO=0): round-trip latency below 50 ms between primary and DR worker nodes
Credentials and access
- Cluster-admin (or equivalent) privileges on all three clusters
- Kubeconfig files for all clusters accessible on the workstation running Helm and kubectl
Environment variables used throughout this guide
export KUBECONFIG_PRIMARY=~/.kube/config-primary
export KUBECONFIG_DR=~/.kube/config-dr
export KUBECONFIG_QUORUM=~/.kube/config-quorum
Set these before running any command on this page.
Installation follows four ordered stages. Do not skip or reorder them.
Stage 1 — Install the workload control plane on the primary cluster
The site-recovery-workload-control-plane chart installs the DRBD node agent DaemonSet, admission webhooks, and supporting RBAC needed for VM protection and replication on a workload cluster. Install it on the primary cluster first.
helm install site-recovery ./helm/site-recovery-workload-control-plane \
--kubeconfig $KUBECONFIG_PRIMARY \
-n trilio-site-recovery-system \
--create-namespace
Wait until the DaemonSet is fully rolled out before continuing:
kubectl --kubeconfig $KUBECONFIG_PRIMARY \
-n trilio-site-recovery-system \
rollout status daemonset/drbd-node-agent
Stage 2 — Install the workload control plane on the DR cluster
Repeat the same Helm install against the DR cluster:
helm install site-recovery ./helm/site-recovery-workload-control-plane \
--kubeconfig $KUBECONFIG_DR \
-n trilio-site-recovery-system \
--create-namespace
Verify the DaemonSet on the DR cluster:
kubectl --kubeconfig $KUBECONFIG_DR \
-n trilio-site-recovery-system \
rollout status daemonset/drbd-node-agent
Stage 3 — Install the ProtectionZone webhook on the quorum cluster
The site-recovery-protectionzone-controller chart installs the ProtectionZone admission webhook and its CRDs. It must be installed before the quorum control plane chart; the quorum control plane's admission webhook depends on the ProtectionZone CRD being registered.
Choose a DR deployment namespace. All quorum-side resources for one DR deployment live in a single namespace named dr-<name>. Replace <deployment-name> with your chosen name (for example, prod):
export DR_NAMESPACE=dr-prod
Install the ProtectionZone controller:
helm install site-recovery-pz ./helm/site-recovery-protectionzone-controller \
--kubeconfig $KUBECONFIG_QUORUM \
-n $DR_NAMESPACE \
--create-namespace
Confirm the ProtectionZone CRD is registered:
kubectl --kubeconfig $KUBECONFIG_QUORUM \
get crd protectionzones.siterecovery.trilio.io
Stage 4 — Install the quorum control plane on the quorum cluster
The site-recovery-quorum-control-plane chart deploys the unified controller manager into the same DR namespace. It packages the failover, protection, pg-sync, and replication-monitor reconcilers, plus admission webhooks, in a single Deployment.
helm install site-recovery-qcp ./helm/site-recovery-quorum-control-plane \
--kubeconfig $KUBECONFIG_QUORUM \
-n $DR_NAMESPACE
Verify the controller manager pod is running:
kubectl --kubeconfig $KUBECONFIG_QUORUM \
-n $DR_NAMESPACE \
get pods -l app.kubernetes.io/name=site-recovery-quorum-control-plane
Expected output:
NAME READY STATUS RESTARTS AGE
site-recovery-quorum-control-plane-<hash> 1/1 Running 0 60s
Stage 5 — Apply the ProtectionZone custom resource
A ProtectionZone CR tells the quorum cluster which primary and DR clusters belong to this DR deployment and which storage backend to use. Apply it in the DR namespace on the quorum cluster:
# protectionzone.yaml
apiVersion: siterecovery.trilio.io/v1alpha1
kind: ProtectionZone
metadata:
name: prod-zone
namespace: dr-prod
spec:
displayName: "Production DR Zone" # required
storageBackendMode: "drbd-operator" # required; use drbd-operator for DRBD Operator deployments
clusters:
- name: primary
kubeconfig: primary-cluster-secret # reference to a Secret holding the kubeconfig
- name: dr
kubeconfig: dr-cluster-secret
description: "Primary-to-DR replication for production VMs"
kubectl --kubeconfig $KUBECONFIG_QUORUM apply -f protectionzone.yaml
Check that the ProtectionZone is accepted:
kubectl --kubeconfig $KUBECONFIG_QUORUM \
-n dr-prod \
get protectionzone prod-zone
Stage 6 — Verify all installed components
Run the following checks before proceeding to configure replication.
Quorum cluster — Helm releases
helm --kubeconfig $KUBECONFIG_QUORUM list -n dr-prod
Expected releases: site-recovery-pz and site-recovery-qcp.
Primary and DR clusters — DRBD node agent DaemonSet health
kubectl --kubeconfig $KUBECONFIG_PRIMARY \
-n trilio-site-recovery-system get daemonset drbd-node-agent
kubectl --kubeconfig $KUBECONFIG_DR \
-n trilio-site-recovery-system get daemonset drbd-node-agent
Both DaemonSets must show DESIRED equal to READY before you proceed.
Quorum cluster — CRD registration
kubectl --kubeconfig $KUBECONFIG_QUORUM get crds | grep siterecovery.trilio.io
You should see entries for protectionzones, protectionrequests, failoverrequests, testfailovers, drbdreplicationpolicies, drbdresources, replicationgroupstatuses, and rpoevents.
After installation, the primary configuration surface is the ProtectionZone CR and the DRBDReplicationPolicy CR. Helm values files allow you to tune controller behavior at deploy time.
ProtectionZone CR — key fields
The ProtectionZone is the root configuration object for a DR deployment. Its required fields control cluster membership and storage backend selection.
| Field | Required | Valid values | Effect |
|---|---|---|---|
spec.displayName | Yes | Any string | Human-readable name shown in the TSR web console |
spec.storageBackendMode | Yes | drbd-operator | Selects the DRBD Operator storage management path |
spec.clusters | Yes | Array of cluster references | Registers primary and DR clusters with the quorum control plane |
spec.description | No | Any string | Optional free-text description |
spec.logging | No | Object | Configures log verbosity for controllers in this zone |
DRBDReplicationPolicy CR — key fields
The DRBDReplicationPolicy defines how volumes replicate between your primary and DR clusters. Create one per DR deployment on the quorum cluster. This CR is consumed by the protection controller when processing ProtectionRequest CRDs.
| Field | Required | Valid values / defaults | Effect |
|---|---|---|---|
spec.clusters | Yes | Array of cluster endpoint objects | Identifies the primary and DR cluster replication endpoints |
spec.drbdProtocol | No | C (default for RPO=0), A | C = synchronous replication, requires <50 ms RTT; A = asynchronous, suits long-distance links |
spec.replicationMode | No | String | Sets the overall replication strategy |
spec.rpo | No | Object | Defines RPO objectives that trigger RPOEvent CRs when violated |
spec.diskConfig | No | Object | Storage class mappings between primary and DR |
spec.networkConfig | No | Object | Network-level DRBD tuning (buffer sizes, timeout) |
spec.resyncConfig | No | Object | Controls resync rate-limiting to protect production I/O |
spec.isDefault | No | true / false | Marks this policy as the default for the deployment namespace |
Replication protocol choice is the most operationally significant setting:
- Use
drbdProtocol: C(Protocol C, synchronous) when your primary-to-DR round-trip latency is below 50 ms. Every write is acknowledged only after it commits on both sides, giving you RPO=0. - Use
drbdProtocol: A(Protocol A, asynchronous) for geographically distant clusters where latency exceeds 50 ms. Writes complete after local commit; replication happens in the background, yielding an RPO of seconds.
Helm values — workload control plane
Pass values at install time with --set or a values file. The most commonly tuned settings for site-recovery-workload-control-plane are node selector and tolerations for the drbd-node-agent DaemonSet, so the agent runs only on nodes that have the DRBD kernel module loaded:
helm install site-recovery ./helm/site-recovery-workload-control-plane \
--kubeconfig $KUBECONFIG_PRIMARY \
-n trilio-site-recovery-system \
--create-namespace \
--set drbdNodeAgent.nodeSelector."drbd-enabled"="true"
Once installation is complete and your ProtectionZone CR is accepted, you interact with Site Recovery exclusively through Kubernetes custom resources applied with kubectl. The following patterns cover the most common day-one operations.
Create a DRBDReplicationPolicy
Before protecting any VM, define how replication works between your clusters. Apply this on the quorum cluster in your DR namespace:
# drbd-replication-policy.yaml
apiVersion: siterecovery.trilio.io/v1alpha1
kind: DRBDReplicationPolicy
metadata:
name: prod-replication-policy
namespace: dr-prod
spec:
drbdProtocol: C
isDefault: true
clusters:
- name: primary
replicationEndpoint: "10.0.1.10:7000"
- name: dr
replicationEndpoint: "10.0.2.10:7000"
diskConfig:
storageClassMappings:
- primaryStorageClass: ocs-storagecluster-ceph-rbd
drStorageClass: ocs-storagecluster-ceph-rbd
kubectl --kubeconfig $KUBECONFIG_QUORUM apply -f drbd-replication-policy.yaml
Protect a virtual machine
A ProtectionRequest CR asks the protection controller to validate a VM, provision a DRBDResource pair covering all of the VM's disks, and switch the VM to DRBD-backed frontend PVCs. Apply it on the quorum cluster:
# protection-request.yaml
apiVersion: siterecovery.trilio.io/v1alpha1
kind: ProtectionRequest
metadata:
name: protect-vm-web
namespace: dr-prod
spec:
sourceCluster: primary # required
virtualMachine: # required
name: vm-web-server
namespace: production
kubectl --kubeconfig $KUBECONFIG_QUORUM apply -f protection-request.yaml
Watch the protection progress:
kubectl --kubeconfig $KUBECONFIG_QUORUM \
-n dr-prod \
get protectionrequest protect-vm-web -w
The protection controller moves the request through phases: Pending → Validating → ProvisioningDRBDResource → SwitchingPVCs → Protected.
Monitor replication health
Check the ReplicationGroupStatus CR to see aggregated health for a set of protected VMs:
kubectl --kubeconfig $KUBECONFIG_QUORUM \
-n dr-prod \
get replicationgroupstatus
Inspect individual DRBDResource CRs on the primary cluster to see per-volume sync progress:
kubectl --kubeconfig $KUBECONFIG_PRIMARY \
-n trilio-site-recovery-system \
get drbdresource -o wide
List all Site Recovery CRDs in use
# Quorum cluster — failover and protection CRDs
kubectl --kubeconfig $KUBECONFIG_QUORUM \
get protectionrequest,failoverrequest,testfailover,replicationgroupstatus,rpoevent -A
# Primary cluster — workload-side CRDs
kubectl --kubeconfig $KUBECONFIG_PRIMARY \
get drbdresource,drbdreplicationpolicy -A
Example 1 — Minimal DRBDReplicationPolicy with asynchronous replication
Use Protocol A when your DR site is geographically distant and latency exceeds 50 ms.
apiVersion: siterecovery.trilio.io/v1alpha1
kind: DRBDReplicationPolicy
metadata:
name: async-replication-policy
namespace: dr-prod
spec:
drbdProtocol: A
isDefault: false
clusters:
- name: primary
replicationEndpoint: "10.0.1.10:7000"
- name: dr
replicationEndpoint: "192.168.100.5:7000"
diskConfig:
storageClassMappings:
- primaryStorageClass: ocs-storagecluster-ceph-rbd
drStorageClass: ocs-storagecluster-ceph-rbd
kubectl --kubeconfig $KUBECONFIG_QUORUM apply -f async-replication-policy.yaml
Expected output:
drbdreplicationpolicy.siterecovery.trilio.io/async-replication-policy created
Example 2 — ProtectionRequest for a single VM
Protect a VM named vm-database running in the production namespace on the primary cluster.
apiVersion: siterecovery.trilio.io/v1alpha1
kind: ProtectionRequest
metadata:
name: protect-vm-database
namespace: dr-prod
spec:
sourceCluster: primary
virtualMachine:
name: vm-database
namespace: production
kubectl --kubeconfig $KUBECONFIG_QUORUM apply -f protect-vm-database.yaml
kubectl --kubeconfig $KUBECONFIG_QUORUM \
-n dr-prod \
get protectionrequest protect-vm-database -o wide
Expected output (after the controller finishes):
NAME STATUS VM NAMESPACE AGE
protect-vm-database Protected vm-database production 3m12s
Example 3 — Non-disruptive test failover
Validate DR readiness without affecting production workloads. The test-failover-controller creates snapshot-based copies of the VM's volumes on the DR cluster, starts test VMs in an isolated namespace, runs verification checks, and then cleans up.
apiVersion: siterecovery.trilio.io/v1alpha1
kind: TestFailover
metadata:
name: test-prod-zone
namespace: dr-prod
spec:
protectionGroupRef: # required
name: prod-protection-group
namespace: dr-prod
cleanupPolicy: Manual
retentionTime: 2h
batchBootTimeoutSeconds: 300
kubectl --kubeconfig $KUBECONFIG_QUORUM apply -f test-failover.yaml
kubectl --kubeconfig $KUBECONFIG_QUORUM \
-n dr-prod \
get testfailover test-prod-zone -w
Expected phase progression:
NAME PHASE AGE
test-prod-zone CreatingSnapshots 10s
test-prod-zone CreatingVolumes 45s
test-prod-zone CreatingVMs 90s
test-prod-zone VerifyingData 2m10s
test-prod-zone Succeeded 3m05s
When you are finished inspecting the test environment, delete the CR to trigger cleanup:
kubectl --kubeconfig $KUBECONFIG_QUORUM \
-n dr-prod \
delete testfailover test-prod-zone
Example 4 — Planned failover
A FailoverRequest with failoverType: planned gracefully shuts down VMs on the primary cluster before promoting DRBD volumes on the DR cluster, ensuring zero data loss.
apiVersion: siterecovery.trilio.io/v1alpha1
kind: FailoverRequest
metadata:
name: planned-failover-prod
namespace: dr-prod
spec:
protectionGroupRef: # required
name: prod-protection-group
targetCluster: dr # required
failoverType: planned
drainTimeoutSeconds: 120
batchBootTimeoutSeconds: 300
kubectl --kubeconfig $KUBECONFIG_QUORUM apply -f planned-failover.yaml
kubectl --kubeconfig $KUBECONFIG_QUORUM \
-n dr-prod \
get failoverrequest planned-failover-prod -w
Expected output:
NAME STATUS AGE
planned-failover-prod Pending 2s
planned-failover-prod InProgress 8s
planned-failover-prod Completed 4m33s
Example 5 — Collect a diagnostic support bundle
Run tsr-gather from the quorum cluster to collect logs and CRD state across all clusters in the DR estate. This tool is compatible with oc adm must-gather.
# Collect the full estate (default 48 h of logs)
oc adm must-gather \
--image=$DOCKER_REGISTRY/site-recovery-must-gather:<tag>
# Limit to the last 2 hours of logs
oc adm must-gather \
--image=$DOCKER_REGISTRY/site-recovery-must-gather:<tag> \
-- /usr/bin/gather --since 2h
# Scope to a single protection zone
oc adm must-gather \
--image=$DOCKER_REGISTRY/site-recovery-must-gather:<tag> \
-- /usr/bin/gather --zone prod-zone
The bundle is written to a local directory. Open SUMMARY.md first to see flagged issues, then manifest.yaml for a full inventory of collected resources. The tool never captures secrets, kubeconfigs, cloud-init payloads, or DRBD shared secrets.
Use a consistent approach for each issue: confirm the symptom with the listed command, identify the likely cause, then apply the fix.
Issue 1 — Quorum control plane pod is not starting
Symptom: The site-recovery-quorum-control-plane pod stays in Pending or CrashLoopBackOff.
kubectl --kubeconfig $KUBECONFIG_QUORUM \
-n dr-prod \
describe pod -l app.kubernetes.io/name=site-recovery-quorum-control-plane
Likely cause — ProtectionZone webhook not installed: The quorum control plane admission webhook depends on the ProtectionZone CRD. If site-recovery-protectionzone-controller was not installed first, webhook calls fail and the pod crashes.
Fix: Install the ProtectionZone controller chart before the quorum control plane chart, then restart the quorum control plane pod.
helm install site-recovery-pz ./helm/site-recovery-protectionzone-controller \
--kubeconfig $KUBECONFIG_QUORUM \
-n dr-prod
kubectl --kubeconfig $KUBECONFIG_QUORUM \
-n dr-prod \
rollout restart deployment site-recovery-quorum-control-plane
Likely cause — missing cluster credentials: The controller cannot reach the primary or DR API servers. Verify that the kubeconfig Secrets referenced in your ProtectionZone CR exist in the DR namespace.
kubectl --kubeconfig $KUBECONFIG_QUORUM \
-n dr-prod \
get secrets
Issue 2 — DRBD node agent DaemonSet has pods not ready
Symptom: DESIRED and READY counts differ for the drbd-node-agent DaemonSet.
kubectl --kubeconfig $KUBECONFIG_PRIMARY \
-n trilio-site-recovery-system \
get daemonset drbd-node-agent
kubectl --kubeconfig $KUBECONFIG_PRIMARY \
-n trilio-site-recovery-system \
describe pod -l app=drbd-node-agent | grep -A 10 Events
Likely cause — DRBD kernel module not loaded: The agent requires the DRBD kernel module ≥ 9.x on each worker node. A missing or incompatible module causes the pod to fail its readiness probe.
Fix: On each affected worker node, verify and load the module:
# Run on each affected worker node
lsmod | grep drbd
modprobe drbd
Confirm the node's pod becomes ready after the module is loaded:
kubectl --kubeconfig $KUBECONFIG_PRIMARY \
-n trilio-site-recovery-system \
get pods -l app=drbd-node-agent -o wide
Issue 3 — ProtectionRequest stays in Pending
Symptom: A ProtectionRequest CR does not advance beyond Pending.
kubectl --kubeconfig $KUBECONFIG_QUORUM \
-n dr-prod \
describe protectionrequest <name>
Likely cause — no DRBDReplicationPolicy: The protection controller requires a DRBDReplicationPolicy in the same DR namespace. If none exists (or isDefault is not set and no policy is referenced), the request stalls.
Fix: Apply a DRBDReplicationPolicy in the same namespace and set isDefault: true, then verify:
kubectl --kubeconfig $KUBECONFIG_QUORUM \
-n dr-prod \
get drbdreplicationpolicy
Likely cause — VM not found on source cluster: The protection controller validates the VM on the source cluster before proceeding. Ensure the VM name and namespace in spec.virtualMachine are correct and that the VM exists and is running.
kubectl --kubeconfig $KUBECONFIG_PRIMARY \
-n <vm-namespace> \
get vm <vm-name>
Issue 4 — TestFailover stuck in CreatingSnapshots
Symptom: A TestFailover CR stays in the CreatingSnapshots phase for more than a few minutes.
kubectl --kubeconfig $KUBECONFIG_QUORUM \
-n dr-prod \
describe testfailover <name>
Likely cause — VolumeSnapshot CRD or snapshot class not present: The test-failover-controller creates VolumeSnapshot resources on the DR cluster. If the CSI snapshot CRDs or a compatible VolumeSnapshotClass are not installed, snapshot creation silently queues.
Fix: On the DR cluster, confirm snapshot infrastructure is present:
kubectl --kubeconfig $KUBECONFIG_DR \
get volumesnapshotclass
kubectl --kubeconfig $KUBECONFIG_DR \
get crd volumesnapshots.snapshot.storage.k8s.io
Install the CSI snapshot controller and a compatible VolumeSnapshotClass if they are missing, then delete and re-create the TestFailover CR.
Issue 5 — FailoverRequest stays in InProgress
Symptom: A FailoverRequest is stuck in InProgress for longer than 10 minutes.
kubectl --kubeconfig $KUBECONFIG_QUORUM \
-n dr-prod \
describe failoverrequest <name>
Likely cause — primary cluster unreachable (planned failover): For a planned failover, the failover controller waits for the primary cluster to confirm VM shutdown. If the primary API server is unreachable, the drain step times out.
Fix: If the primary cluster is truly unavailable and this is a disaster scenario, delete the current FailoverRequest and re-create it with failoverType: unplanned. This force-promotes DRBD volumes on the DR cluster without waiting for the primary.
kubectl --kubeconfig $KUBECONFIG_QUORUM \
-n dr-prod \
delete failoverrequest <name>
# unplanned-failover.yaml
apiVersion: siterecovery.trilio.io/v1alpha1
kind: FailoverRequest
metadata:
name: emergency-failover
namespace: dr-prod
spec:
protectionGroupRef:
name: prod-protection-group
targetCluster: dr
failoverType: unplanned
kubectl --kubeconfig $KUBECONFIG_QUORUM apply -f unplanned-failover.yaml
Warning: An unplanned failover may result in minimal data loss if Protocol A (asynchronous) replication was in use at the time of the failure.
Issue 6 — ReplicationGroupStatus shows Degraded or Critical
Symptom: kubectl get replicationgroupstatus shows a status of Degraded or Critical for one or more protection groups.
kubectl --kubeconfig $KUBECONFIG_QUORUM \
-n dr-prod \
get replicationgroupstatus -o wide
kubectl --kubeconfig $KUBECONFIG_QUORUM \
-n dr-prod \
get rpoevent
Likely cause — DRBD replication link down: A network interruption on TCP ports 7000–7999 between primary and DR worker nodes breaks the DRBD connection.
Fix: Confirm port connectivity from a primary worker node to a DR worker node:
# Run on a primary worker node
nc -zv <dr-worker-node-ip> 7000
Restore network connectivity, then watch the ReplicationGroupStatus return to Healthy as DRBD reconnects and resyncs.
Likely cause — RPO threshold violated: An RPOEvent CR with severity critical indicates replication lag has exceeded the configured RPO objective. Check the event details and consider switching to Protocol C if latency permits.