たごもりすメモ

コードとかその他の話とか。

msgpack-inspect を作った

MessagePackJSONぽいけどバイナリでデータサイズが小さく抑えられ、またシリアライズ/デシリアライズが比較的高速であるとして広く使われておるところであります。なんか #linedevday にいるせいで口調がおかしいな。

が、バイナリなせいでデータを作ったあとその内容が正しいかどうか確認するのがいまいち面倒くさく、いちいちunpackするスクリプトを書いて中身を見る必要がある。JSONみたいに目で見て思った通りの表現になっているかどうかを判別するのは普通の人間にはなかなか難しい*1
このため開発時にデータがバグってるのかコードがバグってるのかが分かりづらく、MessagePackを利用したアプリケーションおよびサービスを開発する上で問題になっていた。

ので、MessagePackのバイナリデータを食わせると内容を分かりやすくダンプしてくれるツールを作った! のです!

msgpack-inspect

github.com

Rubyで書いてあるがmrubyでクロスコンパイルしてあり、releaseページから各プラットフォーム向けのバイナリがダウンロードできる、ので、ダウンロードして展開すればすぐ使える。あとrubygems.orgにもリリースしてあって、CRuby/JRubyがあればその上に gem install msgpack-inspect でインストールもできる。

これはどういうものかというと、msgpackのバイナリが書かれているファイルを引数にして起動すると、以下のように(デフォルトではyamlで)各バイナリオブジェクトの format(型情報)、ヘッダ部の16進ダンプ、データ部の16進ダンプ、(存在する場合は)長さやtypeなどを表示し、その上でunpackした結果のデータも表示してくれる。

$ msgpack-inspect msgpack-example.bin 
---
- format: "false"
  header: "0xc2"
  data: "0xc2"
  value: false
- format: "true"
  header: "0xc3"
  data: "0xc3"
  value: true
- format: "nil"
  header: "0xc0"
  data: "0xc0"
  value: null
- format: "fixint"
  header: "0x01"
  data: "0x01"
  value: 1
- format: "uint8"
  header: "0xcc"
  data: "0xff"
  value: 255
- format: "fixarray"
  header: "0x93"
  length: 3
  children:
    - format: "fixstr"
      header: "0xa1"
      length: 1
      data: "0x61"
      value: "a"
    - format: "fixstr"
      header: "0xa1"
      length: 1
      data: "0x62"
      value: "b"
    - format: "fixstr"
      header: "0xa1"
      length: 1
      data: "0x63"
      value: "c"
- format: "fixmap"
  header: "0x83"
  length: 3
  children:
    - key:
        format: "fixstr"
        header: "0xa4"
        length: 4
        data: "0x686f6765"
        value: "hoge"
      value:
        format: "fixint"
        header: "0x6f"
        data: "0x6f"
        value: 111
    - key:
        format: "fixstr"
        header: "0xa3"
        length: 3
        data: "0x706f73"
        value: "pos"
      value:
        format: "fixarray"
        header: "0x93"
        length: 3
        children:
          - format: "fixstr"
            header: "0xa1"
            length: 1
            data: "0x78"
            value: "x"
          - format: "fixstr"
            header: "0xa1"
            length: 1
            data: "0x79"
            value: "y"
          - format: "fixstr"
            header: "0xa1"
            length: 1
            data: "0x7a"
            value: "z"
    - key:
        format: "fixstr"
        header: "0xa4"
        length: 4
        data: "0x7a65726f"
        value: "zero"
      value:
        format: "fixarray"
        header: "0x90"
        length: 0
        children: []

もちろん map や array などは子要素も再帰的に解析してダンプしてくれるという超絶便利なもので、これがあれば開発が進みまくることは間違いない。
なおダンプの出力フォーマットはオプションで指定でき、現状では yaml, json および jsonl *2 をサポートしていたり、引数としてハイフンを与えることで標準入力からの読み込みができたりする。

$ msgpack-inspect -h
Usage: msgpack-inspect [options] FILE"

Options:
  -f, --format FORMAT   Output format of inspection result (yaml/json/jsonl) [default: yaml]
  -r, --require LIB     (Not supported in binary executable)
  -h, --help            Show this message
  -v, --version         Show version of this software

CRuby/JRuby 実装に限り、オブジェクトのunpack時に ext type 定義が書かれているファイルを読み込んでそれを使うこともできる。MessagePack::DefaultFactory に register_type しておく必要があるけど。これで ext type を使っているデータ表現でもテストはばっちり!

ということで、大変便利なものを作った気がします。みなさんぜひどうぞ。

