システムプラットフォームチーム SREのid:MysticDollです。
この記事は、はてなの SRE が毎月交代で書いている SRE 連載の5月号です。先月分は id:heleeen さんの Mackerel で行った障害対応演習を紹介します でした。
先月 Platform Engineering Meetup #8 にて 「はてなにおけるメール基盤とDMARC対応」というタイトルで登壇させて頂きました。
この記事では資料では紹介しきれなかった、メール送信基盤の監視で気をつけるべきSMTPのステータスの仕様とそれらを踏まえた監視方法について紹介します。
メールのステータス形式
SMTPにおいて、メールのステータスは SMTP Reply CodeとDelivery Status Notification(以下DSN)の2つで構成されています。
メールのエラーを判別する際はこれらの組を確認して内容を区別することになります。
また、SMTP Reply Codeは必ず含まれていますが、DSNについてはMTAによっては含まれていないケースなどもあり、ログからの監視などでは少し注意が必要です。
SMTP Reply Code
SMTP Reply Codeについては RFC5321 で定義されており、正規表現では [2-5][0-5]\d
という形式の3桁で表現できます。
1桁目
The first digit denotes whether the response is good, bad, or incomplete.
引用元: RFC5321
1桁目はレスポンスの状態(正常, 異常, 未完了)を示し、具体的には以下の内容となります。
- 2: 正常終了
- 3: コマンドが受理されたが保留されている状態(他コマンドで追加の要求が必要)
- 4: コマンドは非受理、一時的なエラー状態
- 5: コマンドは非受理、永続的なエラー状態
2桁目
The second digit encodes responses in specific categories
引用元: RFC5321
2桁目はレスポンスのカテゴリを示し、以下の内容で表現されています。
- 0: 構文関係のカテゴリの応答
- 1: ステータスやヘルプに関するカテゴリの応答
- 2: 伝送チャネルを参照する応答
- 3: 未指定
- 4: 未指定
- 5: メールシステムのステータス関連の応答
3桁目
The third digit gives a finer gradation of meaning in each category specified by the second digit.
引用元: RFC5321
3桁目では2桁目のエラーカテゴリに対してエラーの詳細を示しています。
実際の例はRFC5321の方を参照すると良いですが、別RFCで新たに追加されたコードなどもあるので実際の運用では送信先メールベンダのサポートページなどで調べた上で扱うことをおすすめします。
DSN
DSN自体は RFC3464 によって定められ、実際の判別に使うStatus Code部分については RFC3463によって定義されています。
The syntax of the new status codes is defined as: status-code = class "." subject "." detail
class = "2"/"4"/"5"
subject = 1*3digit
detail = 1*3digit
このStatus Codeは正規表現によって [245]\.\d{1,3}\.\d{1,3}
のように表現でき、.
区切りの各数字は以下のような対応でメールのステータスを表現しています。
1つ目
1つ目の数字はStatusのクラス(成功, 失敗等)を示します。
- 2: 成功
- 4: 一時的なエラー
- 5: 永続的なエラー
2つ目
2つ目の数値はStatusの種別を示します。
- 0: その他または未定義
- 1: アドレス指定ステータス
- 2: メールボックスステータス
- 3: メールシステムステータス
- 4: ネットワークとルーティングステータス
- 5: メール配送プロトコルステータス
- 6: メッセージコンテンツまたはメディアステータス
- 7: セキュリティまたはポリシーステータス
3つ目
3つ目の数値は更に詳細な区分を示します。2つ目の数値と組み合わせてStatusを表現しています。
具体的なStatusとの対応については RFC3463 及び、 RFC5248 で指定されたStatus Codesのレジストリである IANAのページを参照すると良いです。
しかし、実際にメールベンダがこのIANAの表に対応した実装でDSNを返してくれるかは確実ではないため、実際の運用ではReply Codeと同様にメールベンダのサポートページなどから調べた上で取り扱うことをおすすめします。*1
(Gmailの例)
Postfixのログからのエラーのメトリクス化
以上のメールのエラー形式を踏まえて、最後にPostfixのログからメトリクス化しているクエリを紹介します。
はてなのメール送信基盤では cloudwatch-logs-aggregator を用いてログを抽出・メトリクス化しています。
実際には以下のようなコードでクエリを設定しています。
filter @message like /said/ | parse @message /(said: |\()(?<error_code>[2-5][0-5]\d)[ -](?<dsn_in_message>[245]\.\d{1,3}\.\d{1,3})?/ | parse @message /dsn=(?<dsn_prop>[245]\.\d{1,3}\.\d{1,3})/ | stats count(*) as count by concat(error_code, "-", replace(coalesce(dsn_prop, dsn_in_message), ".", "_")) as mail_error
Postfixのログの形式は以下のような形となっています。
Feb 05 04:54:08 mailoutbound-fb postfix/smtp[1285887]: 38E2AC2616: to=<****@gmail.com>, relay=alt1.gmail-smtp-in.l.google.com[142.250.141.26]:25, delay=974719, delays=974661/55/2.3/0.22, dsn=4.2.2, status=deferred (host alt1.gmail-smtp-in.l.google.com[142.250.141.26] said: 452-4.2.2 …(省略))
SMTP Reply Codeは MTA からの応答部分に said: <ReplyCode> ...
という形、または (<ReplyCode> ...)
という形で含まれています。
DSNについては
dsn=<DSN>
と入っているパターン- MTAからの応答部分に
said: <ReplyCode>-<DSN>
という形式で入っているパターン - 上記2つの両方があるパターン
- 上記両方がないパターン
と、一筋縄では行かない形式で含まれています。
そのため、クエリ内の抽出ではメッセージ内のDSNと、dsn=<DSN>
となっているDSNを個別に抽出して変数とし、CloudWatch Logsのクエリ関数 coalesce
を用いてどちらかを使用する形で抽出しています。
まとめ
メール送信基盤の監視で注意が必要な、SMTPのステータス仕様及びPostfixにおけるエラー監視方法について紹介しました。
SMTPのステータス仕様は思った以上に複雑かつ、読むべきRFCも多く監視のために重要な点を見つけるのが大変でした。
この記事がメール送信基盤の監視を構築しようとしている方の一助になれば嬉しいです。
*1:IANAではDKIM認証に関するエラーは X.7.20 X.7.21 X.7.22 で取り扱われているが、Gmailの場合は 421-4.7.26 で纏めて取り扱われているなど、これらはベンダも修正対応するケースがあるため追従が難しい