Trilio Site Recovery for Kubernetes
Guide

Custom Resource Schemas

CRD structure and validation rules for site recovery resources


Overview

This page is the authoritative reference for every Custom Resource Definition (CRD) in the siterecovery.trilio.io/v1alpha1 API group. It documents each resource's purpose, its full spec and status schema, the validation rules enforced at admission, and the enum values accepted by each field. All DR operations in Trilio Site Recovery are declared through these custom resources — understanding their schemas is essential for configuring protection, triggering failovers, monitoring replication health, and interpreting operator status. Nine CRDs are registered on the TSR-Manager cluster and one (DRBDResource) is registered on each workload cluster.


Prerequisites

Before working with these custom resources, ensure the following are in place:

  • OpenShift 4.17 or later on all three clusters (TSR-Manager and both workload clusters). OpenShift 4.14–4.16 are within the supported floor but are not validated for Tech Preview.
  • KubeVirt installed on both workload clusters (the kubevirt.io/v1 API group must be served with VirtualMachine, VirtualMachineInstance, and VirtualMachineInstanceMigration kinds).
  • DRBD 9 kernel module (drbd, drbd_transport_tcp, drbd_transport_lb-tcp) available on every workload node that will host a protected VM.
  • The Trilio Site Recovery operator installed on the TSR-Manager cluster via OLM (CatalogSource + OperatorGroup + Subscription for package site-recovery, channel tech-preview).
  • A ProtectionZone CR that has reached Available phase before any ProtectionGroup, ProtectionRequest, FailoverRequest, TestFailover, or Hook can be created or modified. The ClustersValidated condition on the ProtectionZone must be True; while it is not, the TSR-Manager admission webhooks reject those CR creates and spec changes.
  • kubectl and, for OpenShift-specific operations, oc.
  • tsrctl (supplied by Trilio with the release; contact Trilio support to obtain it) for provisioning zone kubeconfig credentials before creating a ProtectionZone.
  • TCP ports 7000–8000 inclusive open between primary and DR worker nodes for DRBD replication (one port per replicated VM; port 8000 is mandatory — a firewall rule stopping at 7999 causes silent replication failure on one volume).
  • RHEL CoreOS 9 nodes on workload clusters (RHEL CoreOS 10 is not validated in Tech Preview).

Installation

CRDs are installed automatically — you do not apply them manually. The installation sequence is:

1. Install the operator via OLM

Create the following objects on the TSR-Manager cluster in the trilio-site-recovery-system namespace:

apiVersion: operators.coreos.com/v1alpha1
kind: CatalogSource
metadata:
  name: site-recovery-catalog
  namespace: trilio-site-recovery-system
spec:
  sourceType: grpc
  image: quay.io/triliodata/site-recovery-catalog:<release-tag>
  displayName: Trilio Site Recovery
  publisher: Trilio Data, Inc.
apiVersion: operators.coreos.com/v1
kind: OperatorGroup
metadata:
  name: site-recovery-og
  namespace: trilio-site-recovery-system
spec: {}
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
  name: site-recovery
  namespace: trilio-site-recovery-system
spec:
  channel: stable
  name: site-recovery
  source: site-recovery-catalog
  sourceNamespace: trilio-site-recovery-system

Apply these with:

kubectl apply -f catalogsource.yaml \
  -f operatorgroup.yaml \
  -f subscription.yaml \
  --context <tsr-manager-context>

OLM installs the ProtectionZone controller, the TSR web console (frontend and backend), and the ProtectionZone CRD. The remaining eight TSR-Manager CRDs (DRBDReplicationPolicy, FailoverRequest, Hook, ProtectionGroup, ProtectionRequest, ReplicationGroupStatus, RPOEvent, TestFailover) are installed by the ProtectionZone controller once a ProtectionZone is created. The DRBDResource CRD is installed on each workload cluster by the same controller.

2. Provision zone credentials

For each workload cluster, use tsrctl (obtained from Trilio support) to create a zone kubeconfig credential:

tsrctl zone-credential create \
  --zone <zone-name> \
  --kubeconfig <path-to-cluster1-admin-kubeconfig>,<path-to-cluster2-admin-kubeconfig>

This creates a per-zone ServiceAccount in the trilio-site-recovery-access namespace on each workload cluster and stores the resulting kubeconfig Secrets on the TSR-Manager cluster.

3. Create the zone namespace and ProtectionZone

