В моменте идея развертывания дома почтового сервера может показаться излишеством, ведь вполне для отправки различного рода уведомлений можно использовать 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

Уже на этом этапе можно создать пару пользователей и покидаться письмами между собой, но этого нам явно мало, ведь хотелось бы еще общаться с получателями во вне. Мы конечно можем попробовать отправить письмо например на гуглопочту, но моментально получим отказ, т. к. указанные выше записи у нас просто отсутствуют. Но добавить их не составит труда.

Переходим в AdministrationMail DomainsВаш доменРедактировать, а затем в правом верхнем углу нажимамем Generate Keys. В итоге увидим примермно следующее:

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

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

Заключение

Не берусь оценивать строго, насколько рационально, да и вообще разумно ли, держать почтовый сервер в kubenetes, да и вообще сможет ли Mailu заменить полновесный почтарь (все же использование докера накладывает ограничения\усложнения в конкурировании). Но в целом как простой способ рассылать уведомления со своих сервисов или для аналогичных небольших задач данное решение на мой взгляд вполне имеет место быть.

Если у кого-есть опыт использования похожих решений, буду рад прочитать в комментариях.