CloudLite
8 (495) 784-61-39 techsupport@cloudlite.ru
Техническая поддержка
8 (495) 784-61-37 sales@cloudlite.ru
Тарифы, оплата, счета
Array
(
    [ELEMENT_CODE] => Kak_podderzhivat_arxivnye_kopii_fajlov_v_Samba
)
Виртуальный сервер Windows/Linux

БАЗА ЗНАНИЙ

Как поддерживать архивные копии файлов в Samba

Весьма распространено применение Samba для организации файл-сервера для Windows-машин.

При этом Windows способна сохранять и просматривать предыдущие версии файлов, и для этого необходимо просто зайти в свойства интересующего файла на расшаренном диске и открыть вкладку «Предыдущие версии».

В свою очередь, с помощью Samba можно хранить предыдущие версии файла с возможностью доступа для механизма учёта предыдущих версий в Windows. Для этого предназначен модуль vfs object с именем shadow_copy. Чтобы активировать его в Samba, нужно добавить в блок, описывающий нужную вам шару, такую строчку:

vfs objects = shadow_copy

Например:


[users]

            comment = Users shared folders

            path = /var/data/users/

            admin users = "@DOMAIN\Администраторы домена"

            read only = No

            create mask = 0600

            directory mask = 0700

            map acl inherit = Yes

            hide unreadable = Yes

            locking = No

            vfs objects = shadow_copy


На этом этапе как раз и можно обустроить механизм сохранения предыдущих копий и их учёта в Samba. Подготовьтесь так:

1.       Нужен LVM, и для обслуживание интересующего нас ресурса Samba выделите отдельный логический том. Сохранение архивных копий будет реализовано через механизм снапшотов LVM (подробнее ниже), при этом нюансы модуля shadow_copy требуют, чтобы расшаренный каталог располагался в корне диска – считается, что в противном случае ничего не заработает. Создайте отдельный том /dev/data/users, монтируемый в /var/data/users, т.е. Samba-ресурс [users] будет располагаться в корне логического тома LVM.

2.      Создайте средствами LVM снапшоты с указанного выше тома, а затем монтируйте их в специально названные каталоги (например @GMT-2010.01.01-12.00.00) в корень Samba-ресурса. Дата и время в имени каталога должны совпадать с параметрами создания снапшота. Каталоги такого вида не отобразятся в Windows, при этом модуль shadow_copy обеспечит им статус архивных копий.

Администрирование этих процессов должно выполняться скриптами – поговорим о них отдельно.

 

Настраиваем LVM и создаем основной логический том и Samba-ресурс

 

Как минимум, Вам необходимо владеть навыками работы с LVM. Создайте группу томов с именем data, добавьте в нее логический том users размером 100Gb с файловой системой ext3. Имя этого логического тома будет: /dev/mapper/data-users или же более простое: /dev/data/users:

# ls -al /dev/data/users

lrwxrwxrwx 1 root root 22 2010-05-19 16:30 /dev/data/users -> /dev/mapper/data-users

Монтируйте этот том в пустой каталог /var/data/users, пример записи из /etc/fstab:

/dev/mapper/data-users      /var/data/users ext3    defaults,nosuid,noexec,acl        0       2

Секция файла, отвечающая за настройки Samba-ресурса: /etc/samba/smb.conf

[users]

  comment = Users shared folders

  path = /var/data/users/

# Пользователи с правами редактирования галочек ;)

admin users = "@DOMAIN\Администраторы домена"

  hide unreadable = yes

  read only = no

# Маски - только пользователь по умолчанию должен иметь доступ

  create mask = 0600

  directory mask = 0700

  # блокировки - иногда бывают грабли без этого пункта

locking                 = no

  # наследуемые права

  map acl inherit         = yes

  # Поддержка архивных версий файлов

  vfs objects = shadow_copy

Как работать с LVM-снапшотами

 