Create the dr-<zone-name> namespace on the TSR-Manager cluster, then apply a ProtectionZone CR (see the ProtectionZone schema section below). Creating the ProtectionZone triggers the controller to validate both workload clusters, install the workload control-plane stacks and DRBD node agents, and register all remaining CRDs. All subsequent CRs (ProtectionGroup, ProtectionRequest, etc.) must be created in this same dr-<zone-name> namespace on the TSR-Manager cluster.

Verify that the ProtectionZone reaches Available phase:

kubectl get pz -n dr-<zone-name> --context <tsr-manager-context>

Configuration

All configuration for Site Recovery is expressed through the custom resources described in this page. There are no separate configuration files for end users. The sections below describe the key configuration choices within each CRD.


API group and version

All Site Recovery CRDs share the API group and version:

siterecovery.trilio.io/v1alpha1

Cluster placement

CRDWhere it livesWhere to query it
ProtectionZoneTSR-Manager cluster, dr-<name> namespaceTSR-Manager
ProtectionGroupTSR-Manager cluster, dr-<name> namespaceTSR-Manager
ProtectionRequestTSR-Manager cluster, dr-<name> namespaceTSR-Manager
DRBDReplicationPolicyTSR-Manager cluster, dr-<name> namespaceTSR-Manager
FailoverRequestTSR-Manager cluster, dr-<name> namespaceTSR-Manager
TestFailoverTSR-Manager cluster, dr-<name> namespaceTSR-Manager
HookTSR-Manager cluster, dr-<name> namespaceTSR-Manager
ReplicationGroupStatusTSR-Manager cluster, dr-<name> namespaceTSR-Manager
RPOEventTSR-Manager cluster, dr-<name> namespaceTSR-Manager
DRBDResourceEach workload cluster, protected VM's namespaceWorkload cluster only

Running kubectl get drbdresource against the TSR-Manager cluster returns server doesn't have a resource type because the CRD is not registered there.


ClusterID values

Every field that references a cluster slot uses the ClusterID enum, which has exactly two values:

ValueMeaning
cluster1The first workload cluster slot
cluster2The second workload cluster slot

These are stable identifiers used across ProtectionZone, ProtectionGroup, ProtectionRequest, FailoverRequest, and DRBDReplicationPolicy. The human-readable cluster name lives in ProtectionZone.spec.clusters[].name.


Replication protocol

Only Protocol C (synchronous, RPO = 0) is supported in Tech Preview. The admission webhook rejects replicationProtocol: A with the message ReplicationProtocolA is not supported yet. Do not attempt to configure asynchronous replication.


Storage backend

Only storageBackendMode: DRBD is supported. The webhook rejects Linstor with Linstor backend is not supported yet; use DRBD.


Runtime log levels

Log levels for all Site Recovery components can be changed at runtime without pod restarts by editing the site-recovery-logging ConfigMap in the relevant namespace. Accepted values are error, info, debug, and trace. There is no warn level. The global key is level; per-component overrides are level.control-plane, level.drbd-node-agent, and level.protectionzone. The ProtectionZone spec.logging field propagates these settings automatically.


DRBDReplicationPolicy

The ProtectionZone controller creates one default DRBDReplicationPolicy named <zoneName>-default with isDefault: true in the zone namespace when a ProtectionZone is created. You do not create or modify this object. Only the default policy in the ProtectionRequest's namespace is consulted by the controller.


Usage

The following describes the most common usage patterns in the order you encounter them during normal operations.


Inspect all CRDs registered on the TSR-Manager cluster

kubectl get crd --context <tsr-manager-context> | grep siterecovery

Check ProtectionZone status

kubectl get pz -n dr-<zone-name> --context <tsr-manager-context>

The printer columns show Display Name, Backend, Clusters, Status (phase), and Age.

Check the ClustersValidated gate

Before you can create ProtectionGroups or ProtectionRequests, the ClustersValidated condition on the ProtectionZone must be True:

kubectl get pz <zone-name> -n dr-<zone-name> \
  -o jsonpath='{.status.conditions[?(@.type=="ClustersValidated")]}' \
  --context <tsr-manager-context>

List ProtectionGroups and their active cluster

kubectl get pg -n dr-<zone-name> --context <tsr-manager-context>

The Active column shows which cluster slot (cluster1 or cluster2) currently runs the VMs.

Check ProtectionRequest phase for a VM

kubectl get pr -n dr-<zone-name> --context <tsr-manager-context>

