В предыдущей статье мы рассказывали о том, как настроить и контролировать репликацию базы данных MySQL или MariaDB. Однако, если речь идет о создании отказоустойчивого интернет-магазина или аналогичного проекта, нужно реплицировать не только базу данных, но и файлы. Это могут быть файлы изображений товаров, html-страниц, стилей CSS, скрипты и другие файлы.

Задача не совсем тривиальная, так как не все файлы должны реплицироваться, на мастер-сервере могут периодически появляться новые файлы и исчезать старые. Кроме того, процесс репликации не должен отнимать слишком много ресурсов, и результаты репликации файлов необходимо контролировать. Важно сделать так, чтобы репликация выполнялась только в том случае, если резервный сервер не переключился в состояние мастера, например, в результате выхода основного сервера из строя.

В этой статье мы расскажем, как настроить репликацию файлов при помощи программы rsync, а также как организовать мониторинг репликации файлов с помощью Zabbix.

Мы будем исходить из предположения, что файлы пользователя shopusr2 реплицируются с мастер-сервера host01master на резервный сервер host01slave.

При этом репликации подлежат все файлы из каталога /home/shopusr2/data, за исключением файлов, перечисленных в файле исключений, расположенном на резервном сервере host01slave.

Установка программы rsync

Для установки программы rsync в ОС Debian 11 используйте следующие команды:

# apt update
# apt install rsync

Чтобы узнать версию установленной программы, запустите ее с параметром --version:

# rsync --version
rsync  version 3.2.3  protocol version 31

Исходные коды программы можно загрузить на сайте https://rsync.samba.org/, а также здесь: https://github.com/WayneD/rsync/

После установки rsync на мастер-сервере нужно отредактировать файл конфигурации /etc/rsyncd.conf. Также нужно создать файлы для ограничения доступа и для исключения из репликации файлов и каталогов, не требующих синхронизации между мастер-сервером и резервным сервером.

Настройка на мастер-сервере

На мастер-сервере, с которого будут копироваться файлы на резервный сервер, необходимо подготовить файл конфигурации /etc/rsyncd.conf, а также файлы паролей для реплицируемых пользователей. 

Для файлов паролей установите владельцем пользователя root и укажите для атрибутов значение 0600.

Подготовка файла конфигурации и файлов с паролями

Создайте на мастер-сервере файл /etc/rsyncd.conf с таким содержимым:

uid = root
gid = root
use chroot = yes
max connections = 4
syslog facility = local5
[shopusr2]
        path = /home/shopusr2/data
        comment = shopusr2
        list = yes
        auth users = shopusr2
        secrets file = /home/shopusr2/data/rsyncd.secrets

Параметры uid и gid задают, соответственно, группу и пользователя, от имени которого будет выполнена синхронизация содержимого заданного каталога. В данном случае операции будут выполняться от имени пользователя root.

Параметр use chroot со значением yes предписывает программе rsync при выполнении операции менять корневой каталог на указанный в параметре path. Такое изменение выполняется из соображений безопасности.

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

Для того чтобы не перегружать сервер в процессе репликации файлов, мы задали в файле конфигурации параметр max connections. Он задает максимальное количество одновременных подключений к сервису rsync, запущенному на мастер-сервере.

Параметр syslog facility задает уровень детализации сообщений от сервиса rsync для syslog. Можно указывать значения local0, local1, local2, local3, local4, local5, local6 и local7.

Далее в файле конфигурации /etc/rsyncd.conf должны следовать блоки определения синхронизируемых каталогов. В нашем случае это блок [shopusr2].

В нем задан путь path к каталогу /home/shopusr2/data, содержимое которого будет реплицироваться на сервер бекапов, произвольный комментарий comment, а также параметры auth users и secrets file, ограничивающие доступ к этому каталогу.

Параметр list позволяет просматривать список файлов и потребуется нам только для отладки.

Файл доступа на мастер-сервере /home/shopusr2/data/rsyncd.secrets должен содержать такую строку:

shopusr2:********

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

Установите для файла rsyncd.secrets владельца и права следующим образом:

# chown root:root /home/shopusr2/data/rsyncd.secrets
# chmod 0600 /home/shopusr2/data/rsyncd.secrets

После этого перезапустите сервис rsync и проверьте, что он работает:

# service rsync restart
# service rsync status

Настройка файрвола на мастер-сервере

Из соображений безопасности нужно ограничить доступ к порту 873 на мастер-сервере с помощью файрвола, разрешив его только для адреса IP резервного сервера.

Настройка на резервном сервере

На сервере реплики подготовьте для каждого реплицируемого пользователя файлы исключений и файлы паролей.

Файл исключений

В файлах исключений нужно перечислить каталоги и файлы, которые не должны реплицироваться, например:

.bash_history
.cache
.config
.filemgr-tmp
.local
.mc
.nano
.viminfo
.ssh
*.tar.gz
*.sql
*.xls
*.xlsx
bash_history
bin-tmp
cache
data
etc
logs
mod-tmp
php-bin
rsync.secrets
rsync
sphinx.socket
ssl
temp
test
tmp

Конечно, вам нужно будет самим определить содержимое файла исключений для своего сайта.

Файл паролей

Файлы паролей на резервном сервере должны содержать только строку пароля. Их нужно разместить в каталоге реплицируемого пользователя и назначить владельцем этого пользователя. Для нашего случая файл паролей находится здесь:

/home/shopusr2/data/rsync.secrets

Установите для файла паролей атрибут 0600:

# chmod 0600 /home/shopusr2/data/rsync.secrets

Проверка репликации вручную

Проверьте, что после запуска сервис rsync занял порт 873 на мастер-сервере:

# netstat -lupin | grep 873
tcp  0 0 0.0.0.0:873       0.0.0.0:*     LISTEN      22395/rsync
tcp6 0 0 :::873            :::*          LISTEN      22395/rsync

На время тестирования уберите символ комментария со строки list = yes в файле конфигурации /etc/rsyncd.conf на мастер-сервере и перезапустите rsync.

Далее на сервере реплики обратитесь к мастер-серверу по его адресу IP:

$ rsync xxx.xxx.xxx.xxx::
shopusr2 shopusr2

В ответ на эту команду на консоли появится список пользователей. В нашем случае здесь будет один пользователь shopusr2.

Просмотрите файлы от имени этого пользователя, указав пароль из файла rsync.secrets:

# sudo -u shopusr2 rsync xxx.xxx.xxx.xxx::shopusr2
Password:

На консоли появится список файлов, доступных для репликации:

drwxr-xr-x 4,096 2021/09/15 19:12:58 .
-rw------- 740 2021/09/07 15:33:36 .Xauthority
-rw------- 14,160 2021/09/15 19:12:58 .bash_history
-rw-r--r-- 289 2020/10/09 09:38:10 .bash_profile
-rw-r--r-- 11 2021/03/05 14:20:34 .fmsettings
-rw-r--r-- 53 2018/04/30 10:34:03 .gitconfig
-rw------- 102 2021/06/28 14:08:51 .lesshst
-rw------- 1,773 2020/12/03 00:53:11 .mysql_history
...
drwxr-x--x 4,096 2018/08/11 12:32:25 php-bin
drwxr-xr-x 4,096 2021/03/05 14:21:02 ssl
drwxr-xr-x 4,096 2020/02/14 10:20:16 temp
drwxr-xr-x 4,096 2021/09/08 14:59:54 www

После этого проверьте работу команды репликации:

# sudo -u shopusr2 /usr/bin/rsync -urltvvv --delete-after --password-file=/home/shopusr2/data/rsync.secrets shopusr2@ xxx.xxx.xxx.xxx::shopusr2 /home/shopusr2/data --exclude-from /home/ shopusr2/rsync/exclude_shopusr2.txt --bwlimit=6000 > /home/shopusr2/data/rsync/rsync_log.txt