Снапшот является моментальной копией диска на момент создания такой копии. Снапшоты позволяют полностью архивировать текущее состояние диска в нужный момент с последующим применением. Для примера создается в момент Х снапшот всего диска users, далее он монтируеется в каталог /var/data/users/@GMT-X, после чего в Windows на вкладке предыдущих версий появится версия файла на момент Х. И если файл с момента Х не будет изменен, на вкладке предыдущих версий не будет ничего отображено!

 

Механизм снапшотов в LVM отличается определенными нюансами:

 

- Снапшот по размеру может быть гораздо меньше архивируемого диска

- В момент создания снапшот в него никакие данные не копируются, он полностью пустой

- После создания снапшота, когда на исходный диск записываются какие-либо данные, они автоматически копируются и в снапшот. При отсутствии каких-либо изменений на диске и на снапшоте ничего не меняется. Так как все процессы контролируются LVM, то снапшот выглядит аналогично исходному диску этого же размера, с полной ФС и всеми файлами на момент создания снапшота.

 

Снапшот хранит НЕ все данные оригинального диска, а только РАЗНИЦУ с ним с момента создания снапшота, именно поэтому размер снапшота бывает значительно меньше исходника. Если размер снапшота становится больше оригинала, он деактивируется автоматически. Но Вы в любом момент можете увеличить размер снапшота.

 

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

 

Создание снапшота – команда lvcreate с ключом -s. Например, следующая команда создаст снапшот с именем 2019.01.01-12.00.00 для логического тома /dev/data/users размером 10Gb:

lvcreate -s -L 10G -n 2019.01.01-12.00.00 /dev/data/users


Создание снапшота можно проверить множеством команд, например:

# lvscan

# lvdisplay /dev/data/users

# lvdisplay /dev/data/2019.01.01-12.00.00

# ls -al /dev/data

Теперь нужно создать директорию с именем @GMT-2019.01.01-12.00.00 в каталоге /var/data/users и вмонтировать туда созданный снапшот:

# mkdir /var/data/users/\@GMT-2019.01.01-12.00.00

# mount -r /dev/data/2019.01.01-12.00.00 /var/data/users/\@GMT-2019.01.01-12.00.00

Можно использовать опцию -r (read only), т.к. LVM позволяет писать на снапшоты, но в данном случае в этом нет необходимости.

 

Автоматизация процесса

Преимущество данного механизма в автоматическом создании архивных копий. Для этого потребуется несколько скриптов. Для примера можно прописать все нужные функции в одном Perl-файле и использовать его как подключаемую библиотеку для скриптов управления:


#!/usr/bin/perl -w

# Библиотека функций работы со снапшотами.

# Author: Nevorotin Vadim aka Malamut

# Лицензия: GPLv3

 

use 5.010;

 

# Проверка элемента на вхождение в массив (а-ля оператор in)

sub isIn {

            @_ > 0 or die "isIn - OOPS!\n";

            my $element = shift @_;

            my @arr = @_;

            foreach (@arr) {

                    if ($_ eq $element) { return 1 }

            }

            return 0;

}

 

# Возвращает текущее время в формате yyyy.mm.dd-hh.mm.ss

sub getDate {

            my @time = localtime;

            return ($time[5] + 1900) . '.' . sprintf("%02d",$time[4] + 1) . '.' . sprintf("%02d",$time[3]) . "-" .

                    sprintf("%02d",$time[2]) . '.' . sprintf("%02d",$time[1]) . '.' . sprintf("%02d",$time[0]);

}

 

# Функция ищет все примонтированные снапшоты из заданной Volume Group в заданную директорию

sub getMounted {

            @_ == 2 or die "getMounted - OOPS!\n";

            my ($vg,$path) = @_;

 

            my @snapshots = ();

            foreach (`mount | grep /dev/mapper/$vg`) {

                    if (/$vg-(\d{4}\.\d{2}\.\d{2})--(\d{2}\.\d{2}.\d{2})\s+\S+\s+$path\/\@GMT-\1-\2\s+/) {

                           push @snapshots, "$1-$2";

                    }

            }

            return @snapshots;

}

 

