/var/lib/azumakuniyuki

Sisimaiとか技術的なことはこっちに書いてみようかという試み

クックパッドさん開発のsisitoを動かしてみた

先日、クックパッドさんの開発者ブログにて「Sisimaiを使ったバウンスメールの管理」という記事が公開されました。記事で言及されているsisitoというプロダクト(Sisimaiの解析結果を管理するアプリケーション)が、バウンスメール管理のUIとしてなかなか良い感じでしたので、試しに入れて動かしてみました。というか、クックパッドさんbounceHammerの時から使っていただいてたんですね、ありがとうございます。

環境

sisitoを稼働させるシステム要件やインストール方法は、github.com/winebarrel/sisitoのREADME.mdに書いてはるとおりですが、僕は以下の環境で動かしてみました。

構築

git clone sisito

Ruby版Sisimaiを開発しているとはいえ、Railsは作法も知らない程度の知識なので、動くまでに少々試行錯誤しました。

$ cd /usr/local
$ sudo git clone https://github.com/winebarrel/sisito.git
...
$ cd ./sisito
$ bundle install
...
Installing rake 12.0.0
Installing concurrent-ruby 1.0.5
...
$ sudo gem install mysql2
...
$ sudo gem install sisimai
...

sisito/usr/local/sisitoにcloneして、まずは必要なRubyGemsを入れる、って作業ですね。続いて、MariaDBに接続するためのmysql2とバウンスメールの解析に必要なsisimaiを入れました。

Railsの準備

まずはcloneしたディレクトリのconfig/database.ymlで、DB名をsisitoに変更しました。その後の作業で、Rails周辺の作法に詳しくないこともあり、README.mdの手順通り(コピペで)に進めたものの途中で「?」となり、少しの試行錯誤が必要でした。

development:
  adapter: mysql2
  encoding: utf8
  reconnect: false
  database: sisito
  pool: 5
  username: root
  password:
  host: localhost
  variables:
    sql_mode: TRADITIONAL # required
$ bundle exec db:create
bundler: command not found: db:create ←ん?

ここでbundle execの動作を調べて、次のコマンドで実行してみると意図した通りの動作になりました。

$ bundle exec rake db:create ← "rake"って入れなアカンのかな?
Created database 'sisito'

$ bundle exec rake db:migrate
== 20170125054953 CreateBounceMails: migrating ================================
-- create_table("bounce_mails")
   -> 0.0093s
== 20170125054953 CreateBounceMails: migrated (0.0094s) =======================

== 20170125094947 CreateWhitelistMails: migrating =============================
-- create_table("whitelist_mails", {:force=>:cascade, :options=>"ENGINE=InnoDB DEFAULT CHARSET=utf8"})
   -> 0.0093s
== 20170125094947 CreateWhitelistMails: migrated (0.0094s) ====================

あとは、UIで見るためのバウンスメールを解析したデータをsisitoのデータベースに突っ込んで、Railsアプリケーションサーバ127.0.0.1だけで起動して、手元のMacからSSHトンネルを通して、ブラウザでアクセスして眺めてみる、です。

バウンスメールを解析して突っ込む

Sisimaiの開発用にだいたい2200通ぐらいのバウンスメールを保持していて、それを全部解析してデータベースに突っ込んでみました。解析してDBにデータを入れるスクリプトsisitoのREADME.mdに記載されているサンプルスクリプトをそのままコピーして、DB名を変えて、MAIL_DIR$*.shiftに変更(スクリプトの第一引数にバウンスメールの入ってるディレクトリを指定したいので)して、サラサラっと解析結果を突っ込みました。

そのままRubyのコードを頂いただけのスクリプトですが、解析そのものはPerl版Sisimaiで実行して、sisitoのDBスキーマに合う形で解析結果のデータを変換してDBに突っ込んでも大丈夫です。あくまでsisitoはフロントエンドですので、バッチ処理Perlで書くこともできます。

