216 March 29, 2024, 1:42 p.m.

Скрипт для автоматического резервного копирования

Задача: реализовать резервное копирование в S3 исходя из типов данных с помощью ansible

Для достижения цели нужно:

  • mc client(minio)
  • наличие пакета cron в системе
  • наличие созданных бакетов и настроенными правами доступа

Ручной метод

Для реализации первых двух пунктов предлагаю использовать в качестве примера ansible роль, которая автоматически установит пакет mc client на любую архитектуру.

Также продублирую код для установки пакета:

- name    : Check arm architecture
  set_fact:
    DEFAULT_ARCHITECTURE: arm64
  when    : ("'armv7l' in ansible_architecture") or ("'aarch64' in ansible_architecture")

- name    : Check amd64 architecture
  set_fact:
    DEFAULT_ARCHITECTURE: amd64
  when    : "'x86_64' in ansible_architecture"

- name: Debug
  debug:
    msg: "{{ ansible_architecture }}"

- name: Install depends
  ansible.builtin.apt:
    name:
      - cron
    state: latest
    update_cache: true

- name: Download MinIO Client
  get_url:
    url: https://dl.min.io/client/mc/release/linux-{{ DEFAULT_ARCHITECTURE }}/mc
    dest: /usr/local/bin/mc
    mode: '0755'

- name: Create a symbolic link
  ansible.builtin.file:
    src: /usr/local/bin/mc
    dest: /usr/bin/mc
    state: link
    mode: 0755

- name: Check version MinIO Client
  command: "mc --version"
  register: mc_version
  changed_when: false

- name: Output version MinIO Client
  debug:
    msg: "{{ mc_version.stdout }}"

Настраиваем alias для подключения к бакету:

mc alias set myminio https://myminio.example.net minioadminuser minioadminpassword

Создаем bash + python скрипт, в дириктории root (можно в любом другом месте с минимальнонеобходимыми правами):

#!/bin/bash

echo "НАЗВАНИЕ НОДЫ backup result" > /var/log/synk.log
echo "" >> /var/log/synk.log

mc cp --recursive ~/mydata/ myminio/mydata/
if [ $? -ne 0 ]; then
  echo "ERROR" >> /var/log/synk.log
else
  echo "SUCCESS" >> /var/log/synk.log
fi

python3 sendemail.py /var/log/synk.log
rm /var/log/synk.log

Данный скрипт, делает копию определенной дириктории, заносит результат выполнения в файл /var/log/synk.log после чего запускает python в который передается путь данного фала.

sendemail.py

import requests
import sys
import smtplib, ssl
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

file_path = sys.argv[1]
with open(file_path, 'r') as file:
    message_content = file.read()

token = '<TELEGRAM TOKEN>'
chat_id = '<CHAT ID>'
url = f'https://api.telegram.org/bot{token}/sendMessage'

# Отправка сообщения
data = {'chat_id': chat_id, 'text': message_content}
response = requests.post(url, data=data)

Данный скрипт пересылает содержание файла в телеграм бота. Также можно использовать EMAIL:

# Отправка сообщения
data = {'chat_id': chat_id, 'text': message_content}
response = requests.post(url, data=data)

message = MIMEMultipart("alternative")
message["Subject"] = "Backup status"
message["From"] = "[email protected]"
message["To"] = "[email protected]"

part1 = MIMEText(message_content, "plain")
message.attach(part1)

context = ssl.create_default_context()
with smtplib.SMTP("smtp.example.com", "587") as server:
    server.ehlo()  # Can be omitted
    server.starttls(context=context)
    server.ehlo()  # Can be omitted
    server.login("[email protected]", "my_super_pass")
    server.sendmail("[email protected]", "[email protected]", message.as_string())

Вносим скприпт в crontab:

0 3 * * * /root/bucket-sync.sh

Теперь каждый день в 3 часа ночи будет совершаться резервное копирование данных.


Метод через Ansible

Файл инвентори:

ssh.example.com    SERVERNAME=example MC_NODE=true

Файл group_vars:

MC_SYNCPAIR:
  - {name: "<название БД>",   src: "<адрес до БД, нужен pgclient>",  dest: "<путь назначения используя mc синтаксис>", target: <Должно совпадать с SERVERNAME>,  type: "pgsql", state: present} 
#!/bin/bash

echo "{{ SERVERNAME }}-node backup result" > /var/log/synk.log
echo "" >> /var/log/synk.log

