¶Runbook: Backup y restore de audit/board/state
Este runbook describe el respaldo de los tres volúmenes de estado de Ciel. Es crítico distinguir los formatos, porque el método de backup difiere:
| Componente | Formato real | Ruta (chart) | Método de backup |
|---|---|---|---|
Audit (enterprise/audit.py) |
JSONL append-only particionado por tenant/session ({base}/{tenant}/{session}/{tenant}-{session}.jsonl) |
/var/lib/ciel/audit |
Copiar archivos JSONL (NO es SQLite) |
Board (orchestration/board.py) |
SQLite (WAL) cuando se pasa path |
/var/lib/ciel/board (si se monta) |
sqlite3 .backup / .dump |
| Checkpoint/session (Fase 14 / F15) | SQLite local (StateBackend) o Postgres compartido |
/var/lib/ciel/state (SQLite) o CIEL_STATE_DSN (Postgres) |
Copiar .sqlite o pg_dump |
⚠️ El audit NO es SQLite. El
HashChainAuditSinkescribe JSONL particionado (versrc/ciel/enterprise/audit.py). Los comandossqlite3sobre el audit del runbook anterior eran incorrectos y se eliminan.
¶Backup del audit (JSONL)
POD=$(kubectl -n ciel get pods -l app.kubernetes.io/name=ciel -o jsonpath='{.items[0].metadata.name}')
# Copiar TODO el árbol de auditoría particionado por tenant/session.
kubectl -n ciel cp "$POD:/var/lib/ciel/audit" /tmp/ciel-audit-$(date +%s)
El script scripts/backup_state.py automatiza esto (volca audit+board+state a
JSON/S3 opcional). Ver abajo.
¶Backup del board (SQLite)
POD=$(kubectl -n ciel get pods -l app.kubernetes.io/name=ciel -o jsonpath='{.items[0].metadata.name}')
# Snapshot coherente de SQLite (WAL) vía .backup
kubectl -n ciel exec "$POD" -- sh -c \
"sqlite3 /var/lib/ciel/board/board.db '.backup /tmp/board.bak'"
kubectl -n ciel cp "$POD:/tmp/board.bak" /tmp/ciel-board-$(date +%s).bak
¶Backup del state (Fase 14 / F15)
- SQLite (default dev): copiar el archivo
.sqlitedirectamente. - Postgres (prod,
CIEL_STATE_BACKEND=postgres):pg_dump "$CIEL_STATE_DSN" > /tmp/ciel-state.sql.
¶Restore
# Detener el Deployment (escalar a 0) para evitar escrituras durante el restore.
kubectl -n ciel scale deploy/ciel --replicas=0
# Audit (JSONL): restaurar el árbol de archivos.
kubectl -n ciel cp /tmp/ciel-audit-<ts> "$POD:/var/lib/ciel/audit"
# Board (SQLite): restaurar el .bak.
kubectl -n ciel cp /tmp/ciel-board-<ts>.bak "$POD:/var/lib/ciel/board/board.db"
kubectl -n ciel scale deploy/ciel --replicas=2
¶Verificar integridad del audit (hash chain)
La cadena de hashes SHA-256 del JSONL se verifica con verify():
uv run python -c "
from ciel.enterprise.audit import HashChainAuditSink
sink = HashChainAuditSink(base_path='/tmp/ciel-audit-<ts>')
import asyncio
print('verify:', asyncio.run(sink.verify(tenant_id='acme', session_id='<sid>')))
"
verify() devuelve True si la cadena no fue alterada; False si algún
registro fue manipulado. El audit es append-only: no hacer DELETE sobre los
JSONL en producción.
¶Script automatizado (scripts/backup_state.py)
# Local: vuelca audit+board+state a un directorio JSON local.
uv run python scripts/backup_state.py --audit-dir /var/lib/ciel/audit \
--board-db /var/lib/ciel/board/board.db --state-db /var/lib/ciel/state/state.sqlite \
--out /tmp/ciel-backup
# Con S3 (opcional): define CIEL_BACKUP_S3=s3://bucket/prefix
CIEL_BACKUP_S3=s3://mi-bucket/ciel uv run python scripts/backup_state.py ...
¶Retención
- Programar backup periódico (CronJob
BackupJoben el chart Helm, verdeploy/helm/ciel/templates/backupjob.yaml) fuera de la ventana de pico. - El audit es append-only; el board/state son reemplazables desde el backup.