Harvey automates Disaster Recovery across two Rancher-managed KubeVirt (SUSE Virtualization / Harvester) clusters.
| Capability |
|---|
| Compare VM state across both sites |
| Ordered stop → verify → start failover |
| Priority groups with startup delays |
| Recovery Plans (YAML runbooks) |
| Pre / post shell hooks |
| Non-disruptive DR test (shadow namespaces) |
| RTO measurement |
| Audit log (JSONL) |
| Split-brain detection |
| Health-check pre-flight |
| Reprotect guidance |
uv manages CLI tools in isolated environments and bundles its own Python — no venv setup, no Homebrew Python conflicts.
# Install uv if you don't have it
brew install uv # macOS
# or: curl -LsSf https://astral.sh/uv/install.sh | sh
# Install Harvey
uv tool install git+https://github.com/abonillabeeche/harvey.gitAfter installation, run harvey --help.
uv tool upgrade harvey# Install pipx if you don't have it
pip install --user pipx && pipx ensurepath
pipx install git+https://github.com/abonillabeeche/harvey.git
# Upgrade
pipx upgrade harveyDownload the .whl from the Releases page and install it directly:
uv tool install harvey-X.Y.Z-py3-none-any.whlgit clone https://github.com/abonillabeeche/harvey.git
cd harvey
pip install .harvey config init
# then edit ~/.config/harvey/config.yamlrancher_url: "https://rancher.mydomain.com"
api_token: "token-abcde:1234567890abcdefghijklmnopqrstuvwxyz"
clusters:
primary: "c-m-xxxxxxxx" # Rancher cluster ID for primary site
dr: "c-m-yyyyyyyy" # Rancher cluster ID for DR siteShow config (token masked):
harvey config showharvey status primary dr
harvey status primary dr --namespace productionFailover State column:
| State | Meaning |
|---|---|
| ✅ Ready | One site running, other stopped — safe to fail over |
| ✅ Synced | Same state on both sides (both stopped) |
| ❌ SPLIT BRAIN | Running on both clusters — requires immediate manual action |
| Standalone | VM exists on only one cluster |
harvey health-check primary drChecks:
- API connectivity to both clusters
- VM inventory coverage (VMs present on both sides)
- Split-brain detection
- Saved recovery plans
All failover commands share these flags:
| Flag | Meaning |
|---|---|
--dry-run |
Print the ordered plan without touching any VMs |
--yes / -y |
Skip confirmation prompt |
--source |
Cluster where VMs are currently running |
--destination |
Cluster to start VMs on |
# Specific VMs
harvey failover vm db-primary app-server-1 \
--namespace production --source primary --destination dr
# All VMs in a namespace
harvey failover namespace production \
--source primary --destination dr
# Everything
harvey failover all --source primary --destination dr --dry-runmetadata:
labels:
failover-priority: "100" # higher = stopped first, started last
failover-startup-time: "60" # seconds to pause after issuing 'start'Stop order: descending priority (100 → 50 → 0)
Start order: ascending priority (0 → 50 → 100) — foundations before dependents
Recovery plans encode your full DR runbook — protection groups, VM ordering, startup delays, and shell hooks — so the same procedure runs identically every time.
harvey plan new production-dr
# edit ~/.config/harvey/plans/production-dr.yamlPlan file structure (~/.config/harvey/plans/<name>.yaml):
name: production-dr
description: "Full production site failover"
source: primary
destination: dr
protection_groups:
- name: database-tier
priority: 100 # stopped first, started last
startup_delay: 60 # seconds to wait after 'start' before next group
vms:
- namespace: production
name: db-primary
- namespace: production
name: db-replica
- name: app-tier
priority: 50
startup_delay: 30
vms:
- namespace: production
name: app-server-1
- name: web-tier
priority: 10
startup_delay: 0
vms:
- namespace: production
name: web-proxy
hooks:
pre_failover:
- name: "Page on-call"
command: "curl -sf -X POST -d 'DR starting' https://hooks.slack.com/..."
timeout: 30
on_failure: warn # warn | abort
pre_start:
- name: "Drain load balancer"
command: "./scripts/lb-drain.sh --site primary"
post_failover:
- name: "Update DNS"
command: "./scripts/update-dns.sh --target dr"
timeout: 60Hook environment variables available in your scripts:
| Variable | Value |
|---|---|
HARVEY_SOURCE |
Source cluster name |
HARVEY_DESTINATION |
Destination cluster name |
harvey plan list
harvey plan show production-drharvey plan run production-dr --dry-runharvey plan run production-dr --testThe test runs in three phases:
- Clone — copies VM specs into shadow namespaces (
*-harvey-test) on the DR cluster - Verify — starts each shadow VM and polls for Running state (5-minute timeout per VM)
- Cleanup — deletes all shadow namespaces regardless of test outcome
Production VMs on both clusters are never touched.
Storage note: Shadow VMs retain their original PVC/DataVolume references. If those volumes don't exist on the DR cluster the VM will time out — which is exactly the kind of readiness gap this test is designed to surface before a real DR event.
harvey plan run production-dr
harvey plan run production-dr --yes # skip confirmationAfter a successful failover the DR cluster is your new production site. reprotect shows what is running there and prints the exact command to fail back once the original site recovers.
harvey reprotect --source primary --destination drharvey audit
harvey audit --limit 100All Harvey operations are appended to ~/.config/harvey/audit.log (newline-delimited JSON).
Add --debug before any command for verbose API request/response output:
harvey --debug status primary dr
harvey --debug plan run production-dr --dry-run1. Day-to-day verification
harvey health-check primary dr # automated / cron
2. Pre-event readiness test (non-disruptive)
harvey plan run production-dr --test # runs weekly / monthly
3. DR event declared
harvey status primary dr # confirm split-brain / ready state
harvey plan run production-dr --dry-run
harvey plan run production-dr # live failover with RTO tracking
4. After DR site stabilises
harvey reprotect --source primary --destination dr
5. Original site recovers — fail back
harvey failover all --source dr --destination primary
To publish a new release, tag the commit and push the tag. GitHub Actions builds the wheel and creates the release automatically:
git tag v0.2.0
git push origin v0.2.0The workflow attaches harvey-X.Y.Z-py3-none-any.whl and the source tarball to the GitHub Release.
Remember to update version in pyproject.toml and src/harvey/__init__.py before tagging.
Issues and pull requests are welcome. Please open an issue first to discuss significant changes.
Apache License 2.0 — see LICENSE for details.
| Version | Key additions |
|---|---|
| 0.1 | Basic stop/start failover, namespace/all commands, priority labels, startup delays, dry-run, recovery plans, DR test mode, hooks, RTO tracking, audit log, health-check, reprotect |