Nodebackup — сохранение данных из контейнеров (докер) и так-же остальных

в 9:30, , рубрики: backups, devops, docker, резервное копирование, Серверное администрирование

Nodebackup — сохранение данных из контейнеров (докер) и так-же остальных - 1

Это рассказ про самописнный велосипед, которым я постарался выполнить главный завет системного администратора — чтобы всё работало, но я ничего не делал. )

Некоторое вступление

У нас на фирме уже несколько лет (два года точно) идёт переход всего на линуск, точнее на контейнеры. Сама программа, которая разрабатывается на фирме тоже может работать под линуксом, поэтому проблем особых нет и путь был предопределён.

В принципе у нас остались только AD, FileServer и некоторое количество других серверов для тестов.

Ещё в начале когда появились контейнеры встал вопрос как их сохранять, тогда были какие-то скрипты, но надо было много делать руками и с увеличением количества контейнеров стало это всё надоедать. Поэтому я написал программу, чтобы можно было сохранять контейнеры, данные с серверов (виндовс и линукс). Это была моя первая программа, которую я написал в node.js и она была соответственно хороша, но работала. Через несколько месяцев я нашел ошибку, но не смог её исправить и так как я уже немного подучил node.js, то было решено всё переписать на async/await и так появилась вторая версия, про которою я и хочу написать.

Реализация

Как я уже написал выше, nodebackup может сохранять только данные. Под капотом там работает duplicity. Nodebackup только подготавливает данные, чтобы их можно было скормить duplicity.

Как это работает

Картинка выше хорошо всё описывает (я надеюсь — сам делал )) ), но я повторю словами.
Есть четыре компонента — starter, exec, client, server, причём они все могут находиться на одном, либо на разных серверах, клиентов тоже может быть много.

Starter — это собственно nodebackup, можно ставить везде, где есть node.js. У меня он работает в контейнере ( я про это потом напишу)
Exec — starter соединяется с этим сервером и уже с него выполняет все команды. Сделано для случая, если надо к примеру делать сохранения из интернета в сеть, тогда можно поставить эту часть в DMZ, у нас он крутиться на бэкап сервере.
Client — собственно это сервер с которого снимаются сохранения, это может быть linux, windows либо докер хост.
Server — это тот самый куда сохраняются данные.

На Starter'е лежит конфигурация, которая говорит, что такой-то Server, делает сохранения для тех-то клиентов и в конфигурации для Client'a уже прописано, что с ним делать. Если Client это докер хост, то тогда читаются Labels от каждого контейнера, в котором написано, что с этим контейнером делать.

Конфигурация

Nodebackup — сохранение данных из контейнеров (докер) и так-же остальных - 2

Итак, у нас есть сервер где установлен nodebackup (это starter), для этого надо просто установить node.js > v6.5 и к примеру три сервера — windows fileserver(winserver), linux(freepbx), docker host(dockerhost1) и всё это сохраняется на backup2.

backup2 — выполняет роли Exec/Server и для работы на него надо установить cifs, duplicy, sshfs. Cifs нужен для того чтобы монтировать smb, sshfs для монтирования данных от freepbx сервера ну и duplicity, чтобы это всё сохранять.

Также на клиенты нужен вход по ssh ключу в случае linux или докер хоста.

Собственно это и есть вся настройка на серверах, теперь нужно создать конфигурацию для nodebackup, чтобы он знал что ему делать.

Итак, создаём файл config.yaml, должен находится в папке nodebackup.

Сперва часть для nodebackup

starter:
    sshkey: id_rsa
    log:
      path: log

id_rsa — это ssh ключ, в данном случае лежит в той-же папке.

Дальше часть для бэкап сервера (в данном случае это backup2)

backupserver:
  backup2:
    passphrase: PASSPHRASE-FOR-DUPLICITY
    HOST: backup2.example.com
    USER: admin2
    PORT: '22'
    prerun: '["echo prerun1", "echo prerun2", "echo prerun3"]'
    #postrun: '["echo postrun1","echo postrun2","echo postrun3"]'
    backuppath: /backup
    duplicityarchiv: /backup/duplicityarchiv
    backuppartsize: 2048
    tmpdir: /backup/backuptemp
    backupfor: '["winserver", "freepbx", "dockerhost1"]'