Phases in order: Pending → Validating → CreatingDRBD → Syncing → ReadyToActivate → Activating → Protected. A VM is fully protected only when the phase reaches Protected.

Trigger a planned failover

Create a FailoverRequest CR targeting the desired cluster:

kubectl apply -f failoverrequest.yaml -n dr-<zone-name> --context <tsr-manager-context>

Watch progress:

kubectl get fr <fr-name> -n dr-<zone-name> \
  -o wide --context <tsr-manager-context>

The printer columns show Protection Group, Target, State, Phase, and Age. Drill into per-VM status:

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

Run a test failover

kubectl apply -f testfailover.yaml -n dr-<zone-name> --context <tsr-manager-context>

Watch phases (Queued → PreparingEnvironment → CreatingSnapshots → CreatingVolumes → CreatingVMs → StartingVMs → Verifying → Succeeded):

kubectl get tfo <tfo-name> -n dr-<zone-name> --context <tsr-manager-context>

The test namespace (on the workload cluster) is reported in status.testNamespace. Do not construct or guess this value; read it from the status.

Inspect replication health for a ProtectionGroup

kubectl get rgs <pg-name>-replication -n dr-<zone-name> --context <tsr-manager-context>

The printer columns show Protection-Group, RPO-Status, Out-Of-Sync bytes, Synced VM count, Connection state, Last-Sync time, and Age.

Inspect DRBDResource status on a workload cluster

kubectl get dres -n <vm-namespace> --context <workload-cluster-context>

DRBDResource names follow the pattern drbd-<namespace>-<vm>-<hash8>. The printer columns show Side, Role, Node, State (connection), Phase, and Age.

Query RPO audit events for a ProtectionGroup

kubectl get rpoe -n dr-<zone-name> \
  -l siterecovery.trilio.io/protection-group=<pg-name> \
  --context <tsr-manager-context>

Use the label siterecovery.trilio.io/protection-group to filter RPOEvents and ReplicationGroupStatus objects — field selectors on spec.* or status.* are not supported on custom resources.

Query protected VMs on a workload cluster

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

This label is stamped on the KubeVirt VirtualMachine only on the workload cluster.

Rotate a zone credential

tsrctl zone-credential rotate \
  --zone <zone-name> \
  --refresh-rbac

Examples

ProtectionZone — minimal example

This CR pairs two workload clusters in a zone using the DRBD storage backend. Both clusters run RHEL CoreOS 9 nodes.

apiVersion: siterecovery.trilio.io/v1alpha1
kind: ProtectionZone
metadata:
  name: primary-dr-zone
  namespace: dr-primary-dr-zone
spec:
  displayName: Primary DR Zone
  description: Two-cluster DRBD zone pairing the primary and DR sites.
  storageBackendMode: DRBD
  clusters:
    - name: primary
      clusterID: cluster1
      distributionName: RHEL-CoreOS-9
      kubeconfigSecretRef:
        name: cluster1-kubeconfig
        key: kubeconfig
    - name: dr
      clusterID: cluster2
      distributionName: RHEL-CoreOS-9
      kubeconfigSecretRef:
        name: cluster2-kubeconfig
        key: kubeconfig

ProtectionZone — with logging and node agent tuning

apiVersion: siterecovery.trilio.io/v1alpha1
kind: ProtectionZone
metadata:
  name: primary-dr-zone
  namespace: dr-primary-dr-zone
spec:
  displayName: Primary DR Zone
  storageBackendMode: DRBD
  clusters:
    - name: primary
      clusterID: cluster1
      distributionName: RHEL-CoreOS-9
      kubeconfigSecretRef:
        name: cluster1-kubeconfig
        key: kubeconfig
    - name: dr
      clusterID: cluster2
      distributionName: RHEL-CoreOS-9
      kubeconfigSecretRef:
        name: cluster2-kubeconfig
        key: kubeconfig
  logging:
    level: info
    controlPlaneLevel: debug
    nodeAgentLevel: info
  nodeAgent:
    startupDeadlineSeconds: 180
  hooks:
    jobTTLSeconds: 7200
    failedJobRetentionSeconds: 604800

ProtectionGroup — two VMs with boot ordering

Both VMs fail over as one atomic unit. web-server boots first (lower bootOrder); app-server boots after.

apiVersion: siterecovery.trilio.io/v1alpha1
kind: ProtectionGroup
metadata:
  name: web-tier
  namespace: dr-primary-dr-zone