$ ./update-sisito-db /var/tmp/bounces ←開発用サンプルを解析したのをDBに突っ込む
$ bundle exec rails server -e development -p 4401 -b 127.0.0.1
=> Booting Puma
=> Rails 5.0.3 application starting in development on http://127.0.0.1:4401
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.8.2 (ruby 2.3.4-p301), codename: Sassy Salamander
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://127.0.0.1:4401
Use Ctrl-C to stop

CentOSが動いているホストの4401番ポート(4401=シシマイ)でアプリケーションサーバがLISTENしたので、手元のMacからトンネルを掘ってみます。

$ ssh -L4401:127.0.0.1:4401 {CentOSが動くホスト} -p 2222 -l kijitora
...

sisitoの画面を確認

手元のMacの4401番ポートを入り口としてSSHトンネルを掘ったので、ブラウザでhttp://127.0.0.1:4401を開いて確認しました。

f:id:azumakuniyuki:20170531162242p:plain

sisitoのロゴがカワイイです。

表示するデータの期間は、730日にしてブラウザで確認しました。Sisimai開発用のサンプルであるバウンスメールはタイムスタンプがここ10年とか15年の広範囲に散らばっているので、直近14日ではほとんど表示されなかったから、という理由です。日常的にメールを配信していて、sisitoでバウンスを管理するなら14日で十分でしょう。お正月やゴールデンウィークの休みよりも長い(もっと長い休みがあっても良いけど)はずですし。

f:id:azumakuniyuki:20170531162621p:plain スマートフォンでもキレイに表示されるResponsiveな画面

ホワイトリスト

Sisimaiはあくまでバウンスメールを解析して構造化データを作るだけのライブラリです。しかし、実際の運用では「メールが来ない」「調べたら過去に何回もバウンスしていたので配信を止めてます」「パスワードリセットのメールが届かなくて困る」「解除します」みたいなやりとりが運営とユーザの間で多々発生するものです。

f:id:azumakuniyuki:20170531164355p:plain f:id:azumakuniyuki:20170531164408p:plain

sisitoホワイトリスト管理機能は、運用担当者がWebのUIから問い合わせのあったメールアドレスを検索して、現在の状況確認とホワイトリストへの登録が容易にできるところが良いですね。エンジニアでなくても(いや、エンジニアであってもDBを触れる技術のある運用担当であっても直接DBを触るのはインフラ担当がメンテナンスする時ぐらいですし普段は触らない)確認と解除のオペレーションが円滑に短時間でできるのが大きな利点です。

ホワイトリストの管理とメンテナンス