{% for chain in MC_SYNCPAIR %}
{% if chain.target == SERVERNAME %}
{% if chain.state == "present" %}
{% if chain.type == "tar" %}
tar cz {{ chain.src }} | mc pipe {{ chain.dest }}/backup-{{ chain.name }}-$(date +%Y-%m-%d).tar.gz
if [ $? -ne 0 ]; then
  echo "Synk error {{ chain.name }}" >> /var/log/synk.log
else
  echo "Sync success {{ chain.name }}" >> /var/log/synk.log
fi
{% elif chain.type == "sync" %}
mc mirror --remove --overwrite {{ chain.src }} {{ chain.dest }}
if [ $? -ne 0 ]; then
  echo "Synk error {{ chain.name }}" >> /var/log/synk.log
else
  echo "Sync success {{ chain.name }}" >> /var/log/synk.log
fi
{% elif chain.type == "file" %}
mc cp {{ chain.src }} {{ chain.dest }}/file-{{ chain.name }}-$(date +%Y-%m-%d)
if [ $? -ne 0 ]; then
  echo "Synk error {{ chain.name }}" >> /var/log/synk.log
else
  echo "Sync success {{ chain.name }}" >> /var/log/synk.log
fi
{% elif chain.type == "pgsql" %}
export PGPASSWORD='{{ DOCKER_COMPOSE_DB_MASTER_PASS }}' 
pg_dump -U {{ DOCKER_COMPOSE_DB_MASTER_USER }} -d {{ chain.name }} -h {{ chain.src }} > /tmp/database-{{ chain.name }}-$(date +%Y-%m-%d).pgsql.sql # | mc pipe {{ chain.dest }}/backup-{{ chain.name }}-$(date +%Y-%m-%d).pgsql.sql
mc mv /tmp/database-{{ chain.name }}-$(date +%Y-%m-%d).pgsql.sql {{ chain.dest }}/
if [ $? -ne 0 ]; then
  echo "Synk error {{ chain.name }}" >> /var/log/synk.log
else
  echo "Sync success {{ chain.name }}" >> /var/log/synk.log
fi
{% endif %}
{% endif %}
{% endif %}
{% endfor %}

python3 sendemail.py /var/log/synk.log
rm /var/log/synk.log
import requests
import sys
import smtplib, ssl
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

file_path = sys.argv[1]
with open(file_path, 'r') as file:
    message_content = file.read()

token = '{{ TELEGRAM_BOT_TOKEN }}'
chat_id = '{{ TELEGRAM_ADMIN_ID }}'
url = f'https://api.telegram.org/bot{token}/sendMessage'

# Отправка сообщения
data = {'chat_id': chat_id, 'text': message_content}
response = requests.post(url, data=data)

# message = MIMEMultipart("alternative")
# message["Subject"] = "Backup status"
# message["From"] = "{{ SERVERNAME }}@{{ SENDEMAIL_DOMAIN }}"
# message["To"] = "{{ SENDEMAIL_RECEIVER }}"

# part1 = MIMEText(message_content, "plain")
# message.attach(part1)

# context = ssl.create_default_context()
# with smtplib.SMTP("{{ SENDEMAIL_HOST }}", "{{ SENDEMAIL_PORT }}") as server:
#     server.ehlo()  # Can be omitted
#     server.starttls(context=context)
#     server.ehlo()  # Can be omitted
#     server.login("{{ SENDEMAIL_LOGIN }}", "{{ SENDEMAIL_PASSWORD }}")
#     server.sendmail("{{ SENDEMAIL_LOGIN }}", "{{ SENDEMAIL_RECEIVER }}", message.as_string())

И всё это дело теперь развертываем

  tasks:
    - name: Install cron
      ansible.builtin.apt:
        name:
          - cron
        state: latest
        update_cache: true

    - name: Create scrypt for sync buckets
      template:
        src : ./files/configs/scripts/bucket-sync.sh.j2
        dest: "/root/bucket-sync.sh"
        mode: 0500

    - name: Copy sent email script
      template:
        src : ./files/configs/scripts/sendemail.py
        dest: "/root/sendemail.py"
        mode: 0500

    - name: Schedule script execution
      ansible.builtin.cron:
        name: "Run my script"
        hour: "3"
        minute: "0"
        job: /root/bucket-sync.sh
        state: present

Данный метод в зависимости от типа в переменной type будет создавать разные части кода для резервного копирования.