Сразу оговорюсь что поставленную задачу можно решить несколькими способами. Этот один из возможных.
Статья рассчитана на тех, кто знает как настраиваются и работают Exim и Dovecot, и в ней я не будут останавливаться на базовых настройках этих сервисов.
Надеюсь что кто-то, прочитав заметку, получит необходимые знания или идеи для воплощения своего решения.
Задача построить отказоустойчивый сервис, с хранением почты на серверах, с доступом по IMAP.
Кластер будет обслуживать компанию с примерно с 60 филиалами, каждый из которых имеет свой домен 3-го уровня.
Главная задача сервиса, беспрерывный доступ к почте. Поэтому для хранилища будем использовать два географически разнесенных сервера, с синхронизацией почтовых каталогов.
Оба сервера будут активными, это значит что мы будем распределять нагрузку между нодами. Часть доменов будет обслуживать одна нода, часть доменов другая. В случае выхода из строя одной из нод, клиенты переключаются на другую.
В качестве фронтенда дляраспределения нагрузки маршрутизации клиентов будем использовать Nginx с модулем mail. Для приема почты, будем использовать два smtp сервера.
Схема:
STORAGE: Хранилище почтовых ящиков. Состоит из двух нод.
Каждая нода выделеный сервер с 2х4Тб HDD, расположенные на разных хостингах
DNS: storage-01.domain.ru и storage-02.domain.ru
ОС: FreeBSD,
ПО: Dovecot, Exim, Postgresql и Nginx
SMTP: Сервера обрабатывающие SMTP трафик, две ноды.
Виртуальные серверы расположенные на разных хостингах,
DNS: smtp-01.domain.ru и smtp-02.domain.ru
ОС: FreeBSD,
ПО: Exim, Postgresql
PROXY: Прокси сервер для пользовательского доступа к сервису IMAP, POP3, SMTP.
Виртуальный сервер. Единственное не продублированное звено в кластере, но в виду своей простоты поднимается в течении нескольких минут из снапшота.
DNS: mail.domain.ru
ОС: FreeBSD,
ПО: Nginx
STORAGE.
В качестве MDA был выбран Dovecot, поскольку он из коробки умеет кластер. Для хранения почты, был выбран формат Maildir, т.к. сразу захотелось дедупликацию, но об этом ниже.
Датасторы принимают почту только от своих smtp серверов и PROXY. Почту в мир отправляют сами, минуя smtp серверы. Можно их совсем спрятать, и исходящую почту отправлять через smtp ноды.
Путь в файловой системе к ящикам /usr/mail/домен 2-го уровня/домен 3-го уровня/ящик/
В авторизации в качестве логина используется полный ящик mail@ldomain.mdomain.ru
БД:
Описание таблиц:
mail таблица для хранения почтовых ящиков
ldomain таблица для описания доменов 3-го уровня
mdomain таблица для описания доменов 2-го уровня
maps таблица маршрутизации
Как уже писал выше, нагрузку (почтовые домены) я распределяю по двум storage, в таблице maps определяет, на каком из storage находится домен 3-го уровня.
Опираясь на эту таблицу Exm-ы стораджей и smtp нод будут определять куда слать письма. А Nginx, к какому стораджу подключать пользователей.
Cоздание БД и таблиц:
Dovecot
Dovecot выполняет функцию MDA. Я оставлю за рамками этой статьи базовую настройку Dovecot, остановлюсь только на тех моментах, которые важны для связки его с DB и MTA
Синхронизация хранилищ
Изначально, я настроил синхронизацию средствами самого Dovecot (dsync) но, в процессе эксплуатации вылезла очень неприятная проблема. Как оказалось, проблема была связана с типом хранилища Maildir. Dsync стал сбоить, плодить копии писем отжирая свободное место на дисках. К тому моменту я уже не мог перевести все почтовые ящики на dbox (фирменный формат Dovecot) поэтому пришлось отказаться от синхронизации посредством dsync. В целом же, к этому механизму других претензий не было.
Пришлось обратиться к rsync, нехитрым скриптом он берет из базы те домены которые обслуживаются сервером на котором он запускается и синхронизирует их каталоги на второй сервер. Соответсвенно, на втором сервере такой же скрипт гонит на первый свои каталоги. Конечно этот механизм менее надежен так как rsync запускается по расписанию, есть окно между запусками в котором если сервер выйдет из строя мы потеряем письма.
скрипт запускается с двумя параметрами — имя_локального_сервера имя_удаленного_сервера
скрипт синхронизации:
На этом с Dovecot, все.
Exim
определяем локальные домены, исходя из записей в таблице maps, для того чтобы Exim «знал» свои домены.
в hostlist relay_from_hosts указываю адреса smtp нод и прокси, от них я принимаю почту без авторизации (клиенты авторизуются на прокси).
Входящую почту отдаю через LMPT Dovecot-у. В остальном все стандартно. Запросы к БД для поиска ящиков и паролей такие же как в листинге для Dovecot-a
SMTP ноды
БД, такая же как на стораджах, за исключением того, что в таблице mail отсутствует поле password, т.к. пользователи не подключаются к этим серверам. smtp ноды обрабатывают исключительно входящий из мира трафик. По базе проверяют существует ли ящик, пропуская дальше письма только для существующих ящиков.
Exim
Стандартный конфиг, за исключением запроса для определения маршрута
SQL запрос вытягивает имя стораджа для получателя, затем адрес стораджа указывается в роутере, в директиве route_list. Таким образом письмо отправляется на тот сторадж где находится активный домен для этого ящика.
Запросы к БД для поиска ящиков и паролей такие же как в листинге для Dovecot.
PROXY
В качестве прокси может выступать тот же Dovecot, но я выбрал Nginx, он показался проще и понятней в этом плане. Стояла одна задача, каким то образом указывать nginx-у куда отправлять пользователя.
nginx.conf на PROXY
Обратите внимание на директиву
На сторадже(на обоих!) тоже работает Nginx, но в режими web сервера, с одной лишь целью — обрабатывать запрос storage-01.domain.ru:8185/auth
Этот запрос в случае удачной авторизации клиента возвращает статус авторизации, имя сторажда и порт сервиса
После чего, nginx на PROXY отправляет клиента на сторадж который вернулся в ответе.
Можно было конечно исключить nginx на сторадже, но для этого пришлось бы и на PROXY держать базу с пользователями. В общем могли быть варианты.
Ниже конфиг Nginx на сторадже, с модулем на perl для реализации вышеописанного.
модуль mailauth.pm
Настройка балансировки, и переключение на резервную ноду
Сейчас переключение на резервную ноду происходит в ручном режиме. Просто в таблице maps меняется значение в поле storage1. Т.К. все сервера увешены мониторингом этого пока было достаточно.
ЗАКЛЮЧЕНИЕ
Кластер работает уже 3 года. За это время пережил несколько падений одной из нод ( в результате эта нода переехала в другой ДЦ).
Кому-то может показатся эта конструкция сложной, запутанной и «велосипедом». Но хочу сделать акцент на том, что архитектура данного решения исходила из решения использовать дешевое, «ненадежное» железо. В результате имеем надежный сервис с минимальной стоимостью аренды серверов.
Возможно заметка получилась не достаточно понятной, и я не отобразил какие то важные детали. В комментариях, если будут, буду дополнять.
ПС. Статья получилась длинной, поэтому про дедупликацию и еще одном, не упомянутом здесь сервисе управления этим кластером расскажу в следующей заметке, если это вызовет интерес.
Спасибо за внимание!
Статья рассчитана на тех, кто знает как настраиваются и работают Exim и Dovecot, и в ней я не будут останавливаться на базовых настройках этих сервисов.
Надеюсь что кто-то, прочитав заметку, получит необходимые знания или идеи для воплощения своего решения.
Задача построить отказоустойчивый сервис, с хранением почты на серверах, с доступом по IMAP.
Кластер будет обслуживать компанию с примерно с 60 филиалами, каждый из которых имеет свой домен 3-го уровня.
Главная задача сервиса, беспрерывный доступ к почте. Поэтому для хранилища будем использовать два географически разнесенных сервера, с синхронизацией почтовых каталогов.
Оба сервера будут активными, это значит что мы будем распределять нагрузку между нодами. Часть доменов будет обслуживать одна нода, часть доменов другая. В случае выхода из строя одной из нод, клиенты переключаются на другую.
В качестве фронтенда для
Схема:
STORAGE: Хранилище почтовых ящиков. Состоит из двух нод.
Каждая нода выделеный сервер с 2х4Тб HDD, расположенные на разных хостингах
DNS: storage-01.domain.ru и storage-02.domain.ru
ОС: FreeBSD,
ПО: Dovecot, Exim, Postgresql и Nginx
SMTP: Сервера обрабатывающие SMTP трафик, две ноды.
Виртуальные серверы расположенные на разных хостингах,
DNS: smtp-01.domain.ru и smtp-02.domain.ru
ОС: FreeBSD,
ПО: Exim, Postgresql
PROXY: Прокси сервер для пользовательского доступа к сервису IMAP, POP3, SMTP.
Виртуальный сервер. Единственное не продублированное звено в кластере, но в виду своей простоты поднимается в течении нескольких минут из снапшота.
DNS: mail.domain.ru
ОС: FreeBSD,
ПО: Nginx
STORAGE.
В качестве MDA был выбран Dovecot, поскольку он из коробки умеет кластер. Для хранения почты, был выбран формат Maildir, т.к. сразу захотелось дедупликацию, но об этом ниже.
Датасторы принимают почту только от своих smtp серверов и PROXY. Почту в мир отправляют сами, минуя smtp серверы. Можно их совсем спрятать, и исходящую почту отправлять через smtp ноды.
Путь в файловой системе к ящикам /usr/mail/домен 2-го уровня/домен 3-го уровня/ящик/
В авторизации в качестве логина используется полный ящик mail@ldomain.mdomain.ru
БД:
Описание таблиц:
mail таблица для хранения почтовых ящиков
- id
- mailbox — название почтового ящика
- password — пароль в MD5
- ldomain_id — id домена 3-го уровня из таблицы ldomain
- mdomain_id — id домена 2-го уровня из таблицы mdomain
- active — статус почтового ящика. вкл/выкл
ldomain таблица для описания доменов 3-го уровня
- id
- domain — название домена
- active — статус домена. вкл/выкл
mdomain таблица для описания доменов 2-го уровня
- id
- domain — название домена
- active — статус домена. вкл/выкл
maps таблица маршрутизации
- id
- ldomain_id — id домена 3-го уровня из таблицы ldomain
- mdomain_id — id домена 2-го уровня из таблицы mdomain
- storage1 — основоной сторадж
- storage2 — резервный сторадж (пока не используется)
Как уже писал выше, нагрузку (почтовые домены) я распределяю по двум storage, в таблице maps определяет, на каком из storage находится домен 3-го уровня.
mail=# select * from maps limit 3;
id | ldomain_id | mdomain_id | storage1 | storage2
----+------------+------------+--------------------+---------------
56 | 56 | 2 | storage-01.domain.ru | storage-02.domain.ru
57 | 57 | 2 | storage-02.domain.ru | storage-01.domain.ru
58 | 58 | 2 | storage-01.domain.ru | storage-02.domain.ru
(3 строк)
Опираясь на эту таблицу Exm-ы стораджей и smtp нод будут определять куда слать письма. А Nginx, к какому стораджу подключать пользователей.
Cоздание БД и таблиц:
psql -Upgsql template1
ctreate database mail;
\q
CREATE TABLE mail (
"id" BIGSERIAL PRIMARY KEY,
"mailbox" CHARACTER VARYING(32) not null,
"password" CHARACTER VARYING(128),
"ldomain_id" int NOT NULL,
"mdomain_id" int NOT NULL,
active BOOLEAN DEFAULT TRUE NOT NULL,
CONSTRAINT "mail_ldomain_id_check" CHECK (("ldomain_id" > 0))
);
CREATE TABLE "ldomain" (
"id" BIGSERIAL PRIMARY KEY,
"domain" CHARACTER VARYING(32) NOT NULL,
"active" BOOLEAN DEFAULT TRUE NOT NULL,
CONSTRAINT ldomain_k UNIQUE (domain)
);
CREATE TABLE "mdomain" (
"id" BIGSERIAL PRIMARY KEY,
"domain" CHARACTER VARYING(32) NOT NULL,
"active" BOOLEAN DEFAULT TRUE NOT NULL,
CONSTRAINT mdomain_k UNIQUE (domain)
);
CREATE TABLE "maps" (
"id" SERIAL PRIMARY KEY,
"ldomain_id" int NOT NULL,
"mdomain_id" int NOT NULL,
"storage1" CHARACTER VARYING(32) NOT NULL,
"storage2" CHARACTER VARYING(32) NOT NULL,
CONSTRAINT maps_ldomain_k UNIQUE (ldomain_id)
);
Dovecot
Dovecot выполняет функцию MDA. Я оставлю за рамками этой статьи базовую настройку Dovecot, остановлюсь только на тех моментах, которые важны для связки его с DB и MTA
/usr/local/etc/dovecot/dovecot.conf
protocols = imap pop3 lmtp # для связки с Exim буду использовать LMTP
/usr/local/etc/dovecot/dovecot-sql.conf.ext
driver = pgsql
connect = host=localhost dbname=mail user=mail password=password
default_pass_scheme = MD5
iterate_query = \
SELECT mail.mailbox || '@' || ldomain.domain || '.' || mdomain.domain AS user \
FROM mail \
INNER JOIN mdomain ON ( mail.mdomain_id = mdomain.id ) \
INNER JOIN ldomain ON ( mail.ldomain_id = ldomain.id )
password_query = \
SELECT mail.mailbox || '@' || ldomain.domain || '.' || mdomain.domain AS mail, mail.password \
FROM mail \
INNER JOIN mdomain ON ( mail.mdomain_id = mdomain.id ) \
INNER JOIN ldomain ON ( mail.ldomain_id = ldomain.id ) \
WHERE mailbox = '%n' AND \
ldomain.domain || '.' || mdomain.domain = '%d' AND \
mail.active = true AND \
ldomain.active = 'true'
user_query = \
SELECT '/usr/mail/' || ldomain.domain || '.' || mdomain.domain || '/' || mail.mailbox AS home \
FROM mail \
INNER JOIN ldomain ON ( mail.ldomain_id = ldomain.id ) \
INNER JOIN mdomain ON ( mail.mdomain_id = mdomain.id ) \
WHERE mail.mailbox = '%n' AND \
ldomain.domain || '.' || mdomain.domain = '%d'
/usr/local/etc/dovecot/conf.d/10-auth.conf
auth_username_format = %Lu # формат для авторизации mail@ldomain.mdomain.ru
!include auth-sql.conf.ext
/usr/local/etc/dovecot/conf.d/10-mail.conf
mail_location = maildir:/usr/mail/%d/%n/Maildir #Путь в файловой системе к ящикам /usr/mail/домен 2-го уровня/домен 3-го уровня/ящик/
Синхронизация хранилищ
Изначально, я настроил синхронизацию средствами самого Dovecot (dsync) но, в процессе эксплуатации вылезла очень неприятная проблема. Как оказалось, проблема была связана с типом хранилища Maildir. Dsync стал сбоить, плодить копии писем отжирая свободное место на дисках. К тому моменту я уже не мог перевести все почтовые ящики на dbox (фирменный формат Dovecot) поэтому пришлось отказаться от синхронизации посредством dsync. В целом же, к этому механизму других претензий не было.
Пришлось обратиться к rsync, нехитрым скриптом он берет из базы те домены которые обслуживаются сервером на котором он запускается и синхронизирует их каталоги на второй сервер. Соответсвенно, на втором сервере такой же скрипт гонит на первый свои каталоги. Конечно этот механизм менее надежен так как rsync запускается по расписанию, есть окно между запусками в котором если сервер выйдет из строя мы потеряем письма.
скрипт запускается с двумя параметрами — имя_локального_сервера имя_удаленного_сервера
#mailrsync.pl storage-01.domain.ru storage-02.domain.ru
скрипт синхронизации:
#!/usr/local/bin/perl
use DBI;
use threads;
use Net::Nslookup;
use Sys::Hostname;
@host = split('\.',hostname);
$dbn="mail";
$dbuser="mail";
$dbpass = "password"
$curdata=`date +%Y-%m`; chop $curdata;
$conn=DBI->connect("DBI:Pg:dbname=$dbn;host=localhost","$dbuser","$dbpass") or die "Cannot connect";
($localhostname,$remotehost)=@ARGV;
$mail_dir = "/usr/mail/";
sub domains {
$q = "SELECT ldomain.domain,mdomain.domain,maps.storage1
FROM mail
INNER JOIN ldomain on (mail.ldomain_id = ldomain.id)
INNER JOIN mdomain on (mail.mdomain_id = mdomain.id)
INNER JOIN maps on (maps.ldomain_id=ldomain.id)
WHERE maps.storage1='".$localhostname."'
AND mail.mailbox ='dir'";
$domain = $conn->prepare($q)
or die "Can't prepare statement: $DBI::errstr";
$domain->execute();
while ( my @domain = $domain->fetchrow_array ) {
@domains=(@domains,$domain[0].".".$domain[1]);
}
print "count of domains: ".($#domains + 1)."\n";
$dt = 2; # количество доменов в одном треде
$count = ($#domains / $dt );
print "count: ".$count."\n";
$i1 = 0;
for ($i2 = 0; $i2< $count; $i2++){
if ($dt > $#domains ){$dt = $#domains ;}
print $dt."\n";
print "loop: ".$i2."\n";
foreach $item (@domains[$i1..$m]){
print "in \@domains: ".$mail_dir.$item."\n";
@stack = (@stack,$mail_dir.$item."/");
}
push @threads,threads->create(\&sync,\@stack);
$i1 = $dt+1;
$dt = $dt + 2;
@stack=();
}
}
sub sync {
print "sync\n";
foreach $target (@stack){
system(`/usr/local/bin/rsync -H --delete-during -azz -e "/usr/bin/ssh -i /root/.ssh/dovecot_dsa" $target vmail\@$remotehost:$target`);
print $target."\n";
}
}
domains();
foreach $thread (@threads) {
$thread->join();
}
На этом с Dovecot, все.
Exim
определяем локальные домены, исходя из записей в таблице maps, для того чтобы Exim «знал» свои домены.
domainlist LOCAL_DOMAINS = \
${lookup pgsql{\
SELECT ldomain.domain || '.' || mdomain.domain AS domainname \
FROM ldomain, mdomain,maps \
WHERE ldomain.domain || '.' || mdomain.domain = LOWER('${quote_pgsql:$domain}') \
AND ldomain.active = 'true' \
AND maps.storage1 = 'storage-01.domain.ru' \
AND maps.ldomain_id = ldomain.id}}
в hostlist relay_from_hosts указываю адреса smtp нод и прокси, от них я принимаю почту без авторизации (клиенты авторизуются на прокси).
relay_from_hosts = localhost : smtp01.domain.ru : smtp02.domain.ru : mail.domain.ru
Входящую почту отдаю через LMPT Dovecot-у. В остальном все стандартно. Запросы к БД для поиска ящиков и паролей такие же как в листинге для Dovecot-a
SMTP ноды
БД, такая же как на стораджах, за исключением того, что в таблице mail отсутствует поле password, т.к. пользователи не подключаются к этим серверам. smtp ноды обрабатывают исключительно входящий из мира трафик. По базе проверяют существует ли ящик, пропуская дальше письма только для существующих ящиков.
Exim
Стандартный конфиг, за исключением запроса для определения маршрута
ROUTE_LIST = "${lookup pgsql{\
SELECT COALESCE(storage1,'') || ' : ' || COALESCE(storage2,'') \
FROM (\
SELECT storage1,storage2 \
FROM maps \
INNER JOIN ldomain ON ( maps.ldomain_id = ldomain.id ) \
INNER JOIN mdomain ON ( maps.mdomain_id = mdomain.id ) \
WHERE ldomain.domain || '.' || mdomain.domain = '${quote_pgsql:$domain}' \
UNION ALL \
SELECT storage1,storage2 \
FROM co_maps \
INNER JOIN co_domain ON ( co_maps.domain_id = co_domain.id ) \
WHERE co_domain.domain = '${quote_pgsql:$domain}') AS foo}}"
SQL запрос вытягивает имя стораджа для получателя, затем адрес стораджа указывается в роутере, в директиве route_list. Таким образом письмо отправляется на тот сторадж где находится активный домен для этого ящика.
begin routers
DATASTORE:
driver = manualroute
domains = DOMAINS
transport = remote_smtp
condition = MAILS
route_list = * ROUTE_LIST
no_more
Запросы к БД для поиска ящиков и паролей такие же как в листинге для Dovecot.
PROXY
В качестве прокси может выступать тот же Dovecot, но я выбрал Nginx, он показался проще и понятней в этом плане. Стояла одна задача, каким то образом указывать nginx-у куда отправлять пользователя.
nginx.conf на PROXY
cat /usr/local/etc/nginx/nginx.conf
worker_processes 1;
worker_rlimit_nofile 8192;
pid /var/run/nginx.pid;
error_log /var/log/nginx-error.log debug;
error_log /var/log/nginx-error.log notice;
error_log /var/log/nginx-error.log info;
events {
worker_connections 8192;
multi_accept on;
use kqueue;
}
mail {
ssl_certificate /usr/local/etc/ssl/proxy.crt;
ssl_certificate_key /usr/local/etc/ssl/proxy.key;
ssl_session_timeout 5m;
xclient off;
auth_http storage-01.domain.ru:8185/auth;
pop3_capabilities "LAST" "TOP" "USER" "PIPELINING" "UIDL" "RESP-CODES" "EXPIRE" "IMPLEMENTATION";
imap_capabilities "IMAP4" "IMAP4rev1" "UIDPLUS" "IDLE" "LITERAL+" "QUOTA" "LIST-EXTENDED";
smtp_capabilities "SIZE 52428800" "8BITMIME" "PIPELINING" "STARTTLS" "HELP";
server {
smtp_auth login plain;
listen 25;
protocol smtp;
proxy on;
starttls on;
}
server {
smtp_auth login plain;
listen 587;
protocol smtp;
proxy on;
starttls on;
}
server {
listen 110;
protocol pop3;
proxy on;
starttls on;
}
server {
listen 995;
protocol pop3;
proxy on;
starttls on;
}
server {
listen 143;
protocol imap;
proxy on;
starttls on;
}
server {
listen 993;
protocol imap;
proxy on;
starttls on;
}
}
Обратите внимание на директиву
auth_http storage-01.domain.ru:8185/auth;
На сторадже(на обоих!) тоже работает Nginx, но в режими web сервера, с одной лишь целью — обрабатывать запрос storage-01.domain.ru:8185/auth
Этот запрос в случае удачной авторизации клиента возвращает статус авторизации, имя сторажда и порт сервиса
"Auth-Status", "OK";
"Auth-Server", "storage-01.domain.ru";
"Auth-Port", "143";
После чего, nginx на PROXY отправляет клиента на сторадж который вернулся в ответе.
Можно было конечно исключить nginx на сторадже, но для этого пришлось бы и на PROXY держать базу с пользователями. В общем могли быть варианты.
Ниже конфиг Nginx на сторадже, с модулем на perl для реализации вышеописанного.
worker_processes 4;
worker_rlimit_nofile 8192;
error_log /var/log/nginx-error.log info;
events {
worker_connections 8192;
multi_accept on;
}
http {
perl_modules perl/lib;
perl_require mailauth.pm;
perl_require Digest.pm;
access_log off;
server {
listen 8185;
ssl_certificate /usr/local/etc/ssl/storage-01.crt;
ssl_certificate_key /usr/local/etc/ssl/storage-01.key;
ssl_session_timeout 5m;
location /auth {
perl mailauth::handler;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
модуль mailauth.pm
package mailauth;
use nginx;
use DBI;
use Net::Nslookup;
use Digest::MD5 qw(md5_hex);
$pg_user = "mail";
$pg_pass = "password";
$passhost = "localhost";
$mapshost = "localhost";
our $auth_ok;
$protocol_ports->{'pop3'}=110;
$protocol_ports->{'imap'}=143;
$protocol_ports->{'smtp'}=25;
$protocol_ports->{'smtpssl'}=465;
sub handler {
$r = shift;
$Passdbh=DBI->connect("DBI:Pg:dbname=mail;host=$passhost","$pg_user","$pg_pass");
if (!$Passdbh) {
$r->header_out("Auth-Status", "OK") ;
$r->header_out("Auth-Server", '0.0.0.0');
$r->header_out("Auth-Port", $protocol_ports->{$r->header_in("Auth-Protocol")});
$r->send_http_header("text/html");
return OK;
exit;
};
$Mapsdbh=DBI->connect("DBI:Pg:dbname=mail;host=$mapshost","$pg_user","$pg_pass");
$auth_ok=0;
$mailbox = $r->header_in("Auth-User");
our $get_pass_from_db=$Passdbh->prepare("SELECT password FROM mail
INNER JOIN ldomain ON ( mail.ldomain_id = ldomain.id )
INNER JOIN mdomain ON ( mail.mdomain_id = mdomain.id )
WHERE mail.mailbox || '\@' || ldomain.domain || '.' || mdomain.domain = ? ");
$get_pass_from_db->execute($mailbox);
@row=$get_pass_from_db->fetchrow_array();
$passfromDB=@row[0];
$md5passFromConnect = md5_hex($r->header_in("Auth-Pass"));
if ( $passfromDB eq $md5passFromConnect ){
$auth_ok=1;
}
if ($auth_ok==1){
@domain = split('\@',$mailbox);
$get_server_from_maps = $Mapsdbh->prepare(
"SELECT storage1 FROM maps
INNER JOIN ldomain ON ( maps.ldomain_id = ldomain.id ) \
INNER JOIN mdomain ON ( maps.mdomain_id = mdomain.id ) \
WHERE ldomain.domain || '.' || mdomain.domain = ? "
);
$get_server_from_maps->execute(@domain[1]);
@row=$get_server_from_maps->fetchrow_array();
$server_from_maps = nslookup(host => $row[0], type => "A");
$r->header_out("Auth-Status", "OK") ;
$r->header_out("Auth-Server", $server_from_maps);
$r->header_out("Auth-Port", $protocol_ports->{$r->header_in("Auth-Protocol")});
} else {
$r->header_out("mail:", $r->header_in("Auth-User"));
$r->header_out("Auth-Status", "Invalid login or password") ;
}
$r->send_http_header("text/html");
return OK;
}
sub db_fail {
$r->header_out("Auth-Status", "OK") ;
$r->header_out("Auth-Server", '127.0.0.1');
$r->send_http_header("text/html");
}
1;
__END__
Настройка балансировки, и переключение на резервную ноду
Сейчас переключение на резервную ноду происходит в ручном режиме. Просто в таблице maps меняется значение в поле storage1. Т.К. все сервера увешены мониторингом этого пока было достаточно.
ЗАКЛЮЧЕНИЕ
Кластер работает уже 3 года. За это время пережил несколько падений одной из нод ( в результате эта нода переехала в другой ДЦ).
Кому-то может показатся эта конструкция сложной, запутанной и «велосипедом». Но хочу сделать акцент на том, что архитектура данного решения исходила из решения использовать дешевое, «ненадежное» железо. В результате имеем надежный сервис с минимальной стоимостью аренды серверов.
Возможно заметка получилась не достаточно понятной, и я не отобразил какие то важные детали. В комментариях, если будут, буду дополнять.
ПС. Статья получилась длинной, поэтому про дедупликацию и еще одном, не упомянутом здесь сервисе управления этим кластером расскажу в следующей заметке, если это вызовет интерес.
Спасибо за внимание!