Skip to main content

Архивация и отправка архивов

Задача: есть некритичный ко времени сервис с небольшим объемом данных. Необходимо настроить отправку резервных файлов раз в 15 минут на внешний сервис и отображение имен последних отправленных файлов на дашборде в Zabbix, при отсутствии файлов в течение 20 минут генерировать ошибку. При каждом получении файла отправлять сообщение в канал об успешном получении файлов.

В дальнейшем нужно добавить удаление старых архивов.

Решил сначала собрать образ и сохранить его на локальном хабе, затем запустить через compose.

Структура файлов проекта.

db_archiver (dir)
    app (dir)
        votes_db_arch (dir)
        .env (file)
        db_archiver (file)
    cron_job (file)
    Dockerfile (file)
    requirements.txt (file)
docker-compose.yml


Название Назначение
db_archiver Директория. Хранится исходный код, настройки для создания образа и архивы.
db_archiver/app Директория. Исходный код и архивы
db_archiver/app/votes_db_arch Директория. В ней локально будут сохраняться архивы перед отправкой на сервер.
db_archiver/app/.env Переменные для скрипта
db_archiver/app/db_archiver.py Скрипт архивации
db_archiver/cron_job Настройка планировщика в образе. После сборки образа не нужен.
db_archiver/Dockerfile После сборки образ не нужен.
db_archiver/requirements.txt Дополнительные python пакеты. После сборки образа не нужен.
docker-compose.yml

Состав файлов

.env 

VOTE_DB_HOST=
VOTE_DB_PORT=
VOTE_DB_USER=
VOTE_DB_PASS=
VOTE_DB_NAME=

ARCHIVE_SERVER_IP=
ARCHIVE_SERVER_LOGIN=
ARCHIVE_SERVER_PASSWORD=
ARCHIVE_SERVER_DIR=

ZABBIX_SERVER_IP=

db_archiver.py 

import subprocess
import os
from datetime import datetime
import logging
from dotenv import load_dotenv
import paramiko
from zabbix_utils import Sender
import time

# Константы и лог
LOCAL_ARCH_DIR = "votes_db_arch"

logging.basicConfig(level=logging.INFO, filename="votes_db_archive.log",filemode="w",
                    format="%(asctime)s %(levelname)s %(message)s")

# Загружаем переменные из .env
load_dotenv()

# Читаем переменные
host = os.getenv("VOTE_DB_HOST")
port = os.getenv("VOTE_DB_PORT", "5432")
user = os.getenv("VOTE_DB_USER")
password = os.getenv("VOTE_DB_PASS")
dbname = os.getenv("VOTE_DB_NAME")

arch_server = os.getenv("ARCHIVE_SERVER_IP")
arch_username = os.getenv("ARCHIVE_SERVER_LOGIN")
arch_password = os.getenv("ARCHIVE_SERVER_PASSWORD")
arch_dir = os.getenv("ARCHIVE_SERVER_DIR")

zabbix_ip = os.getenv("ZABBIX_SERVER_IP")

def create_file_name() -> str:
    timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
    return f"{LOCAL_ARCH_DIR}/{timestamp}_backup.sql", f"{arch_dir}/{timestamp}_backup.sql"

def create_backup(fname):
    command = [
        "pg_dump",
        "-h", host,
        "-p", port,
        "-U", user,
        "-d", dbname,
        "-F", "p",
    ]
    # Подмешиваем пароль в окружение
    env = {
        **os.environ,
        "PGPASSWORD": password
    }
    is_ok = True
    try:
        with open(fname, "w", encoding="utf-8") as f:
            subprocess.run(command, env=env, stdout=f, check=True)
        
    except Exception as e:
        is_ok = False
        logging.error(f"Ошибка в создании бэкапа: {e}")
    return is_ok

def remove_transaction_timeout(outputfile) -> bool:
    """
    Удаляет строки 'SET transaction_timeout = 0;' из указанного файла.
    """
    is_ok = True
    try:
        # Читаем файл построчно
        with open(outputfile, "r", encoding="utf-8") as f:
            lines = f.readlines()

        # Фильтруем лишние строки
        cleaned_lines = [line for line in lines if line.strip() != "SET transaction_timeout = 0;"]

        # Перезаписываем файл
        with open(outputfile, "w", encoding="utf-8") as f:
            f.writelines(cleaned_lines)
    except Exception as e:
        is_ok = False
        logging.error(f"Ошибка в удалении строк: {e}")
    return is_ok

def send_file_via_ssh(outputfile, remote_path):
    is_ok = True
    try:
        # Подключаемся по SSH
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())  # автоматическое добавление ключа
        ssh.connect(arch_server, port=22, username=arch_username, password=arch_password)

        # Используем SFTP для передачи файла
        sftp = ssh.open_sftp()
        sftp.put(outputfile, remote_path)
        sftp.close()

        ssh.close()
        logging.info(f"Бэкап успешно отправлен на {arch_server}:{remote_path}")
    except Exception as e:
        is_ok = False
        logging.error(f'Ошибка при отправке файла по SSH: {e}')
    return is_ok

def send_to_zabbix(local_output_file):
    sender = Sender(server=zabbix_ip, port=10051)
    d = datetime.now()
    unix_time = int(time.mktime(d.timetuple()))
    # Parameters: (host, key, value, clock)
    resp = sender.send_value('', 'filename', local_output_file, unix_time)
    if resp.failed == 0:
        # Print a success message along with the response time
        logging.info(f"Value sent successfully in {resp.time}")
    else:
        # Print a failure message
        logging.info("Failed to send value")
        logging.info(resp.details)

if __name__ == "__main__":
    local_output_file, remote_output_file = create_file_name()
    if not create_backup(local_output_file):
        raise SystemExit()
    if not remove_transaction_timeout(local_output_file):
        raise SystemExit()
    if not send_file_via_ssh(local_output_file, remote_output_file):
        raise SystemExit()
    if not send_to_zabbix(local_output_file):
        raise SystemExit()

cron_job 

PATH=/usr/local/bin:/usr/bin:/bin
*/2 * * * * root cd /app && python3 db_archiver.py >> /var/log/cron.log 2>&1

* обязательно последняя пустая строка!

requirements.txt 

dotenv
paramiko
zabbix-utils

Dockerfile

FROM python:3.11-slim-bookworm

WORKDIR /app
# Устанавливаем зависимости и zabbix-utils
RUN apt-get update && \ 
    apt-get install -y --no-install-recommends postgresql-client postgresql-client-common libpq-dev cron && \
    rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# Копируем скрипт и настройки cron
COPY cron_job /etc/cron.d/zabbix-cron
# Даем права на выполнение cron-файла
RUN chmod 0644 /etc/cron.d/zabbix-cron && \
    touch /var/log/cron.log
CMD ["cron", "-f"]

Сборка образа:

docker build -t db-archiver:0.3.0 .

Сохранение в репозитории
docker login https://hub.bobrobotirk.ru
docker tag db-archiver:0.3.0 hub.bobrobotirk.ru/db-archiver:0.3.0
docker push hub.bobrobotirk.ru/db-archiver:0.3.0

Создание контейнера:

docker-compose.yml 

db_archiver:
    image: hub.bobrobotirk.ru/db-archiver:0.3.0
    container_name: votes_archiver
    restart: unless-stopped  # Автоперезапуск при ошибках
    volumes:
      - ./db_archiver/app:/app

Теперь нужно настроить zabbix trapper и вывести его на дашборд. Все!