# Функция ищет все снапшоты для указанного тома из указанной VG. Возвращяет хеш снапшот-состояние

sub getActive {

            @_ == 2 or die "getActive - OOPS!\n";

            my ($lv,$vg) = @_;

 

            my %snapshots = ();

            my $flag = 0; 

            foreach (`lvdisplay /dev/$vg/$lv 2>&1`) {

                    if (/LV\ssnapshot\sstatus/) { $flag = 1 }

                    elsif ($flag) {

                            if (m#^\s+/dev/\S+?/(\S+)\s+\[(\S+)]#) {

                                   $snapshots{$1} = lc $2;

                            } else { $flag = 0 }          

                    }

            }

            return %snapshots;

}

 

# Функция ищет все каталоги для снапшотов в заданной директории

sub getListed {

            @_ == 1 or die "getListed - OOPS!\n";

        my $path = shift @_;

 

            my @dirs = ();

            my @content = glob "$path/\@GMT*";

            foreach (@content) {

                    if (-d $_ and /GMT-(\d{4}\.\d{2}\.\d{2}-\d{2}\.\d{2}.\d{2})/) {

                            push @dirs, $1;

                    }

            }

            return @dirs;

}

 

# Создаёт снапшот с текущим временем для тома LV в группе VG и монтирует

# в подкаталог path с именем @GMT-sn_name

sub createSnapshot {

            @_ == 4 or die "createSnapshot - OOPS!\n";

            my ($lv, $vg, $path, $sn_size) = @_;

 

            my $sn_name = getDate;

 

            # Создаём директорию под снапшот

            mkdir "$path/\@GMT-$sn_name", 0777 or die "I can't create a directory for snapshot $sn_name! ($!)\n";

 

            # Создаём снапшот

            if (system "lvcreate -L ${sn_size}G -s -n $sn_name /dev/$vg/$lv 1>/dev/null") {

                    rmdir "$path/\@GMT-$sn_name" or warn "Very big error: I can't remove a directory for snapshot :(";

                    die "I can't create a snapshot $sn_name!\n";

            }

 

            # Монтируем

            if (system "mount -o ro,acl,user_xattr /dev/$vg/$sn_name $path/\@GMT-$sn_name") {

                    !system "lvremove -f /dev/$vg/$sn_name 1>/dev/null" or warn "Very big error: I can't remove a snapshot :(";

                    rmdir "$path/\@GMT-$sn_name" or warn "Very big error: I can't remove a directory for snapshot :(";

                    die "I can't mount a new snapshot $sn_name to directory!";

            }

}

 

# Удаляет снапшот из группы томов VG с именем snName, а так же пытается удалить каталог для снапшота в директории

# с адресом path и если снапшот примонтирован, то и тот каталог, куда примонтирован

sub removeSnapshot {

            @_ == 3 or die "removeSnapshot - OOPS!\n";

            my ($sn_name, $vg, $path) = @_;

 

            # Проверяем смонтирован ли, и если да - то куда

            my $mpath = $path;

            chomp(my $ms = `mount | grep $sn_name`);

            if ($ms) {

                    ($mpath) = $ms =~ /^\S+\s+\S+\s+(\S+)/;

                    !system "umount -lf /dev/$vg/$sn_name" or die "I can't umount $sn_name!\n";

                    rmdir $mpath or die "I can't remove directory $mpath!\n";   

            }

            # Удаляем директорию для снапшота

            if (-e "$path/\@GMT-$sn_name") {

                    rmdir "$path/\@GMT-$sn_name" or die "I can't remove directory $path/\@GMT-$sn_name!\n";      

            }

            # Удаляем снапшот

            !system "lvremove -f /dev/$vg/$sn_name 1>/dev/null 2>/dev/null" or die "I can't remove a snapshot $sn_name!\n";

}

 

