Trilio Site Recovery for Kubernetes
CLI reference

Kubectl Commands

Common kubectl commands for managing site recovery resources, checking status, and inspecting events


Description

This page is a reference for the kubectl commands you use day-to-day to manage Trilio Site Recovery resources. Because every DR operation in Site Recovery is declared through Kubernetes custom resources in the siterecovery.trilio.io/v1alpha1 API group, kubectl (and oc on OpenShift) is the primary operational interface. Use this page to look up short names, label selectors, field meanings, and common command patterns for each resource kind.


Synopsis

All Site Recovery custom resources live in the siterecovery.trilio.io/v1alpha1 API group. The general form for any kubectl command against them is:

kubectl <verb> <resource>[/<name>] -n <namespace> [flags]

Resources on the TSR-Manager cluster live in the zone namespace dr-<zone-name>. Resources on each workload cluster live in the namespace of the protected VM (for DRBDResource) or in trilio-site-recovery-system (for controllers and the node-agent DaemonSet).

Important: Always confirm which cluster your kubectl context targets before running commands. TSR-Manager resources (ProtectionZone, ProtectionGroup, ProtectionRequest, FailoverRequest, TestFailover, Hook, ReplicationGroupStatus, RPOEvent, DRBDReplicationPolicy) exist only on the TSR-Manager cluster. DRBDResource exists only on workload clusters. Running kubectl get dres against the TSR-Manager cluster returns server doesn't have a resource type.


Flags

The table below covers the kubectl flags most frequently used with Site Recovery resources. These are standard kubectl flags; no Site Recovery-specific flags exist on kubectl itself.

FlagShortDefaultDescription
--namespace-n(active context)Target namespace. For TSR-Manager resources use dr-<zone-name>; for workload resources use the VM's namespace.
--all-namespaces-A—List resources across all namespaces on the targeted cluster.
--context—(kubeconfig default)Kubeconfig context to use. Use this to switch between the TSR-Manager cluster and each workload cluster.
--kubeconfig—~/.kube/configPath to a kubeconfig file.
--output-o(table)Output format: yaml, json, wide, jsonpath=..., custom-columns=....
--watch-w—Watch for changes and stream updates to the terminal.
--selector-l—Filter by label selector (see Label selectors below).
--field-selector——Filter by field. Custom resources support only metadata.name and metadata.namespace; spec.* and status.* field selectors are not supported on any Site Recovery CRD. Use label selectors instead.
--patch——Inline patch payload.
--type—strategicPatch type: strategic, merge, or json.
--dry-run—noneclient or server; validates a manifest without applying it.
--force——Force-delete a resource, bypassing finalizers. Use with caution.
--since——With kubectl logs, limit log output to a relative duration (e.g. 1h, 30m).
--tail——With kubectl logs, limit output to the last N lines.
--timestamps——With kubectl logs, prefix each line with its RFC3339 timestamp.

Subcommands

The sections below are organised by resource kind. Each kind table shows the kubectl short name, which cluster the resource lives on, and the most useful commands.

Resource quick-reference

KindShort name(s)ClusterNamespace
ProtectionZonepzTSR-Managerdr-<zone-name>
ProtectionGrouppgTSR-Managerdr-<zone-name>
ProtectionRequestpr, protectTSR-Managerdr-<zone-name>
FailoverRequestfrTSR-Managerdr-<zone-name>
TestFailovertfoTSR-Managerdr-<zone-name>
DRBDReplicationPolicydrp, drbdpolicyTSR-Managerdr-<zone-name>
ReplicationGroupStatusrgsTSR-Managerdr-<zone-name>
RPOEventrpoeTSR-Managerdr-<zone-name>
Hook(none)TSR-Managerdr-<zone-name>
DRBDResourcedresEach workload clusterVM's namespace

Hook has no short name; kubectl get hooks is the only form.


ProtectionZone (pz)

A ProtectionZone registers a pair of workload clusters with the TSR-Manager. The ClustersValidated condition must be True before ProtectionGroup or ProtectionRequest resources can be created or modified in the zone.

Printer columns: Display Name, Backend, Clusters, Status (phase), Age

kubectl get pz -n dr-<zone-name>
kubectl describe pz <zone-name> -n dr-<zone-name>
kubectl get pz <zone-name> -n dr-<zone-name> -o yaml
kubectl get pz <zone-name> -n dr-<zone-name> -o jsonpath='{.status.phase}'
kubectl get pz <zone-name> -n dr-<zone-name> -o jsonpath='{.status.conditions}'