passphrare — пароль для сохранения, он используется для всех клиентов, которые сохраняются на сервер
prerun, postrun — команды которые соответственно исполняются до и после бэкапа
backuppath — место где хранятся бэкапы.
duplicityarchiv — опционально, duplicity сохраняет для себя данные, их довольно много, если не выставить опцию то это всё хранится в /home/user/.config/duplicity
backuppartsize — бэкап разбивается на куски по 2048Мб, здесь можно что-то своё написать.
backupfor — тут пишутся названия клиентов, конфигурация для них будет описана ниже.

Теперь часть для клиентов, сперва winserver

clientserver:
  backup2:
    HOST: backup2.example.com
    USER: admin2
    PORT: '22'
    sftpServer:
      sudo: true
      path: /usr/lib/sftp-server
  winserver:
    HOST: "windows.example.com"
    SMB:
      SET: "true"
      PATH: "d$"
      PASS: "userpass"
      DOMAIN: "example.com"
    USER: "backupservice"
    PORT: 22
    backup: /cygdrive/d
    include: '["usershare", "publicshare", "Geschäftsleitung"]'
    exclude: '["publicshare/Softwarepool", "**"]'
    confprefixes: '["asynchronous-upload", "allow-source-mismatch"]'

Тут есть некоторая ошибка, так как программа писалась, в основном для линукса, то тут остались некоторые артефакты из конфигурации, которые всё равно нельзя стирать — это PORT, backup.
SMB — это собственно настройки для самбы, как это будет работать с сервером если он не в домене я не знаю
include, exclude — здесь берётся путь из SMB/PATH и к нему приделывается include и соответсвенно убирается exclude
confprefixes — здесь можно добавить опции из duplicity, это всё есть в документации к нему.

Добавляем сервер с линуксом, где надо сохранить только папку, ну или несколько.

  freepbx:
    HOST: "freepbx.example.com"
    USER: root
    PORT: '22'
    nextfullbackup: 1M
    noffullbackup: 2
    backup: /var/spool/asterisk/backup/taeglich
    prerun: '["mv /var/spool/asterisk/backup/taeglich/*.tgz /var/spool/asterisk/backup/taeglich/backup.tgz"]'
    postrun: '["rm -Rf /var/spool/asterisk/backup/taeglich/backup.tgz"]'
    confprefixes: '["asynchronous-upload"]'

nextfullbackup — говорит сколько можно сделать инкрементальных бэкапом, прежде чем будет сделан новый полный бэкап.
noffullbackup — количество бэкапов (number of fullbackups)
prerun,postrun — команды которые выполняются до и после бэкапа

Теперь самая важная часть — бэкап контейнеров
В начала конфигурация из config.yaml

  dockerhost1:
    HOST: dockerHostN010912m.example.local
    USER: docker
    PORT: 22
    docker: 'true'
    sftpServer:
      sudo: true
      path: /usr/lib/sftp-server

docker — true говорит, что это докер хост
sftpServer — если у вас нету root'a, но есть пользователь с sudo (без пароля), то нужно это прописть, так это выглядит в ubuntu

После того, как программа видит, что это докер хост, он считывает из docker.sock(/var/run/docker.sock) информацию о контейнерах и смотрит у каждого его Labels, если он находит нужные, то тогда делается бэкап.
Вот это часть из docker-compose.yaml, в которой прописаны лэйблы, они похоже на конфигурацию из части про клиентов

  labels:
    prerun: '["echoc prerun1", "echo prerun2"]'
    postrun: '["echo postrun1","echo postrun2"]'
    nextfullbackup: "1M"
    noffullbackup: "2"
    backup: "/a/data/backup-new-test"
    strategy: "off"
    confprefixes: '["allow-source-mismatch"]'
    name: "backup-new-test"
    processes: '["httpd"]'
    failovercustom: "echo 'failover ####################'"
    passphrase: "+++++containerPASS#######"

