Docker上のLaravelとホストのsendmailでメールを送信する

課題

Docker上のLaravelアプリに集まった情報をメール送信する、というのが今回の課題だ。
あるアプリを開発していて、そのアプリで収集した文章をメール送信したいのだが、LaravelがDocker上にあるので、Docker上でsendmailの設定をするか、ホストとの連携が必要だ。

最終的な解決策

結論としては、Laravelからsendmailのフォーマットに合わせたメール内容を出力し、ホストのsendmailを使ってメール送信する、という方法を取った。
Docker上でメール送信の設定をしたりなどの面倒な作業をすることを避けるためだ。

詰まったところ

1. Docker内のsendmailを実行しようとしたこと

当初、Laravelのメール送信機能を使って実装していた。
開発中だったので.envファイルはMAIL_DRIVER=logにして、ログに出力される本文等を見ながら「よしよし、実装できたな」なんて思い、いざ本番環境でMAIL_DRIVER=sendmailにしてテストしてみるとメールが送信されない。

本番環境のsendmailは以前に自身のGmailアドレスを使って送信できるように設定しており、実装時も普通に稼働していた。
なのに動かない。
何故だ。

答えは簡単でLaravelが叩こうとしていたsendmailはDocker上にあるものだったのだ。
私はこの問題に気づくまでにしばらくかかった。
どのシステムがsendmailを実行するのかという主語をあいまいに捉えていたからだ。
この場合で言うと「Docker上のOS」が実行するのだが、そこを「Dockerを動かしているホスト」が実行すると勘違いしていたのだ。
設計書作成時などで散々主語を明確にと言われる理由を痛感した。

2. 「Input device is not a TTY」

定期的にメールを送信するためにCRONを使おうとしていた時の話だ。
sendmailコマンドにパイプで渡すためのメール本文を作るコマンドをLaravelで作成し、

$ docker-compose exec php php artisan email:send

をCRONで実行してスケジューリングを行おうとした。
ところが「Input device is not a TTY」とエラーが出る。

これはCRON実行の際にはTTYは割り当てられないためである。
ところが公式によるとDockerComposeのexecコマンドはデフォルトではTTYが割り当てられる。
そのため-Tオプションを付けてexecコマンドを実行し、TTYを割り当てないようにする必要がある。

参考サイト

3. CRON実行時のエラーログの出し方

上記2で詰まっていた時にCRON実行時のエラー出力を取得する必要があった。
だがファイルディスクリプタ等の知識があいまいでエラー出力をファイルに書き出す方法がわからなかった。
なので簡単に調べた。

$ docker-compose exec php php artisan email:send > /home/user/exec.log 2>&1

という風に「2>&1」を末尾につければ良い。

Linuxにおけるファイルディスクリプタは0:標準入力、1:標準出力、2:エラー出力という風になっており、2>&1とすることでエラー出力を標準出力に統合している。
その上でファイルに書き出しているので、exec.logには標準出力とエラー出力の内容が出力される。

まとめ

今回の件でシステム開発で問題解決するときは主語を明確にする必要があると強く感じた。
設計書などでも厳密な指摘が入るのは問題を具体的に捉えるためであって、ただ面倒な作業ではないのだ。

今後何か詰まった際には「明確な主語を用いて文章化する」ということを覚えておこうと思う。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA