При этом 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 {
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 - партнерская программа