prerun,postrun — они выполняются не во контейнере, а на докер хосте.
strategy — это опция описывает, надо ли для бэкапа выключать контейнер или нет. ВАЖНО: При этом будут выключатся так-же слинковынные контейнеры.
name — опционально, это будет имя папки на бэкап сервере, если тут ничего нет, то тогда просто берётся имя контейнера.
processes — это из темы мониторинга и попал сюда случайно.
failovercustom — опционально, если во время бэкапа произошла ошибка, то тогда выполняется эта команда, если этой опции нет, то тогда просто "docker start CONTAINER+LINKS"
passphrase — можно прописывать для каждого контейнера отдельно, либо глобально для всего докер хоста. Приоритет — контейнер, потом докер хост.

config.yaml

starter:
    sshkey: id_rsa
    log:
      path: log
backupserver:
  backup2:
    passphrase: PASSPHRASE-FOR-DUPLICITY
    HOST: backup2.example.com
    USER: admin2
    PORT: '22'
    prerun: '["echo prerun1", "echo prerun2", "echo prerun3"]'
    #postrun: '["echo postrun1","echo postrun2","echo postrun3"]'
    backuppath: /backup
    duplicityarchiv: /backup/duplicityarchiv
    backuppartsize: 2048
    tmpdir: /backup/backuptemp
    backupfor: '["winserver", "freepbx", "dockerhost1"]'
clientserver:
  backup2:
    HOST: backup2.example.com
    USER: admin2
    PORT: '22'
    sftpServer:
      sudo: true
      path: /usr/lib/sftp-server
  server2:
    HOST: "windows.example.com"
    SMB:
      SET: "true"
      PATH: "d$"
      PASS: "userpass"
      DOMAIN: "example.com"
    USER: "backupservice"
    PORT: 22
    backup: /cygdrive/d
    include: '["usershare", "publicshare", "Geschäftsleitung"]'
    exclude: '["publicshare/Softwarepool", "**"]'
    confprefixes: '["asynchronous-upload", "allow-source-mismatch"]'
  freepbx:
    HOST: "freepbx.example.com"
    USER: root
    PORT: '22'
    nextfullbackup: 1M
    noffullbackup: 2
    backup: /var/spool/asterisk/backup/taeglich
    prerun: '["mv /var/spool/asterisk/backup/taeglich/*.tgz /var/spool/asterisk/backup/taeglich/backup.tgz"]'
    postrun: '["rm -Rf /var/spool/asterisk/backup/taeglich/backup.tgz"]'
    confprefixes: '["asynchronous-upload"]'
  dockerhost1:
    HOST: dockerHostN010912m.example.local
    USER: docker
    PORT: 22
    docker: 'true'
    sftpServer:
      sudo: true
      path: /usr/lib/sftp-server

В принципе правильнo выставляя настройки можно сделать много разных вещей
Ещё один пример для контейнера. У нас есть один контейнер с большой БД, где не хотелось бы ждать пока будет скопированна база данных, чтобы было быстро, мы выключаем контейнер делаем снапшот, включаем контейнер, делаем не спеша бэкап со снапшота и стираем его потом, вот конфигурация для контейнера

    labels:
      nextfullbackup: "1M"
      noffullbackup: "2"
      backup: "/cephrbd/mariadb-intern-prod/snapshot"
      prerun: '["sudo docker stop adito4internproddb_mariadb-dev_1 adito4internproddb_mariadb_1","sudo btrfs subvolume snapshot -r /cephrbd/mariadb-intern-prod /cephrbd/mariadb-intern-prod/snapshot","sudo docker start adito4internproddb_mariadb-dev_1 adito4internproddb_mariadb_1"]'
      postrun: '["sudo btrfs subvolume delete /cephrbd/mariadb-intern-prod/snapshot"]'
      strategy: "on"

Также разумеется может быть, что надо сохранить папку с сервера и этот же сервер является докер хостом, то это тоже работает.

В одной config.yaml может быть много бэкап серверов и также много клиентов. У нас есть к примеру несколько серверов у Hetzner и они все тоже прописаны в config.yaml

Старт

Теперь когда у нас есть config.yaml можно сделать бэкап.
Список команд