ProtectionGroup (pg)

A ProtectionGroup groups one or more KubeVirt VMs for coordinated failover. It holds SLA targets (RPO/RTO), boot ordering, MAC address policy, and hook bindings.

Printer columns: VMs, State, Active (activeClusterID), Age

kubectl get pg -n dr-<zone-name>
kubectl describe pg <group-name> -n dr-<zone-name>
kubectl get pg <group-name> -n dr-<zone-name> -o jsonpath='{.status.state}'
kubectl get pg <group-name> -n dr-<zone-name> -o jsonpath='{.status.activeClusterID}'

To watch state transitions during a failover:

kubectl get pg <group-name> -n dr-<zone-name> -w

ProtectionRequest (pr, protect)

A ProtectionRequest declares protection for a single KubeVirt VM. The spec.sourceCluster field is the failover lever — patching it to the DR cluster triggers a role flip.

Printer columns: VM, Namespace, Phase, Sync (syncProgress %), Age

Phase values: Pending → Validating → CreatingDRBD → Syncing → ReadyToActivate → Activating → Protected → Unprotecting → Failed

kubectl get pr -n dr-<zone-name>
kubectl get protect -n dr-<zone-name>
kubectl describe pr <request-name> -n dr-<zone-name>
kubectl get pr <request-name> -n dr-<zone-name> -o jsonpath='{.status.phase}'
kubectl get pr <request-name> -n dr-<zone-name> -o jsonpath='{.status.syncProgress}'

To watch sync progress:

kubectl get pr -n dr-<zone-name> -w

FailoverRequest (fr)

A FailoverRequest triggers a planned or unplanned failover of all VMs in a ProtectionGroup. Per-VM detail is in status.vmStatuses; there is no status.failedVMs field.

Printer columns: Protection Group, Target, State, Phase, Age

Phase values: Initializing → Validating → RunningPreHooks → StoppingSource → WaitingForLocks → RemovingTaints → StartingTarget → Monitoring → RunningPostHooks → Completed → Failed

kubectl get fr -n dr-<zone-name>
kubectl describe fr <request-name> -n dr-<zone-name>
kubectl get fr <request-name> -n dr-<zone-name> -o jsonpath='{.status.state}'
kubectl get fr <request-name> -n dr-<zone-name> -o jsonpath='{.status.phase}'
kubectl get fr <request-name> -n dr-<zone-name> -o jsonpath='{.status.vmStatuses}'

To watch a failover in progress:

kubectl get fr <request-name> -n dr-<zone-name> -w

TestFailover (tfo)

A TestFailover triggers a non-disruptive DR drill on the standby cluster. The test namespace is reported in status.testNamespace and is generated by the controller; you never construct or specify it.

Printer columns: Protection Group, Phase, Result, Passed, Scheduling, Age

Phase values: Queued → PreparingEnvironment → CreatingSnapshots → CreatingVolumes → CreatingVMs → StartingVMs → Verifying → Succeeded → Failed → CleaningUp → Cleaned

kubectl get tfo -n dr-<zone-name>
kubectl describe tfo <tfo-name> -n dr-<zone-name>
kubectl get tfo <tfo-name> -n dr-<zone-name> -o jsonpath='{.status.phase}'
kubectl get tfo <tfo-name> -n dr-<zone-name> -o jsonpath='{.status.result}'
kubectl get tfo <tfo-name> -n dr-<zone-name> -o jsonpath='{.status.testNamespace}'
kubectl get tfo <tfo-name> -n dr-<zone-name> -o jsonpath='{.status.verificationResults}'

DRBDReplicationPolicy (drp, drbdpolicy)

A DRBDReplicationPolicy is created automatically by the ProtectionZone controller when a zone becomes available. The auto-created policy is named <zone-name>-default with isDefault: true. You do not create or modify replication policies.

Printer columns: Clusters, Mode, Protocol, Default, Phase, Age

kubectl get drp -n dr-<zone-name>
kubectl describe drp <zone-name>-default -n dr-<zone-name>
kubectl get drp <zone-name>-default -n dr-<zone-name> -o yaml

ReplicationGroupStatus (rgs)

