Вопрос или проблема
Я запускаю сервер на Ubuntu 14.04 (Linux). Я установил и настроил Postfix и OpenDKIM на сервере; я могу отправлять письма самому себе с помощью команд, таких как echo hi | sendmail root
, и postfix/opendkim добавят заголовки, такие как Message-Id
, Date
и DKIM-Signature
, переадресуют письмо на мой личный адрес электронной почты, и все работает отлично.
Теперь я хочу создать приложение, которое будет работать в Docker контейнере и сможет отправлять письма с такой же легкостью. В частности, я не хочу беспокоиться о добавлении заголовков, таких как Message-Id
, и не хочу делать много конфигурации или установки программного обеспечения внутри самого контейнера.
Какой лучший способ сделать это?
Существует ли способ позволить контейнеру запустить исполняемый файл sendmail
на хосте?
Я попытался установить соединение с Postfix из контейнера, используя SMTP-протокол на порту 25, но Postfix, похоже, обрабатывает сообщения, полученные таким образом, иначе; я думаю, что он не добавил никаких заголовков, и сообщение было отклонено как спам Gmail (оно даже не было достаточно хорошим, чтобы попасть в мою папку “Спам”).
Вот содержимое maillog
Sep 28 23:35:52 dantooine postfix/smtpd[4306]: connect from unknown[172.17.0.95]
Sep 28 23:35:52 dantooine postfix/smtpd[4306]: DD457889B: client=unknown[172.17.0.95]
Sep 28 23:35:52 dantooine postfix/cleanup[4309]: DD457889B: message-id=<>
Sep 28 23:35:52 dantooine spamd[3175]: spamd: connection from localhost [::1]:59471 to port 783, fd 6
Sep 28 23:35:52 dantooine spamd[3175]: spamd: handle_user (getpwnam) unable to find user: 'someone'
Sep 28 23:35:52 dantooine spamd[3175]: spamd: still running as root: user not specified with -u, not found, or set to root, falling back to nobody
Sep 28 23:35:52 dantooine spamd[3175]: spamd: processing message (unknown) for someone:65534
Sep 28 23:35:52 dantooine spamd[3175]: spamd: clean message (2.5/5.0) for someone:65534 in 0.0 seconds, 331 bytes.
Sep 28 23:35:52 dantooine spamd[3175]: spamd: result: . 2 - MISSING_DATE,MISSING_FROM,MISSING_MID,UNPARSEABLE_RELAY scantime=0.0,size=331,user=someone,uid=65534,required_score=5.0,rhost=localhost,raddr=::1,rport=59471,mid=(unknown),autolearn=no autolearn_force=no
Sep 28 23:35:52 dantooine opendkim[3179]: DD457889B: can't determine message sender; accepting
Sep 28 23:35:53 dantooine postfix/qmgr[3664]: DD457889B: from=<[email protected]>, size=275, nrcpt=1 (queue active)
Sep 28 23:35:53 dantooine postfix/smtpd[4306]: disconnect from unknown[172.17.0.95]
Sep 28 23:35:53 dantooine postfix/smtp[4311]: DD457889B: to=<[email protected]>, relay=gmail-smtp-in.l.google.com[2607:f8b0:4003:c05::1b]:25, delay=0.25, delays=0.12/0.01/0.03/0.09, dsn=5.7.1, status=bounced (host gmail-smtp-in.l.google.com[2607:f8b0:4003:c05::1b] said: 550-5.7.1 [fd17:8b70:893a:44bf:fe73:6c21] Our system has detected that 550-5.7.1 this message is likely unsolicited mail. To reduce the amount of spam 550-5.7.1 sent to Gmail, this message has been blocked. Please visit 550-5.7.1 http://support.google.com/mail/bin/answer.py?hl=en&answer=188131 for 550 5.7.1 more information. su20si7357528oeb.94 - gsmtp (in reply to end of DATA command))
Sep 28 23:35:53 dantooine postfix/cleanup[4309]: 254E688A0: message-id=<[email protected]>
Sep 28 23:35:53 dantooine postfix/bounce[4330]: DD457889B: sender non-delivery notification: 254E688A0
Sep 28 23:35:53 dantooine postfix/qmgr[3664]: 254E688A0: from=<>, size=3374, nrcpt=1 (queue active)
Sep 28 23:35:53 dantooine postfix/qmgr[3664]: DD457889B: removed
Sep 28 23:35:53 dantooine postfix/virtual[4331]: 254E688A0: to=<[email protected]>, relay=virtual, delay=0.01, delays=0/0/0/0, dsn=2.0.0, status=sent (delivered to maildir)
Sep 28 23:35:53 dantooine postfix/qmgr[3664]: 254E688A0: removed
Вам нужно указать inet_interfaces
на докерный мост (docker0
) в конфигурации postfix, которая находится в /etc/postfix/main.cf
inet_interfaces = <docker0_ip>
Больше внутренних деталей работы на
sending-email-from-docker-through-postfix-installed-on-the-host
Поскольку у вас есть работающее решение, я здесь постараюсь объяснить различия в поведении, когда вы подключаетесь к postfix через telnet (SMTP) и когда вы используете sendmail (недоступный SMTP).
Для вашего сведения, OpenDKIM будет запущен postfix через механизм Milter. Вы можете получить некоторую информацию о том, как реализация milter в postfix, через эту официальную документацию. Вот диаграмма milter hook в postfix.
Только SMTP не-SMTP
фильтры фильтры
^ | ^ |
| v | |
Сеть -> smtpd(8) | |
\ | V
Сеть -> qmqpd(8) -> cleanup(8) -> входящие
/
pickup(8)
:
Локально -> sendmail(1)
Вы можете увидеть, что путь sendmail (не-SMTP) и путь telnet (SMTP) имеют различный порядок обработки.
-
Не-SMTP электронная почта будет обработана очисткой перед впрыском в milter. Демон очистки был ответственен за добавление недостающих заголовков: (Resent-) From:, To:, Message-Id:, и Date:. Поэтому ваше письмо будет иметь полный заголовок, когда оно будет впрыскиваться в milter OpenDKIM, даже если оригинальное письмо имело неполный заголовок.
-
SMTP-electronic mail будет впрыснута в milter OpenDKIM прежде, чем какое-либо очистка произойдет. Поэтому, если ваше оригинальное письмо имело неполный заголовок, OpenDKIM может отказаться подписывать письмо. Заголовок From: был обязательным (см. RFC 6376), и если письмо не имеет его, OpenDKIM откажется подписать письмо и даст вам предупреждение
can't determine message sender; accepting
Поскольку я никогда не использую docker, я не знаю, какие ограничения на sendmail/pickup внутри контейнера. Я думаю, что обходное решение Дэвида Грейсона было достаточно безопасным, чтобы убедиться, что OpenDKIM подписывает сообщение.
Это полуаприятие, или, по крайней мере, полутестируемое, поскольку я в настоящее время работаю над той же проблемой. Я надеюсь, что кто-то сможет помочь заполнить то, что я пропустил.
Ответ от OP (Дэвид Грейсон) звучит для меня как изобретение почтового спула postdrop, но использование этого почтового спула кажется многообещающим подходом, поэтому вот где я оказался.
Совместимый интерфейс /usr/bin/sendmail, предоставляемый postfix, передает почту в postdrop, который имеет sgid postdrop, позволяя ему сохранять почту в очереди maildrop по адресу /var/spool/postfix/maildrop. Это должно происходить в контейнере docker. Остальной часть postfix, надеюсь, не придется запускать в контейнере.
Итак, я монтирую /var/spool/postfix/maildrop и /var/spool/postfix/public. Я могу доставить почту в /var/spool/postfix/maildrop в окружении хоста, поскольку я смонтировал каталог очереди maildrop. Поскольку я смонтировал /var/spool/postfix/public
, maildrop
может сигнализировать pickup
, чтобы собрать почту из очереди. К сожалению, uid и gid, вовлеченные в это, если я не позабочусь об этом, что означает, что pickup в каталоге хоста не может читать файлы спула, и еще хуже, установка postfix нарушает разрешения на каталоге maildrop в окружении хоста.
Тем не менее, это кажется работающим:
$ cat Dockerfile
FROM debian:jessie
# Идентификаторы из родительского окружения
RUN groupadd -g 124 postfix && \
groupadd -g 125 postdrop && \
useradd -u 116 -g 124 postfix
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \
postfix \
bsd-mailx
CMD echo test mail | mail [email protected]
$ sudo docker build .
...
Успешно собран 16316fcd44b6
$ sudo docker run -v /var/spool/postfix/maildrop:/var/spool/postfix/maildrop \
-v /var/spool/postfix/public:/var/spool/postfix/public 16316fcd44b6
Хотя это работает, я не очень доволен жесткой кодировкой uid и gid. Это означает, что тот же контейнер не может быть признан работающим везде. Я думаю, что, если вместо того, чтобы смонтировать объем с хоста, я смонтирую его из контейнера, который запускает postfix, тогда он никогда не будет конфликтовать, и мне понадобится только одна установка postfix для отправки почты из многих контейнеров. Я бы установил эти uid и gid в базовом образе, от которого наследуются все мои контейнеры.
Я все же задаюсь вопросом, является ли это действительно хорошим подходом. С такой простой конфигурацией почты и без демона в контейнере для повторной доставки, более простой локальный MTA, такой как msmtp, может быть более подходящим. Он будет доставлять через TCP на реле на том же хосте, где будет происходить спулирование.
Проблемы с подходом msmtp включают:
- больше возможностей потерять почту, если smtp-ретранслятор, к которому он отправляет, недоступен. Если это релей на том же хосте, то вероятность сетевых проблем низка, но мне нужно быть осторожным с тем, как я перезапущу контейнер реле.
- производительность?
- Если пройдет большой поток почты, начнется ли потеря почты?
В общем, подход со.shared postfix spool кажется более вероятным для хрупкой конфигурации, но менее вероятным для отказа в режиме выполнения (ретранслятор недоступен, так что почта потеряна).
Я решил, что способ, которым контейнер будет отправлять почту, заключается в том, чтобы записывать ее в файл в определенном каталоге, который будет доступен как из контейнера, так и из хоста в качестве “объема” Docker.
Я сделал shell-скрипт с именем mailsender.sh, который считывает письма из указанного каталога, отправляет их в sendmail, а затем удаляет их:
#!/bin/bash
# Запускается на системе хоста, считывая письма из каталога
# и передавая их в sendmail -t, а затем удаляя их.
DIR=$1
if [ \! \( -d "$DIR" -a -w "$DIR" \) ]
then
echo "Недопустимый каталог: $DIR"
exit 1
fi
echo "`date`: Запуск mailsender в каталоге $DIR"
cd $DIR
while :
do
for file in `find . -maxdepth 1 -type f`
do
echo "`date`: Отправка $file"
sendmail -t < $file
rm $file
done
sleep 1
done
Ubuntu использует upstart, поэтому я создал файл с именем /etc/init/mailsender.conf
, чтобы превратить этот скрипт в демон:
description "отправляет письма из каталога"
start on stopped rc RUNLEVEL=[2345]
stop on runlevel[!2345]
respawn
exec start-stop-daemon --start --make-pidfile --pidfile /var/run/mailsender.pid --exec
/path/to/mailsender.sh /var/mailsend
Я могу запустить службу с помощью start mailsender
и остановить ее с помощью stop mailsender
. Я могу просмотреть его журналы в /var/log/upstart/mailsender.log
и, конечно, могу отслеживать его с помощью pid-файла.
Вам нужно создать каталог /var/mailsend
, а затем сделать его доступным из контейнера Docker, добавив аргумент -v /var/mailsend:/var/mailsend
к вашей команде docker run
.
Я использую msmtp-mta
в качестве пассивного реле (не требуется работающий демон). Каждый пользователь в контейнере с ~/.msmtprc
может отправлять электронные письма, используя команду sendmail, команда mail, также хорошо работает в php функции mail. Больше не нужно никакой настройки и изменений. Возможно, это вам поможет.
$ cat ~/.msmtprc
defaults
port 25
account gw
from [email protected]
host host.docker.internal
port 25
auth off
account default : gw
Конечно, вы должны запустить docker с --add-host=host.docker.internal:host-gateway
на Linux и Mac.
На хосте по умолчанию настроен postfix с этими изменениями в main.cf
:
mynetworks = 127.0.0.0/8 172.17.0.0/16
# ipv6 [::ffff:127.0.0.0]/104 [::1]/128
inet_interfaces = 127.0.0.1 172.17.0.1
Это работает аналогично postfix в контейнере, конечно.
Развивая Ответ Листера и, в некоторой степени, на Ответ mc0e:
Я также использую msmtp-mta
, но без сетевого соединения с хостом. Вместо этого я использую файл сокета потока, который монтирую в контейнер.
Чтобы это сработало, я добавил новую строку в /etc/postfix/master.cf
на хосте:
# служба тип приватный непривилегированный chroot пробуждение maxproc команда + аргументы
local_smtp unix n - - - - smtpd
Это создает файл сокета потока в /var/spool/postfix/public/local_smtp
, который принимает почту через протокол SMTP.
Контейнер запускается с
--mount type=bind,source=/var/spool/postfix/public/local_smtp,destination=/var/spool/local_smtp
Внутри контейнера /etc/msmtprc
выглядит так:
socket /var/spool/local_smtp
from [email protected]
Ответ или решение
Отправка электронной почты из контейнера Docker через Postfix на хосте
Отправка почты из приложения, работающего в контейнере Docker, через Postfix, установленный на хосте, требует некоторых настроек и понимания архитектуры. В этой статье мы рассмотрим, как организовать такой процесс без необходимости устанавливать дополнительные компоненты внутри контейнера.
Настройка Postfix
Первое, что нужно сделать, это убедиться, что Postfix правильно настроен для приема почты от контейнеров Docker. В файле конфигурации Postfix (/etc/postfix/main.cf
) необходимо указать интерфейсы, на которых должен слушать Postfix. Например, если Docker использует мост (docker0
), вы можете указать его IP адрес.
inet_interfaces = <docker0_ip>
Где <docker0_ip>
— это IP адрес, присвоенный интерфейсу docker0
. Обычно он находится в диапазоне 172.xx.xx.xx
.
Также убедитесь, что ваши настройки mynetworks
позволяют принимать почту от IP адресов, используемых Docker контейнерами.
mynetworks = 127.0.0.0/8 172.17.0.0/16
Эти строки обеспечить основное взаимодействие между контейнером и вашим Postfix’ом.
Использование Sendmail внутри контейнера
Для отправки электронной почты можно использовать sendmail
, который должен обратиться к Postfix на хосте. Однако, чтобы избежать проблем с заголовками, предпочтительно использовать стандартный почтовый процесс, а не просто подключение через SMTP.
Одним из рабочих решений будет использование механизма Maildrop. Как уже упоминалось, Postfix предоставляет интерфейс совместимости с sendmail
, который передает почту в postdrop
. Это можно настроить следующим образом:
- Создайте и настраивайте Docker контейнер: Вам нужно создать Dockerfile, в котором вы установите необходимые инструменты и сконфигурируете их.
FROM debian:jessie
RUN apt-get update && \
apt-get install -y postfix bsd-mailx && \
rm -rf /var/lib/apt/lists/*
CMD ["bash", "-c", "echo 'Subject: Test' | sendmail -t <ваша почта>"]
- Смонтируйте необходимые директории в контейнере: Для того чтобы контейнер мог взаимодействовать с Postfix на хосте, необходимо смонтировать
/var/spool/postfix/maildrop
для использования Maildrop.
sudo docker run \
-v /var/spool/postfix/maildrop:/var/spool/postfix/maildrop \
<your_container_image>
Использование msmtp как альтернативы
Другой простой способ отправки почты из Docker-контейнера — использование msmtp
. Это инструмент без демонов, который можно настраивать через конфигурационные файлы и который будет отправлять почту напрямую на SMTP-сервер Postfix.
- Установите
msmtp
в контейнер:
RUN apt-get update && \
apt-get install -y msmtp-mta && \
rm -rf /var/lib/apt/lists/*
- Создайте файл конфигурации
~/.msmtprc
внутри контейнера:
defaults
auth off
host <host_ip>
port 25
from <ваша почта>
- Запустите контейнер с правами доступа к сети хоста:
docker run --add-host=host.docker.internal:host-gateway <image>
Запуск и тестирование
После всех вышеуказанных настроек можно запускать контейнер и получать уверенность в том, что ваши почтовые отправления успешно обрабатываются Postfix. Тестируйте отправку почты через контейнер, чтобы убедиться, что все заголовки правильно добавляются и сообщения не помечаются как спам.
Заключение
Отправка электронной почты из контейнера Docker через Postfix, установленный на хосте, требует правильной настройки связи и конфигурации. Используя методы, описанные выше, вы сможете обеспечить оптимальную работу вашего приложения с отправкой почты без избыточной настройки внутри контейнера.