Copias 3-2-1-0 en mi homelab
Tres copias, dos soportes, una fuera de casa y cero errores: copias con restic entre mis dos mini PC y Backblaze B2, y una restauración real cada semana para comprobar que sirven.
En el trabajo he montado copias y DR con Veeam para clientes. En casa no tenía nada: si un disco del homelab moría, perdía la configuración de todo. Así que lo he resuelto con la misma regla que aplicaría en un cliente, a escala de dos mini PC.
3-2-1-0
- 3 copias: los datos originales, una copia en el otro mini PC y otra en Backblaze B2.
- 2 soportes: los discos de la otra máquina y la nube.
- 1 fuera de casa: B2, en la región de Ámsterdam.
- 0 errores: una copia que nunca se ha restaurado no es una copia. Cada semana se restaura de verdad y se comprueba.
El último número es el que casi siempre falta.
Las piezas
- restic: copias cifradas, incrementales y deduplicadas. Un solo binario, instalado en el sistema y no en Docker. El día que haya que restaurar, lo más probable es que Docker sea justo lo que está roto.
- SFTP enjaulado entre los mini PC: cada máquina tiene un usuario que solo puede usar SFTP y solo ve su carpeta de copias.
- Backblaze B2 por la API compatible con S3. Restic cifra todo antes de subirlo: en B2 solo hay bloques cifrados.
- systemd: un timer para la copia diaria y otro para la verificación semanal.
- Uptime Kuma: un monitor Push por destino y otro para la verificación. Si algo falla, o si simplemente no se ejecuta, me llega un Telegram.
Bases de datos sin parar servicios
Copiar un fichero SQLite mientras el servicio escribe puede dejar una copia inconsistente. Antes de cada copia, el script hace un volcado en caliente:
sqlite3 /opt/uptime-kuma/data/kuma.db ".timeout 10000" ".backup '/var/backups/homelab-dumps/kuma.sqlite3'"
El volcado es consistente y no hay que parar nada. Esto importa sobre todo en AdGuard: si lo paro, la casa se queda sin DNS.
El "0": restaurar de verdad
Cada domingo, verify.sh hace tres cosas en cada destino:
restic check --read-data-subset 5%: no solo revisa los índices, también lee y valida un 5 % de los datos reales. En cinco semanas se ha leído todo.- Restaura un canario: un fichero con un valor aleatorio que la copia escribe cada noche. Si lo restaurado no coincide exactamente con lo escrito, algo va mal.
- Restaura los volcados de las bases de datos y pasa
PRAGMA integrity_check.
Si cualquiera de las tres falla, Kuma se pone en rojo.
Probado antes de llegar al servidor
Los scripts se prueban en GitHub Actions con restic real cada vez que los cambio. La prueba copia, comprueba la retención, verifica y restaura byte a byte. También corrompe a propósito un repositorio, para comprobar que la verificación lo detecta, y elimina un destino, para comprobar que el otro se sigue copiando.
La prueba encontró un fallo antes de llegar a producción: si pegaba la URL de Kuma tal cual (...?status=up&msg=OK), bash interpretaba los & y la verificación nunca avisaba.
Lo que me encontré al instalarlo
Los scripts estaban probados, pero los servidores reales siempre tienen sorpresas:
- Datos de Docker en el NVMe. Kuma, Grafana y Prometheus guardaban sus datos en volúmenes de Docker dentro de
/var/lib/docker, en el disco del sistema. Si ese disco moría, se iban con él. Los pasé a carpetas junto a cadacompose, dentro de/opten el SSD. Ahora, si cambio el NVMe, reinstalo, monto el SSD y hagodocker compose up -d. - La jaula SFTP exige root. SSH aceptaba la clave y cortaba la conexión al instante. El motivo era que
ChrootDirectoryexige que todas las carpetas del camino (/,/opt,/opt/backups) sean de root y que nadie más pueda escribir en ellas, y/optera de mi usuario. - No todo lo que acaba en
.dbes SQLite. Losstats.dbysessions.dbde AdGuard son BoltDB, así que no se pueden volcar consqlite3. Se copian como ficheros normales. - Capas de Docker que no hacía falta copiar. En la otra máquina,
/opttenía 11 GB de capas de imágenes y caché de compilación. Se vuelven a descargar solas, así que solo copio lo que tiene valor:/opt/ciy/etc. - Las URL de Kuma, por IP. Kuma daba las URL de los avisos con un nombre que solo resuelve mi AdGuard. Si AdGuard se cae, el aviso no llega justo cuando más falta hace.
Lo que cuesta
| Máquina | Datos copiados | Ocupado en cada destino | Primera copia a B2 |
|---|---|---|---|
| homelab-red | 361 MiB | 115 MiB | 7 s |
| homelab | 53 MiB | 10 MiB | 4 s |
Restic comprime y deduplica: los 414 MiB de datos ocupan unos 125 MiB en B2. Las copias de cada noche solo suben lo que ha cambiado. B2 cobra 6,95 $/TB al mes y los primeros 10 GB son gratis, así que ahora mismo esto me cuesta 0 €.
La primera verificación restauró desde los cuatro destinos y comprobó el canario y las dos bases de datos de homelab-red. Todo correcto.
El código y la guía de instalación están en github.com/mateocouso94/mcouso-web/tree/main/infra/backup. El estado de las copias se ve en directo en /estado/ y con el comando backup de la consola.