Потеря данных из-за отсутствия резервных копий или непроверенных бэкапов, одна из самых частых болезненных ситуаций как для обычных разработчиков, так и для крупных компаний. Несмотря на серьезность проблемы, многие до сих пор хранят бэкапы там же, где и сама база, либо не сохраняют бэкапы вовсе (в Южной Корее уже доказали, что это не лучшая идея).

Но даже если бэкапы делаются, возникает следующий вопрос: где именно они хранятся? Если база лежит на одном облаке, а дампы пишутся на тот же диск, никакой защиты это не дает. Нормальный бэкап должен жить в другом независимом облаке.

Что мы будем делать для создания бэкапов 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.

Все, что будет в нем происходить - это:

  1. Сбор значений переменных окружения,

  2. Выполнение дампа и сохранение по указанному пути,

  3. Если включено: отсылка статуса в 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 - адрес хоста PSQL

  • PG_PORT - порт PSQL

  • PG_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.

Комментарии (2)


  1. pnetmon
    30.11.2025 11:02

    У российских разработчиков год назад была бесплатная утилита (не отслеживаю что было при выпуске крайней версии постгресса) pg_probackup которая и ускорит и уменьшит объем данных для следующих копий в том числе уменьшит за счет уменьшения нагрузки на сеть т.к. будет выполняться на сервере с базой, и автоматически удалять старое (при запуске в кроне), и скачивать и сохранять wal файлы (при настройке службы на сервере бэкапа), и много чего (включая файлы настроек и не только из нужных папок). Чем простой pg_dump. Ихмо.


  1. SlFed
    30.11.2025 11:02

    Вот еще бы гайд по тому как рестор делать....