Regional-DR build runbook (the proper way)

Stand up ODF Regional-DR (async RBD mirroring) between two air-gapped OCP clusters, from an ACM hub, using oc + GitOps only. Build in the order below. Each phase has a do-it-right note, a verify, and the pitfall it avoids. Doing Phase 3 correctly is what saves you days.

Components

Layer Component Namespace
Network Submariner (libreswan IPsec) submariner-operator
Storage peering ODF MirrorPeer → StorageClusterPeer openshift-storage
Replication Ceph RBD async mirror (rook-ceph-rbd-mirror) openshift-storage
Orchestration (hub) ODR Hub Operator (Ramen) openshift-operators
Orchestration (spoke) ODR Cluster Operator (Ramen) openshift-dr-system
DR metadata store Ramen S3 = ODF NooBaa bucket openshift-storage

Prerequisites (fix these before you start)

Why non-overlapping CIDRs matter: overlap forces Submariner Globalnet, and ODF StorageClusterPeer uses raw ClusterIPs that Globalnet cannot NAT — peering never completes. Fixing this later means rebuilding a cluster. Plan it up front.

Phase 1 — Submariner

Do it right:

Pitfall — gateway-node pod scheduling (this one is subtle):

Verify:

Phase 2 — ODF MirrorPeer (storage peering)

Do it right:

Pitfall — the peering gRPC to the remote provider (:50051) is issued by ocs-operator. If it sits on the gateway node, StorageClusterPeer = Failed (see Phase 1). Move it off.

Verify:

Check Expected
oc get mirrorpeer ... .status.phase ExchangedSecret (or later)
StorageClusterPeer .status.state (both clusters) Peered
CephBlockPool .spec.mirroring.enabled (both) true
rook-ceph-rbd-mirror pods Running, off the gateway node

Phase 3 — Ramen hub operator + S3 trust (the step most builds get wrong)

Do these before creating any DRCluster, in this order.

3a. Install ODR Hub Operator

3b. Point Ramen at the air-gap catalog

Config reload: Ramen reads its config only at startup. oc rollout restart is reverted by OLM — instead oc delete pod <ramen-hub-pod>.

3c. Make Ramen trust the NooBaa route cert — the RIGHT way

Key facts (this is why the naive fix fails):

Steps:

  1. Get each cluster’s ingress CA: oc --kubeconfig <c> get cm default-ingress-cert -n openshift-config-managed -o jsonpath='{.data.ca-bundle\.crt}'
  2. Add both CAs to cm user-ca-bundle (openshift-config) — the one referenced by proxy/cluster .spec.trustedCA.
    • GitOps: user-ca-bundle is Argo-managed (hub/cluster-trust/user-ca-bundle.yaml). Edit it in Git, not with oc — Argo self-heal reverts manual oc edits.
  3. CNO merges it into openshift-trusted-cabundle, which ramen-hub mounts.
  4. Delete the ramen-hub pod (loads system roots once at startup; won’t auto-detect new certs — documented ODF known issue).

Verify trust before going further — from the ramen-hub pod:

curl -s -o /dev/null -w '%{ssl_verify_result}\n' \
  https://s3-openshift-storage.apps.<c>.<domain>/<bucket>      # want 0 (trusts)

3d. S3 profiles + secrets

Phase 4 — DRClusters + DRPolicy

Only after Phase 3 trust check returns 0.

DRCluster/wl     spec.region: region-wl      s3ProfileName: s3profile-wl-...
DRCluster/wl-dr  spec.region: region-wl-dr   s3ProfileName: s3profile-wl-dr-...
DRPolicy/dr-policy-5m   drClusters: [wl, wl-dr]   schedulingInterval: 5m

Pitfall — never oc delete a DRCluster that is mid-reconcile. Its finalizer hangs it in Terminating, Ramen takes the delete code path, and validation freezes. If stuck:

oc patch drcluster <n> --type merge -p '{"metadata":{"finalizers":[]}}'   # then recreate

Verify (spoke operator install can take ~10 min):

Check Expected
oc get drcluster (both) Validated=True/Succeeded, phase=Available
spoke oc get csv -n openshift-dr-system \| grep odr-cluster-operator Succeeded
oc get drpolicy dr-policy-5m Validated True
oc get drpolicy dr-policy-5m -o jsonpath='{.status.async.peerClasses}' RBD class with replicationID

Phase 5 — Protect a workload (DRPlacementControl)

GitOps ownership map

Artifact Location Owner
DRPolicy, DRClusters, DRPlacementControls hub/regional-dr/ Git / Argo
Ingress-CA trust (user-ca-bundle) hub/cluster-trust/ Git / Argo
Ramen s3StoreProfiles + caCertificates + catalogSourceName cm ramen-hub-operator-config Operator runtime — re-apply after ODF upgrade
s3secret-* openshift-operators Secret — never in Git

Upgrade caution: an ODF operator upgrade wipes the runtime Ramen config (s3 profiles, CA, catalog name). Re-apply them after every ODF upgrade. Documented ODF known issue.

Failure → cause → fix (quick reference)

Symptom Cause Fix
DRCluster s3ListFailed / send request failed Ramen doesn’t trust NooBaa route cert (system trust) Add ingress CA to user-ca-bundle (GitOps), delete ramen-hub pod
Spoke subscription ResolutionFailed Wrong catalog name in ramen config Set drClusterOperator.catalogSourceName to air-gap catalog, delete ramen-hub pod
DRCluster Validated frozen; “delete” in logs Stuck Terminating (finalizer) Clear finalizers, recreate
StorageClusterPeer Failed ocs-operator on Submariner gateway node Move ocs-operator off gateway
Cross-cluster pod traffic fails one direction Source pod on gateway node (OVN SNAT) Keep DR pods off gateway node
Config edits ignored Ramen reads config only at startup oc delete pod <ramen-hub> (not rollout restart)
Everything breaks after ODF upgrade Upgrade wiped runtime ramen config Re-apply s3 profiles / CA / catalog

Debugging notes