Назад | Перейти на главную страницу

Отправлять почту из контейнера Docker с Postfix хоста

Я использую сервер Ubuntu 14.04 (Linux). Я установил и настроил Постфикс и OpenDKIM очень красиво на сервере; Я могу отправлять себе электронные письма с такими командами, как echo hi | sendmail root, а postfix / opendkim добавит заголовки, например Message-Id, Date, и DKIM-Signature, перешлите письмо на мой личный адрес электронной почты, и все работает отлично.

Теперь я хотел бы создать приложение, работающее в Докер контейнер и может с такой же легкостью отправлять электронные письма. В частности, я не хочу беспокоиться о добавлении заголовков вроде Message-Id, и я не хочу сильно настраивать или устанавливать программное обеспечение внутри самого контейнера.

Как лучше всего это сделать?

Есть ли способ позволить контейнеру запускать sendmail исполняемый на хосте?

Я попытался установить соединение с Postfix из контейнера, используя протокол SMTP на порту 25, но Postfix, похоже, по-разному обрабатывает полученные таким образом сообщения; Я думаю, что он не добавлял никаких заголовков, поэтому сообщение было полностью отклонено Gmail как спам (его даже не было достаточно, чтобы поместить в мою папку для спама).

Здесь содержимое почтового журнала

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=<whoever@example.com>, 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=<someone@gmail.com>, 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=<20140928233553.254E688A0@myserver.example.com>
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=<whoever@example.com>, 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

Поскольку у вас есть рабочее решение, здесь я попытаюсь объяснить различное поведение, когда вы используете telnet для postfix (SMTP) и когда вы используете sendmail (не SMTP).

FYI, OpenDKIM будет вызываться постфиксом с Механизм Мильтера. Вы можете получить некоторую информацию о том, как реализовать milter в postfix через это официальная документация. Вот схема перехвата milter в postfix.

             SMTP-only       non-SMTP
             filters         filters
                ^ |            ^ |
                | v            | |
Network ->  smtpd(8)           | |
                       \       | V
Network ->  qmqpd(8)    ->  cleanup(8)  ->  incoming
                       /
            pickup(8)
               :
Local   ->  sendmail(1)

Вы можете видеть, что методы sendmail (не SMTP) и Telnet (SMTP) имеют разный порядок обработки.

  • Электронное письмо, не являющееся SMTP, будет обработано очисткой перед тем, как оно будет введено в milter. Демон очистки отвечал за добавление недостающих заголовков: (Resent-) From :, To :, Message-Id :, и Дата:. Следовательно, ваше электронное письмо будет иметь полный заголовок при введении в OpenDKIM milter, даже если исходное электронное письмо имело неполный заголовок.

  • Электронное сообщение SMTP будет введено в Milter OpenDKIM до того, как будет произведена очистка. Поэтому, если в исходном письме был неполный заголовок, opendkim может отказаться подписывать письмо. В Из: заголовок был обязательным (см. RFC 6376), а если в электронном письме его нет, OpenDKIM откажется подписать письмо и выдаст вам предупреждение.

    can't determine message sender; accepting
    

Поскольку я никогда не использую докер, я не знаю, какие ограничения на sendmail / pickup внутри контейнера. Я думаю, что обходной путь Дэвида Грейсона был достаточно безопасным, чтобы гарантировать, что OpenDKIM подписывает сообщение.

Вы должны указать inet_interfaces к мосту докеров (docker0) в конфигурации postfix, расположенной по адресу set /etc/postfix/main.cf

inet_interfaces = <docker0_ip>

Подробнее о внутренней работе см. отправка электронной почты из докера через постфикс, установленного на хосте

Это наполовину ответ или, по крайней мере, наполовину проверенный, поскольку в настоящее время я работаю над той же проблемой. Я надеюсь, что кто-то поможет раскрыть то, что я упустил.

Ответ от OP (Дэвид Грейсон) звучит для меня как повторное изобретение почтовой катушки postdrop, но использование этой почтовой катушки звучит как многообещающий подход, так что вот к чему я пришел.

Интерфейс совместимости / usr / bin / sendmail, предоставляемый postfix, передает почту в postdrop, что является sgid postdrop, позволяя хранить почту в очереди maildrop в / var / spool / postfix / maildrop. Это должно происходить в контейнере докера. Остальная часть постфикса, надеюсь, не должна запускаться в контейнере.

Итак, я размещаю / var / spool / postfix / maildrop и / var / spool / postfix / public. Я могу получать почту, доставляемую в / var / spool / postfix / maildrop в среде хоста, поскольку я смонтировал каталог очереди maildrop. Потому что я установил /var/spool/postfix/public, maildrop может сигнализировать pickup забрать почту из очереди. К сожалению, если я не позабочусь об этом, задействованы uids и gids, а это означает, что сборщик в каталоге хоста не может читать файлы спула, и, что еще хуже, установка постфикса нарушает разрешения в каталоге maildrop в среде хоста.

Тем не менее, похоже, что это работает:

$ cat Dockerfile 
FROM debian:jessie
# Ids from parent environment

    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 myemail@example.com

$ sudo docker build   .
...
Successfully built 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-реле, на которое оно отправляется, недоступно. Если это реле на том же хосте, то вероятность проблем с сетью мала, но мне нужно быть осторожным с тем, как я перезапускаю контейнер реле.
  • производительность?
  • Если проходит большой пакет почты, начинает ли она отбрасываться?

В общем, подход с общей буферизацией постфиксов с большей вероятностью представляет собой хрупкую конфигурацию для настройки, но с меньшей вероятностью произойдет сбой во время выполнения (ретранслятор недоступен, поэтому почта отброшена).

Я решил, что контейнер будет отправлять почту, записывая ее в файл в определенном каталоге, который будет доступен как из контейнера, так и из хоста как «том» Docker.

Я создал сценарий оболочки mailsender.sh, который читает письма из указанного каталога, отправляет их в sendmail, а затем удаляет их:

#!/bin/bash
# Runs on the host system, reading mails files from a directory
# and piping them to sendmail -t and then deleting them.

DIR=$1

if [ \! \( -d "$DIR" -a -w "$DIR" \) ]
then
  echo "Invalid directory given: $DIR"
  exit 1
fi

echo "`date`: Starting mailsender on directory $DIR"

cd $DIR

while :
do
  for file in `find . -maxdepth 1 -type f`
  do
    echo "`date`: Sending $file"
    sendmail -t < $file
    rm $file
  done
  sleep 1
done

Ubuntu использует выскочку, поэтому я создал файл с именем /etc/init/mailsender.conf чтобы превратить этот скрипт в демона:

description "sends mails from directory"
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 команда.