Архивация и отправка архивов
Задача: есть некритичный ко времени сервис с небольшим объемом данных. Необходимо настроить отправку резервных файлов раз в 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 и вывести его на дашборд. Все!
No Comments