A ReplicationGroupStatus is auto-created by the replication monitor for each ProtectionGroup. It is named <protection-group-name>-replication. Use the siterecovery.trilio.io/protection-group label to filter these objects.

Printer columns: Protection-Group, RPO-Status, Out-Of-Sync, Synced, Connection, Last-Sync, Age

kubectl get rgs -n dr-<zone-name>
kubectl get rgs -n dr-<zone-name> -l siterecovery.trilio.io/protection-group=<group-name>
kubectl describe rgs <group-name>-replication -n dr-<zone-name>
kubectl get rgs <group-name>-replication -n dr-<zone-name> -o jsonpath='{.status.rpoStatus}'
kubectl get rgs <group-name>-replication -n dr-<zone-name> -o jsonpath='{.status.aggregated}'
kubectl get rgs <group-name>-replication -n dr-<zone-name> -o jsonpath='{.status.currentRPOSeconds}'

RPOEvent (rpoe)

An RPOEvent is an immutable audit record created on each replication status transition. Use the siterecovery.trilio.io/protection-group label to filter events for a specific group.

Printer columns: Protection-Group, Event-Type, RPO-Seconds, Out-Of-Sync, Reason, Timestamp, Age

kubectl get rpoe -n dr-<zone-name>
kubectl get rpoe -n dr-<zone-name> -l siterecovery.trilio.io/protection-group=<group-name>
kubectl get rpoe -n dr-<zone-name> --sort-by=.spec.timestamp
kubectl describe rpoe <event-name> -n dr-<zone-name>

Hook

A Hook defines a Kubernetes Job to run at failover or test lifecycle points. It has no short name. The admission webhook prevents deletion or spec edits while a hook is actively used by an operation.

Printer columns: Image, Timeout, Retries, Age

kubectl get hooks -n dr-<zone-name>
kubectl describe hook <hook-name> -n dr-<zone-name>
kubectl get hook <hook-name> -n dr-<zone-name> -o yaml

DRBDResource (dres)

A DRBDResource lives on each workload cluster in the protected VM's namespace. It is created and managed by the TSR-Manager-side ProtectionRequest controller. Each resource is named drbd-<namespace>-<vm>-<hash8>. Do not run kubectl get dres against the TSR-Manager cluster — the CRD is not registered there.

Printer columns: Side, Role, Node, State (connectionState), Phase, Age

# Run these commands against a workload cluster context
kubectl get dres -n <vm-namespace> --context <workload-cluster-context>
kubectl describe dres <drbd-resource-name> -n <vm-namespace> --context <workload-cluster-context>
kubectl get dres <drbd-resource-name> -n <vm-namespace> --context <workload-cluster-context> -o jsonpath='{.status.phase}'
kubectl get dres <drbd-resource-name> -n <vm-namespace> --context <workload-cluster-context> -o jsonpath='{.status.connectionState}'
kubectl get dres <drbd-resource-name> -n <vm-namespace> --context <workload-cluster-context> -o jsonpath='{.status.role}'
kubectl get dres <drbd-resource-name> -n <vm-namespace> --context <workload-cluster-context> -o jsonpath='{.status.syncProgress}'

Environment variables

kubectl reads its configuration from the KUBECONFIG environment variable and standard flag defaults. Site Recovery adds no custom environment variables to kubectl.

VariableDefaultDescription
KUBECONFIG~/.kube/configColon-separated list of kubeconfig files. When managing multiple clusters (TSR-Manager plus two workload clusters), you can merge them here or use --kubeconfig and --context per command.

Note: kubectl does not read a KUBE_CONTEXT environment variable. To target a specific cluster, always pass --context <name> explicitly or set the active context with kubectl config use-context <name>.


Exit codes

kubectl exits with standard Unix exit codes.

CodeMeaning
0Command succeeded.
1General error (invalid flags, connection failure, API error, patch rejected by admission webhook).

When an admission webhook rejects a resource (for example, attempting to create a ProtectionGroup before ClustersValidated is True, or setting spec.storageBackendMode: Linstor), the command exits 1 and prints the webhook rejection message to stderr.


Examples

The examples below are organised by common workflow. Replace <zone-name>, <group-name>, <vm-name>, <vm-namespace>, <request-name>, and <cluster-context> with your actual values.


Check zone status

Run this on the TSR-Manager cluster context.

