Добрый день, Хабр!
Задача
В моей организации используют почтовый сервер на платформе Kerio Connect, в разных городах установлены почтовые сервера обслуживающие своих пользователей. Распределенной структуры изначально не было, так как домены отличаются на третьем уровне с указанием города площадки. Все работало и всех устраивало. В один прекрасный день — руководство поставила задачу, общий календарь дел между всеми площадками!
Предыстория
Изначально идея была — поднять Распределенный почтовый домен Kerio и он сам все сделает. Сказано, сделано распределенный домен был создан, но не тут-то было, сервер готов был синхронизировать календари, папки, контакты — между доменами находящимися на одном сервере, но вовсе не собирался синхронизировать данные между несколькими серверами.
Такого подвоха я, конечно, не ожидал и долго не мог поверить в отсутствие необходимого мне функционала. Позже нашел документальное подтверждение этому факту. Чем был очень сильно озадачен и разочарован.
Задача плавно перетекла в проблему.
Какие были варианты
- Создать двух клиентов на разных серверах, которые каким-то сторонним ПО обменивали необходимые данные. Нужно было найти это самое сторонние ПО, которое бы реализовывало данный функционал – не люблю такие грабли, но казалось что это единственно быстрое решение.
- Написать свой собственный скрипт синхронизации данных между серверами. Дело в том, что Kerio хранит каждый объект как отдельный файл, соответственно требовалась разработать скрипт работы с файлами, но в виду достаточного кол-ва источников задача казалось несколько сложной, тем более что нужно было выполнять множественные проверки корректности данных, вдруг кто-то создаст задачу в одинаковый промежуток времени и тд и тп.
Забегая вперед скажу, что Kerio хоть и хранит объект как отдельный файл, он не настолько глуп, чтобы при каждом обращении к объекту спрашивать — как дела у файловой системы.
Потратив на размышления большое количество времени, изрисовав кучу бумажек с планами “по захвату вражеской территории”, в 6 часу ура я принял два верных решения:
- Первое решение – делать свое и ничего стороннего не искать.
- Второе решение — идти спать.
Уже утром я проснулся с одной единственно и верной мыслью, которая сократилась до нескольких букв – DFS
Решение
Само решение выглядело следующим образом
- привести все сервера, которые будут участвовать в синхронизации к OS Windows. (Часть была на Linux. требовалась миграция почтовых данных на другую OS)
- Определится со структурой каталогов, которые будут участвовать в синхронизации — они должны быть идентичные.
- Определить все почтовые сервера под один домен с единым DFS пространством.
- Создать выше упомянутый распределенный домен Kerio, так как в моем случае требуется синхронизация данных, не только между серверами но и между доменами, вторым может заниматься сервер Kerio самостоятельно. (в отличие от первого)
- Натравить синхронизируемые каталоги на пространство DFS.
- Придумать какой-нибудь костыль (ведь без костыля нельзя)
Реализация
Пример на двух почтовых серверах (может быть больше)
1. Kerio Distributed domain
Master не участвует в синхронизации, но это не обязательное условие.
Расписывать, как поднять распределенный домен Kerio не буду, ничего сложного в этом нет, можно изучить официальный
В конечном итоге в консоли администрирования вы должны увидеть следующую картинку:
Далее меня интересовали общие папки, на сервере Master можно указать следующие варианты:
Особая для каждого домена — сервер не будет синхронизировать общедоступные папки между доменами
Общие для всех доменов — все сервера откажутся от существующих общих папок в каждом домене и создадут новые единые папки для всех доменов на каждом из почтовом сервере.
Внимание! Данная опция хоть и меняет политику настройки на всех серверах, синхронизацию производит отдельно от каждого из серверов (то есть — без единого общего пространства)
У администратора — останется возможность, распределять доступы между пользователями.
в моем случае — все свои и мне нужна полная синхронизация (В вашем случае решение может быть другим) на каждом сервере нужно создать одинаковые наборы доменов которые необходимо синхронизировать.
2. Каталоги данных Kerio
Теперь необходимо создать одинаковые общие каталоги, которые необходимо синхронизировать на каждом из серверов. Папки, Календари, Контакты.
Совет – создавайте каталоги на английском языке, в том случаи, если вы создадите их на латинице, директория будет иметь название в какой-то непонятной кодировке, это как минимум неудобно.
Теперь необходимо найти физические пути почтовых папок на каждом сервере.
Общие для всех доменов ~DataMailmail#publicСинхронизируемый каталог#msgs
Особая для каждого домена ~DataMailmail**Domain**#publicСинхронизируемый каталог#msgs
Обратите внимании, что мы будем синхронизировать не весь каталог, а только контейнер с данными #msgs — тут хранятся сами объекты, все остальные данные для каждого из серверов должны быть свои.
3. DFS
Подробно как настраивать DFS, также расписывать не буду, информации по данному вопросу достаточно.
DFS — это служба роли в Windows Server, которая предоставляет возможность объединения общих папок, находящихся на разных серверах
Перед настройкой DFS – необходимо остановить все почтовые сервера которые будут участвовать в синхронизации данных.
По окончанию настройки вы должны получить следующую картинку для каждой из синхронизируемых папок
публиковать реплицируемые папки нам естественно не нужно.
После того как произойдет реплицирование (а реплецировать там особо нечего — папки пусты) почтовые сервера можно запустить.
Далее можно наполнить данными один из почтовых серверов и проверить что данные реплицируются корректно.
4. Костыль
Описание размышления
Как вы можете убедиться после того как данные начали синхронизироваться (DFS), в том случаи если вы что либо создали на первом сервере — на втором сервере что-то как-то ничего не появляется, либо появляется но как-то не всегда.
Отчаивается не стоит оно конечно рано или поздно там появится, но лучше рано, чем поздно. Потому как поздно это через 6 – 12 часов.
Все дело в том, что как только вы что-то создали на первом сервере, на втором и последующих файл конечно сразу же появится благодаря системе DFS, однако в том случае если данный почтовый каталог уже был кем-то прочитан ранее и его запросят повторно, сервер не будет перечитывать папку #msgs а выплюнет данные из своего собственного индекса, который может уже давно не соответствовать нашей действительности.
У Kerio есть механизм перечитывания индекса, но он может сработать часов этак через шесть, при этом за эти 6 часов актуальность задачи в календаре может быть несколько утеряна.
Для того чтобы проверить работу синхронизации прям сейчас, можно удалить файл в соответствующем синхронизируемом каталоге index.fld, после повторного обращения к папке на почтовом сервере и при отсутствии данного файла Kerio перечитает каталог и данные появятся. Казалась бы, вот оно решение, удаляй файл при изменении данных, но это работает не каждый раз, а только первый раз, далее Kerio почему-то теряет всякий интерес к index.fld
Так и еще начинает выплевывать непонятные для пользователя сообщения — про какой-то индекс и что он там уже что-то делает.
Есть еще вариант, что-то создать — в момент создания нового объекта сервер вдруг сообразит, что имя файла, которое он хотел присвоить уже занято, но тут снежный ком и это тупиковый вариант.
Как же быть?
Если еще раз обратить внимание на уже знакомую нам картинку.
Но в другой плоскости, можно заметить очень интересную и нужную нам сейчас кнопку — Переиндексировать папки
И действительно. Если нажать на данную кнопку на почтовом сервере, который не знает о том что у него уже что-то изменилось в синхронизируемом #msgs, мы получим стабильный, быстрый результат. Все скрытое станет явным.
В журнале можно посмотреть, сколько занимает времени данный процесс, в моем случаи с несколькими тысячами (15 тыс) записей около 3-4 минут.
Остались только придумать, как собственно нажать на данную кнопку, тогда когда нам это нужно.
Оказывается у Kerio есть свой API
Функция, которая выполняет нашу задачу, выглядит так –
session = callMethod("Domains.checkPublicFoldersIntegrity",{}, token)
Из всего выше изложенного нам нужно написать скрипт, который мониторил бы состояние интересующих папок и в том случаи, если что-то изменилось, выполнял для нас нужную нам функцию.
Хочу сказать, что я написал несколько разных версий скриптов выполняющих разные проверки, остановился на том, который все выводы строит исходя из количества файлов.
Реализация скрипта
Пример скрипта CMD и описание
Re-index.bat
@echo off
set dir=%~dp0
%dir:~0,2%
CD "%~dp0"
md "%CD%LOG"
md "%CD%Setup"
ECHO -Start- >> "%CD%LOG%Computername%.log"
ECHO Start -> %Computername% %Date% %Time% >> "%CD%LOG%Computername%.log"
SetLocal EnableDelayedExpansion
for /f "UseBackQ Delims=" %%A IN ("%CD%Setup%Computername%.List") do (
set /a c+=1
set "m!c!=%%A"
)
set d=%c%
Echo Folder = %c%
ECHO Folder = %c% >> "%CD%LOG%Computername%.log"
ECHO.
ECHO. >> "%CD%LOG%Computername%.log"
:start
cls
if %c% LSS 1 exit
set /a id=1
set R=0
:Find
REM PF-Start
if "%id%" gtr "%c%" if %R% == 1 Goto Reindex
if "%id%" gtr "%c%" timeout 60 && Goto start
For /F "tokens=1-3" %%a IN ('Dir "!m%id%!#msgs" /-C/S/A:-D') Do Set 2DirSize!id!=!DS!& Set DS=%%c
if "2DirSize!id!" == "" set 1DirSize!id!=!2DirSize%id%!
echo %id%
ECHO !m%id%!
echo Count [ !1DirSize%id%! -- !2DirSize%id%! ]
if "!1DirSize%id%!" == "!2DirSize%id%!" ECHO Synk
REM DEL index.fld
if "!1DirSize%id%!" NEQ "!2DirSize%id%!" del /f /q !m%id%!index.fld && del /f /q !m%id%!indexlog.fld && del /f /q !m%id%!search.fld && set R=1 && ECHO RE-index Count && ECHO RE-index Count %Date% %Time% - Delete !m%id%! >> "%CD%LOG%Computername%.log"
set 1DirSize!id!=!2DirSize%id%!
ECHO.
ECHO.
set /a id+=1
goto Find
:Reindex
ECHO. >> "%CD%LOG%Computername%.log"
ECHO --- RE-INDEX - Start - %Date% %Time% --- >> "%CD%LOG%Computername%.log"
ECHO. >> ----------------------------------- >> "%CD%LOG%Computername%.log"
call PublicFolders.py
timeout 60
goto start
exit
Копия скрипта запускается на каждом почтовом сервере (можно в качестве службы, Адм права не требуются)
Скрипт читает файл Setup%Computername%.List
Где %Computername% — имя текущего сервера (Директория может содержать сразу списки всех серверов.)
Файл %Computername%.List – содержит полные пути синхронизируемых директорий, каждый путь записан в новой строке, пустых строк содержать не должен.
После первого запуска скрипт выполняет процедуру индексации, в не зависимости необходима она или нет, также скрипт создает индекс количества файлов в каждой из синхронизируемой директории.
Задача скрипта сводится к подсчету всех файлов в указанной директории.
По окончанию подсчета каждой директории, если хотя бы в одной директории текущее значение файлов, не совпадает с предыдущем, скрипт удаляет файлы из корневой директории синхронизируемого почтового каталога: index.fld, indexlog.fld, search.fld
и запускает процесс индексации — общих почтовых папок.
В директорию LOG сваливается информация о выполнении задач.
Процесс индексации
Процесс индексации сводится к выполнению функции API Kerio
Session = callMethod("Domains.checkPublicFoldersIntegrity",{}, token)
Пример выполнения приведен на – python
PublicFolders.py
import json
import urllib.request
import http.cookiejar
""" Cookie storage is necessary for session handling """
jar = http.cookiejar.CookieJar()
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(jar))
urllib.request.install_opener(opener)
""" Hostname or ip address of your Kerio Control instance with protocol, port and credentials """
server = "http://127.0.0.1:4040"
username = "user"
password = "password"
def callMethod(method, params, token = None):
"""
Remotely calls given method with given params.
:param: method string with fully qualified method name
:param: params dict with parameters of remotely called method
:param: token CSRF token is always required except login method. Use method "Session.login" to obtain this token.
"""
data = {"method": method ,"id":1, "jsonrpc":"2.0", "params": params}
req = urllib.request.Request(url = server + '/admin/api/jsonrpc/')
req.add_header('Content-Type', 'application/json')
if (token is not None):
req.add_header('X-Token', token)
httpResponse = urllib.request.urlopen(req, json.dumps(data).encode())
if (httpResponse.status == 200):
body = httpResponse.read().decode()
return json.loads(body)
session = callMethod("Session.login", {"userName":username, "password":password, "application":{"vendor":"Kerio", "name":"Control Api-Local", "version":"Python"}})
token = session["result"]["token"]
print (session)
session = callMethod("Domains.checkPublicFoldersIntegrity",{"domainId": "test2.local"}, token)
print (session)
callMethod("Session.logout",{}, token)
Также в файле необходимо указать учетную запись с правами на выполнения данной функции (Адм – общих почтовых папок) почтового сервера.
Надеюсь, моя статья будет полезна администраторам Kerio Connect.
Источник: habr.com