spec:
  virtualMachines:
    - name: web-server
      namespace: production
      bootOrder: 1
    - name: app-server
      namespace: production
      bootOrder: 2
  sourceCluster: cluster1
  desiredState: running
  sla:
    rtoTargetSeconds: 300
    rpoTargetSeconds: 0

ProtectionRequest — protect a single VM

The sourceCluster field is also the failover lever: patching it to the DR cluster triggers a role flip.

apiVersion: siterecovery.trilio.io/v1alpha1
kind: ProtectionRequest
metadata:
  name: protect-web-server
  namespace: dr-primary-dr-zone
spec:
  sourceCluster: cluster1
  virtualMachine:
    name: web-server
    namespace: production
  replicationConfig:
    replicationProtocol: C
    drbdMetadataConfig:
      storageClassName: fast-ssd
      size: 128Mi

ProtectionRequest — with DR-side placement override

When the DR cluster uses different node labels, use standbyPlacement to adjust the scheduling rules applied only to the DR copy. standbyPlacement is immutable once set.

apiVersion: siterecovery.trilio.io/v1alpha1
kind: ProtectionRequest
metadata:
  name: protect-web-server
  namespace: dr-primary-dr-zone
spec:
  sourceCluster: cluster1
  virtualMachine:
    name: web-server
    namespace: production
  replicationConfig:
    replicationProtocol: C
  standbyPlacement:
    nodeSelector:
      add:
        topology.kubernetes.io/zone: dr-zone-a
      remove:
        - topology.kubernetes.io/zone

FailoverRequest — planned failover to cluster2

failoverType: planned stops VMs gracefully on the source cluster, waits for DRBD sync, then promotes the DR side.

apiVersion: siterecovery.trilio.io/v1alpha1
kind: FailoverRequest
metadata:
  name: failover-web-tier-to-dr
  namespace: dr-primary-dr-zone
spec:
  protectionGroupRef:
    name: web-tier
  targetCluster: cluster2
  failoverType: planned
  batchBootTimeoutSeconds: 300
  skipHooks: false

FailoverRequest — unplanned failover

failoverType: unplanned force-promotes the DR side without waiting for the source to stop cleanly. Use during a site outage when the primary cluster is unreachable.

apiVersion: siterecovery.trilio.io/v1alpha1
kind: FailoverRequest
metadata:
  name: emergency-failover-web-tier
  namespace: dr-primary-dr-zone
spec:
  protectionGroupRef:
    name: web-tier
  targetCluster: cluster2
  failoverType: unplanned

TestFailover — DR drill with automatic cleanup

Spins up isolated VM copies from DRBD snapshots in a network-fenced namespace on the DR cluster. Resources are removed automatically after 2 hours.

apiVersion: siterecovery.trilio.io/v1alpha1
kind: TestFailover
metadata:
  name: drill-web-tier-01
  namespace: dr-primary-dr-zone
spec:
  protectionGroupRef:
    name: web-tier
  cleanupPolicy: Automatic
  retentionTime: 2h
  schedulingCheck: Fail
  verification:
    reachability:
      - type: tcp
        vmName: web-server
        port: 443
        timeout: 30s
      - type: http
        vmName: web-server
        port: 8080
        endpoint: /healthz
        expectedStatus: 200
        timeout: 30s

After creating this CR, read the controller-generated test namespace from status:

kubectl get tfo drill-web-tier-01 -n dr-primary-dr-zone \
  -o jsonpath='{.status.testNamespace}' \
  --context <tsr-manager-context>

TestFailover — manual cleanup to preserve logs

apiVersion: siterecovery.trilio.io/v1alpha1
kind: TestFailover
metadata:
  name: drill-web-tier-02
  namespace: dr-primary-dr-zone
spec:
  protectionGroupRef:
    name: web-tier
  cleanupPolicy: Manual
  schedulingCheck: Warn

To trigger cleanup while keeping the TestFailover record:

kubectl patch tfo drill-web-tier-02 -n dr-primary-dr-zone \
  --type merge \
  -p '{"spec":{"cleanupRequested":true}}' \
  --context <tsr-manager-context>

Hook — reusable lifecycle job definition

apiVersion: siterecovery.trilio.io/v1alpha1
kind: Hook
metadata:
  name: notify-on-failover
  namespace: dr-primary-dr-zone