kubectl get pz -n dr-<zone-name>
kubectl get pz <zone-name> -n dr-<zone-name> \
  -o jsonpath='{range .status.conditions[*]}{.type}{"\t"}{.status}{"\t"}{.reason}{"\n"}{end}'

The ClustersValidated condition must be True before you can create ProtectionGroup or ProtectionRequest resources.


List all protected VMs on a workload cluster

Run this on a workload cluster context. The siterecovery.trilio.io/protected-vm=true label is stamped only on VirtualMachine objects on workload clusters.

kubectl get vm -A -l siterecovery.trilio.io/protected-vm=true \
  --context <workload-cluster-context>

Watch protection progress for a VM

After creating a ProtectionRequest, watch its phase advance from Syncing to Protected.

kubectl get pr <request-name> -n dr-<zone-name> -w

To extract sync percentage:

kubectl get pr <request-name> -n dr-<zone-name> \
  -o jsonpath='{.status.syncProgress}'

Inspect replication health for a ProtectionGroup

The ReplicationGroupStatus is named <group-name>-replication.

kubectl get rgs <group-name>-replication -n dr-<zone-name>

To see aggregated out-of-sync bytes, connection state, and synced volume count:

kubectl get rgs <group-name>-replication -n dr-<zone-name> \
  -o jsonpath='{.status.aggregated}'

To check RPO status:

kubectl get rgs <group-name>-replication -n dr-<zone-name> \
  -o jsonpath='{.status.rpoStatus}'

Filter RPO events for a ProtectionGroup

Field selectors on spec.* are not supported on custom resources. Use the label selector instead.

kubectl get rpoe -n dr-<zone-name> \
  -l siterecovery.trilio.io/protection-group=<group-name> \
  --sort-by=.spec.timestamp

Inspect DRBD resource state on a workload cluster

DRBDResource names follow the pattern drbd-<namespace>-<vm>-<hash8>.

# List all DRBDResources in a VM namespace on the primary workload cluster
kubectl get dres -n <vm-namespace> --context <workload-cluster-context>

To check connection state and DRBD role:

kubectl get dres <drbd-resource-name> -n <vm-namespace> \
  --context <workload-cluster-context> \
  -o jsonpath='{.status.connectionState}{" "}{.status.role}'

To check sync progress per volume:

kubectl get dres <drbd-resource-name> -n <vm-namespace> \
  --context <workload-cluster-context> \
  -o jsonpath='{.status.volumes[*].syncProgress}'

Create a FailoverRequest (planned failover)

kubectl apply -f - <<EOF
apiVersion: siterecovery.trilio.io/v1alpha1
kind: FailoverRequest
metadata:
  name: failover-<group-name>
  namespace: dr-<zone-name>
spec:
  protectionGroupRef:
    name: <group-name>
  targetCluster: cluster2
  failoverType: planned
  skipHooks: false
EOF

Watch the failover phase:

kubectl get fr failover-<group-name> -n dr-<zone-name> -w

Inspect per-VM status:

kubectl get fr failover-<group-name> -n dr-<zone-name> \
  -o jsonpath='{.status.vmStatuses}'

Trigger a test failover (DR drill)

kubectl apply -f - <<EOF
apiVersion: siterecovery.trilio.io/v1alpha1
kind: TestFailover
metadata:
  name: drill-<group-name>
  namespace: dr-<zone-name>
spec:
  protectionGroupRef:
    name: <group-name>
  cleanupPolicy: Automatic
  skipHooks: false
EOF

Watch phase progression:

kubectl get tfo drill-<group-name> -n dr-<zone-name> -w

After the drill completes, retrieve the test namespace (controller-generated; never user-specified):

kubectl get tfo drill-<group-name> -n dr-<zone-name> \
  -o jsonpath='{.status.testNamespace}'

Check verification results:

kubectl get tfo drill-<group-name> -n dr-<zone-name> \
  -o jsonpath='{.status.verificationResults}'

Retrieve controller logs on the TSR-Manager cluster

The TSR-Manager control plane runs in the zone namespace dr-<zone-name>. All Site Recovery pods carry app.kubernetes.io/part-of: site-recovery.

# List TSR-Manager control-plane pods
kubectl get pods -n dr-<zone-name> \
  -l app.kubernetes.io/component=manager-control-plane