Sisimaiは、その前身であるbounceHammerの更に前身であるバウンスメール解析スクリプト(過去に勤めていた会社で作ったSendmail専用・スクリプトのファイル名すら忘れた)が源流といえば源流なのですが、当時は運営から「このユーザさん、メールが来ないって」「調べます、あ、ドメイン指定拒否してるから除外対象にしてます」「解除してください」「解除します(ホワイトリストに入れます」ってやりとりをしていました。解析とDBに突っ込むのと、配信時に参照するぐらいの仕組みしかなかったので、手動でホワイトリスト登録をする必要があったわけです。

都度解除していましたし、ホワイトリストに入れる割合もさほどではなかったのですが、長期間運用しているとホワイトリストに入れたアドレスがたくさんバウンスしているような状況になります。そうなってくるとメールの遅延がちょいちょい出るようになって、配信全体に悪影響が生じてしまいます。

メールが遅延すると結局、僕が遅延処理専用のメールサーバに、リレー先でも遅延しないように加減しながらリレーさせる作業をしていました。

実際のところは、登録系(登録しましたメールとかパスワードリセットメールとかいろいろ大事なやつ)は専用のメールサーバを多段構成にして、なるべく10分以内に遅延せず配信できるようにしていて、お知らせ系はまぁ多少遅延しても良い(ただし携帯電話宛は夜中に鳴らないように遅延しても22時-8時はキューに溜めてた)感じで配信サーバを構成していました。

なんか話がそれた気がしますが、一旦ホワイトリストに入れたメールアドレスでも、ずっと存在するわけではない(メールアドレスが変更されたり受信拒否設定に変更されたり)ので、一定回数バウンスしたら再度ホワイトリストから除外する仕組みも必要やなぁってことです。

Webでのバウンスメール管理UI

経験上、運営側から容易にホワイトリストの制御ができるインターフェイスは必要であると知っていたので、bounceHammerにはWebUIをつけていました。ただ、bounceHammerを案件で使用する際に、あるいは導入先さんに使用実態をヒアリングする過程で、WebUIはあまり使われていない点、実装する時に解析結果をサイトやアプリケーションのDBに入れるから管理UIは不要という点が浮き彫りになってきました。

事例の一つとしては、サイトに解析結果の参照機能を組み込んでユーザがログインした画面に「この前送ったメールやけど、エラーで返ってきてるから確認してや」的なメッセージを出して、確認メールを送るボタンがついていて一定時間以内にバウンスしなかったら自動でホワイトリストに入る仕組みがありました。

こういったことから、バウンスメールの管理UIは一定の需要があるんやけど、自分もあまり使わないし、そうしているうちにWebUIが依存しているモジュールの幾つかがメンテされなくなって、維持するのが難しくなったこともあり、Sisimaiは解析だけするライブラリとして開発しました。

Sisimaiの開発に必要なもの

いろんなパターンのバウンスメール

こういうと大言壮語な印象が強くなる、というか大言壮語そのものなんですが、Sisimaiは世界最高精度の解析能力を持つ世界標準のバウンスメール解析ライブラリを目指して開発しています。バウンスメール解析の分野で世界征服をする、といった感じです。って書いておくと後に引けないし撤回もしにくいので文字として書いておきます。

で、精度を維持しつつ高めるには、なるべく多くのバウンスメールが必要になります。同じパターンのバウンスメールばかりでは解析精度の向上に繋がらないので、いろんなパターンのバウンスメールがいります。現在、開発用に保持しているバウンスメールは2200通ぐらい(全部は公開していない)で、Date:ヘッダが20世紀な古いバウンスメールもあります。

リポジトリのテストコードに全部書いているのですが、このメールはこのバウンス理由になる、というのを2200通全部定義していて、コードを変更するたびにmake testが全部通るように開発しています。

なので、Sisimaiをお使いのユーザさん、もしも解析結果のreasonの値がundefinedonholdになっているバウンスメールがあったら、開発元に提供していただけると大変助かります。 IPアドレスとかホスト名とか、メールアドレスのローカルパートとか、秘匿すべき情報は適当な文字列に変えて、リポジトリに突っ込んだPull-Requestを送ってもらったり、Issueに書きなぐってもらっても構いません。

あ、それと、Microsoft Exchange Serverとかの商用MTAが作るバウンスメールのサンプルもたくさんあると嬉しいです。というのも、SendmailPostfixなどのオープンソースなMTAはソースコードを読んで内部のエラーメッセージを直接Sisimaiのコードに正規表現として実装しているのですが、商用MTAやアプライアンスのエラーメッセージとエラーメールの構造は、サンプルとして入手できたバウンスメールからしか得られないので、そのあたりも重厚にしたいなと思っています。

プルリクも

SisimaiへのPull-Requestですが、意図がはっきりしていて開発方針に一致するものであればテストが無くてもテストが落ちても、一旦いただいたPull-Requestは取り込む方針です。そのあとで僕がテストを実装したり、手直ししたりしてますので。なので、前述の解析結果が変なバウンスメールでも、そもそもバウンスメールなのに解析できなかったものでも、コードそのものでも、いろいろ(少し前にコメントの文章で冠詞が無いのを直すプルリクもらった)フィードバックをいただけると嬉しいです。

関連プロダクトとか導入事例とか

Perl版Sisimaiでは2月22日(ネコの日)にリリースされたMicrosoft Azureで動くSisimaiのAzure Functionに続いて、Ruby版Sisimaiのフロントエンドであるsisitoがリリースされて「今年は良い年やなぁ」と思いつつ、関連プロダクトを作っていただいて開発者冥利につきるところです、ありがとうございます。

それと、やはり開発者としては「うちのシシマイはどこでどんな仕事をしてるんやろ?」ってのも気になりますので「うちもSisimaiを使ったってるで」ってところがありましたら教えてもらえると同じく嬉しいです。