Потеря данных из-за отсутствия резервных копий или непроверенных бэкапов, одна из самых частых болезненных ситуаций как для обычных разработчиков, так и для крупных компаний. Несмотря на серьезность проблемы, многие до сих пор хранят бэкапы там же, где и сама база, либо не сохраняют бэкапы вовсе (в Южной Корее уже доказали, что это не лучшая идея).
Но даже если бэкапы делаются, возникает следующий вопрос: где именно они хранятся? Если база лежит на одном облаке, а дампы пишутся на тот же диск, никакой защиты это не дает. Нормальный бэкап должен жить в другом независимом облаке.
Что мы будем делать для создания бэкапов PostgreSQL
В этом примере мы покажем, как с помощью Cron Jobs настроить регулярное резервное копирование внешней PostgreSQL-базы с отсылкой статуса дампа в Telegram.
Разберём как:
Подключиться к СУБД PostgreSQL;
Снять дамп с помощью
pg_dump;Сохранить его в любое стороннее облако;
Держать статус в сообщении в Telegram с информацией о последнем бэкапе;
и все это без лишних трат.
И тут появляется новая проблема
Такой сценарий требует своего VPS, настроенного cron'a, тестирования, контроля зависимостей и т. п. То есть требуется долгая работа для настройки. И помимо этого еще нужно проверять, действительно ли сохранился последний дамп.
И при том сам скрипт будет работать только раз в сутки (а то и меньше). Вопрос: зачем ради этого оплачивать целый сервер?
Как раз под такую задачу я рассмотрю использование запуска по расписанию на примере сервиса Cron Jobs в Amvera Cloud, который сам будет запускать собранный контейнер с тарификацией фактического времени выполнения кода и писать о статусе запуска.
Если у вас уже есть мощности у другого облачного провайдера, вы сможете использовать код из инструкции для запуска на нем с минимальной адаптацией. Данный скрипт не обязательно запускать именно так, как описано в примере, вы можете выбрать любого понравившегося провайдера.
В Amvera вы сможете:
Подключить Cron Jobs в Amvera к любой СУБД. Хоть к локальной (если есть доступ извне), хоть к облачной;
По расписанию снимать дамп (pg_dump) и сохранять его в постоянное хранилище
/dataили на стороннее облако;Не держать отдельный VPS и платить только за отработанное время и копейки на ожидание запуска.
Начало работы
Итак, перед тем, как начать писать скрипт, нам нужно понимать, как вообще работает сервис Cron Jobs.
Тут все очень просто: при создании проекта вы указываете cron-выражение, которое можно сгенерировать с помощью любого удобного конфигуратора в интернете или взять из примеров в документации.
Например, возьмём cron-выражение */5 * * * *. Это выражение будет запускать выполнение задачи приложения каждые 5 минут.
Из приятного: вы платите лишь за отработанное время приложения, плюс буквально копейки за время ожидания запуска если используете сервис Cron Jobs Amvera. А учитывая то, что снятие дампа на условный 1 Гб будет занимать около минуты, платить много мы не будем - получится около 100 р. в месяц.
Разработка скрипта создания бэкапа PostgreSQL
Сам скрипт будет на Python.
Все, что будет в нем происходить - это:
Сбор значений переменных окружения,
Выполнение дампа и сохранение по указанному пути,
Если включено: отсылка статуса в Telegram.
Сама реализация будет выглядеть следующим образом:
import os
import subprocess
import datetime
import requests
import sys
import logging
from pathlib import Path
# Настройка логгера
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# очистка старых бэкапов (чтобы было 2)
def cleanup_old_backups():
backups = sorted(Path(BACKUP_DIR).glob(f"{BACKUP_NAME_PREFIX}*.dump"))
if len(backups) > 2:
for old_file in backups[:-2]:
try:
old_file.unlink()
logger.info(f"Удален старый бэкап: {old_file.name}")
except Exception as e:
logger.warning(f"Не удалось удалить {old_file.name}: {e}")
def run_pg_dump():
timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M")
dump_name = f"{BACKUP_NAME_PREFIX}{timestamp}.dump"
dump_path = os.path.join(BACKUP_DIR, dump_name)
os.makedirs(BACKUP_DIR, exist_ok=True)
cmd = [
"pg_dump",
"-h", PG_HOST,
"-p", PG_PORT,
"-U", PG_USER,
"-d", PG_DATABASE,
"-F", "c",
"-f", dump_path
]
env = os.environ.copy()
env["PGPASSWORD"] = PG_PASSWORD
try:
logger.info(f"Запуск pg_dump в {dump_path}...")
subprocess.check_output(cmd, stderr=subprocess.STDOUT, env=env)
logger.info("Бэкап успешно завершен")
return dump_name, True, ""
except subprocess.CalledProcessError as e:
logger.error("Ошибка при снятии бэкапа:")
logger.error(e.output.decode())
return None, False, e.output.decode()
def update_telegram(status, dump_name=None, error=None):
text = ""
if status:
text = f"✅ Бэкап успешно сохранен в {datetime.datetime.now().strftime('%Y-%m-%d %H:%M')}\n"
text += f"Файл: `{dump_name}`"
else:
text = f"❌ Ошибка при сохранении бэкапа в {datetime.datetime.now().strftime('%Y-%m-%d %H:%M')}\n"
text += f"Ошибка: `{error.strip()[:200]}`"
payload = {
"chat_id": TELEGRAM_CHAT_ID,
"message_id": TELEGRAM_MESSAGE_ID,
"text": text,
"parse_mode": "Markdown"
}
try:
resp = requests.post(f"https://api.telegram.org/bot{TELEGRAM_TOKEN}/editMessageText", data=payload)
logger.info(f"Telegram: {resp.status_code} {resp.text}")
except Exception as e:
logger.warning(f"Не удалось обновить Telegram-сообщение: {e}")
# Переменные окружения
PG_HOST = os.getenv("PG_HOST", "localhost")
PG_PORT = os.getenv("PG_PORT", "5432")
PG_USER = os.getenv("PG_USER", "asd")
PG_PASSWORD = os.getenv("PG_PASSWORD", "asd")
PG_DATABASE = os.getenv("PG_DATABASE", "asd")
BACKUP_NAME_PREFIX = os.getenv("BACKUP_NAME_PREFIX", "backup-")
BACKUP_DIR = os.getenv("BACKUP_DIR", "/data")
TELEGRAM_ENABLED = os.getenv("TELEGRAM", "false").lower() == "true"
TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN", "")
TELEGRAM_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID", 0)
TELEGRAM_MESSAGE_ID = os.getenv("TELEGRAM_MESSAGE_ID", 0)
cleanup_old_backups()
dump_name, success, error = run_pg_dump()
if TELEGRAM_ENABLED:
update_telegram(success, dump_name, error if not success else None)
То есть мы буквально просто собираем команду из переменных окружения, выполняем команду и сохраняем дамп в постоянное хранилище /data.
Используемые переменные окружения
Мы используем следующие переменные окружения:
PG_HOST- адрес хоста PSQLPG_PORT- порт PSQLPG_USER- имя пользователяPG_PASSWORD- парольPG_DATABASE- имя бдBACKUP_NAME_PREFIXопционально - префикс имени файла дампаBACKUP_DIR-/dataдля сохранения в постоянное хранилищеTELEGRAM- включена ли отсылка в телеграм. Если false - переменные ниже заполнять не обязательно. (true/false)TELEGRAM_TOKEN- токен от бота, который будет изменять сообщениеTELEGRAM_CHAT_ID- айди чата, в котором лежит сообщениеTELEGRAM_MESSAGE_ID- айди самого сообщения
Создание проекта и запуск
Как я писал ранее, создавать проект мы будем в Amvera Cloud. Очевидно, для этого нужно зарегистрировать в сервисе по ссылке.
Из важных бонусов:
Есть сервис для запуска Cron Jobs c оплатой за время работы кода.
Встроенный CI/CD, логирование, мониторинг, алерты. Вам не нужно настраивать VPS, достаточно сделать push в привязанный Git и все развернется автоматически.
Сразу после регистрации на ваш баланс будет начислено 111 рублей для теста.
Итак, когда у нас все готово: есть код, есть аккаунт в Amvera, нам остается лишь создать сам проект и выполнить минимальные настройки.
Создание проекта
Переходим на главную страницу проектов и открываем плитку Cron Jobs - Создайте первый Cron Job.
В открывшемся окне выбираем:
Название проекта: совершенно произвольное, это не играет роли;
Выберите тариф: для нашего проекта будет достаточно Начального Плюс CPU;
Подключить постоянное хранилище: жмем галочку;
Обьем: тут все зависит от того, сколько будет весить именно два бэкапа (т.к. будем сохранять последние два). Важно: логика очистки завязана на имени. Так что, если вы используете другой префикс для имен файлов, измените логику в коде.