Параметры этой команды описаны в следующем разделе этой статьи. Результат выполнения синхронизации будет записан в журнал rsync_log.txt.

Программа запуска и контроля репликации

Для запуска и контроля результатов выполнения репликации файлов мы используем программу run_rsync_free.pl, доступную здесь: ссылка на GitHub.

Вам необходимо отредактировать программу, указав адрес IP и домены своих серверов. Кроме того, вместо сервиса keepalived вы можете использовать другие средства переключения с мастер-сервера на сервер бекапов, и это тоже необходимо учесть при подготовке своей программы репликации и мониторинга.

Проверка хоста и его состояния

Сразу после запуска программа проверяет, что она работает на резервном сервере, и этот сервер находится в состоянии BACKUP. Для этого программа определяет имя хоста, а также проверяет содержимое файла node_keepalive_state.txt, в который сервис keepalived записывает состояние хоста:

my $node_keepalive_state_file = '/home/shopusr/node_keepalive_state.txt';
my $state_file_content = read_file($node_keepalive_state_file);
my $host = hostname; 
if($host eq 'host01slave.domain.ru' and $state_file_content=~/INSTANCE_myshop_BACKUP/)
{
  if( $debug) { print "do replication \n"; }
  …
}
else
{
  print 'Can run files replication only for BACKUP state and for host host01slave.domain.ru'."\n";
  print 'Host: '.$host.', state: '.$state_file_content."\n";
}

Если имя хоста определилось как host01slave.domain.ru, а файл состояния keepalived содержит строку «INSTANCE_myshop_BACKUP», запускается процесс репликации. В противном случае репликация не выполняется, а на консоль выводится сообщение об ошибке.

Запуск репликации файлов

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

my $lock_file = '/tmp/.rsync_lock';
sub _locked
{
  return 1 if ( -e $lock_file );
}
sub _lock
{
  open( FILE, ">$lock_file" );
  close FILE;
}
sub _unlock
{
  unlink($lock_file);
}  

Функция do_sync запускает программу /usr/bin/rsync, которая синхронизирует файлы:

sub do_sync($$)
{
  my ($user, $master_server_ip) = @_;
  my $begin = time();
  my $log='/home/'.$user.'/data/rsync/rsync_log.txt';
  my $cmd = 'sudo -u '.$user.' /usr/bin/rsync -urltvvv --delete-after --password-file=/home/'
.$user.'/data/rsync.secrets '.$user.'@'.$master_server_ip.'::'.$user.' /home/'.$user.'/data --exclude-from /home/shopusr2/data/rsync/exclude_'.$user.'.txt --bwlimit=6000 > '.$log;
  my @rqout = ();
  @rqout = split /\n/, `$cmd`;
  my $status;
  my $rc;
  my $rsync_rc = $?;
  if($rsync_rc == 0)
  {
    $status = analyze_log($log);
    $rc="ok";
  }
  else
  {
    $rc=$rsync_rc;
  }
  my $end = time();
  my $diff = $end - $begin;
  return {'rc' => $rc, 'sync_status_from_log' => $status, 'sync_duration' => $diff, 'user' => $user,  'master_server_ip' => $master_server_ip};
}

В качестве параметров этой функции нужно передать имя пользователя, для которого выполняется репликация файлов, а также адрес IP мастер-сервера.

При запуске rsync мы используем следующие параметры:

  • u — нужно выполнять только обновление файлов, но не перезаписывать обновленные файлы;

  • r — обрабатывать каталоги рекурсивно;

  • l — сохранять ссылки;

  • t — сохранять информацию о дате и времени файла;

  • vvv — выводить информацию о результатах работы в подробном виде;

  • delete-after — удалять файлы завершения передачи;

  • password-file — взять пароль из файла, указанного в этом параметре;

  • exclude-from — взять список исключений из файла, указанного в этом параметре;

  • bwlimit — ограничить скорость передачи данных значением, указанным в Кбайт/c.