/nodebackup # node BackupExecV3.js -h

  Usage: BackupExecV3 [options]

  Options:

    -h, --help                       output usage information
    -V, --version                    output the version number
    -b, --backup                     start backup
    -e, --exec [exec]                server, which start backup job
    -t, --target [target]            single server for backup
    -s, --server [server]            backup server
    -d, --debug                      debug to console
    -f, --debugfile                  debug to file
    -r, --restore                    starte restore
    -c, --pathcustom [pathcustom]    path to restore data (custom path)
    -o, --overwrite                  overwrite existing folder by recovery
    -p, --pathdefault [pathdefault]  default path from config.yml
    -m, --time [time]                backup from [time]
    -i, --verify                     verify all backups on a backup server

  Info:
   Read README.md for more info

  Backup:
    Backup: sudo ./BackupExecV3 -b -e backup2 -s backup2
    Single backup: sudo ./BackupExecV3 -b -e backup2 -s backup2 -t freepbx

  Recovery:
    Backup to original path: sudo ./BackupExecV3.js -r -t backup-new-test -s backup2 -p freepbx -o
    Backup to custom path: sudo ./BackupExecV3.js -r -t backup-new-test -s backup2 -c freepbx:/tmp/newFolder
    Backup to custom path: sudo ./BackupExecV3.js -r -t backup-new-test -s backup2 -c freepbx:/tmp/newFolder -o -m (2D|1W|10s|50m)

Пример 1

Делаем бэкап:

node BackupExecV3.js -b -e backup2 -s backup2 -d/f

Программа смотри в config.yaml и определяет, какие сервера должны быть сохранены. После чего делается бэкап.

Пример 2

Делаем бэкап отдельного сервера или контейнера

node BackupExecV3.js -b -e backup -s bacup2 -t CONTAINERNAME -d/f

Ещё немного упрощаем

Чтобы в ручную не устанавливать nodebackup, я сделал образ для докер (тут)

Этому контейнеру надо скормить три файла — config.yaml, crontab.tmp и ssh ключь
docker-compose.yaml

nodebackup:
  image: adito/nodebackup
  hostname: nodebackup
  environment:
   - SSMTP_SENDER_ADDRESS=no-reply@example.com
   - SSMTP_MAIL_SERVER=mail.example.com
   - SSMTP_HOST=nodebackup.example.com
  volumes:
    - ./config/config.yml:/nodebackup/config.yml:ro
    - ./config/id_rsa:/nodebackup/id_rsa:ro
    - ./config/crontab.tmp:/crontab.tmp:ro
    - /etc/timezone:/etc/timezone:ro
    - /etc/localtime:/etc/localtime:ro
    - /a/data/nodebackup/log:/a/data/log
  restart: always

сrontab.tmp


MAILTO=admin@example.com
NODE_PATH=/usr/lib/nodejs:/usr/lib/node_modules:/usr/share/javascript

# m  h  dom mon dow   command
  0  22  *   *   *    node /nodebackup/BackupExecV3.js -b -s backup2 -e backup2 -f
  0  22  *   *   *    node /nodebackup/BackupExecV3.js -b -s hetznerdocker -e hetznerdocker -f
  0  22  *   *   *    node /nodebackup/BackupExecV3.js -b -s hetznerdocker2 -e hetznerdocker2 -f
  0  22  *   *   *    node /nodebackup/BackupExecV3.js -b -s hetznerdocker3 -e hetznerdocker3 -f
  0  22  *   *   *    node /nodebackup/BackupExecV3.js -b -s hetznerdocker4 -e hetznerdocker4 -f
  0  22  *   *   *    node /nodebackup/BackupExecV3.js -b -s hetznerdocker5 -e hetznerdocker5 -f

Заключение

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

В readme на гитхабе написаны другие примеры для востановления и проверки, но я их тут не стал приводить, так как там всё похоже, главное чтобы была создана правильная концигурация — config.yaml.

Писал я это собственно по двум причинам.

  1. Мы потихоньку подумываем перейти на kubernetes и там по моему мнению это будет неправильное решение. Может быть кто-то подскажет в комментариях правильное решение.
  2. Я это всё довольно долго делал и писал и хотелось бы чтобы этим пользовались другие люди.

Ссылки

  1. Nodebackup
  2. Nodebackup контейнер
  3. Duplicity

Автор: de1m

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js