Теперь нужно настроить сам Cron Job.
Cron выражение: у вас будет свое значение. Его можно взять из примеров в документации или из онлайн-генераторов. Для теста я выберу
*/5 * * * *, что будет запускать код каждые 5 минут.Максимальное время работы в секундах: можно не заполнять
Политика запуска: в нашем случае рекомендуется ставить
ALLOW
Все остальные этапы пропускаем - можно будет выполнить настройку позже.
После создания проекта, в разделе Cron Jobs появится ваш проект - откройте его и перейдите во вкладку "Переменные".
Здесь вам нужно создать вышеуказанные переменные окружения:

Теперь необходимо загрузить файлы. Всего нужно будет загрузить 3 файла: main.py, amvera.yml и Dockerfile. Наверное вы удивились, откуда взялись два последних файла.
Их можно взять из нашего нашего GitHub репозитория. Из них amvera.yml - файл, который задает конфигурацию проекта, а Dockerfile - файл с инструкциями для сборки (устанавливаем клиент postgres для pg_dump и зависимости Python). Можно загружать прямо в том виде, что они предложены в репозитории.
Если вы все загрузили, во вкладке "Конфигурация" должны прогрузиться новые параметры. Если они появились (например, инструмент "Docker") - можно нажимать кнопку "Собрать" в этой же вкладке.
Итог настройки дампов Postgres по расписанию
Мы настроили правильное резервное копирование на независимую инфраструктуру по расписанию, не переплачивая за VPS. Так можно не переживать за сохранность данных.
А главное, этот код можно улучшать, добавлять загрузку на облачный диск, строить аналитику и делать любые другие апгрейды. Скачать дампы можно во вкладке "Репозиторий" - "Data" или по тому пути, по которому вы произведете сохранение.
Полный код проекта можно скачать в GitHub.
pnetmon
У российских разработчиков год назад была бесплатная утилита (не отслеживаю что было при выпуске крайней версии постгресса) pg_probackup которая и ускорит и уменьшит объем данных для следующих копий в том числе уменьшит за счет уменьшения нагрузки на сеть т.к. будет выполняться на сервере с базой, и автоматически удалять старое (при запуске в кроне), и скачивать и сохранять wal файлы (при настройке службы на сервере бэкапа), и много чего (включая файлы настроек и не только из нужных папок). Чем простой pg_dump. Ихмо.