# Проверяет размер снапшота и при необходимости и возможности увеличивает его

sub checkSize {

            @_ == 4 or die "checkSize - OOPS!\n";

            my ($sn_name, $vg, $sn_limit, $sn_add) = @_;

 

            my $size = 0;

            foreach (`lvdisplay /dev/$vg/$sn_name 2>&1`) {

                    if (/Allocated\s+to\s+snapshot\s+(\S+)%/i) { $size = $1      }      

            }

            if ( $size > $sn_limit ) {

                    !system "lvextend -L +${sn_add}G /dev/$vg/$sn_name 1>/dev/null 2>/dev/null" or warn "I can't extend snapshot $sn_name!\n";

            }

}

 

# Функция ротации снапшотов. Для заданного тома LV в заданной VG пытается поддерживать ровно COUNT снапшотов.

# При вызове всегда создаёт новый снапшот, при этом если надо - удаляет самый старый.

# Проверяет также текущие снапшоты, удаляет INACTIVE и расширяет те, которым необходимо расширение.

# sn_limit - в процентах (0..100), sn_size и sn_add - в гигабайтах

# snapshotsRotate($lv, $vg, $path, $count, $sn_size, $sn_limit, $sn_add)

sub snapshotsRotate {

            @_ == 7 or die "snapshotsRotate - OOPS!\n";

            my ($lv, $vg, $path, $count, $sn_size, $sn_limit, $sn_add) = @_;

 

            my %snapshots = getActive($lv,$vg);

 

            # Удаляем неактивные снапшоты в принципе и снапшоты с неизвестными именами из списка

            foreach (keys %snapshots) {

                    if (! $snapshots{$_} =~ /active/i) {

                            removeSnapshot($_, $vg, $path);

                            delete $snapshots{$_};

                    }

                    if (! /^\d{4}\.\d{2}\.\d{2}-\d{2}\.\d{2}.\d{2}$/) {

                            delete $snapshots{$_};        

                    }

            }

            # Все оставшиеся снапшоты пишем в отсортированный список

            @snapshots = sort keys %snapshots;

 

            # Если нужно - удаляем самые старые, чтобы в итоге осталось $count-1 штук

            foreach ( 0..(@snapshots-$count) ) {

                    removeSnapshot($snapshots[$_], $vg, $path);

            }

            splice @snapshots, 0, @snapshots-$count+1 if @snapshots-$count+1 > 0;

 

            # Теперь проверяем, не надо ли чего увеличить в размерах

            foreach (@snapshots) {

                    checkSize($_, $vg, $sn_limit, $sn_add);

            }

 

            # А теперь создаём новый снапшотик

            createSnapshot($lv, $vg, $path, $sn_size);

}

 

# Пытаемся примонтировать все снапшоты для указанного тома в указанной группе в их целевые каталоги в path

sub snapshotsRemount {

            @_ == 3 or die "snapshotsRemount - OOPS!\n";

            my ($lv, $vg, $path) = @_;

 

            my %snapshots = getActive($lv,$vg);

 

            # Удаляем неактивные снапшоты в принципе и снапшоты с неизвестными именами из списка

            foreach (keys %snapshots) {

                    if (! $snapshots{$_} =~ /active/i) {

                            removeSnapshot($_, $vg, $path);

                            delete $snapshots{$_};

                    }

                    if (! /^\d{4}\.\d{2}\.\d{2}-\d{2}\.\d{2}.\d{2}$/) {

                            delete $snapshots{$_};        

                    }

            }

 

            my @mounted = getMounted($vg,$path);

            my @listed = getListed($path);

 

            # Монтируем все снапшоты в предназначенные для них директории

            foreach my $sn_name (keys %snapshots) {

                    unless (isIn($sn_name, @listed)) {

                            mkdir "$path/\@GMT-$sn_name", 0777 or die "I can't create a directory for snapshot $sn_name! ($!)\n";         

                    }

                    unless (isIn($sn_name, @mounted)) {

                            if (system "mount -o ro,acl,user_xattr /dev/$vg/$sn_name $path/\@GMT-$sn_name") {

                                   rmdir "$path/\@GMT-$sn_name" or warn "Very big error: I can't remove a directory for snapshot $sn_name!:(\n";

                                   die "I can't mount a snapshot $sn_name to it's directory!\n";

                            }

                    }

            }

 

            # Удаляем директории, для которых нету снапшотов

            foreach (@listed) {

                    unless (isIn($_, keys %snapshots)) {

                            rmdir "$path/\@GMT-$_" or die "Error: I can't remove an unused directory $_!:(\n";

                    }

            }

}

 

