---
title: Kubectl Commands
product: trilio-site-recovery-for-kubernetes
doc_type: cli_reference
version: 0.2.0
source: git2docs (code-derived, validation-filtered)
canonical: https://docs-tsr.trilio.io/trilio-site-recovery-for-kubernetes/site-recovery/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.

| Flag | Short | Default | Description |
|------|-------|---------|-------------|
| `--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/config` | Path 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](#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` | — | `strategic` | Patch type: `strategic`, `merge`, or `json`. |
| `--dry-run` | — | `none` | `client` 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

| Kind | Short name(s) | Cluster | Namespace |
|------|--------------|---------|----------|
| `ProtectionZone` | `pz` | TSR-Manager | `dr-<zone-name>` |
| `ProtectionGroup` | `pg` | TSR-Manager | `dr-<zone-name>` |
| `ProtectionRequest` | `pr`, `protect` | TSR-Manager | `dr-<zone-name>` |
| `FailoverRequest` | `fr` | TSR-Manager | `dr-<zone-name>` |
| `TestFailover` | `tfo` | TSR-Manager | `dr-<zone-name>` |
| `DRBDReplicationPolicy` | `drp`, `drbdpolicy` | TSR-Manager | `dr-<zone-name>` |
| `ReplicationGroupStatus` | `rgs` | TSR-Manager | `dr-<zone-name>` |
| `RPOEvent` | `rpoe` | TSR-Manager | `dr-<zone-name>` |
| `Hook` | *(none)* | TSR-Manager | `dr-<zone-name>` |
| `DRBDResource` | `dres` | Each workload cluster | VM'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`.

| Variable | Default | Description |
|----------|---------|-------------|
| `KUBECONFIG` | `~/.kube/config` | Colon-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.

| Code | Meaning |
|------|---------|
| `0` | Command succeeded. |
| `1` | General 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.

```bash
kubectl get pz -n dr-<zone-name>
```

```bash
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.

```bash
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`.

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

To extract sync percentage:

```bash
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`.

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

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

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

To check RPO status:

```bash
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.

```bash
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>`.

```bash
# 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:

```bash
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:

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

---

### Create a FailoverRequest (planned failover)

```bash
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:

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

Inspect per-VM status:

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

---

### Trigger a test failover (DR drill)

```bash
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:

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

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

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

Check verification results:

```bash
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`.

```bash
# 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:

```bash
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

```bash
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.

```bash
# 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`.

```bash
# 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

```bash
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:

| Condition | Reason | Meaning |
|-----------|--------|---------|
| `ClustersValidated` | `InsufficientClusterPermissions` | Zone credential authenticated but RBAC checks failed; rotate with `tsrctl zone-credential rotate --refresh-rbac`. |
| `ClustersValidated` | `ClusterAlreadyClaimed` | Workload cluster is already leased to another zone. |
| `ClustersValidated` | `ClusterIdentityChanged` | Credential now reaches a different cluster (UID mismatch); the zone must be re-created. |
| `ClustersValidated` | `KubeVirtNotInstalled` | KubeVirt CRDs were not found on the workload cluster. |
| `ClustersValidated` | `SCCPrerequisiteMissing` | Required SCC not present on the OpenShift workload cluster. |
| `WorkloadStacksReady` | `SCCAdmissionDenied` | The 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>`.

```bash
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.

| Label | Value | What it selects |
|-------|-------|----------------|
| `app.kubernetes.io/part-of` | `site-recovery` | All Site Recovery objects on any cluster |
| `app.kubernetes.io/component` | `manager-control-plane` | TSR-Manager control-plane pods |
| `app.kubernetes.io/component` | `workload-control-plane` | Workload control-plane pods |
| `app.kubernetes.io/component` | `protectionzone-controller` | ProtectionZone controller pods |
| `app.kubernetes.io/component` | `drbd-node-agent` | DRBD node-agent DaemonSet pods |
| `siterecovery.trilio.io/protection-group` | `<group-name>` | Auto-created `ReplicationGroupStatus` and `RPOEvent` objects for a group |
| `siterecovery.trilio.io/protected-vm` | `true` | `VirtualMachine` 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.
