人間とウェブの未来

「ウェブの歴史は人類の歴史の繰り返し」という観点から色々勉強しています。

メール送受信システムを幸せにするべく受信サーバdovecotのmruby拡張を書き始めた

以前、メール送信(SMTP)サーバの振る舞いを制御するために、mrubyで機能拡張できるpmilterというMilterプロトコルベースのミドルウェアを作りました。

hb.matsumoto-r.jp

その流れで、メール受信(POPやIMAP)サーバの振る舞いも同様に制御することによって、トータルでメール送受信システムの流量制限だけでなく、アクセス制御や不正な認証の検知、DoSのようなアクセスや大量メールの送受信をうまくプログラマブルな設定を書くことによって解決していきたいと思いはじめ、昨日からpmilterだけでなくメール受信サーバの開発にも取り組みはじめました。

それらのメール系ミドルウェアを組み合わせることにより、既に使われているメールの定番ミドルウェア(Postfixやdovecot)はそのままに、平易に導入できて、性能を落とすことなく自由にRubyで拡張ができるようになる基盤を作りたいと考えています。

メールは使いたくないと言われる時代の中でも、やはり今でもメールでのやり取りというのはなかなか避けることができず、極端に言えばWeb以上に、メールが止まったりスパムや不正アクセスがあったり、IPレピュテーションの問題でメールが止まった時には、クレームに繋がったり、サービスの売上低下などに繋がったりと、未だに社会的に影響の大きいプロトコルだと言えるでしょう。

そこで、これからメール受信サーバを弄るならどのミドルウェアかなぁと思ってtwitterで色々聞いていると、dovecotでしょ、という話を沢山頂いたので、昨日と今日でdovecot-mruby-pluginのプロトタイプを作りました。プロトタイプといっても、先ずはビルドシステムとテスト環境を作るところに力をいれて、今後の開発をやりやすくして、概ねプロトタイプが動くような状態を目指しました。後は地道に引き続き機能開発をしていく予定です。

github.com

大体READMEの通りですが、以下のようにまずはMRUBYコマンドを作って引数のコードを実行できるようにしました。ドキュメントはほぼなかったので、dovecotの実装を読みながら書いています。

$ telnet 127.0.0.1 6070
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
* OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE AUTH=PLAIN] Dovecot ready.

1 login test testPassword

1 OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS THREAD=ORDEREDSUBJECT MULTIAPPEND URL-PARTIAL CATENATE UNSELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH LIST-STATUS BINARY MOVE MRUBY] Logged in

1 MRUBY 3*9    #=> 3*9は27という結果が返ってきている
1 27 (0.001 + 0.000 secs).

引き続き受信サーバの振る舞いをIMAP/POPのセッションレベルから多岐に渡ってmrubyで制御できるように実装していこうと思います。

最終的にはdovecot-mruby-pluginとpmilterを連携させて、その基盤の上にRubyでイケてる振る舞いを実装したメールシステムを作って、メールのシステムが幸せになる時代を見てみたいものです。

ngx_mruby v1.20.0で動的listener設定をサポートしました

タイトルの通り、ngx_mrubyのhttpモジュールとstreamモジュール両方で、mrubyによる動的Listener設定をサポートしました。

動的Listenerとは、nginxのlistenの設定をmrubyで書いて、起動時に動的に設定を読み込めるようにできる機能です。以下の例を見た方が分かりやすいかと思います。

# $ ulimit -n 60000

worker_processes  1;

events {
    worker_connections  30000;
}

daemon off;
master_process off;
error_log logs/error.log debug;

stream {
  upstream dynamic_server {
    server 127.0.0.1:8080;
  }

  server {
      mruby_stream_server_context_code '
        (20001..30000).each { |port| Nginx::Stream.add_listener({address: port.to_s}) }
      ';

      mruby_stream_code '
        c = Nginx::Stream::Connection.new "dynamic_server"
        c.upstream_server = "127.0.0.1:#{Nginx::Stream::Connection.local_port * 2}"
      ';

      proxy_pass dynamic_server;
  }
}

http {
    server {
        mruby_server_context_handler_code '
          s = Nginx::Server.new
          (20001..30000).each { |port| s.add_listener({address: (port * 2).to_s}) }
        ';

        location /mruby {
          mruby_content_handler_code 'Nginx.rputs "#{Nginx::Connection.new.local_port} sann hello"';
        }
    }
}

このように書くと、nginxのTCPロードバランサが20001ポートから30000ポートまでListenした上で、接続のあったポート番号の数値の2倍のポート番号へTCPプロキシします。さらに、nginxのhttp側では20001ポートから30000ポートの2倍のポート番号でListenし、そのポート番号に応じてhelloを返します。

この設定でnginxを起動すると、これまでlistenディレクティブを沢山並べる必要があり、CRubyとerbなどを使ってプロビジョニング時にゴニョゴニョ書かなければいけなかった処理を、スッキリとmrubyで書くことができるようになります。また、ポート番号をデータベースやホストの状態に合わせていい感じにリッスンするような実装も可能でしょう。

$ netstat -lnpt | grep nginx | wc -l
20000

上記のような設定でcurlでアクセスすると、以下のようにレスポンスが返ってきます。