# Удаляет все снапшоты для заданного тома

sub removeAllSnapshots {

            @_ == 3 or die "removeAllSnapshots - OOPS!\n";

            my ($lv, $vg, $path) = @_;

 

            my %snapshots = getActive($lv, $vg);

 

            # Удаляем все снапшоты

            foreach (keys %snapshots) {

                    removeSnapshot($_, $vg, $path);

            }

}

 

# pm же!

1;


Файл управления снапшотами для указанного выше тома /var/data/users выглядит так:


#!/usr/bin/perl -w

# Скрипт управления ротацией снапшотов.

# Author: Nevorotin Vadim aka Malamut

# Лицензия: GPLv3

 

use 5.010;

use Getopt::Long;   # Для разбора опций

 

# Библиотека с необходимыми функциями

require "/etc/samba/snapshots/libsnapshot.pm";

 

########################################

# Параметры тома для ротации снапшотов #

########################################

 

# Группа томов

$vg = 'data';

# Логический том

$lv = 'users';

# Точка монтирования

$path = '/var/data/users';

 

# Количество поддерживаемых снапшотов

$count = 5;

 

# Начальный размер снапшота, Gb

$sn_size = 5;

# Предел заполнения до ресайза, %

$sn_limit = 80;

# Шаг увеличения снапшота при переполнении, Gb

$sn_add = 3;

 

#########################################

 

$clear = 0;

$rotate = 0;

$remount = 0;

 

Getopt::Long::Configure ("bundling");                     # Конфигурирование getopt дабы воспринимать склейку коротких аргументов

GetOptions(

            "clear|c" => \$clear,                         # Удалить все снапшоты

            "rotate|r" => \$rotate,                               # Провести ротацию

            "remount|m" => \$remount,                     # Перемонтировать имеющиеся снапшоты

            "help|h" => \$help);                          # Помощь же

 

if (@ARGV or $help) {

            die "Usage: snapshots.pl [--clear|--rotate|--remount]\n\t-c = --clear\n\t-r = --rotate\n\t-m = --remount\n";

} elsif ($clear) {

            removeAllSnapshots($lv, $vg, $path);

} elsif ($rotate) {

            snapshotsRotate($lv, $vg, $path, $count, $sn_size, $sn_limit, $sn_add);

} elsif ($remount) {

            snapshotsRemount($lv, $vg, $path);

} else {

            die "Usage: snapshots.pl [--clear|--rotate|--remount]\n\t-c = --clear\n\t-r = --rotate\n\t-m = --remount\n";

}

Его нужно запускать по cron с опцией -r (ротация), например раз в сутки в 00:00.

И не забудьте после рестарта сервера перемонтировать все снапшоты! Для этого запустите скрипт с параметром -m, например, из /etc/rc.local. Для удаления всех снапшотов используйте скрипт с параметром -c, поменяв и переименовав нужные значения. И для каждого тома нужно будет создать свою копию скрипта с соответствующими параметрами.



Не нашли ответа на Ваш вопрос? Напишите нам!

techsupport@cloudlite.ru - служба техподдержки

sales@cloudlite.ru - вопросы по услугам, оплате, документам и партнерству

partner@cloudlite.ru - партнерская программа