spec:
  description: Posts a Slack notification after failover completes.
  jobTemplate:
    image: quay.io/myorg/notifier:v1.2.3
    command: ["/usr/bin/notify"]
    args: ["--channel", "#dr-alerts"]
    env:
      - name: SLACK_WEBHOOK_URL
        valueFrom:
          secretKeyRef:
            name: slack-credentials
            key: webhook-url
    timeoutSeconds: 60
  retries: 2
  retryDelaySeconds: 10
  timeoutSeconds: 60

Bind this Hook to a ProtectionGroup by adding it to spec.hooks:

hooks:
  - name: notify-post-failover
    hookName: notify-on-failover
    type: post-failover
    runOn: target
    onFailure: Continue
    order: 0

Inspect ReplicationGroupStatus for a ProtectionGroup

The ReplicationGroupStatus is auto-created by the replication monitor. Its name is always <pg-name>-replication.

kubectl get rgs web-tier-replication -n dr-primary-dr-zone \
  -o yaml --context <tsr-manager-context>

Inspect RPO audit events filtered by ProtectionGroup

kubectl get rpoe -n dr-primary-dr-zone \
  -l siterecovery.trilio.io/protection-group=web-tier \
  --context <tsr-manager-context>

Inspect DRBDResource status on a workload cluster

kubectl get dres -n production \
  --context <cluster1-context>

To get per-node agent pod logs, first find the pod on the relevant node:

kubectl get pods -n trilio-site-recovery-system \
  --field-selector spec.nodeName=<node-name> \
  -l app.kubernetes.io/component=drbd-node-agent \
  -o jsonpath='{.items[0].metadata.name}' \
  --context <workload-cluster-context>

Then retrieve its logs:

kubectl logs <pod-name> -n trilio-site-recovery-system \
  --context <workload-cluster-context>

Troubleshooting

ProtectionGroup or ProtectionRequest admission rejected

Symptom: kubectl apply returns a webhook denial when creating or updating a ProtectionGroup or ProtectionRequest.

Cause: The ClustersValidated condition on the ProtectionZone is not True. The TSR-Manager admission webhooks block all ProtectionGroup and ProtectionRequest creates and spec changes until both workload clusters have passed validation.

Fix: Check the zone status:

kubectl get pz <zone-name> -n dr-<zone-name> \
  -o jsonpath='{.status.conditions}' \
  --context <tsr-manager-context>

Resolve the specific condition reason (see the table below), then retry.


ProtectionZone validation failure reasons

Condition reasonMeaningResolution
ClusterAlreadyClaimedThe workload cluster's trilio-site-recovery-system namespace is already leased to another zone.Each workload cluster can belong to only one zone. Delete the other zone or use a different cluster.
InsufficientClusterPermissionsThe zone credential authenticates but lacks required RBAC verbs.Run tsrctl zone-credential rotate --refresh-rbac --zone <zone-name> to re-mint the credential with updated RBAC.
ClusterIdentityChangedThe credential now reaches a cluster whose kube-system namespace UID differs from the one recorded at zone creation.There is no override. Restore the original cluster or delete and re-create the zone.
SCCPrerequisiteMissingThe privileged SCC or its ClusterRole does not exist on the workload cluster.Ensure OpenShift is healthy and the privileged SCC is present before retrying zone creation.
KubeVirtNotInstalledThe workload cluster does not serve kubevirt.io/v1.Install OpenShift Virtualization (KubeVirt) on the workload cluster before creating the zone.
SCCAdmissionDeniedThe node-agent DaemonSet was rejected by OpenShift SCC admission after install.Check the workload cluster's SCC configuration and ensure the agent's ServiceAccount is bound to the privileged SCC.

ProtectionRequest stuck in CreatingDRBD or Syncing

Symptom: kubectl get pr shows the phase has not advanced after several minutes.

Cause: The DRBDResource on one or both workload clusters has not reached ReadyToActivate or Consistent.

Fix:

  1. Identify the DRBDResource name from the ProtectionRequest status:
    kubectl get pr <pr-name> -n dr-<zone-name> \
      -o jsonpath='{.status.drbdResourceRef}' \
      --context <tsr-manager-context>
    
  2. Check the DRBDResource on both workload clusters:
    kubectl get dres <dres-name> -n <vm-namespace> \
      -o yaml --context <cluster1-context>
    kubectl get dres <dres-name> -n <vm-namespace> \
      -o yaml --context <cluster2-context>
    
  3. Check the status.phase, status.connectionState, and status.conditions fields. A ConditionPlacementReady=False condition indicates metadata PVC provisioning has not completed. Verify that the metadata StorageClass binds immediately (WaitForFirstConsumer classes are rejected during validation).
  4. Check the DRBD node agent logs on the relevant node (see the DRBDResource spec.nodeName field to identify the node):
    kubectl get pods -n trilio-site-recovery-system \
      --field-selector spec.nodeName=<node-name> \
      -l app.kubernetes.io/component=drbd-node-agent \
      -o jsonpath='{.items[0].metadata.name}' \
      --context <workload-cluster-context>
    kubectl logs <pod-name> -n trilio-site-recovery-system \
      --context <workload-cluster-context>
    

