Files
sqlite-backup-s3/README.md
T
vasyansk 99bc9665c3
docker-build / Build image (push) Successful in 14s
fix cron
2026-09-02 16:51:19 +07:00

6.4 KiB
Raw Blame History

бекапирование sqlite в minio

services:
  sqlite_backup:
    image: git.realmanual.ru/pub/sqlite-backup-s3
    container_name: sqlite_backup
    restart: always
    volumes:
      - ./var/lib/data/:/data
    environment:
      - DB_FILE
      - CRONTAB
      - DELETE_AFTER
      - KEEP_LAST
      - TZ
      - SQLITE_TIMEOUT
      - BACKUP_RETRIES
      - BACKUP_RETRY_DELAY
      - MINIO_PATH
      - MINIO_ACCOUNT_ID
      - MINIO_APPLICATION_KEY
      - MINIO_ENDPOINT
      - MINIO_LOCATION
      - HEALTHCHECK_URL
      - HEALTHCHECK_UUID

пример .env

DB_FILE=/data/db.sqlite
CRONTAB=00 06 * * *
DELETE_AFTER=10
KEEP_LAST=10
TZ=Asia/Novosibirsk
SQLITE_TIMEOUT=60000
BACKUP_RETRIES=5
BACKUP_RETRY_DELAY=30
MINIO_PATH=myminio://sqlite/master/
MINIO_ACCOUNT_ID=account
MINIO_APPLICATION_KEY=key
MINIO_ENDPOINT=https://s3.domain.ru
MINIO_LOCATION=ru-nsk

# необязательно: пинг в healthchecks.io (в т.ч. self-hosted)
HEALTHCHECK_URL=https://hc.domain.ru/ping
HEALTHCHECK_UUID=00000000-0000-0000-0000-000000000000
  1. myminio - системно! не менять
  2. проверьте настройки локации, не должно быть пусто
  3. DELETE_AFTER - в днях
  4. KEEP_LAST - минимальное число копий, которые сохраняются всегда

Ротация

Удаление старых бекапов работает по двум условиям одновременно:

  1. бекап старше DELETE_AFTER дней
  2. и при этом не входит в KEEP_LAST самых свежих копий

То есть последние KEEP_LAST бекапов не удаляются никогда, даже если они старше DELETE_AFTER дней. Это защищает от потери всех копий, если контейнер долго стоял и новые бекапы не создавались.

KEEP_LAST по умолчанию 10, если переменная не задана.

Заблокированная база

Если базу пишет живой процесс (grafana, или не добитый старый под), sqlite3 .backup падает с Error: database is locked.

Бекап это переживает: соединение открывается с .timeout SQLITE_TIMEOUT (мс) — sqlite ждёт освобождения блокировки вместо мгновенной ошибки. Если за это время писатель не отпустил базу, попытка повторяется BACKUP_RETRIES раз с паузой BACKUP_RETRY_DELAY секунд.

Переменная Default Что делает
SQLITE_TIMEOUT 60000 сколько мс ждать блокировку в рамках одной попытки
BACKUP_RETRIES 5 сколько попыток снять дамп
BACKUP_RETRY_DELAY 30 пауза между попытками, сек

Максимальное время ожидания с дефолтами: 60s * 5 + 30s * 4 = 7 минут. После этого — exit 1 и пинг /fail.

Если база залочена постоянно — это не про бекап, а про то, что старый под не умер. Ретраи только прикрывают короткие окна.

Healthchecks

Если HEALTHCHECK_URL пуст — пинги не отправляются, бекап работает как обычно.

Итоговый адрес: ${HEALTHCHECK_URL}/${HEALTHCHECK_UUID}

Момент Запрос
старт бекапа .../${HEALTHCHECK_UUID}/start
дамп снят, проверен PRAGMA integrity_check, запакован, залит в s3, размер в s3 сверен с локальным, старое удалено .../${HEALTHCHECK_UUID}
любая ошибка на любом шаге .../${HEALTHCHECK_UUID}/fail

Для self-hosted инстанса HEALTHCHECK_URL — это адрес его ping-эндпоинта, например https://hc.domain.ru/ping.

Планировщик

crond из busybox не используется: под non-root uid (обычная ситуация в kubernetes с securityContext.runAsUser) он не загружает ни одного crontab и не сообщает об этом — в логе бесконечно идёт wakeup dt=60, а бекапов нет. Вместо него entrypoint.sh содержит свой планировщик на sh.

Что это даёт:

  • работает под любым uid;
  • окружение наследуется напрямую, без выгрузки env в файл;
  • бекапы никогда не накладываются друг на друга — следующий запуск возможен только после завершения предыдущего;
  • некорректный CRONTAB роняет контейнер на старте с внятным сообщением ([scheduler] FATAL: minute: '60' is outside 0-59), а не приводит к тихому простою.

Синтаксис CRONTAB — стандартные пять полей: *, 5, 1,15, 2-6, */15, 8-18/2, 30/5. В месяцах и днях недели понимаются имена (jan, mon). Воскресенье — и 0, и 7. Если ограничены одновременно день месяца и день недели, запуск происходит при совпадении любого из них — как в crontab(5).

При старте контейнера бекап делается сразу, эта минута считается отработанной.

Расписание видно в логе при запуске:

[scheduler] schedule: 30 07 * * * (TZ=Asia/Novosibirsk, now 2026-09-02 16:45:21 +0700)
[scheduler] expanded: min=[30] hour=[7] dom=[1 2 3 ...] mon=[1 2 ...] dow=[0 1 ...] day-match=and

Ручной запуск: docker exec ... /scripts/backup.sh — использует то окружение, с которым его вызвали.

Период опроса часов — 20 секунд, меняется через POLL_INTERVAL (секунды).