Application: "Bacula 5.2/7.4/9.0", "clickhouse-client".
Задача: наладить выгрузку резервной копии "баз данных" колоночной (column-oriented) СУБД "(Yandex) Clickhouse" посредством "Bacula".
Резервное копирование содержимого "баз данных" прямым копированием из файлов как правило невозможно и требуется предварительная выгрузка консистентного "дампа" в заранее известную директорию. Сделаем это, воспользовавшись встроенной возможностью "Bacula" исполнения произвольных скриптов на стороне клиента до и после процедуры непосредственного резервного копирования.
Для резервного копирования БД "Clickhouse" большого объёма, от сотен гигабайт до петабайт, лучше воспользоваться специализированными инструментами "clickhouse-copier" или "clickhouse-backup". В случае скромных размеров до сотни гигабайт вполне достаточно выгрузки конфигурации и данных в текстовых форматах SQL и TSV.
Предварительная настройка СУБД.
Подготавливать к выгрузке SQL-дампа сервер СУБД не требуется. Нужен только доступ от имени обладающего достаточными привилегиями пользователя.
СУБД "Clickouse" активно развивается, и в репозиториях несущих операционных систем наверняка будет сильно устаревшая, относительно резервируемого сервера, версия клиента. Лучше подключить репозиторий разработчиков СУБД и загрузить свежее программное обеспечение оттуда:
# apt-get install -y apt-transport-https ca-certificates dirmngr
# apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv E0C56BD4
# echo -e "# Official APT-repository (Yandex) Clickhouse\ndeb [arch=amd64] https://repo.clickhouse.tech/deb/stable/ main/" >> /etc/apt/sources.list.d/clickhouse.list
# apt-get update && apt-get install -y clickhouse-client pigz
# apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv E0C56BD4
# echo -e "# Official APT-repository (Yandex) Clickhouse\ndeb [arch=amd64] https://repo.clickhouse.tech/deb/stable/ main/" >> /etc/apt/sources.list.d/clickhouse.list
# apt-get update && apt-get install -y clickhouse-client pigz
Предварительная настройка клиента "Clickhouse".
Для упрощения дальнейших процедур опишем параметры подключения к целевой СУБД в конфигурационном файле:
# vi /root/.clickhouse-client.xml
<config>
<user>default</user>
<password></password>
<host>127.0.0.1</host>
<port>9000</port>
<secure>False</secure>
</config>
<user>default</user>
<password></password>
<host>127.0.0.1</host>
<port>9000</port>
<secure>False</secure>
</config>
Защитим файл, содержащий секретные сведения, от доступа посторонних:
# chmod go-rwx /root/.clickhouse-client.xml
Проверим, возможно ли подключение к СУБД с использованием нашего файла конфигурации:
# clickhouse-client -q "SELECT 1"
Скрипт резервного копирования БД "Clickhouse".
Немного странно, но официальный арсенал инструментов "Clickhouse" не позволяет выгружать одной командой содержимое сразу всей БД - это можно делать только применительно к каждой таблице по отдельности. Для реализации недостающего функционала мною был написан простой bash-скрипт, запрашивающий список БД, запрашивающий список таблиц этих БД, и выгружающий отдельно структуру таблиц, а также данные как таковые в виде текстовых файлов:
# vi /usr/local/bin/clickhouse-backup-tsv.sh && chmod +x /usr/local/bin/clickhouse-backup-tsv.sh
#!/bin/bash
# Author: Narozhniy Andrey, 2021, NSU
# Usage: /usr/local/bin/clickhouse-backup-tsv.sh /output/backup/dir
# Checking existence configuration with connection parameters
[ ! -f "/root/.clickhouse-client.xml" ] && { echo "Configuration file missing. Backup aborted."; exit 1; }
CONFIG="--config-file /root/.clickhouse-client.xml"
# Defining a directory for uploading backups
OUTDIR=${1:-'./'}
mkdir -p "${OUTDIR}"
[ ! -z "$(ls -A ${OUTDIR})" ] && { echo "Output directory not empty. Backup aborted."; exit 1; }
# Query and iterating over databases
while read -r DB ; do
# Skipping system database
[ "${DB}" == "system" ] && { echo "Skip \"system\" database."; continue; }
# Preparing directories for data
mkdir -p "${OUTDIR}/${DB}"
# Query and iterating over tables from database
while read -r TABLE ; do
# Skipping table views
[[ "${TABLE}" == ".inner."* ]] && { echo "Skip materialized view \"${TABLE}\" (${DB})."; continue; }
echo "Export table \"${TABLE}\" from database \"${DB}\"..."
# Querying the table structure as SQL
clickhouse-client ${CONFIG} -q "SHOW CREATE TABLE ${DB}.${TABLE}" > "${OUTDIR}/${DB}/${TABLE}_schema.sql"
# Dump table data as TSV (tab separated values)
clickhouse-client ${CONFIG} -q "SELECT * FROM ${DB}.${TABLE} FORMAT TabSeparated" | pigz > "${OUTDIR}/${DB}/${TABLE}_data.tsv.gz"
done < <(clickhouse-client ${CONFIG} -q "SHOW TABLES FROM ${DB}")
done < <(clickhouse-client ${CONFIG} -q "SHOW DATABASES")
exit $?
# Author: Narozhniy Andrey, 2021, NSU
# Usage: /usr/local/bin/clickhouse-backup-tsv.sh /output/backup/dir
# Checking existence configuration with connection parameters
[ ! -f "/root/.clickhouse-client.xml" ] && { echo "Configuration file missing. Backup aborted."; exit 1; }
CONFIG="--config-file /root/.clickhouse-client.xml"
# Defining a directory for uploading backups
OUTDIR=${1:-'./'}
mkdir -p "${OUTDIR}"
[ ! -z "$(ls -A ${OUTDIR})" ] && { echo "Output directory not empty. Backup aborted."; exit 1; }
# Query and iterating over databases
while read -r DB ; do
# Skipping system database
[ "${DB}" == "system" ] && { echo "Skip \"system\" database."; continue; }
# Preparing directories for data
mkdir -p "${OUTDIR}/${DB}"
# Query and iterating over tables from database
while read -r TABLE ; do
# Skipping table views
[[ "${TABLE}" == ".inner."* ]] && { echo "Skip materialized view \"${TABLE}\" (${DB})."; continue; }
echo "Export table \"${TABLE}\" from database \"${DB}\"..."
# Querying the table structure as SQL
clickhouse-client ${CONFIG} -q "SHOW CREATE TABLE ${DB}.${TABLE}" > "${OUTDIR}/${DB}/${TABLE}_schema.sql"
# Dump table data as TSV (tab separated values)
clickhouse-client ${CONFIG} -q "SELECT * FROM ${DB}.${TABLE} FORMAT TabSeparated" | pigz > "${OUTDIR}/${DB}/${TABLE}_data.tsv.gz"
done < <(clickhouse-client ${CONFIG} -q "SHOW TABLES FROM ${DB}")
done < <(clickhouse-client ${CONFIG} -q "SHOW DATABASES")
exit $?
Конфигурирование "Bacula".
Дополняем описание задания резервного копирования сервиса "Clickhouse" следующими блоками конфигурации:
# vi /etc/bacula/client.d/example.net.conf
....
FileSet {
....
Include {
....
# Директория резервных копий "дампов баз данных"
File = "/var/backups/bacula-clickhouse"
}
}
....
Job {
Name = "example.net"
Type = Backup
....
# Запуск выгрузки резервной копии всех локальных "баз данных" Clickhouse в формате TSV:
RunScript {
RunsWhen = Before
FailJobOnError = No
# Зачищаем и воссоздаём место для сохранения "дампа"
Command = "rm -rf /var/backups/bacula-clickhouse"
Command = "mkdir -p /var/backups/bacula-clickhouse"
# Запускаем выгрузку резервной копии БД:
# (с парольной аутентификацией, из типового месторасположения такового)
Command = "/bin/bash -c '/usr/local/bin/clickhouse-backup-tsv.sh /var/backups/bacula-clickhouse 1>/var/log/bacula-clickhouse.log 2>&1'"
}
#
RunScript {
RunsWhen = After
RunsOnFailure = yes
# По завершению всех процедур задания высвобождаем ресурсы
Command = "rm -rf /var/backups/bacula-clickhouse"
}
}
FileSet {
....
Include {
....
# Директория резервных копий "дампов баз данных"
File = "/var/backups/bacula-clickhouse"
}
}
....
Job {
Name = "example.net"
Type = Backup
....
# Запуск выгрузки резервной копии всех локальных "баз данных" Clickhouse в формате TSV:
RunScript {
RunsWhen = Before
FailJobOnError = No
# Зачищаем и воссоздаём место для сохранения "дампа"
Command = "rm -rf /var/backups/bacula-clickhouse"
Command = "mkdir -p /var/backups/bacula-clickhouse"
# Запускаем выгрузку резервной копии БД:
# (с парольной аутентификацией, из типового месторасположения такового)
Command = "/bin/bash -c '/usr/local/bin/clickhouse-backup-tsv.sh /var/backups/bacula-clickhouse 1>/var/log/bacula-clickhouse.log 2>&1'"
}
#
RunScript {
RunsWhen = After
RunsOnFailure = yes
# По завершению всех процедур задания высвобождаем ресурсы
Command = "rm -rf /var/backups/bacula-clickhouse"
}
}
Проверяем корректность конфигурации и применяем таковую:
# bacula-dir -c /etc/bacula/bacula-dir.conf -t
# /etc/init.d/bacula-dir reload
# /etc/init.d/bacula-dir reload
Выше я заворачиваю содержимое директивы "Command" в дополнительный контейнер из "/bin/bash" - это требуется потому, что по умолчанию "Bacula FD" запускает свои команды в интерпретаторе "Dash", синтаксис которого отличается от привычного мне "Bash".