ProtectionRequest shows Ready=False, reason=RoleConflict

Symptom: The ProtectionRequest status.conditions contains a Ready=False condition with reason RoleConflict.

Cause: Both DRBDResource copies are reporting the DRBD Primary role simultaneously, which is an inconsistent state.

Fix: Do not attempt to manually patch DRBDResource CRs. Check for a stalled or incomplete failover by inspecting any recent FailoverRequest CRs in the zone namespace. If a planned failover did not complete, check the FailoverRequest status.phase and status.vmStatuses for the per-VM failure detail. Contact Trilio support if the conflict cannot be resolved by completing or deleting the stalled FailoverRequest.


DRBD replication appears disconnected despite network connectivity

Symptom: kubectl get dres shows State=Disconnected or State=StandAlone, but ping between the worker nodes succeeds.

Cause: TCP ports 7000–8000 may not be fully open. Each protected VM uses one port from this range; port 8000 is allocatable and is not optional. A firewall rule ending at 7999 silently blocks replication on the last allocated port.

Fix: Verify that ports 7000–8000 inclusive are open between primary and DR worker nodes. Check the specific port assigned to the VM from the DRBDResource:

kubectl get dres <dres-name> -n <vm-namespace> \
  -o jsonpath='{.spec.port}' \
  --context <workload-cluster-context>

TestFailover stuck or not progressing

Symptom: kubectl get tfo shows the phase has not advanced.

Cause: Common causes include: VolumeSnapshot not completing (check snapshot status on the workload cluster), test VMs not starting (check the test namespace on the workload cluster), or a hook failure with onFailure: Abort.

Fix:

  1. Read status.testNamespace from the TestFailover:
    kubectl get tfo <tfo-name> -n dr-<zone-name> \
      -o jsonpath='{.status.testNamespace}' \
      --context <tsr-manager-context>
    
  2. Inspect resources in that namespace on the non-active workload cluster.
  3. Check status.hookStatuses for any hook in Failed or TimedOut phase:
    kubectl get tfo <tfo-name> -n dr-<zone-name> \
      -o jsonpath='{.status.hookStatuses}' \
      --context <tsr-manager-context>
    
  4. To bypass hooks during an incident, set spec.skipHooks: true on the next TestFailover CR (this field is immutable on an existing CR, so you must create a new one).

Node agent taint not removed after startup

Symptom: A workload node remains tainted with siterecovery.trilio.io/not-ready:NoSchedule after the startup deadline.

Cause: One or more DRBD resources on that node have not come up within the startupDeadlineSeconds window (default 120 seconds). The taint is removed at the deadline, but the affected DRBDResource carries NodeStartupGate=False.

Fix: This condition clears itself once the DRBD resource recovers — no manual action is required. Check the DRBDResource conditions on the workload cluster to identify what the agent is waiting for. Common causes are: DRBD kernel module not loaded (check agent init container logs), metadata PVC not bound, or peer connectivity not yet established after a node reboot. If the node agent's startup deadline is consistently too short for your hardware, increase it in the ProtectionZone:

spec:
  nodeAgent:
    startupDeadlineSeconds: 240

kubectl get drbdresource returns server doesn't have a resource type on TSR-Manager

Symptom: Running kubectl get dres or kubectl get drbdresource against the TSR-Manager cluster context returns an error.

Cause: The DRBDResource CRD is registered only on workload clusters, not on the TSR-Manager cluster.

Fix: Run DRBDResource queries against a workload cluster context:

kubectl get dres -n <vm-namespace> --context <workload-cluster-context>

Zone credential expiry warning

Symptom: The ProtectionZone has a CredentialExpiring reason on the CredentialsValid condition.

Cause: The zone kubeconfig credential for one or both workload clusters is approaching its expiry date (the zone warns 30 days before expiry). This is advisory and does not change the zone phase.

Fix: Rotate the credential before it expires:

tsrctl zone-credential rotate --zone <zone-name>