[ubuntu@ubuntu-xenial:~]$ curl http://127.0.0.1:20001/mruby
40002 sann hello
[ubuntu@ubuntu-xenial:~]$ curl http://127.0.0.1:20002/mruby
40004 sann hello
[ubuntu@ubuntu-xenial:~]$ curl http://127.0.0.1:29999/mruby
59998 sann hello

ふむふむなるほどべんり!

ということで皆様是非ngx_mruby v1.20.0の新機能をご活用ください。

github.com

nginxのworkerプロセス数をCPUコア数の倍数で自動的に設定できるモジュールを書いた

nginxはworkerプロセスの数をCPUコア(スレッド)数で決定するworker_processes autoという便利設定があります。

これが多用されているのは、nginxがノンブロッキングでリクエスト処理を行うため、コンテキストスイッチなどを考慮した場合に、コア数で立ち上げておけば効率よくCPUを使い切れるという前提があるからです。

一方で、例えば僕の用途では、現在画像の処理だったりとか、ngx_mrubyのようにリクエストの過程で一部ブロッキングされるような処理も増えてきているため、コア数以上の値に設定しておいた方が性能を発揮できるような状況も増えてきています。

そうなると、現状、非常に便利な設定であるauto設定を使えずに静的に数字を設定する必要があり、例えばサーバリプレース時には古いコア数を考慮した値になっていたりして、リプレース後にCPUのコア設定がそのままで性能が出せていないという事故が起きる場合もあります。また、だいたいCPUコア数の2倍ぐらいにしておけば、処理を効率的にさばけることがわかっていても、サーバのコア数に応じてworkerプロセスの値を静的に書き換えないと行けないこともあり面倒でした。

そこで、auto設定によってコア数を自動で取得した上で、そのコア数の何倍のworker数とするかをルールとして記述できるようにしておけば、サーバのコア数に依存しないworker数を定義できる上に、設定が動的になりメンテナンスしやすくなります。

ということで、論文の解放感から勢いで作りました。

github.com

設定はREADMEの通りで、例えばCPUコア数が4の場合は、autoで設定すると4個のworkerプロセスが起動します。そのような状況で、

worker_processes        auto;
worker_processes_factor 3;

のように設定すると、4コアの3倍の12個のworkerプロセスとして起動するようになります。

是非ご活用ください。

また、倍数だけでなく、autoで取得した値を色々カスタマイズできると良いなとは思っているので、何か案がありましたら是非PRを頂けるとマージします。

Webサーバのベンチマーク結果をレスポンスタイムの時系列データとして計測する簡単な方法

単純に特定のURLに対してミドルウェアの性能を計測したい場合などには、今でもabやwrk、h2loadのようなシナリオベースではないシンプルなベンチマークはとても有用です。

一方で、最近ではgatlingやtsungといった、豊富な機能やリッチな計測結果を取得できるベンチマークソフトウェアも豊富になってきました。

ただ、例えば、単に「Webサーバのベンチマーク結果をレスポンスタイムの時系列データとして計測したい」時に、僕のようなめんどくさがりの人間はcliで適当にオプションを渡して1回の実行でシュシュシュっと取りたいものですが、それができるツールが見当たらず、うーむ、gatlingやtsuningでやるかなぁとおもっていたところ、なんとApache Bench(ab)で簡単にシュシュシュっとできてしまうことに気づいたのでした。

続きを読む

ngx_mrubyで最初のHTTPSアクセス時に自動で証明書を設定可能にするFastCertificateの提案とPoC

Let’s EncryptやACMEプロトコルによるDV証明書取得の自動化に伴い、証明書の取得と設定が簡単になってきました。

一方で、ACMEをツール化したものが増えるに従って、ACMEってそもそもどういう動きになっているのか、とか、自分たちの用途でどういう使い方がありえるのかとかが余計にわかりにくくなってきており、どこまで自動化できるかもよくわからない場合が多いのではないでしょうか。

続きを読む

LinuxカーネルのTCPスタックとシステムコールの組み合わせによる手法よりも高速にポートのListenチェックを行う

まずは前回の記事で盛大な誤認をしていたことを訂正しなければなりません。

hb.matsumoto-r.jp

前回の記事では、高速にリモートホストのポートチェックを3パケットで実現する実装を行うために、RAWソケットとユーザランドの簡易TCPスタックを実装してパケットを放出しましたが、カーネルのTCPスタックによって自動的にRSTパケットが返されるため、RSTパケットが返されるよりもはやくrecvfromしないとSYN+ACKを受け取れないと述べました。

続きを読む

高速にリモートホストのポートがListenしているかを調べる

hb.matsumoto-r.jp

以下のエントリは一部誤認が含まれていたので、上記エントリにその旨をまとめましたので御覧ください。


とある事情でミドルウェア上から高速にリモートホストのポートのListenチェックをしたくなりました。ローカルホストのポートであれば、/procやnetlinkなどを使って素早くチェックする方法がありますが、今回は対象がリモートホストなのでソケットでなんとかする必要があります。

そこで、誰もがまず思いつくのは、connect()システムコールによってリモートホストのポートに接続しにいって、connectできればOK、できなければNGと判定する方法があり得るでしょう。(高負荷時に接続できないパターンはListenしていないと判定してよい)

続きを読む