Key Concepts
Kubernetes concepts and site-recovery-specific terminology such as recovery plans, failover, and failback
This page defines the core Kubernetes and Trilio Site Recovery (TSR)-specific concepts you need to understand before planning, deploying, or operating a disaster recovery environment. TSR is a Kubernetes operator (Tech Preview) that automates synchronous block-level replication of KubeVirt virtual machines across two workload clusters, with a third quorum cluster hosting the management plane. Every DR operation—protecting VMs, triggering failover, running non-disruptive tests, and monitoring replication health—is driven by Custom Resource Definitions in the siterecovery.trilio.io/v1alpha1 API group. Understanding the concepts on this page will help you map your infrastructure to TSR's object model and interpret the status fields and events the operator produces.
Cluster topology
A TSR deployment always involves exactly three clusters:
| Cluster | Role |
|---|---|
| Quorum cluster | Hosts the management plane: the ProtectionZone controller, the unified quorum controller manager (failover, protection, pg-sync, replication-monitor, and test-failover reconcilers), the TSR web console, and all quorum-side CRDs. Holds no VM workloads and no DRBD storage. Required—not optional. |
| Primary cluster (cluster1 or cluster2) | Runs the active KubeVirt VMs and the workload control plane (DRBD node agent DaemonSet, admission webhooks). |
| DR cluster (cluster2 or cluster1) | Hosts standby VMs and a mirror workload control plane. DRBD replicates every protected VM's volumes here in real time. |
The two workload clusters occupy the fixed deployment slots cluster1 and cluster2. These identifiers are used throughout the API (for example, ProtectionRequest.spec.sourceCluster and FailoverRequest.spec.targetCluster). The slot assignment is fixed at zone creation time and cannot be changed without re-creating the zone.
Single-Node OpenShift (SNO) is supported for both workload clusters and the quorum cluster. On a single-node workload cluster, a node failure means the entire cluster side is lost—plan accordingly.
ProtectionZone
A ProtectionZone (pz) is the top-level configuration object for one TSR deployment. It lives on the quorum cluster in a namespace named dr-<zone-name> and declares:
- Which two workload clusters participate (
spec.clusters, exactly two entries, one per slot) - The kubeconfig Secrets the quorum controllers use to reach each cluster (
kubeconfigSecretRef) - The storage backend (
storageBackendMode: DRBD—the only supported value in Tech Preview) - The OS family of each cluster's nodes (
distributionName: RHEL-CoreOS-9orRHEL-CoreOS-10), which selects the DRBD kernel-module loader image - Optional runtime log-level tuning (
spec.logging)
When a ProtectionZone reaches the Available phase, the ProtectionZone controller automatically deploys:
- The site-recovery-quorum-control-plane Helm chart to the quorum cluster (into the
dr-<name>namespace) - The site-recovery-workload-control-plane Helm chart to each workload cluster (into
trilio-site-recovery-system)
Customers do not run helm install directly. The charts are embedded in the operator binary and deployed automatically.
The ProtectionZone controller also creates the TSR web console (site-recovery-backend and site-recovery-frontend) and, on OpenShift, an oauth-proxy Route and console link that provide browser access. On upstream Kubernetes, the console pods are deployed but no ingress or authentication path is created—browser access is not a supported use case on non-OpenShift clusters.
Zone lifecycle phases: Provisioning → Available → Degraded or Unavailable → Deleting
Namespace placement rule: Every quorum-side CR (ProtectionGroup, ProtectionRequest, FailoverRequest, TestFailover, ReplicationGroupStatus, RPOEvent, DRBDReplicationPolicy) must live in the dr-<name> namespace. Workload-side CRs (DRBDResource) live in the protected VM's own namespace on each workload cluster, in trilio-site-recovery-system. DRBD CRs submitted outside a dr--prefixed namespace are rejected by the admission webhook.
DRBDReplicationPolicy
A DRBDReplicationPolicy (drp, drbdpolicy) defines how volumes replicate between the two clusters: storage class mappings, replication endpoints, and DRBD protocol settings.
You do not create or modify this object. When a ProtectionZone is created, the ProtectionZone controller automatically creates one default policy named <zoneName>-default with isDefault: true in the zone's dr-<name> namespace. Only the isDefault policy in a ProtectionRequest's namespace is ever consulted.
Key characteristics of the default policy:
drbdProtocol: C— Protocol C synchronous replication only (see below)replicationMode: SynchronousisDefault: true
The networkConfig.portRange field is accepted by the API but has no effect on port allocation in Tech Preview. Ports are always allocated from the fixed range 7000–8000 (one port per replicated VM).
Protocol C (synchronous replication)
Protocol C is DRBD's synchronous replication mode. A write is acknowledged to the application only after it has been committed to disk on both the primary and DR clusters. This guarantees zero data loss (RPO = 0).
Protocol C is the only supported replication protocol in Tech Preview. The admission webhook rejects replicationProtocol: A with the error ReplicationProtocolA is not supported yet. Protocol B is not supported either.
Because replication is always synchronous:
- RPO is always 0 and is not tunable. Setting a non-zero
rpowith Protocol C is rejected:rpo must be 0 when spec.replicationConfig.replicationProtocol is C. - Synchronous replication requires round-trip latency below approximately 50 ms between primary and DR worker nodes. Higher latency increases write latency for VM workloads proportionally.
- TCP ports 7000–8000 inclusive (one port per replicated VM volume) must be open between primary and DR worker nodes. Port 8000 is mandatory—a firewall rule stopping at 7999 silently prevents replication on one volume.
ProtectionGroup
A ProtectionGroup (pg) is a logical bundle of KubeVirt VMs that replicate and fail over together as an atomic unit. It lives on the quorum cluster. All members of a ProtectionGroup share the same failover outcome: you cannot fail over individual VMs from within a group.
Key fields:
spec.virtualMachines— the list of VMs to protect (at least one required); each entry can carry abootOrderto control startup sequencing during failoverspec.sourceCluster— the deployment-time source cluster slot (cluster1orcluster2); updated by the failover controller when the active cluster changesspec.desiredState— whether VMs should berunningorstoppedon the active cluster
Status fields:
status.state— one ofProtected,Syncing,Degraded,Failedstatus.activeClusterID— which cluster slot (cluster1orcluster2) currently holds the running VMsstatus.protectedVMs— per-VM replication status (Protected,Syncing,Degraded,NotProtected)
Important:
spec.sla.rpoTargetSecondsandspec.sla.rtoTargetSecondsare accepted by the API for compliance reporting but have no operational effect on replication or failover behavior in Tech Preview.
ProtectionRequest
A ProtectionRequest (pr, protect) requests block-level DRBD replication protection for a single KubeVirt VM. It lives on the quorum cluster in the zone's dr-<name> namespace.
What the protection-controller does when you create a ProtectionRequest:
- Validates the VM and its PVCs on the source cluster
- Creates a DRBDResource pair—one CR per cluster side, in the VM's namespace on each workload cluster—covering all of the VM's disks as a single DRBD resource
- Syncs data from the source to the DR side (initial resynchronization)
- Switches the VM to DRBD-backed frontend PVCs once the initial sync completes
Protection phases (exact, in order): Pending → Validating → CreatingDRBD → Syncing → ReadyToActivate → Activating → Protected
Additional phases: Unprotecting (teardown in progress), Failed.
Node pinning: A protected VM is pinned to one node per cluster for the lifetime of the protection. The source-side node is the node the VM was running on at protect time (the VM must be running to be protected). The DR-side node is chosen by the scheduler and then latched. spec.nodeName is immutable once set. There is no rebind operation—moving a protected VM to a different node requires a full unprotect and re-protect cycle.
Label on protected VMs: The label siterecovery.trilio.io/protected-vm=true is stamped on the KubeVirt VirtualMachine object on the workload cluster. Use oc get vm -A -l siterecovery.trilio.io/protected-vm=true to list all protected VMs.
Constraints on protected VMs:
- Live migration is denied by admission webhook
- Cold migration (stop, move, start) is not supported for the lifetime of the protection—the node binding is immutable
oc adm drainof a node hosting a running protected VM will not complete- Disk addition, removal, resize, and online volume expansion are not supported
Re-protection: Re-protecting a VM always requires a full unprotect and a full initial resynchronization. There is no incremental re-protect path.
DRBDResource
A DRBDResource (dres) represents all disks of a single protected VM as one replicated DRBD resource. One CR exists per VM per cluster side, in the VM's namespace on that workload cluster—never on the quorum cluster.
DRBDResource naming follows the pattern drbd-<namespace>-<vmName>-<hash8>. One CR is created on the primary cluster (with spec.side: Source) and one on the DR cluster (with spec.side: Target). Both share the same metadata.name, the same replication port, and the same volume numbers—they differ in side, role, node binding, and local PVC names.
A VM with up to 15 replicated disks is supported (the API enforces MaxItems: 15 on spec.volumes). Each disk is a volume within the single DRBD resource, numbered from 0 (boot disk).
Status phases: Pending → Provisioning → ReadyToActivate → Syncing → Consistent → Degraded → Failed → Deleting
Each DRBDResource also creates one external-metadata PVC per volume, named drbd-md-<resourceName>-v<volumeNumber>.
The DRBD node agent applies a siterecovery.trilio.io/not-ready:NoSchedule taint to its own node at startup, before touching DRBD, and removes it once every DRBD resource on that node has converged. Workloads that do not tolerate this taint will not schedule on a freshly booted node until DRBD has converged.
FailoverRequest
A FailoverRequest (fr) triggers a failover of a ProtectionGroup from its currently active cluster to a designated target cluster. It lives on the quorum cluster. You create one FailoverRequest per failover event; the object is not reused.
Two failover types:
Planned failover
spec.failoverType: planned (the default). Used when the primary cluster is available and you want a graceful, zero-data-loss switchover. The failover controller:
- Stops VMs on the source cluster in reverse boot-order
- Waits for DRBD to confirm the final sync
- Promotes the DR DRBD volumes to Primary
- Starts VMs on the target cluster in boot-order sequence
Because Protocol C guarantees all writes were committed to both sides before acknowledgment, a successful planned failover results in zero data loss.
Unplanned failover
spec.failoverType: unplanned. Used when the primary cluster is unavailable (a disaster scenario). DRBD volumes are force-promoted on the DR cluster without waiting for the primary to shut down gracefully. If replication was Healthy and Consistent at the moment of failure, zero data loss occurs. If replication was Degraded, minimal data loss may occur.
Failover phases (granular): Initializing → Validating → StoppingSource → WaitingForLocks → RemovingTaints → StartingTarget → Monitoring → Completed or Failed
Overall state: Pending → InProgress → Completed or Failed or Cancelled
Per-VM status: The status.vmStatuses array reports each VM's individual phase (Pending, Stopping, Stopped, Starting, Running, Failed). There is no failedVMs field—use status.vmStatuses to find failed VMs.
Wall-clock budget: The failover controller operates within a 5-minute wall-clock budget.
Note on drainTimeoutSeconds: This field is accepted and range-checked by the admission webhook but is not consumed by the failover controller in Tech Preview. It has no effect on drain or failover behavior and should not be relied upon.
TestFailover
A TestFailover (tfo) triggers a non-disruptive DR validation. It does not affect production VMs. The test-failover controller:
- Prepares an isolated test namespace (
status.testNamespace, generated assite-recovery-<protection-group>-<random>) with a NetworkPolicy fence - Creates volume snapshots of the DR-side DRBD volumes
- Provisions test PVCs from those snapshots
- Starts test VMs in the isolated namespace
- Runs verification checks (automatic boot/agent/filesystem checks, plus optional user-configured network reachability probes)
- Cleans up all test resources
The test namespace is generated by the controller and reported on status.testNamespace. You do not specify, construct, or guess it.
TestFailover phases (exact, in order): Queued → PreparingEnvironment → CreatingSnapshots → CreatingVolumes → CreatingVMs → StartingVMs → Verifying → Succeeded or Failed → CleaningUp → Cleaned
Target cluster selection: The controller automatically selects the cluster that is not currently running the production VMs. The selection signal is stamped on status.targetClusterReason (one of vm_runstrategy, pg_status, or pg_spec_sourceCluster).
Cleanup policy: spec.cleanupPolicy: Automatic (default) tears down test resources automatically after spec.retentionTime (default 2h). cleanupPolicy: Manual leaves test resources until you delete the TestFailover CR.
Admission gates: A TestFailover is rejected at creation if a FailoverRequest in Pending or InProgress state already exists for the same ProtectionGroup. A Completed FailoverRequest does not block.
ReplicationGroupStatus
A ReplicationGroupStatus (rgs) aggregates replication health for a ProtectionGroup into a single object. The replication-monitor reconciler auto-creates exactly one per ProtectionGroup, named <protection-group-name>-replication. You do not create or delete this object—it is owned by the replication monitor.
Key status fields:
status.rpoStatus—Met,Degraded,Violated, orUnknownstatus.aggregated.connectionState— worst-case connection state across all VMs:Connected,Degraded, orDisconnectedstatus.aggregated.syncedVolumeCount/totalVolumeCount— how many VMs (not disks) are fully in syncstatus.volumeReplicationStatuses— one entry per VM (one per DRBD resource, since all of a VM's disks are volumes of a single resource since TSR-90)status.lastFullySyncedAt— when every VM in the group was last fully in sync
To find the ReplicationGroupStatus for a ProtectionGroup, use the label siterecovery.trilio.io/protection-group=<pg-name>. Field selectors on spec.* or status.* do not work on custom resources.
RPOEvent
An RPOEvent (rpoe) is an immutable audit record of a single replication event for a ProtectionGroup. The replication monitor writes these automatically; you cannot create or modify them. Each RPOEvent captures the event type, RPO at the time of the event, affected volumes, and suggested remediation actions.
Event types: SyncCompleted, RPODegraded, RPOViolated, ConnectionLost, ConnectionRestored, ResyncStarted, ResyncCompleted, VolumeAdded, VolumeRemoved, MetadataUndersized, ResizePending, ResizeComplete
RPOEvents provide an audit trail for compliance reporting. Because they are immutable, any update attempt is rejected.
To find RPOEvents for a specific ProtectionGroup, filter by the label siterecovery.trilio.io/protection-group=<pg-name>.
DRBD node agent
The drbd-node-agent runs as a DaemonSet on every worker node of the primary and DR clusters. It manages DRBD kernel-level block replication for individual VM volumes and reports volume and connection state back to the quorum controllers via DRBDResource status.
The DaemonSet object name follows the pattern <release>-site-recovery-workload-control-plane-agent. The component label is app.kubernetes.io/component=drbd-node-agent.
The agent requires privileged access (privileged: true, hostNetwork: true, hostPID: true) and is bound to the stock privileged SCC on OpenShift automatically.
TSR web console
The TSR web console consists of two components installed by the operator with no feature flag and no opt-out:
- site-recovery-frontend — a Next.js/React UI
- site-recovery-backend — a Python Flask API served under gunicorn
On OpenShift, the ProtectionZone controller creates an oauth-proxy Route and console link, providing browser access automatically. On upstream Kubernetes, the pods are deployed but no ingress or authentication path is provided—browser access is not a supported use case.
Zone-scoped console pages derive the dr-<zoneId> namespace from the zone ID. A zone in a differently named namespace will appear in listings but its detail pages will not work.
tsr-gather (support bundle)
tsr-gather is an oc adm must-gather-compatible tool packaged as the site-recovery-must-gather image. Run it against the quorum cluster to collect logs, CRD state, and configuration from all clusters in the DR estate into a single diagnostic bundle.
The binary inside the image is /usr/bin/gather. No -- <command> override is needed when invoking oc adm must-gather—it runs the ENTRYPOINT automatically.
Scale limits and constraints summary
| Constraint | Value |
|---|---|
| Protected VMs per deployment | ~1,000 (one DRBD port per VM from 7000–8000) |
| Replicated disks per VM | 15 maximum |
| Workload clusters per zone | Exactly 2 |
| Protection zones per namespace | 1 |
| Failover wall-clock budget | 5 minutes |
| Maximum RTT for Protocol C | ~50 ms |
| DRBD replication port range | TCP 7000–8000 inclusive (mandatory) |
| Supported replication protocol | Protocol C only (RPO = 0) |
| Supported storage backend | DRBD only |
| Supported platform | OpenShift 4.17+ (quorum and workload clusters) |
Namespace reference
| Namespace | Contents |
|---|---|
dr-<zone-name> (quorum cluster) | ProtectionZone CR, quorum control plane pods, all quorum-side CRs (ProtectionGroup, ProtectionRequest, FailoverRequest, TestFailover, ReplicationGroupStatus, RPOEvent, DRBDReplicationPolicy), TSR web console pods |
trilio-site-recovery-system (each workload cluster) | Workload control plane pods, DRBD node agent DaemonSet, zone-exclusivity lease |
| VM application namespace (each workload cluster) | DRBDResource CRs, DRBD metadata PVCs, frontend PVCs |
site-recovery-<pg>-<random> (DR workload cluster) | Ephemeral test namespace created by the test-failover controller; reported on status.testNamespace |
Examining a ProtectionZone
After installing TSR and creating a ProtectionZone, check its phase and per-cluster reachability:
# On the quorum cluster — short name 'pz' works
oc get pz -n dr-prod
Expected output (columns from the CRD's additionalPrinterColumns):
NAME DISPLAY NAME BACKEND CLUSTERS STATUS AGE
prod Production Zone DRBD cluster1,cluster2 Available 12m
Inspect cluster reachability:
oc get pz prod -n dr-prod -o jsonpath='{.status.clusters}' | python3 -m json.tool
[
{"name": "cluster1", "clusterID": "cluster1", "status": "Reachable"},
{"name": "cluster2", "clusterID": "cluster2", "status": "Reachable"}
]
Checking a ProtectionRequest's phase and sync progress
# Short names: pr or protect
oc get pr -n dr-prod
Expected output:
NAME VM NAMESPACE PHASE SYNC AGE
protect-vm1 vm1 prod-vms Syncing 47 8m
To watch initial sync progress on a specific ProtectionRequest:
oc get pr protect-vm1 -n dr-prod -w \
-o custom-columns='PHASE:.status.phase,SYNC:.status.syncProgress,SLOWEST:.status.slowestVolumeSyncProgress.percentage'
Listing all protected VMs
The label siterecovery.trilio.io/protected-vm=true is stamped on the KubeVirt VirtualMachine object on the workload cluster:
# Run against the primary workload cluster
oc get vm -A -l siterecovery.trilio.io/protected-vm=true
NAMESPACE NAME AGE STATUS READY
prod-vms vm1 10m Running True
prod-vms vm2 10m Running True
Viewing replication health for a ProtectionGroup
The ReplicationGroupStatus is auto-created as <pg-name>-replication:
oc get rgs my-pg-replication -n dr-prod
NAME PROTECTION-GROUP RPO-STATUS OUT-OF-SYNC SYNCED CONNECTION LAST-SYNC
my-pg-replication my-pg Met 0 3 Connected 2m ago
For per-VM detail:
oc get rgs my-pg-replication -n dr-prod \
-o jsonpath='{.status.volumeReplicationStatuses}' | python3 -m json.tool
Checking DRBD node agent status on a specific node
The DaemonSet object name follows the pattern <release>-site-recovery-workload-control-plane-agent. To find the agent pod on a specific node (two steps, because kubectl logs has no --field-selector):
# Step 1: find the pod name
POD=$(oc get pods -n trilio-site-recovery-system \
-l app.kubernetes.io/component=drbd-node-agent \
--field-selector spec.nodeName=worker-1 \
-o jsonpath='{.items[0].metadata.name}')
# Step 2: tail its logs
oc logs -n trilio-site-recovery-system "$POD" --tail=50
Monitoring a planned failover
oc get fr -n dr-prod -w
NAME PROTECTION GROUP TARGET STATE PHASE AGE
pg-failover-1 my-pg cluster2 InProgress StoppingSource 45s
pg-failover-1 my-pg cluster2 InProgress RemovingTaints 90s
pg-failover-1 my-pg cluster2 InProgress StartingTarget 2m
pg-failover-1 my-pg cluster2 Completed Completed 4m
To see per-VM status during the failover:
oc get fr pg-failover-1 -n dr-prod \
-o jsonpath='{.status.vmStatuses}' | python3 -m json.tool
Monitoring a TestFailover
oc get tfo my-test -n dr-prod -w
NAME PROTECTION GROUP PHASE PASSED AGE
my-test my-pg Verifying 3m
my-test my-pg Succeeded true 6m
To find the isolated test namespace the controller generated:
oc get tfo my-test -n dr-prod -o jsonpath='{.status.testNamespace}'
site-recovery-my-pg-a3f7c2
Collecting a diagnostic bundle
Run tsr-gather from the quorum cluster. The oc adm must-gather command runs the /usr/bin/gather binary inside the image automatically:
oc adm must-gather --image=quay.io/triliovault/site-recovery-must-gather:<tag>
To restrict collection to a shorter log window or a single zone:
oc adm must-gather --image=quay.io/triliovault/site-recovery-must-gather:<tag> \
-- /usr/bin/gather --since 2h
oc adm must-gather --image=quay.io/triliovault/site-recovery-must-gather:<tag> \
-- /usr/bin/gather --zone prod
- Plan and verify infrastructure — prerequisite checklist for network ports, DRBD kernel module, LVM thin pools, and cluster connectivity before deploying TSR
- Deploy the workload control plane — how the ProtectionZone controller installs the DRBD node agent DaemonSet and workload admission webhooks automatically
- Deploy the quorum control plane — OLM installation of the operator bundle and ProtectionZone CR creation
- Configure cross-cluster replication — understanding storage class mappings and the auto-created DRBDReplicationPolicy
- Protect virtual machines — creating ProtectionRequests and interpreting protection phases
- Verify replication health — reading ReplicationGroupStatus and RPOEvent objects
- Run a test failover — creating a TestFailover CR and interpreting verification results
- Execute a planned failover — creating a FailoverRequest with
failoverType: planned - Execute an unplanned failover — creating a FailoverRequest with
failoverType: unplannedwhen the primary cluster is unavailable - Perform failback — returning VMs to the original primary after a failover
- Diagnose a degraded or failed node or replication link — runbooks for DRBDResource node loss and replication health issues
- Upgrade Site Recovery components — operator upgrade path via OLM