В моменте идея развертывания дома почтового сервера может показаться излишеством, ведь вполне для отправки различного рода уведомлений можно использовать Telegram или любой другой мессенджер, а для переписок со своего домена почту можно завернуть на какой-нибудь почтовый сервис, предлагающий возможность использования своего домена бесплатно, например mail.ru. Однако с учетом последних телодвижений РКН в сторону блокировки Telegram и ограничение в 5 ящиков на mail.ru (а с недавнего времени и использование сторонних почтовых клиентов только на платных тарифах), вариант использования собственного почтового сервера кажется все более интересной альтернативой.

Сама по себе настройка классического почтового сервера из связки Postfix и Dovecot на Linux процедура весьма нетривиальная. Существуют и чуть более простые альтернативы, например Exim, но и там процедура настройки весьма непростая. И что делать в ситуации когда хочется получить почтовый сервер с минимум телодвижений, но при этом еще и развернуть его в уже имеющимся дома Docker’e или кластере Kubernetes?
И тут на помощью приходит Mailu — простой в настройке, полностью функциональный почтовый сервер, который нацелен для использования в контейнерной среде.
Ранее на Хабре уже были статьи по развертыванию Mailu, но все они касались именно развертыванию в Docker’е. В данном руководстве я постараюсь максимально кратко изложить настройку на примере своего домашнего кластера k8s.
Нам потребуется:
наличие собственного домена и возможности управления DNS записями для него (я использую домен от reg.ru), для примера буду использовать yourdomain.ru
«белый» ip-адрес, так же для примера это будет 1.2.3.4
готовность вашего провайдера сделать обратную (PTR) запись для вашего белого ip
развернутый кластер Kubernetes, настроенный helm
CSI и соответствующий StorageClass
возможность раздавать внешние ip-адреса в кластере k8s через l2advertisements (это умеет например CNI Cillium или MetalLB в связки с другим CNI)
наличие настроенных ingress, cert-manager’a и автоматизации получения сертификата LetsEncrypt (в моем случае это ClusterIssuer для reg.ru от Флант)
Подготовка
Для начала создадим DNS запись типа А с поддоменом mail, итоговая запись будет выглядеть как:
1.2.3.4 А mail.yourdomain.ru
Далее направляем письмо вашему провайдеру с просьбой создать обратную запись.
Так же нужно создать MX:
@ MX mail.yourdomain.ru
После того, как nslookup 1.2.3.4 вернет заветный ответ mail.yourdomain.ru можно приступать к установке.
Развертывание
Рекомендую сразу создать отдельный namespace, т. к. почтовому серверу в любом случае потребуется открыть хотя бы порт 21/TCP из вне для обмена почтой, а любой открытый во вне порт — это потенциальная уязвимость. Поэтому вынесем почту в отдельный namespace:
kubectl create namespace dmz-mailu
И сразу обрежем в этом неймспейсе все лишнее, создадим network-policy-dmz-mail.yaml со следующим содержимом:
### Block all by default apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-dmz-mail-deny namespace: dmz-mail spec: podSelector: {} policyTypes: - Ingress - Egress --- ### Allow dmz-mail outgoing ports apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-mailu-ports namespace: dmz-mail spec: podSelector: {} egress: - to: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: kube-system ports: - protocol: UDP port: 53 - protocol: TCP port: 21 - protocol: TCP port: 465
Эта политика заблокирует в неймспейсе любой доступ, кроме указанных портов. Применим наш файл:
kubectl -n dmz-mail apply -f network-policy-dmz-mail.yaml
Теперь добавим репозиторий mailu в helm:
helm repo add mailu https://mailu.github.io/helm-charts/
Далее нам потребуется values для чарта:
helm show values mailu/mailu > mailu_values.yaml
Минимальный values.yaml выглядит примерно так:
hostnames: - mail.youdomain.ru domain: youdomain.ru timezone: "Europe/Moscow" ## тут задается создание первого аккаунта, пароль от него будет сгенерирован initialAccount: enabled: true username: "mail-admin-user" domain: "youdomain.ru" mode: "ifmissing" ## сверьте на всякий случай с сеть для pod'ов subnet: 10.42.0.0/16 ## я использовал Postgres из чарта, но вы можете так же использовать MariaDB или внешнюю БД postgresql: enabled: true auth: enablePostgresUser: true postgresPassword: "SomeStrongPassword" username: mailu password: "AnotherStrongPassword" primary: persistence: enabled: true storageClass: "your-storageClass" ## Настройки хранилища для вашего почтового сервера persistence: single_pvc: true # используем один PVC для Postfix, Dovecot, etc size: 50Gi storageClass: "your_storageclass" ingress: ingressClassName: "nginx" annotations: {cert-manager.io/cluster-issuer: regru-dns} # для автогенерации TLS сертификата extraHosts: - name: mail-local.youdomain.ru #Зачем нам дополнительный хост расскажу ниже path: / extraTls: - hosts: - mail-local.youdomain.ru secretName: mail.local-tls front: hostPort: enabled: false # в принципе можете попробовать использовать hostport, но это в данном руководстве этого касаться не буду externalService: enabled: true type: LoadBalancer loadBalancerIP: 192.168.10.200 # тут укажите ip из пула адресов для L2Advertisment externalTrafficPolicy: Local ## если есть еще один, более быстрый StorageClass, то возможно стоит разместить redis на нем redis: master: persistence: enabled: true size: 4Gi storageClass: "your-storageClass-nvme" ## rsamd не стоит отключать, т.к. письма в любом случае будут идти через него rspamd: enabled: true ## а вот clamav вполне можно для экономии ресурсов clamav: enabled: false ## и OleTools - софт для анализа офисных файлов oletools: enabled: false ## как и Tika - он отвечает за обработку метаданных tika: enabled: false
После правки под свой домен и кластер примените файл:
helm install mailu mailu/mailu -n dmz-mailu --values mailu_values.yaml
После того, как все поды в namespace перейду в статус Running мы можем добавить еще одну сущность — service для фронта. В принципе он и не должен требоваться, т. к. есть Ingress, но из-за того, что Mailu хочет контролировать TLS сам, терминация между Ingres и подом mailu-front происходит некорректно и получается ошибка SSL, обойти которую мне не удалось (возможно я что-то сделал не так, буду рад помощи в комментариях). Поэтому было применено решение «в лоб» - просто вытянть фронт на отдельный ip.
Создадим файл svc-mailu-front-web.yaml:
apiVersion: v1 kind: Service metadata: name: svc-mailu-front-web namespace: dmz-mail spec: externalTrafficPolicy: Local ports: - port: 443 protocol: TCP targetPort: 443 selector: app.kubernetes.io/component: front app.kubernetes.io/instance: mailu app.kubernetes.io/name: mailu type: LoadBalancer loadBalancerIP: 192.168.10.201 # тут укажите еще один ip из пула адресов для L2Advertisment
и применим его:
kubectl -n dmz-mail apply -f svc-mailu-front-web.yaml
Остается только связать на вашем локальном DNS сервере (или просто в hosts) запись mail-local.youdomain.ru и ip 192.168.1.230. После этого заходим на mail-local.youdomain.ru, логинимся данными админа из values.yaml и переходим в webadmin.
Так же потребуется прокинуть 21й порт с вашего шлюза до loadBalancerIP из values.yaml.
DKIM, DMARC, SPIF
Уже на этом этапе можно создать пару пользователей и покидаться письмами между собой, но этого нам явно мало, ведь хотелось бы еще общаться с получателями во вне. Мы конечно можем попробовать отправить письмо например на гуглопочту, но моментально получим отказ, т. к. указанные выше записи у нас просто отсутствуют. Но добавить их не составит труда.
Переходим в Administration — Mail Domains — Ваш домен — Редактировать, а затем в правом верхнем углу нажимамем Generate Keys. В итоге увидим примермно следующее:

Далее нам необходимо перенести недостающие записи в ваш DNS:

Через некоторое время (как публичные DNS серверы начнут отдавать TXT или после успешной проверки на https://mxtoolbox.com/dkim.aspx ) можно попробовать покидаться почтой с почтовыми ящиками на публичных серверах. Письма могут попасть в спам на почтовых сервисах, поэтому если что-то не пришло, то стоит проверить нежелательную почту.
Заключение
Не берусь оценивать строго, насколько рационально, да и вообще разумно ли, держать почтовый сервер в kubenetes, да и вообще сможет ли Mailu заменить полновесный почтарь (все же использование докера накладывает ограничения\усложнения в конкурировании). Но в целом как простой способ рассылать уведомления со своих сервисов или для аналогичных небольших задач данное решение на мой взгляд вполне имеет место быть.
Если у кого-есть опыт использования похожих решений, буду рад прочитать в комментариях.