Полный список параметров программы rsync можно посмотреть, например, здесь.

Если копирование файлов завершилось успешно, то подробный журнал копирования будет записан в переменную $log для дальнейшего анализа функцией analyze_log. В противном случае сообщение об ошибке будет сохранено в переменной $rc.

Функция do_sync возвращает хэш с кодом завершения rc, результатом анализа журнала синхронизации файлов sync_status_from_log, длительностью процесса синхронизации sync_duration, а также именем пользователя user и адресом IP мастер-сервера master_server_ip.

Анализ результатов синхронизации файлов

Задачей функции analyze_log является разбор файла журнала, созданного программой rsync в процессе синхронизации файлов. В ходе разбора функция отыскивает сообщения об ошибках, отфильтровывая «безобидные» сообщения:

sub analyze_log
{
  my ($log) = @_;
  my $action = {};
  my $errors = [];
  my $rc={};
  open( FILE, "<$log" );
  while (<FILE>)
  {
    if (m/recv_file_name/)
    {
      $action->{ recv_file_name }++;
    }
    elsif (m/recv_generator/)
    {
      $action->{ recv_generator }++;
    }
    elsif (m/recv_files/)
    {
      $action->{ recv_files }++;
    }
    elsif (m/is\suptodate/)
    {
      $action->{ is_uptodate }++;
    }
    elsif (m/\[generator\]\smake_file/)
    {
      $action->{ make_file }++;
    }
    elsif (m/delete_in_dir/)
    {
      $action->{ delete_in_dir }++;
    }
    elsif (m/Permission\sdenied/)
    {
      $action->{ permission_denied }++;
      push @$errors, $_;
    }
    elsif (m/is\snewer/)
    {
      $action->{ is_newer }++;
      push @$errors, $_;
    }
    elsif(m/opening\stcp\sconnection/
          || m/Connected\sto/
          || m/sending\sdaemon\sargs/
          || m/receiving\sfile\slist/
          || m/expand\sfile_list/
          || m/received\s\d+\snames/
          || m/done/
          || m/recv_file_list\sdone/
          || m/get_local_name\scount/
          || m/generator\sstarting\spid/
          || m/\[generator\]\spushing\slocal\sfilters\sfor/
          || m/\[generator\]\sprotecting\sfile/
          || m/\[generator\]\sprotecting\sdirectory/
          || m/\[generator\]\spopping\slocal\sfilters/
          || m/delta-transmission\senabled/
          || m/generate_files\sphase/
          || m/deleting\sin/
          || m/generate_files\sfinished/
          || m/sent\s\d+([\.,]\d+)*\sbytes/
          || m/total\ssize\sis/
          || m/_exit_cleanup/
          || m/set\smodtime\sof/
          || m/got\sfile_sum/
          || m/renaming/
          || m/delete_item/
          || m/deleting\s/
          || m/generating\sand\ssending\ssums/
          || m/count=/
          || m/recv\smapped/
          || m/parse_filter_file/
          || m/add_rule/

          # упоминания файлов и папок 
          || m/^[^(\s]+$/ # любые символы кроме скобок и пробелов (это имя папки или файла) 
          )
    {
      # nothing to do
    }
    # все остальное считаем ошибками
    else
    {
      $_ =~ s/^[\s\n\r\t]+//;
      $_ =~ s/[\s\n\r\t]+$//;
      if ($_)
      {
        $action->{ $_ }++;
        push @$errors, $_;
      }
    }
  }
  if ( scalar @$errors )
  {
    return { 'rc' => 'error', 'errors' => $errors };
  }
  return { 'rc' => 'ok', 'action' => $action };
}

Если ошибок не обнаружено, функция записывает в поле rc возвращаемого хэша строку «ok», а через хэш action передает статистику по выполненным действиям.

При ошибке в поле rc записывается строка «error», а в поле errors — ссылка на массив ошибок.

Подготовка данных для отправки в Zabbix

На следующем этапе наша программа записывает в хэш $sync_info информацию для отправки в Zabbix:

my $sync_info;
my $shopusr2_error_rc = 0;
if($sync_info-> { rc } ne 'ok')
{
  $shopusr2_error_rc = 1;
}
my $shopusr2_warning_rc = $sync_info->{sync_status_from_log }->{ rc };
if($shopusr2_warning_rc eq 'error')
{
  $shopusr2_warning_rc = 1;
}
else
{
  $shopusr2_warning_rc = 0;
}
my $shopusr2_sync_duration = $sync_info-> { sync_duration };

Прежде всего, при ошибке в переменную $shopusr2_error_rc записывается значение 1, а если команда rsync отработала правильно, то значение 0.

Если же ошибка была обнаружена в результате анализа журнала функцией analyze_log, то эта ситуация рассматривается как предупреждение. В переменную $shopusr2_warning_rc при этом записывается значение 1.

Появление предупреждения означает, что нужно проанализировать журнал выполнения программы rsync. При этом следует понять, насколько критична ошибка и как ее можно устранить. Если ошибка не критична, можно добавить соответствующий фильтр в функцию analyze_log. Возможно, потребуется отредактировать файл исключений.

И, наконец, в переменную $shopusr2_sync_duration записывается длительность выполнения команды синхронизации файлов.

Отправка результатов репликации в Zabbix

Для отправки данных в Zabbix используется программа zabbix_sender. Для этой программы подготавливается файл с данными, который затем отправляется на все серверы Zabbix:

open(my $fh, '>', $zabbix_sender_data_file) or die "Не могу открыть '$zabbix_sender_data_file' $!";
print $fh $hostname." shopusr2_sync.Error ".$shopusr2_error_rc."\n";
print $fh $hostname." shopusr2_sync.Warning ".$shopusr2_warning_rc."\n";
print $fh $hostname." shopusr2_sync.SyncDuration ".$shopusr2_sync_duration."\n";
close $fh;

my @zabbix_server_ip_array = split(',', $zabbix_server_ip);
foreach my $server_ip (@zabbix_server_ip_array)
{
  my $trap_cmd = $zabbix_sender.' -vv -z '.$server_ip.' -i '.$zabbix_sender_data_file;
  if ($debug) {
     print "Trap cmd: ".$trap_cmd."\n";
  };
  my @trapout = ();
  @trapout = split /\n/, `$trap_cmd`;
  if ($debug) { print Dumper(\@trapout); }
}

Адреса серверов Zabbix передаются программе run_rsync_free.pl при ее запуске в качестве параметра.

Запуск программы синхронизации через crontab

Для запуска программы run_rsync_free.pl каждые 10 минут вы можете использовать такое задание crontab:

*/10 * * * * /usr/bin/perl /home/shopusr2/data/run_rsync_free.pl xxx.xxx.xxx.xxx,yyy.yyy.yyy.yyy host01slave

Здесь вместо xxx.xxx.xxx.xxx и yyy.yyy.yyy.yyy укажите адреса ваших серверов Zabbix через запятую.

Шаблон для Zabbix

Мы подготовили шаблон мониторинга репликации файлов для Zabbix (рис. 1), доступный здесь: ссылка на GitHub.

Рис. 1. Шаблон мониторинга репликации файлов для Zabbix

В шаблоне определены метрики для обнаружения ошибок и предупреждений, появляющихся в процессе репликации, а также для контроля времени, необходимого для выполнения репликации.

Также в шаблоне предусмотрены триггеры, показанные на рис. 2.

Рис. 2. Триггеры шаблона мониторинга репликации файлов

Эти триггеры срабатывают, если возникают ошибки и предупреждения, а также если данные мониторинга репликации не поступают на сервер Zabbix слишком долго.

Автор: Александр Фролов.


НЛО прилетело и оставило здесь промокод для читателей нашего блога:

— 15% на все тарифы VDS (кроме тарифа Прогрев) — HABRFIRSTVDS.