# Stream logs from the manager-control-plane pod
kubectl logs -n dr-<zone-name> \
  -l app.kubernetes.io/component=manager-control-plane \
  --since=1h --timestamps

For the ProtectionZone controller:

kubectl logs -n dr-<zone-name> \
  -l app.kubernetes.io/component=protectionzone-controller \
  --since=1h --timestamps

Retrieve workload control-plane logs on a workload cluster

kubectl logs -n trilio-site-recovery-system \
  -l app.kubernetes.io/component=workload-control-plane \
  --context <workload-cluster-context> \
  --since=1h --timestamps

Retrieve DRBD node-agent logs from a specific node

The node-agent DaemonSet pod selector uses app.kubernetes.io/component=drbd-node-agent. Because kubectl logs has no --field-selector flag, selecting by node requires two steps.

# Step 1: find the agent pod name for a specific node
AGENT_POD=$(kubectl get pods -n trilio-site-recovery-system \
  --context <workload-cluster-context> \
  --field-selector spec.nodeName=<node-name> \
  -l app.kubernetes.io/component=drbd-node-agent \
  -o jsonpath='{.items[0].metadata.name}')

# Step 2: stream logs from that pod
kubectl logs -n trilio-site-recovery-system \
  --context <workload-cluster-context> \
  "$AGENT_POD" --since=1h --timestamps

Adjust runtime log level without restarting pods

Site Recovery reads the site-recovery-logging ConfigMap at runtime. The global key is level; per-component overrides use level.control-plane, level.drbd-node-agent, or level.protectionzone. Accepted values: error, info, debug, trace.

# Raise TSR-Manager control-plane to debug on the TSR-Manager cluster
kubectl patch configmap site-recovery-logging -n dr-<zone-name> \
  --type merge \
  -p '{"data":{"level.control-plane":"debug"}}'

# Raise node-agent to debug on a workload cluster
kubectl patch configmap site-recovery-logging \
  -n trilio-site-recovery-system \
  --context <workload-cluster-context> \
  --type merge \
  -p '{"data":{"level.drbd-node-agent":"debug"}}'

Inspect ProtectionZone conditions

kubectl get pz <zone-name> -n dr-<zone-name> \
  -o jsonpath='{range .status.conditions[*]}{.type}{"\t"}{.status}{"\t"}{.reason}{"\t"}{.message}{"\n"}{end}'

Key condition types and reasons to look for:

ConditionReasonMeaning
ClustersValidatedInsufficientClusterPermissionsZone credential authenticated but RBAC checks failed; rotate with tsrctl zone-credential rotate --refresh-rbac.
ClustersValidatedClusterAlreadyClaimedWorkload cluster is already leased to another zone.
ClustersValidatedClusterIdentityChangedCredential now reaches a different cluster (UID mismatch); the zone must be re-created.
ClustersValidatedKubeVirtNotInstalledKubeVirt CRDs were not found on the workload cluster.
ClustersValidatedSCCPrerequisiteMissingRequired SCC not present on the OpenShift workload cluster.
WorkloadStacksReadySCCAdmissionDeniedThe node-agent DaemonSet was rejected by OpenShift SCC admission after install.

Check DRBD replication metadata PVCs on a workload cluster

Metadata PVCs are named drbd-md-<resourceName>-v<volumeNumber>.

kubectl get pvc -n <vm-namespace> \
  --context <workload-cluster-context> \
  -l app.kubernetes.io/part-of=site-recovery

Label selectors reference

Use these labels with -l on any kubectl get command.

LabelValueWhat it selects
app.kubernetes.io/part-ofsite-recoveryAll Site Recovery objects on any cluster
app.kubernetes.io/componentmanager-control-planeTSR-Manager control-plane pods
app.kubernetes.io/componentworkload-control-planeWorkload control-plane pods
app.kubernetes.io/componentprotectionzone-controllerProtectionZone controller pods
app.kubernetes.io/componentdrbd-node-agentDRBD node-agent DaemonSet pods
siterecovery.trilio.io/protection-group<group-name>Auto-created ReplicationGroupStatus and RPOEvent objects for a group
siterecovery.trilio.io/protected-vmtrueVirtualMachine objects on workload clusters that are currently protected

Field selector limitation: Custom resources support only metadata.name and metadata.namespace as field selectors. Filtering on spec.* or status.* fields returns an error. Always use label selectors for filtering Site Recovery custom resources.