*1:……が、じつは訓練されるとだんだん分かるようになってくる、んだけども

*2:トップレベルオブジェクトひとつごとに1行のJSON表現で出力

#ISUCON 6 予選を戦ってなんとか通過した

今年のISUCONはどうするかなあ、出ないのも寂しいけど同僚や元同僚と出るのもなー……とか考えてたんだけど飲みにいった時に声かけてたらうまい感じのチームが結成できたので @joker1007 @tnmt との3人でチームJingisukan*1として出ることにしました。やった!

事前には一度だけ集まって、Azureをちょっとさわってみたり全体の流れを確認したり、とか。集まってのちゃんとした練習は結局やらなかった。個人的にはalpの使い方とかを事前に学習したくらいかな。

予選参加は2日目にしたんだけど、事前に2日目参加メンバーをidobataで見てゲエッとか思ってた……が、真の恐怖は1日目のトップ3通過チームリストを見たときに訪れたのであった。これシングルプr(ry

予選経過

開始前にちゃんと集まってわいわい。

開始後しばらく(1時間くらい)は事前の打合せ通り、インスタンスの立ち上げ、SSHログイン準備からのコードのgit共有、実際に自分でアクセスしてアプリを確認、アクセスログを変更してPerlRubyでそれぞれ初期ベンチ走行、といったあたりを実施して、それから手が空き次第コードを読んで全員でアプリケーションの中身を把握したり。

  • Perl初期スコア: 3628
  • Ruby初期スコア: 0

このとき isupam とかいう謎のプログラムがあって、うわっなんだこれという警戒心が先に立ち、考え無しに手元環境でのアプリケーション動作を諦めてしまった。なんとなく話しあってAzureのインスタンスでだけやるかーということにしたんだけど、結論だけ言うとこいつは失敗だった。マージ待ちや他の人の作業待ちみたいな時間が増えちゃってたいへん良くなかった。

それはともかくアプリの改修内容について議論。当初話した内容は以下の通り(だったと思う)。

  • static fileはnginxで返す
  • isutarのisudaへの統合、マイクロサービス的なやつ撲滅
  • / における keyword の N+1 の抹殺
  • keywordまわりをいくらかRedis化する
  • starも完全にRedis化する? 付けたユーザ名が入ってるんだよなあ
  • その後 htmlify が遅かったらなんか考える
  • その後 isupam が遅かったらなんか考える

元のアプリが遅過ぎたのと、(後から明確になったことだけど)ログインとかのどう見ても問題なさそうな処理でも大量に遅いレスポンスが出てて、なんだこりゃってなってこの時点ではあまり明確な方針が立てられていなかった。htmlifyが遅いだろうことは見ればわかるけど、というかそこしか明確に問題点になりそうな部分がなくて、本当か? みたいに疑ってたのもある。

まあとにかく確実に必要でやれることをやろう、みたいな感じでお昼前にある程度やっつけた。自分がisutar統合で盛大にハマって時間をロスト。じょーかーさんに助けてもらった。最初はあれこれやってみてもスコアがふた桁とかでウケてたが、まあちょいちょい直した結果無事上がってきたので安心したところでお昼。

  • keyword listのRedis化 + isutar統合 + static fileのnginx配信: 5006

お昼食べたあと、早目に疑念を潰しとこうと思って isupam へのリクエストにかかるレスポンスタイムを全部計測してみた、ら、特にワザと時間を稼いでいるとかいうこともなさそうだったので、あまり考えないことにした。これは正解だったように思う。検討しないといけない要素を順次減らしていくのは大事。
そんなあたりでじょーかーさんがガリガリと修正を入れる。

  • N+1抹殺: 3332、4143
  • Puma化: 5261

ログインまわりとかの軽い処理にやたら時間がかかっていたのは、本当に何か時間がかかる原因があるのか、それともhtmlifyとかの遅い処理にプロセスが占有されたせいで待たされてるだけなのかの区別が付かずちょっと混乱してたんだけど、Puma化により無事そのへんの処理の request time が下がっていったので、待たされてるだけだっためでたしめでたし、となった。今回ちゃんとそのへんは毎回アクセスログを確認しながらやっていけてたのは良かった。alp便利。

自分は htmlify の最適化にとりかかっていた。まずキーワードの置換処理を 2 pass でやっていたのを 1 pass に修正。キーワードにHTMLタグに関連する文字が含まれてたらこの処理は 2 pass じゃないといけないんだけど < も > も無いことは確認したので、まあいいじゃろ、と。
結論から言うと間違いだった……。シングルクォートを含むキーワードが「Kissin' Christmas (クリスマスだからじゃない) 」と「TV's HIGH 」のふたつあることが後から判明したのであった。見付けたときにクリスマスだからじゃない、じゃねー!とか半ギレになってた。しかしこういう確認とかでDBを叩くのとか、もう常様べったりだったので本当に助かった。ISUCONにはインフラまわり担当のスペシャリストがまじ欠かせない。

で、htmlify を 1 pass にしてみてもほとんどスコアは変わらず5000前後のまま。明らかに正規表現マッチが遅いのはわかってるんだけど、Perlにあった正規表現コンパイルモジュールとか Trie とかのが使いたい!と思って rubygems とかを漁ってみるんだけど見付からず。うう、Perlのエコシステムの強力さよ……。。。
他に String#gsub でマッチ対象に固定文字列リストを渡したりできないかなーとか見たり、あれこれもやってたけど、有効そうな手段が見付からず。

このまま悶々と時間が過ぎていって、15時半くらいでようやく htmlify 結果をキャッシュしないとどうにもならん、と決断。
今回のアプリケーションはキーワードが追加されると既存文書がすべて影響を受けるという性質がある上にレンダリング済み文書の生成コストが極めて重いので、正直、内容を把握したときからシングルプロセスアプリケーション向きだなーとは思ってた。思ってたけど、マルチプロセスでもそこに踏み込まないことにはどうにもならん、とようやく覚悟が固まった感じになる。正直、この決断が15時半というのは遅かった。

決断ができたのは cache invalidation にかかるコストが実は思ったより安かったというのもある。転置インデックスとか作りたくないよー、と悶々考えながらふとやってみた LIKE 全文検索が 0.1 秒で返ってくるじゃありませんか。おやおや。データが全部メモリに載ってると競技が変わるなあ。

UPDATE entry SET rendered = NULL WHERE description LIKE '%#{keyword}%'

これだけで cache invalidation できるじゃん! 便利!
データ量小さいからおもむろに entry テーブルで ALTER TABLE して rendered カラムを追加、htmlifyまわりをやるコードあたりをくくり出して適当なスクリプトにまとめて、id 7101までの全件に対してガッと実行した。ちょっとかかったけど10分くらいで完了。同時に keyword 追加時には htmlify を実行して rendered にストア、既存 keyword への description アップデートなら同じく htmlify して更新、description更新も無いなら rendered は触らない、という感じで。
あとは参照時、keywordに対して rendered が存在していればそれを返す、rendered が存在しなければ htmlify してストアした上で返す、あたりの変更をガツッとやって突っ込んだ。これが16時ちょうどだったらしい。

  • pre-rendered: 53872

上位陣が数万点とってるのに俺達は5000点……みたいに果てしなくダウナーな気分だったところ、いきなりスコアが10倍になってチーム全体の雰囲気が超改善した。pre-renderedなコンテンツを返すようにしたことでボトルネックが一気に解消、これまでの変更がようやく全部有効になったんだと思う。戦える、俺達も戦えるぞ!みたいな。

ただしここでエラーが大量に出ていて、なんだこりゃ、と思っていたらキーワードのマッチ順序に気を遣っていなかったことがわかった。エラーメッセージの意味がわかんなくて唸ってたんだけど、実際にレンダリングされてたページを見てみたら割とすぐわかった。実際の動作を確認するの、大事だなあ。
キーワードをぜんぶ最長マッチに直して、事前レンダリングのもやりなおして(ちょっと時間がかかるのがキツい)、えいやっと。

  • keyword match longer first: 108373

ほーらほらほら上位に来たぞ!!! みたいにテンション上がる。16時半。
しかしここでシングルクォートを含むキーワードでエラーが出ているのを確認。ベンチマークがPASSになってるんだけど、果たしてこれがどんだけの減点要素になってるのかが分からなくて、直すべきかどうかで悩む。個人的には、こういうエラーがどのくらいの減点要素になるのかちゃんとレギュレーションに書いてなかったのは出題のミスだと思う。(まあ難しいよね……。)

さて残りの時間でどうするか、ということでまたアクセスログを見たら / へのリクエストが予想以上に多く、当初はキーワード登録された後に必ず invalidation 対象になるので意味がないかと思ってたんだけど、やったほうが良さそうだね、という結果だった。そこはじょーかーさんにお任せしてやってもらい、他に何か無いかなあ……と探して回る。結局自分はずっと htmlify の中の正規表現をどうにかする方法がないか探してた気がする*2。思わぬボトルネックが隠れてないか、とかは継続的に常様に slow query log を見てもらったり。安心のクオリティ。

じょーかーさんが / のキャッシュを実装し終えたのが17時半過ぎ、これはギリギリかー? と思いながらベンチを回してみたら12万点くらい出る*3ものの、エラーが大量に並んでる。5万→10万のときの経験からして、ここでエラーを全部無くせば20万点くらいには行くんじゃね? などとテンションが上がってギリギリまでエラーを無くすためにbugfixとかやってた。
しかしあれこれ改善しても変わらず、「既に表示されたページが表示されています(GET / )」とかの意味不明なエラーが消えない。えぇー、インデックスページってキーワードが追加されたら既存キーワードは次のページにズレ込んだりするから、pagenation進めていったときに表示済みのやつが出たりするのは普通なんじゃないのー? とか不満をぶいぶい言わせてた。

この付近、スコアの改善どころかベンチを回すたびにスコアが大幅に下がったりすることもあって、うーんこれはダメか、みたいな感じでいったんはトップページのキャッシュ戦略を放棄、masterブランチに戻して再起動チェックしておしまいにしようか、10万点くらい出るはずだったし、ということになった。systemd上での各ソフトウェアの自動起動等は安心のtnmtクオリティで一発で問題なし。さあベンチ回しておしまいにしよう!

  • master branchに戻して再起動後: 約6万点

えええええええええええ、なんでだよ、ちょっと待ってよおい、みたいな感じ。さっきまで10万点前後だったスコアが急降下して、予選まあ通るんじゃないの、という空気から予選落ち確定に転落。おいおいおい。
このままじゃ正直マズいだろ、ちょっと待てよ、と緊急で相談、こうなったらもう今からトップページキャッシュ有効のコードにイチかバチか切り替えてチャレンジしよう、ということでブランチを切り替え、ベンチマークをエンキューして走行を待ってたら18時タイムアップした。走ってるのはなんとなくわかるけどスコアが採用されるかどうかはタイミング的にギリギリだし、結果の確認もできないしで、あぁー終わったー予選落ちだー、とがっくりして終わった……。。。





その後

完全に自棄酒モードになった我々はPCの前に居るとコードを読みながら悪い酒を飲んでしまう、ということで雨の中を近所の築地へ。日曜なのを忘れててだいぶ店も閉まってたんだけど、運よくナイスな店がやってて入れそうだったので店の前でしばらく待機。
……してたら、なんと発表された予選通過リストに我々の名前が! やったー!

築地の店の前でハイタッチしてた。スコアを見ると最後に走行したベンチマークが有効スコアとして認められたっぽい。結局最終スコアが10万点なので予選中の最高スコアに及んでいなくて、そういう意味では結局謎のスコア低下は残り、なんだったんだという感じはする。一方で最後の走行が有効扱いになるとはあまり思っていなくて、幸運に拾われた面もあるかもしれない。

まあとにかく通ったぞ、ということで。予選は通りゃいいんだよ通りゃ。勝負は決勝だ!



反省などあれこれ

とにかく @joker1007 さんの実装力が高くて、Rubyのコード改善はお願いしまくり。あと今回自分はMySQLやらsystemdやらは全く見てなくて、そこやら全部再起動便利スクリプトやらの足回りは @tnmt さんに完全に丸投げしてた。この2人とチーム組めてほんとに良かった、助かった、という結果だった。
最終的なアプリケーションコードも全体の設計やオペレーションも、今回の予選も完全に複数ノード構成にそのまま持っていける内容になっていた。ので、今回のチームも*4決勝でこそ実力を発揮できるチームだと思ってる。来月もがんばりたい。

自分については、もうこれまでRedisから逃げ続けてきたツケがついに、という感じ。来月までにRedis使ったWebアプリケーションを一度書いてみよう…… orz ということで反省しきり。そういえば俺のWebアプリケーション実装力はこの2〜3年は改善ゼロなのであった。もうちょっとちゃんとしよう。

今回のアプリケーションとベンチマークについては、htmlifyを理想的に高速化できるかどうかがまずひとつで、これはイチから実装して突破するか、ナイスな機能・モジュールのある言語を使うか、ということに尽きた感じ。Rubyまわりだとそのまま使えるものが何もなかったので少し厳しかった。まーC拡張をゴリゴリ書けなかった自分が悪いといえば、そのとおりかもしれぬ。
あとは生成したレンダリング済みcacheの共有や invalidation をやるのにシングルプロセスだと超絶やりやすいよなーってことで、これもまあ思うところが無くはない……が、予選はそういう傾向のことが多いので、分かっていたことではある。あるけど、マルチプロセスでも通用する構成でも今までは予選通過は固かったところ、今回はそのハードルがだいぶ上がっていた気はした。

キャッシュ戦略については、正直ベンチマークツールが出したエラーがどういう理由を示しているのかが分からなくて、本当に不整合が出ているのはどういうケースなのかを特定し切れなかった部分がある。
あとはコンテンツが正常にレンダリングされていないのにベンチは PASS になっている状態で、そのままスループットを上げる方向の判断をしちゃった方がじつは高スコアが狙えるんじゃないの、という……自分たちはエラーを消す方向に思考を向けて足を止めちゃったけど、あのままガンガンスループットを上げればいいんだったらそれなりの手はまだいくつもあったので、どうだったのかなあ、というのは気になる。

しかしコンテンツに関して出ているエラーがどういう減点要素になっているのかがレギュレーションのどこにも書いてなかったので、どう出たらいいのかが判断つかなかったんだよねー、ということで、これは決勝では改善されるといいなあ、というところだった。

全体を見ると、とにかく pre-rendered なコンテンツを準備する & キャッシュを頑張る、という方針への決断が遅かったのは最大の反省点。次はもっと決断を早目にしていきたい。あと手元環境は作ること、かな。本番ノード上でデバッグとかしてちゃ時間の無駄だ。

まとめ

出題者&運営の方々はお疲れさまでした! 決勝もよろしく! 決勝頑張るぞ!

*1:チーム名は優勝したら賞金で食べに行きたいものでなんかつけよう、という話をして決めた。綴りはどうなんだと思ったけど英語版WikipediaJingisukanって書いてあった!

*2:しかも徒労に終わった

*3:正確なスコアをメモり忘れてた……

*4:生ハム原木に引き続き

RubyKaigi 2016 に行ってきた & しゃべってきた

今年は京都で開催されたRubyKaigi、トーク募集に応募してたものが1件採択されたので、行ってしゃべってきた。

RubyKaigi 2016

京都ですよ京都。いいよねー。前日入りして飲みにいったり、最終日も泊まりにして飲みにいったりしていた。たのしい。カンファレンスとかで日本各地に行きたいなあ。


しゃべってきた

採択されたトークはこちら。

Modern Black Mages Fighting in the Real World - RubyKaigi 2016

内容的には実はヤパチーでしゃべったやつのマイナーアップデートだった*1んだけど、時間がこちらの方がちょっと長いので、もうひとつ実用例を足したりしていた。

Fluentd v0.14 におけるプラグイン互換性対応 『現代の"リアル"黒魔術師』登場 #RubyKaigi #RubyKaigiB - Togetterまとめ

おかげさまで会場は超満員、反応を見てもけっこう楽しんでもらえたようで、ひと安心というところ。内容としてはRubyメタプログラミングにおける基本的な操作(include, extend, singleton_class および prepend)を説明しながら、それを武器に現実の問題をどう片付けたかという感じ。これをやるべきかというと普段のお仕事では絶対にやるべきではないテクニックの塊なんだけど、しかしそれでも何をやってでも問題を解決しないといけないケースというのはごく稀にあって、そういう時のための知識としてはよいのではないんでしょうか。Module.new して define_method して extend するとextendされたオブジェクトのメソッドが呼ばれたときに define_method 実行時のローカル変数がいじれて便利だよね!!!!

行ってきた

RubyKaigiはなんといってもRubyコミッタをはじめ言語実装そのもの(や、その近い周辺)をやっている人達が多くて、サービス開発というよりはソフトウェア開発という視点にフォーカスしているように見えるのがなかなか毎年面白いなーと思いながら眺めている。CRuby以外にJRubyとかErRuby(Ruby on Erlang/OTP)みたいなのも出てくるしねえ。
聞いた中だと圧巻は @nalsh さんのクロージングキーノート、なんだけど同僚なのであれこれ知っている話もあるので印象としては @shyouhei さんの最適化の話かなあ。しかしRuby3x3はやっぱりそれをどう計測するかっていうのが今のところ抜けてて、これが実は来年に向けていちばん面白いことができるトピックなんじゃないかという気もする。

個人的には自分のお仕事に関係する点でもあって @_ko1 さんの並行性制御の話(Guildの話)が楽しみだったんだけど、1日目は体調崩して午後は帰って寝てたから、聞けなかった。1日目のAfter Partyも出られなかった。残念。

あとはなんか普段と違うふらふらした気分のままコードを書いたりもしていた。これはあとで別エントリに。

観光してた

京都、中学生のころに修学旅行で行ったのと、あとごく短期の滞在で行ったことが1回か2回あるだけで、あんまりちゃんと観光して回ったことがなかった。ので、泊まった近所の祇園とか四条とかを存分にふらふらできたのは満足。
あとカンファレンス翌日にせっかくなので御所と二条城だけ見てきた。

御所、いい場所だなあ。昔の情報で入るのはかなり厳しいというのを見てたんだけど、いま調べてみると行けば(手荷物検査くらいはあったけど)普通に入れる状態で、どこもかしこも良さそうな光景だった。あと周囲も含めた面積が広大すぎて、権力者というやつは……! みたいな感じがする。
次の二条城も、もう、京都のど真ん中にこんなの作っちゃってー、という感じだったけど石垣というか城塞はいいよね。

伏見稲荷に行く時間がとれなかったのが残念。まあ、人生またいつか行く機会もあるだろう。

まとめ

もっとカンファレンスを東京以外でやってくれるとあちこち行けて人生が楽しくなる。京都楽しかった!

*1:もうひとつ完全新作もCFPに出してたんだけど、こっちが通っちゃったので……

TCP serverをSSL/TLS化するのに nginx の stream_ssl_module/stream_proxy_module が便利

最近 Fluentd の通信プロトコルまわりをアップデートするためにあれこれいじっている*1んだけど、これはおおむね fluent-plugin-secure-forward がサポートしていた内容を Fluentd 組込みの forward plugin でもサポートしますよ、というものになる。
んで問題なのが secure-forward は SSL/TLS での接続のみしかサポートしてなかったんだけど forward では生の TCP で通信する*2ので、本当に secure-forward と forward それぞれの実装間で互換性が保たれているのか、直接的には確認する手段がない、ということになってしまう。

TCP server の SSL/TLS

一方世の中には SSL/TLS ターミネータという機能があって、たとえばロードバランサなんかがこの機能を持っている。何をやるかというとSSL/TLSの処理だけはフロントエンドのサーバがやって、バックエンドで実際の処理を担当するサーバはSSL/TLSのことなんか知らず生のTCPに対して処理を行う、というものだ。Webサービスの提供をするときにもこの構成はよく使われるし、AWS ELBなどをはじめ各種ロードバランサで広くサポートされている。

[client] -----> (SSL/TLS) -----> [Load Balancer] -----> (TCP) -----> [server]

これは Fluentd においては次の構成にあたる。

[out_secure_forward] -----> (SSL/TLS) -----> [Load Balancer] -----> (TCP) -----> [in_forward]
TCP client の SSL/TLS

いっぽう、本番システムにはあまり存在しない構成として、SSL/TLSに対応していないクライアントを無理矢理にでも SSL/TLS のみしかサポートしないサーバと通信させるたい、という要望がある。おそらくほとんどのユースケースは開発時のものだと思う。強いて言うと tcpdump で中身がわかって便利、みたいな SSL/TLS に正面から挑戦するような利点がある。

[client] -----> (TCP) -----> [Load Balancer] -----> (SSL/TLS) -----> [server]

Fluentd においてはさっきと真逆の構成。

[out_forward] -----> (TCP) -----> [Load Balancer] -----> (SSL/TLS) -----> [in_secure_forward]

プラグイン実装の対称性が保たれているかを確認するためにはこっちのテストも必要だったのです。

nginx の stream_ssl_module と stream_proxy_module

さてこのような構成を手元で作って動作確認したいと思ったときにどうするか。本番システムなら AWS ELB とか使えばいいけどちょっと動作を試したいとかオレオレ証明書とかであれこれ実験したいとかいうときにはいかにも面倒くさい。
おっさんとしては昔ながらの安定のツール stone とかも思い付くけど、うーん、さすがにちょっと今更感がある*3し、英語が大前提のOSSプロジェクトではオススメしづらい。

どうしようかなと思っていたら、最近の nginx はTCPのproxyもできる stream という機能が足されてたのを思い出した。自分でちゃんと使ったことがなかったんだけど、ドキュメントを見ているといかにも SSL/TLS の処理もできそうに書かれてていかにも使えそうだったので、試してみたところ、見事に(簡単に!)使えた。期待通りに動くソフトウェアというのはすばらしい。えらい。

なお stream およびその他今回使用したモジュールは通常の nginx では有効になっていない。自分は --with-stream --with-stream_ssl_module を有効にしてソースコードから configure && make && make install した。バージョンは最新安定板の 1.10.1 を使っている。

stream_ssl_module

https://nginx.org/en/docs/stream/ngx_stream_ssl_module.html

これは SSL/TLS なクライアントからの通信において TLS termination を nginx で行い、TCPの通信としてバックエンドに流すために使う。つまり、こっちの構成用。

[out_secure_forward] -----> (SSL/TLS) -----> [Load Balancer] -----> (TCP) -----> [in_forward]

もちろん手元で適当なオレオレ証明書を事前に用意しておく。その上で以下のように設定を書くだけ。簡単!

worker_processes auto;
error_log /Users/tagomoris/nginx.err;
events {
  worker_connections 64;
}
stream {
  upstream backend {
    server 127.0.0.1:24224;
  }
  server {
    listen 24284 ssl;
    proxy_pass backend;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers         AES128-SHA:AES256-SHA:RC4-SHA:DES-CBC3-SHA:RC4-MD5;
    ssl_certificate     /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    ssl_password_file   /path/to/pass.txt;
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 10m;
  }
}

port 24284 を SSL 有効にして listen し、その通信をバックエンドの Fluentd port 24224 に流す。こっちはTCP
手元テスト用なので ssl 関連の設定は適当、本番環境にこれをコピペしてはいけない……のはともかく、これで動く。素晴らしい。

stream_proxy_module

https://nginx.org/en/docs/stream/ngx_stream_proxy_module.html#proxy_ssl

こちらは TCP で受けた通信を SSL/TLS が有効なサーバに向けてプロキシしてやるために使う。こっちの構成用。

[out_forward] -----> (TCP) -----> [Load Balancer] -----> (SSL/TLS) -----> [in_secure_forward]

こっちは証明書まわりの設定がいらない*4から、もっと簡単。こんな。

worker_processes auto;
error_log /Users/tagomoris/nginx.err;

events {
  worker_connections 64;
}
stream {
  upstream backend {
    server 127.0.0.1:24284;
  }
  server {
    listen 24224;
    proxy_pass backend;
    proxy_ssl on;
  }
}

TCP port 24224 で受けた通信をバックエンドの port 24284 に流す、その際に SSL を有効にする、と指定してやるだけ。超簡単。
このとき証明書の検証まわりの制御について設定が必要なら、適宜 proxy_ssl_verify などを正しく指定してやればいい。各自がんばれ。

まとめ

nginx の stream がすばらしくて、なんといっても手元で使いやすいので開発時にも特に便利にお使いいただけます、というものになってた。
今後の選択肢のひとつとして覚えておきたい。

*1:なお過去の通信プロトコルと互換性があるので心配性のみなさんも安心の内容でございます

*2:SSL/TLSのサポートも予定にあるんだけど今のところはまだコードが無い

*3:と思ってたけどリポジトリを見るとまだアップデートされてるんだなあ、すごい https://osdn.jp/cvs/view/stone/stone/stone.c?view=log

*4:in_secure_forward側でやってある前提だ

松江市でOSSの話をした & Fluentd meetup をやった

なんか最近プレゼンテーションやった話しかエントリに書いていない……。。。

それはともかく、しまねOSS協議会というところから周年記念講演で話さないかというお誘いがありまして、お受けしたので島根県松江市で話してきた。せっかくRuby City Matsueに行くことだしついでにイベントやってこようということで Fluentd meetup in Matsue もやることになり、そんなわけでふたつ発表してきた。

第106回オープンソースサロン・総会記念講演

第106回オープンソースサロン・総会記念講演 | しまねOSS協議会 OS4

しまねOSS協議会ということで、オープンソースソフトウェアの話。

というか、トレジャーデータという会社がOSSを使い倒してビジネスをやってるのは何故なのか、それは内外の開発者にとってはどういう意味があってどう見えているのか、みたいなことを中心に話していた。具体例についてはどっちかというとRubyよりはJava(というかデータ処理ミドルウェア界隈)の話に寄ったけど、一般化できる内容ではあったと思う。

自社サービスの話、USとJPをまたいだ話(採用とか)、業務としてOSSのパッチを書く話など、あれやこれや話してた。質問もいろいろ頂いて、話しに行けてよかった。お呼びいただきありがとうございました。

Fluentd meetup in Matsue

Fluentd meetup in Matsue - dots. [ドッツ]

せっかくなので開催してきた。東京以外でのFluentd meetupは福岡、San Franciscoに続いてのなんと3ヶ所目。とりあえず自分からはFluentdそのものの導入やら最近のユースケースやらアップデートやらをざーっと話すという感じにしてみた。

Fluentdを使ったことがない人もいたので、これはこれでとりあず必要な内容というか。こういう総論的なFluentdの紹介スライドはどっちにしろ定期的にアップデートする必要があって、ちょうどいい機会でもあった。
他の発表も募集したところちゃんと話してくれる人が出てきてくれたので本当によかった。しかも素晴らしい内容だった。

あとFluentdの新ロゴが載ってるノベルティが配られた初のFluentd meetupでもありました。よかったよかった。

Stream Processing Casual Talks #1 に行ってきた&しゃべってきた

なんか、やろうぜー、という話になってそのような勉強会が行われたので、参加してた。

connpass.com

会場&飲食の提供はYahoo! Japanさん。ありがとうございました。面白かった。

しゃべってきた

Norikraそのものの話はさすがに今更感あったので、Norikraの大事なところと、あとPerfect Norikra (仮)について話してきた。

まあ、まだ1byteも存在しないソフトウェアなんだけどな! 存在しないソフトウェアについて話せるようになったの、ちょっと実績解除感があるのではないだろうか。だめか。

Norikraを分散処理対応にするにはクエリ処理エンジンおよびデータ転送層を丸ごと書き直すしかないなー、しかもそこに色々なアイデアが必要だなとは思っていて、そのうちのひとつであるところの動的スケールアウトには最低限何が必要なのか、みたいな話をしてた。
この他にもまだあれこれ必要なんだけど、この先はどんどん話の前提が増えていくから、さすがに勉強会の単発の発表でやるのはキツそう。というか、書かないとなあ……。

行ってきた

Hadoop Summit での空気感*1とか、Y!Jでの実際のアプリケーションの話とか、その他各ミドルウェアの話とか聞いてた。たのしかったー。

特にPipeline DBの話がじつに面白かった。Norikraと完全に逆の方向を向いてて、RDBMSの外側にハックをかぶせていかにストリーム処理をストリーム処理に見せないか、という方向に突っ走ってた。なるほどなあ、それならJDBC使えるわー。でも自分はそっちの方向には絶対行かないなあ。

そのあとも飲みながらあれこれ話したりとか、じつに面白い勉強会でした。またやりたいですね。

*1:やっぱり各ベンダーの人の話はその意向が出るよなあ、という強烈な印象だけがある……

LinuxCon Japan 2016に行ってきた&しゃべってきた

LinuxCon Japan、今回は ContainerCon Japanとの併催。サブトラックみたいなのもいくつかあった。
とにかく普段に自分が行くカンファレンスとぜんぜん空気が違ってて、東京都内で行われてるカンファレンスなのに会場の感じとか食事・飲み物の提供とか冷房のキツさとか、あれこれがだいぶ海外のカンファレンスっぽい。参加費が高いからできることなんだろうけど。そして海外からと思しき参加者も非常に多かった。すごいな。
日本国内からも大手メーカー系のスポンサードと参加者が非常に多かった。普段見ないあたりの世界だなあ。

しゃべってきた

プロポーザルを出してみたら通ったので初めての参加&発表だった。行ったことないので全体的にどういう空気なのかも分からないまま、初日のキーノート後の通常の発表枠の中で最初に時間だった。お、おう……。

今回はロギングに関してのアーキテクチャを議論するよ、ということで、特にコンテナを使うのが当たりまえという世界でロギングってどうやるんだっけ、どうすればちゃんとスケールさせられるんだっけ、という話をあれこれやった。

実例で特にFluentdを取り上げているけど、話の内容自体は一般論として何でもやれるはず。アプリケーション側をできるだけ単純にしつつ、ログのトラフィックの増大にどう対応するか、ログの種類の増大にどう対応するか、ストレージの性能特性によってどのように構成を考える必要があるか、などなど。
ちょっと英語でのスピーチがうまくやれなかったなと思ってたけど、聴衆から参考になったって感想言いにきてくれた人がいたので良かった。ありがたいことです。

そのほか

最近 Treasure Data が Linux Foundation のプロジェクトのひとつであるCNCFに参加したということもあって、LinuxConの分科会のひとつとしてCNCFのトラックもあって、そっちでは @repeatedly がFluentdの話をしていた。これはこれで、普段リーチしてない層に届いたのかなあ、という感じ。
あとはふらふらと各セッションを巡ってたんだけど、面白いのもあればなんだこりゃ、というのもあった。総じて、うーん、もうちょっとこう……まあカンファレンスごとのノリもあるしね、難しいよね、みたいな感じはする。

そして期間中ひたすら強烈な冷房を浴びつづけたせいか、しまいには盛大に体調を崩してしまった。きびしい。