株式会社はてなを退職しました

2018年12月21日の今日がはてなでの最終出社日となりました。 はてなには、2013年12月に新卒として入社し、その後5年間に渡りお世話になりました。

はてなとの出会いは、2011年のはてなインターンに参加したことがきっかけでした。 はてなインターンの特徴の一つに、ほとんどの参加者が参加したときの内容をブログ記事として書いていることがあります。インターン参加記事には、技術やWebに対する大きな熱量がこもっており、すっかり自分もWeb技術をやっていくのだと感化されました。 ダメ元で選考に望んだところ、運良く選考通過のお知らせをいただいてとてもうれしかったことを今もよく覚えています。 そこから毎年インターンの参加者をみてきていますが、とてもハイレベルで、よく自分が選考通過したものだと今でも思います。 この出来事が自身の人生にとって大きな転機だったと言えるでしょう。

インターンの1年後にアルバイトスタッフとして入社し、配属されたのは、はてなのITインフラを担当するシステムプラットフォーム部という部署でした。 今でこそ当たり前のようにSREのような基盤技術を専門としていますが、当時はサービス開発をするアプリケーションエンジニアを志望していました。 しかし、システムプラットフォーム部での仕事を通して、サーバがたくさんあってそれらが相互に通信してひとつの系をなすというWebシステムに魅せられ、研究もたまたまTCP/IPスタックとGPGPUに関するテーマを提案してやっていたこともあり、今でいうところのSREを志しました。はてなで大規模サービスのインフラを学んだ - ゆううきブログ の記事にて、志した結果、何を学んだかを書いています。 そこから新卒で内定をいただいたにも関わらず、大学院を中途退学してしまいましたが、その後も快く受け入れてくださったはてなにはとても感謝しています。

はてなでは、Webシステムの基盤という軸はかえずに、様々な仕事をさせていただきました。 サーバ監視サービスMackerelのリリースから、その後の運用、はてなブログ、はてなプラットフォーム系サービスの運用、仮想化基盤などの基盤システムの運用、レガシーOSの移行、データセンター移行、時系列データベースの開発、プロジェクトマネジメント、チームマネジメント、シニアエンジニアとしてのメンタリング、採用、技術広報などをやってきました。 このような直接の業務以外に、オープンにアウトプットをするということに力を注いできました。 ブログ、登壇、OSS、最後は論文というように複数の媒体で、エンジニアリングからアカデミアまで様々な場で、技術を自分なりに表現するということをやってきました。*1 大した特技のない自分が曲がりなりにもエンジニアとしてやれているのは、オープンネスの風土が根付いていたはてなにいられたこと、その風土に大きく影響を受け、はてなのエンジニアらしくあろうと実践してきたおかげであると思っています。 また、オープンにいろいろやっていると、副次的に社内のエンジニア職種以外の方からも覚えていただいたり、PR記事にて対談させていただいたり、様々な方々と関われたことをうれしく思います。

調子よく仕事をさせていただいていたおかげで、どんどん等級もあがり、給料もあがっていきました。 その一方で、入社前に思い描いていた仕事と実際の仕事の内容には乖離があり、入社後の5年間はこの乖離を埋めるために奔走してきました。 入社時期の2013年といえば、クラウドやdevopsといった新しい技術や考え方が浸透し、それまで他の人が作ったソフトウェアを動かすまでが主な仕事だったインフラエンジニアが自分でソフトウェアを書いていくぞという時代になってきたころだと思います。 そのような世の中の影響を受けているのか、入社面接で自分が用意したプレゼン資料を今みると、基盤のためのソフトウェア開発をやっていきたいという思いが垣間見えました。 しかし実際には、なかなかソフトウェアを書いて貢献していくという時間を業務内でつくることが難しく「スタートラインに立てていない」という思いが募っていきました。

そこで、状況を変えるために、業務として期待される以外に自主的に様々なことをやってきました。 入社1~3年目は、とにかく人数の問題だと捉えて、ブログ執筆や登壇を繰り返し、会社のWebオペレーションエンジニア(現SRE)のプレゼンスを向上させることに努めました。 このようなアウトプット活動は実を結び、最終的に自分だけでなく2016年はてなWebオペレーションエンジニアのアウトプット にまとめたようにチームメンバー全員で多くのアウトプットを生み出すことができました。*2 さらに、この頃から等級もエンジニアの主力クラスに上がり、シニアエンジニアやプロジェクトリーダーも任されるようになり、自分が制御できる範囲も徐々に増えてきました。 プロジェクトマネジメント、メンタリング、チームビルディングなどのスキルを学び、実践する機会が増えてきました。 その中には、多くの失敗があり、制御できないことを強引に制御しようとしてしまうなど、うまくいかないこともたくさんありました。 たくさんの失敗をしてきた中で「インフラオーナーシップ推進*3」、「本格的なシステム移行」、「基盤領域におけるプロジェクト推進のフレームづくり」の自分のなかでの3本柱が、様々な方々の協力の元に、この1年で所属部署や開発本部全体の取り組みとして扱われ、軌道に乗りつつあり、これからはてなのインフラが成長していくための種は残せたかと思います。 システムプラットフォーム部では、新メンバーもずいぶん増えて、新しいチームとしてどんどん進んでいるのをみて、非常に頼もしく思います。

じゃあそれで問題なしでいいじゃんということになりますが、自分自身について振り返ってみると、自分の仕事を管理・運用・開発・研究というような区分をしたときに、もともとは運用*4が支配的だったところに、人や組織など専門技術以外の管理に関する仕事も増えてきました。 それらの仕事は決して嫌いではなく、ものによってはむしろ率先してやっていたこともあります。 しかし、最もやりたいことであるはずの開発・研究*5については、業務時間の1割にも満たないという状況でした。 そして、自分の性質上、9割を占める業務を放り投げて、やりたいことをやるということはなかなかできませんでした。 となると、必然的にプライベート時間でどれだけ開発・研究をやっていくかという戦いになります。 また人の問題に関わるようになると、リフレッシュのための時間を多く必要とするようになりました。

このままではまずいなあと思っていたところに、僕の師匠であるところのまつもとりーさんが退職されるというご連絡をご本人からいただき、深く考えさせられることとなりました。 というのは、まつもとりーさんが退職される理由そのものが、あれほど高いレベルではないにしろ、自分が抱える問題意識とよく似ていたからです。 そこから内省した結果、いくつかのタイミングの問題もあり、転職することを決めました。

1ヶ月の無職期間を経て、2019年2月からは、さくらインターネット研究所で研究員として、インターネットやウェブ基盤技術の分野で研究開発に取り組んでいきます。 職種はSRE(Site Reliability Engineer)から研究員となり、業種はウェブコンテンツ事業者からホスティング・クラウド事業者へと変わり、新しい挑戦をしていきます。 ある日の休日に散歩していたら突然転職の選択肢が脳内に降ってきてすぐに、まつもとりーさんに声をかけさせていただき、快く相談に乗ってくださったことにとても感謝しています。 いきなり田中さんへつないでいただいたことにはびっくりしました。 お声がけさせていただいてからすぐにお時間を作っていただいた田中さんと鷲北さん、お話をさせていただく時間をつくっていただいた熊谷さん、@yamamasa23さん、@hnakamur2さんに感謝しています。(誤って@yamamoto_febcさんと書いており、申し訳ありません。ご確認いただいたhnakamur2さんありがとうございます。) さくらインターネットの本社が大阪のグランフロントにあり、京都から通勤可能かつ在宅勤務もやりやすい環境とのことなので、これまで通り、京都でのんびり過ごしていきます。

研究開発をやっていくことに決めた後に、印象的なできごとがありました。 先日のAWS re:Invent 2018にて、Amazon Timestreamというフルマネージド時系列データベースが発表されました。 時系列データベースの論文を書いた - ゆううきブログ にも書いたように、これまで時系列データベースの開発・運用を長くやってきて、学術の世界で、新しいアーキテクチャとして提案し、クラウドを新たな基盤として、これまで実効性の観点で採用しなかったであろうアーキテクチャを考えていくことについて書きました。 しかし、IoTの流行による後押しがあるとはいえ、Timestreamの登場により、AWSのようなグローバルの大手クラウド事業者は、ある程度アプリケーションに近いドメインの基盤ソフトウェアをサービス化してくることがわかりました。 最近のクラウド上のマネージドサービスの進化はとても興味深いものであり、AWSのアップデートは熱心に追いかけていますし、クラウド以外にインフラストラクチャに関するOSSプロダクトの進化もすさまじいものがあります。 しかし、それらを使うだけになってしまうという危機感も同時にありました。 そこで、これまでクラウド前提でやってきた頭を切り替えて、あえて自分たちでつくっていくという挑戦ができればと思っています。

はてなの皆様、5年間大変お世話になりました。はてなで得たことは間違いなく今後の人生のベースになるものばかりだと思います。そして、はてなの方々の優秀さをこれから相対的に知っていくのだと思います。これからも、引き続きよろしくお願い致します。

*1:実績ページ https://yuuk.io/のアウトプットはすべてはてなでの経験が元になっています。

*2:一方で、継続してチームでアウトプットしていくというところまでは届きませんでした。

*3:プロダクトオーナーシップという大きな取り組みのうちのインフラ領域に関する方針

*4:新規サービス構築も含む

*5:SREでいうところのソフトウェアエンジニアリング・システムエンジニアリング、論文執筆、アウトプット活動など

時系列データベースの論文を書いた

先週、第11回インターネットと運用技術シンポジウム (IOTS2018)にて、投稿した論文の発表をしてきました。 IOTSは査読付の国内の研究会であり、2年前に招待講演をさせていただいた研究会でもあります情報処理学会でウェブオペレーション技術について招待講演した話 - ゆううきブログ。 実は、そのときに、来年論文を投稿するぞと意気込んでいました。 実際にはそこから2年かかりましたが、この度論文を投稿することができました。

予稿

スライド

実務から研究へ

今回投稿した論文の内容は、Mackerelで開発した時系列データベースに関するものです。 これらはすでにAWS Summit Tokyo 2017Web System Architecture研究会で発表済みのものになります。

エンジニアリングの発表とは異なり、今回は学術論文ということで、「新規性」「有効性」「信頼性」「了解性」の4点を記述できていることが求められます。 学術的貢献の記述の仕方については、特にソフトウェアエンジニアが論文を書くのであれば、学生,若手研究者向けの論文の書き方術 ─システム開発・ソフトウェア開発論文編─の文献が参考になります。 もともと、要素技術の新しさがなければ論文にならないと思いこんでいましたが、こちらの文献を読んだことで、システム開発論文の場合、個別の要素が既知であっても、組み合せが新しく工夫があり、さらに他のシステムに応用可能であり、先行手法と比較して優れていることを主張できていることが重視されるということがわかりました。 ある前提条件の中で、他のシステムに応用可能なレベルで記述された要素技術の組み合わせの法則をアーキテクチャと呼び、新規性の観点では、アーキテクチャが新しいことを示し、有効性の観点では,実装が優れているのではなく、アーキテクチャが優れていることを示すことを目指すというのが自分の理解したろことになります。

当たり前ですが、あらゆる局面に適用可能な大統一アーキテクチャのようなものをいきなり提案できるわけはありません。 サーバモニタリングのための時系列データベースという強い制約条件下においても、あらゆる観点において優位となるアーキテクチャを提案することさえ難しいでしょう。 今回の論文では、Mackerelのようなモニタリングサービス向けの時系列データベースという立ち位置をとり、モニタリングサービスを成長させていくための拡張性をもつ時系列データベースがないという問題意識から、書き込みスケールアウト性、高可用性、書き込み処理効率、データ保存効率といった性能要件を満たしつつ、データ構造の拡張性をもたせるという要件を具体的に設定しました。 これらの要件に対して、分散KVSを利用することで、書き込みスケールアウト性と高可用性を解決し、書き込み処理効率とデータ保存効率、拡張性については、万能なDBMSはないことから単一のデータベース管理システム(DBMS)で対応するのではなく、異なる特性をもつDBMSを複数組み合わせるという時系列データベースの構成法を考えました。 DBMSを複数組み合わせるといっても、どのように組み合わせるのかは自明でないため、組み合わせのための手法として、キーバリューという単純な形式で時系列データ構造をどう表現するのか、DBMS間のデータ移動をどうやるのか、データ構造の拡張をどうやるのかを実装に依存しない手法として落とし込み提案しました。

新規性がどこにあるかについては、論文表題にあるHeteroTSDBというアーキテクチャ名に込めています。 つまり、異種混合(Heterogeneous)のDBMSを組み合わせて、各DBMSの特徴を活かしつつ、欠点は別のDBMSの特徴で補い、ひとつの疎結合な時系列データベースをつくるという考え方です。 異種混合のDBMSを組み合わせること自体は、大規模なWebアプリケーションを構成する上で、よく採用される考え方であり、Martin Kleppmann著「Designing Data-Intensive Applications」での原則になっています。 論文では、時系列データベースとして構成するための法則が世の中になかったために、新規性のあるアーキテクチャであると主張しています。

今回論文化する上で苦労したことは2点あり、先行手法に対する有用性をいかに示すかということと、分量を研究会指定の8ページに収めることでした。 まず、先行手法に対する有用性については、前提として、HeteroTSDBは、単純な性能では、密結合に構成され、処理効率に最適化されたアーキテクチャにはかないません。 当初、提案手法はサーバレスプラットフォームの活用により運用しやすいということを主張しようとしましたが、先行手法の一実装であるOpenTSDBやInfluxDBをサーバレスプラットフォームとして実装すればよいだけで、提案手法が有用ということにはならないと結論付けました。 となるとどのように有用であることを示すのかということについて、HeteroTSDBが疎結合な構成であることに着目し、一般に密結合なソフトウェアよりも疎結合なソフトウェアのほうが拡張しやすいという性質があるために、拡張性という後付けではあるものの、提案手法が有用となる要素を見出しました。 2つ目の分量については、もともとが実務の大きな開発プロジェクトであり、複雑に絡まりあった多くの要素があり、それらをよく知っている分、シンプルに書き上げることが難しく感じました。 例えば、論文では、いろいろ削った上で5つの要件を設定していますが、それでも読み手には負担のかかる数であるため、書き込みスケールアウト性と高可用性については、当たり前に必要ということで要件から外し、よりシンプルにみせるということができたと思います。

ここまで読むと、実務から研究に落とすのは大変なだけなように思います。 しかし、ソフトウェア開発や運用技術の研究では、実用足りうる実効性をもつことを重視されるようになってきていると聞いています。 実効性をもたせることは、実務での成果を直接担うエンジニアが得意とすることであるため、実効性の評価に関しては、記述の仕方について工夫が必要ではあるものの、エンジニアであることが大きなアドバンテージとなると感じました。 Facebookの時系列データベースであるGorillaの論文AmazonのAuroraの論文では、Lessons Learnedという章に、実環境での適用が書かれています。 IOTSの質疑応答の場においても、実効性の観点から生まれた質問がいくつもありました。

なぜ論文化したのか

4年前に書いた インフラエンジニア向けシステム系論文 - ゆううきブログ の記事を書いたころに、自分が得意なシステム系の分野について、数多くの論文が書かれていることを知りました。 このときから、自分が考えたり、作ったりしたものをいつか論文としてアウトプットできるとよいなあと漠然と思っていた気がします。

ソフトウェアエンジニアであれば、自分がつくったソフトウェアをOSSとして公開し、あわよくばいろんなところで使われることで世界に1石を投じるということを夢見ることがあるように思います。 ブログでのアウトプットに一通り満足してきたあるとき、僕はこれに近いモチベーションで論文を書こうと思い立ちました。 近いモチベーションなのであればOSSでいいんじゃないとなるかもしれません。 しかし、新規性があったり、有用性のあるソフトウェアを作るための明確なステップは存在しないようにみえますし、それができる人は高いセンスと努力により飛び越えていっているようにみえます。

そこで、論文として投稿することで、良いものなのかどうなのか客観的に評価を得ることができます。 学術の世界では、長い歴史の中で数多くの知見が蓄積されているために、前述したように評価の観点が整理され、査読というプロセスも確立され、国際会議やジャーナルなど場としてのランク付けもあるために、良いものをつくっていくための客観的ハードルが設けられているように思います。 これらのハードルを超えていくことで、自分を成長させ、最終的に新しいものを作り続けることができるようになることができるのではないか、と考えています。

その他、論文化する意義については、@matsumotoryさんの企業におけるエンジニアリングと研究開発 - GeekOutコラムエンジニアリングや研究開発について思うこと - 人間とウェブの未来 の記事を読むとよいと思います。

論文の裏にあるコンセプト

HeteroTSDBアーキテクチャでは、クラウド上で基盤ソフトウェアを開発していくための一つの考え方を示そうとしています。 基盤技術と一口に言っても、CPUのようにIntelとAMDが大勢を占める層から、Linux、Windows、BSD系などが主に利用されるOS層、その上のミドルウェア層やプログラミング言語処理系の層というように、技術の階層を登れば登るほど、要素技術とその組み合わせの数が多くなっていき、個々の事業固有の層まで達すると、基盤として吸収することは難しくなります。 いかにAWSと言えど、これらの組み合わせをサービスとして揃えていくことは難しいのではないかと考えています。 実際、AWSの個々のサービスは制約を強くかけることにより、スケーラビリティを確保し、AWS利用者は、各サービスを組み合わせ、やりたいことを実現していけるようにできているようにみえます。

これに加えて、HeteroTSDBでは、昨今のデータベース技術の文脈で、サーバレス時代におけるヘテロジニアス時系列データベースアーキテクチャ - ゆううきブログ では、次のように分解と再構築の考え方に従い、データベースを新しい要素技術の上に再構築しようと試みています。

Martin Kleppmann著、「Designing Data-Intensive Applications」の12章 The Future Of Data Systemsにて、データシステムを構築する上で、すべての異なる状況に適した1つのソフトウェアは存在せず、異なるソフトウェアを組み合わせアプリケーションを開発していく必要があるという趣旨の意見が書かれている。 Diamondアーキテクチャは、この考え方の延長線上にあり、インデックス、WAL、メモリ上のデータ構造、ディスク上のデータ構造など、古典的なデータベースエンジンの構成要素を、ヘテロジニアス環境で再構築したものになる。 そして、サーバレスアーキテクチャにより、分散システム上での複数箇所にまたがるデータの更新、移動を、信頼性のあるビルディングブロックの上に構築しやすくなった。

まとめると、クラウド事業者が吸収できる基盤の限界の外では、分解と再構築の考え方に従い、伝統的な要素技術をクラウド上で再構築し、クラウドを前提としたアーキテクチャを築いていくという考え方になります。 こうしたコンセプトが国外にだしても通用するのかということを問うべく、次は国際会議にだしていきたいと思います。

参考文献

あとがき

業務そのものに直接関係しないにも関わらずおつきあいいただいたはてなの共著者のみなさま(astjさん、itchynyさん、Songmuさん)ありがとうございました。 また、論文化するにあたって、matsumotoryさんとhirolovesbeerさんには、お忙しい中、時間をとっていただいて快く指導していただいたこと、指導いただかなければ論文として形にならなかっただろうということと、指導いただいている間に成長を実感できたことに大変感謝しています。

TCP接続の追跡による簡略化したネットワーク依存関係グラフの可視化基盤

はじめに

ウェブシステムは,一般的に,分散したホスト上で動作するソフトウェアが互いにネットワーク通信することにより構成される. 相互にネットワーク通信するシステムにおいて,システム管理者があるネットワーク内のノードに変更を加えた結果,ノードと通信している他のノードに変更の影響がでることがある. ネットワーク接続数が多いまたはノードが提供するサービスの種類が多くなるほど,システム管理者が個々の通信の依存関係を記憶することは難しくなる. さらに,常時接続しておらず必要なタイミングで一時的に通信するケースでは,あるタイミングの通信状況を記録するだけでは通信の依存関係を把握できない. その結果,システムを変更するときの影響範囲がわからず,変更のたびに依存関係を調査しなければならなくなるという問題がある.

先行手法では,ネットワーク内の各ノード上で動作するiptablesのようなファイアウォールのロギング機構を利用し,TCP/UDPの通信ログをログ集計サーバに転送し,ネットワークトポロジを可視化する研究[1]がある. 次に,tcpdumpのようなパケットキャプチャにより,パケットを収集し,解析することにより,ネットワーク通信の依存関係を解析できる. さらに,sFlowやNetFlowのように,ネットワークスイッチからサンプリングした統計情報を取得するツールもある. また,アプリケーションのログを解析し,依存関係を推定する研究[2]がある. マイクロサービスの文脈において,分散トレーシングは,各サービスが特定のフォーマットのリクエスト単位でのログを出力し,ログを収集することにより,リクエスト単位でのサービス依存関係の解析と性能測定を可能とする[3].

しかし,ファイアウォールのロギング機構とパケットキャプチャには,パケット通信レイテンシにオーバーヘッドが追加されるという課題がある. さらに,サーバ間の依存関係を知るだけであれば,どのリッスンポートに対する接続であるかを知るだけで十分なため,パケット単位や接続単位で接続情報を収集すると,TCPクライアントのエフェメラルポート単位での情報も含まれ,システム管理者が取得する情報が冗長となる. 分散トレーシングには,アプリケーションの変更が必要となる課題がある.

そこで,本研究では,TCP接続に着目し,アプリケーションに変更を加えることなく,アプリケーション性能への影響が小さい手法により,ネットワーク通信の依存関係を追跡可能なシステムを提案する. 本システムは,TCP接続情報を各サーバ上のエージェントが定期的に収集し,収集した結果を接続管理データベースに保存し,システム管理者がデータベースを参照し,TCP通信の依存関係を可視化する. まず,パケット通信やアプリケーション処理に割り込まずに,netstatのような手段でOSのTCP通信状況のスナップショットを取得する. 次に,ネットワークグラフのエッジの冗長性を削減するために,TCPポート単位ではなく,リッスンポートごとにTCP接続を集約したホストフロー単位でTCP通信情報を管理する. さらに,ネットワークグラフのノードの冗長性を削減するために,ホスト単位ではなくホストの複製グループ単位で管理する. 最後に,過去の一時的な接続情報を確認できるように,接続管理データベースには時間範囲で依存関係を問い合わせ可能である.

提案システムを実現することにより,システム管理者は,アプリケーションへの変更の必要なく,アプリケーションに影響を与えずに,ネットワーク構成要素を適切に抽象化した単位でネットワーク依存関係を把握できる.

提案手法

システム概要

図1に提案手法の外観図を示す.

図1: 提案手法の外観図

提案システムの動作フローを以下に示す.

  1. 各ホスト上のエージェントが定期的にTCP接続情報を取得する.
  2. エージェントはCMDB(接続管理データベース)のホストフロー情報を送信する.
  3. システム管理者はアナライザーを通して,CMDBに格納されたホストフロー情報を取得し,解析された結果を表示する.

これらのフローにより,システム管理者が管理するシステム全体のネットワーク接続情報をリアルタイムに収集し,集中管理できる.

ホストフロー集約

個々のTCP接続情報は,通常<送信元IPアドレス,送信先IPアドレス,送信元ポート,送信先ポート>の4つの値のタプルにより表現する. ホストフローは,送信元ポートまたは送信先ポートのいずれかをリッスンポートとして,同じ送信元IPアドレスとを送信先IPアドレスをもち,同じリッスンポートに対してアクティブオープンしている接続を集約したものを指す. ホストフローの具体例は次のようになる.

Local Address:Port   <-->   Peer Address:Port     Connections
10.0.1.9:many        -->    10.0.1.10:3306        22
10.0.1.9:many        -->    10.0.1.11:3306        14
10.0.2.10:22         <--    192.168.10.10:many    1
10.0.1.9:80          <--    10.0.2.13:many        120
10.0.1.9:80          <--    10.0.2.14:many        202

接続管理データベース

CMDBは,ノードとホストフローを格納する. ノードは,ユニークなIDをもち,IPアドレスとポートが紐付けられる. ホストフローは,ユニークなID,アクティブオープンかパッシブオープンかのフラグ,送信元ノード,送信先ノードをもつ.

アナライザー

アナライザーがCMDBに対して問い合わせるパターンは次の2つである.

  • a) ある特定のノードを指定し,指定したノードからアクティブオープンで接続するノード一覧を取得する
  • b) ある特定のノードを指定し,指定したノードがパッシブオープンで接続されるノード一覧を取得する

実装

概要

提案手法を実現するプロトタイプ実装であるmftracerをGitHubに公開している.https://github.com/yuuki/mftracer mftracerの概略図を以下に示す.

+-----------+
| mftracerd |----------+
+-----------+          | INSERT or UPDATE
                       V
+-----------+         +------------+
| mftracerd |------>  | PostgreSQL |
+-----------+         +------------+
                       ^       | SELECT
+-----------+          |       |            +----------+
| mftracerd |----------+       | <--------- | Mackerel |
+-----------+                  v            +----------+
                          +--------+  
                          | mftctl |
                          +--------+

ロールと実装の対応表を以下に示す.

ロール名 実装名
agent mftracerd
CMDB PostgreSQL
analyzer mftracer

mftracerでは,予め各ホストをMackerelに登録し,サービス・ロール[4]という単位でグルーピングを設定しておくことにより,mftctlがホスト単位ではなく,サービス・ロール単位でノードを集約し,扱うことができる.

使い方

mftracerの使い方の例を以下に示す.mftracerはmftctlコマンドにより,CMDBに接続し,引数で指定した条件に応じてネットワークグラフを表示する.

$ mftctl --level 2 --dest-ipv4 10.0.0.21
10.0.0.21
└<-- 10.0.0.22:many (connections:30)
└<-- 10.0.0.23:many (connections:30)
└<-- 10.0.0.24:many (connections:30)
    └<-- 10.0.0.30:many (connections:1)
    └<-- 10.0.0.31:many (connections:1)
└<-- 10.0.0.25:many (connections:30)
...
$ mftctl --level 2 --dest-service blog --dest-roles redis --dest-roles memcached
blog:redis
└<-- 10.0.0.22:many (connections:30)
└<-- 10.0.0.23:many (connections:30)
└<-- 10.0.0.24:many (connections:30)
    └<-- 10.0.0.30:many (connections:1)
    └<-- 10.0.0.31:many (connections:1)
└<-- 10.0.0.25:many (connections:30)
blog:memcached
└<-- 10.0.0.23:many (connections:30)
└<-- 10.0.0.25:many (connections:30)
...

 ホストフロー

プロトタイプでは,netstatとssコマンドで利用されているLinuxのNetlink APIを利用して,TCP接続情報を取得している. TCP接続を集約表示するlstfでNetlinkにより実行速度が1.6倍になった - ゆううきメモ

各接続の方式がアクティブオープンかパッシブオープンかを判定する実装は次のようにになっている.

  1. Netlink APIによりTCP接続情報を取得する
  2. LISTENステートの接続のローカルポートのみ抽出
  3. 1.と2.を突き合わせ,接続先ポートがリッスンポートであればアクティブオープン,それ以外の接続はパッシブオープンと判定する.

CMDBのスキーマ

CBDBのスキーマ定義を以下に示す.

CREATE TYPE flow_direction AS ENUM ('active', 'passive');

CREATE TABLE IF NOT EXISTS nodes (
    node_id bigserial NOT NULL PRIMARY KEY,
    ipv4    inet NOT NULL,
    port    integer NOT NULL CHECK (port >= 0)
);
CREATE UNIQUE INDEX IF NOT EXISTS nodes_ipv4_port ON nodes USING btree (ipv4, port);

CREATE TABLE IF NOT EXISTS flows (
    flow_id                 bigserial NOT NULL PRIMARY KEY,
    direction               flow_direction NOT NULL,
    source_node_id          bigint NOT NULL REFERENCES nodes (node_id) ON DELETE CASCADE,
    destination_node_id     bigint NOT NULL REFERENCES nodes (node_id) ON DELETE CASCADE,
    connections             integer NOT NULL CHECK (connections > 0),
    created                 timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated                 timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,

    UNIQUE (source_node_id, destination_node_id, direction)
);
CREATE UNIQUE INDEX IF NOT EXISTS flows_source_dest_direction_idx ON flows USING btree (source_node_id, destination_node_id, direction);
CREATE INDEX IF NOT EXISTS flows_dest_source_idx ON flows USING btree (destination_node_id, source_node_id);

nodesテーブルはノード情報を表現し,とflowsテーブルはホストフロー情報を表現する.

実装の課題

  • ネットワークトポロジの循環に対する考慮
  • クラウド事業者が提供するマネージドサービスを利用している場合,IPアドレスから実体をたどることの困難
  • パターンa)の実装
  • 時間範囲を指定した依存関係の取得

むすび

システムの複雑化に伴い,システム管理者が個々のネットワーク通信の依存関係を記憶することが難しくなっている. そこで,アプリケーションを変更せずに,アプリケーションに影響を与えることなく,適切な抽象度で情報を取得可能な依存関係解析システムを提案した. 実装では,Go言語で書かれたエージェントがLinuxのNetlink APIを利用し,RDBMSにホストフロー情報を格納し,Go言語で書かれたCLIから依存関係を可視化できた.

今後の課題として,問題の整理,サーベイ,評価がある. 問題の整理では,ネットワークの依存関係といっても,OSI参照モデルにおけるレイヤごとにシステム管理者が必要とする情報は異なるため,最終的にレイヤ4のTCP通信に着目する理由を明らかにする必要がある. サーベイについては,ネットワークの依存関係解析に関する先行研究は多岐に渡るため,調査し、本研究の立ち位置を明確にする必要がある. 評価については,先行手法となるファイアウォールロギングとパケットキャプチャによるレイテンシ増大による影響を定量評価し,提案手法の優位性を示す必要がある. また,実装では,すべての接続情報を取得できるわけではないため,接続情報の取得率を確認し,実運用において,十分な精度であることを確認する必要がある. さらに将来の展望として,同じような通信をしているホストをクラスタリング推定し,システム管理者がより抽象化された情報だけをみて依存関係を把握できるようにしたい. また,コンテナ型仮想化環境での依存関係の解析への発展を考えている.

参考文献

  • [1]: John K Clawson, "Service Dependency Analysis via TCP/UDP Port Tracing", Master thesis, Brigham Young University, 2015
  • [2]: Jian-Guang LOU, Qiang FU, Yi WANG, Jiang LI, "Mining dependency in distributed systems through unstructured logs analysis", ACM SIGOPS Operating Systems Review, 2010, vol 44, no 1, pp. 91
  • [3]: @itkq, "サービスのパフォーマンス数値と依存関係を用いたサービス同士の協調スケール構想", 第1回Web System Architecture研究会, https://gist.github.com/itkq/6fcdaa31e6c50df0250f765be5577b59
  • [4]: id:masayoshi, "ミドルウェア実行環境の多様化を考慮したインフラアーキテクチャの一検討", 第2回Web System Architecture研究会,https://masayoshi.hatenablog.jp/entry/2018/05/19/001806

発表スライド

質疑応答

発表時の質疑応答では,次のような議論をしました.

まず,接続が観測されたホストでも実はトラヒックはほとんど流れていなくて接続しているだけで,実際は利用していないホストであったりすることがある。提案手法は(分散トレーシングのようなより小さなリクエスト単位で追跡する手法と比較して)真の依存をみていないのではないか?という質問がありました.議論した結果の回答としては,どちらの手法が真かそうでないかというわけではなく,要件の違いにより,要件を満たす手法がかわってくるという話であると考えています.例えば,先行手法では,直接エンドユーザーへの影響のある接続を詳細に可視化することはできるが,システム管理のための接続(LDAP,SSHなど)を見落とすことがあります.

次に,UDPには対応しないのか?という質問がありました. UDPにはおそらく対応可能で,今の所はTCPのみサポートしています. ネットワーク層以下の依存関係可視化については,数多くの先行研究があります. アプリケーション層についても,ここ数年で多くの手法が提案されています. 一方で,トランスポート層に着目した依存関係の可視化の提案は少ないように思います. そこで,UDPに対応させ,トランスポート層の依存関係を満遍なく分析できる基盤という立ち位置を確保していくといいのではないかという議論をしました.

さらに,1日1回といった頻度で依存関係情報を収集するのではなく,リアルタイムに収集できるため,異常検知などに利用できるのではないかというアイデアもいただきました. あらかじめシステム管理者があるべき依存関係を設定しておき,その設定に反した通信を検知すると異常とみなすといった手法など,新しい監視手法を提案できるかもしれません.

また,Dockerのようなコンテナ環境であればリッスンポートが再起動するたびに変化していくため,ポート単位で追跡する手法は向いていないのではないかという質問もありました. 現在の提案手法では,IPアドレスとポートの組をノードとして扱っており,IPアドレスのほうはロールのようにグルーピングして扱えるようになっています. そこで,接続先のポート集合に名前をつけて管理するような仕組みが必要になってくるのではと考えています. エンドポイントの管理の課題として,他にもVIPのように実態のIPアドレスと異なるエンドポイントを利用することもあるため,仮想的には同じエンドポイントを参照していても,実態としてはエンドポイントが変化している問題を一般化して解くような提案を考えていくのが望ましいでしょう.

その他,エージェントをインストールするだけで使える導入の容易さも重要ではないかという指摘もいただきました.

あとがき

今回のWSA研も前回前々回と同様に,参加者の各種アイデアに対して,みんなで白熱した議論を展開するという流れになりました. みんな話をしたいことが多すぎて,いつも以上に,議論時間が長くなりました. 会場をご提供いただいたレピダムさんに感謝します.9人で議論するのにちょうどよいかつきれいな会議室で快適に過ごすことができました.

WSA研の特徴として,Web技術が主体でありながらも,隣接する技術領域の議論が飛び出してくることが挙げられます. 社内であったり,いつもの勉強会であれば,なんとなく要件が似通っていて,同じようなコンテキストで話をしていることが多いでしょう. 一方で,この研究会では,技術者と研究者が交わり,議論による創発を目指しているため,例えば,メールやIoTのような、いつものWebとは異なる要件をもつシステムに対しても,Webの技術を応用し,議論することにより,共通点や差異を理解し,新たなアイデアがでてくるといった場面があります. 僕自身はWSA研を開催するたびに,モチベーションがあがるということを体感しているため,これからも継続して開催していきたいと考えています.次回は4/13(土)で京都開催の予定です.

サーバレス時代におけるヘテロジニアス時系列データベースアーキテクチャ

この記事は、第2回ウェブシステムアーキテクチャ研究会の予稿です。

ウェブシステムをモニタリングするために、高可用性、高書き込みスケーラビリティ、メトリックの長期保存が可能な時系列データベースが求められている。 これらを実現するために、性能特性の異なる汎用Key-Value Store(以下KVS)を組み合わせ、透過的に問い合わせ可能な、ヘテロジニアス時系列データベースであるDiamondを開発した。 この記事では、Diamondを分散システムの観点で捉え、アーキテクチャ、データ構造、実装を紹介し、考察によりFuture Workを議論する。

1. はじめに

クラウドコンピューティング、コンテナ型仮想化、マイクロサービスなどの普及により、ウェブシステムの複雑度が向上した結果、人間が複雑な構造を観測するために、よりよいモニタリングが必要とされている1。 モニタリング技術のうち、時系列データモニタリングがあり、時系列データの可視化と時系列データによるアラーティングが一般的である。 時系列データモニタリングのために、多数のサーバから複数のメトリックを定期的に時系列データとして収集する必要があり、ストレージとして時系列データに特化した時系列データベースが利用される2。 時系列データベースには、様々な要求があるが、ここでは、メトリックの長期保持、高書き込みスケーラビリティ、高信頼性が要求されるユースケースに着目する。メトリックの長期保持については、異常検知でのユースケースなどがあり3、高書き込みスケーラビリティについては時系列データベース一般に求められ、高信頼性についてはService as a Service(SaaS)型のモニタリングサービスにおいて強く求められる。

時系列データベースには、大きく分類すると専用DBMS方式と汎用DBMS方式、RDBMS拡張の3種類がある4。RDBMS拡張については、ここでは汎用DBMS方式に含むこととする。 専用DBMS方式は、時系列データベースに最適化されたデータベースエンジンを実装しており、汎用DBMS方式は汎用DBMS上に時系列データベースとして動作させるためのアプリケーションを実装している。 専用DBMS方式は、性能とデータ保存効率がよいが、分散システムとしての信頼性の観点では、汎用DBMS方式と比較して、成熟度が劣る傾向にあると考える。 例えば、Graphite5、Gorilla6、BTrDB7などがある。 一方、汎用DBMS方式は、汎用であるがゆえに専用DBMS方式と比較して性能は劣りやすいが、分散システムとしての信頼性が高い。 例えば、OpenTSDB、KairosDBなどがある。

このように、長期保存、高書き込みスケーラビリティ、高信頼性を満たす時系列DBはある一方で、特殊なストレージエンジンのため運用コストが高い、または、長期保存かつ高書き込みスループットであってもリソース使用量が高いといういずれかの課題がある。 リソース使用量の高さは最終的に費用の高さに結びつく。

そこで、低運用コスト、低費用で、メトリックの長期保存、高書き込みスケーラビリティ、高信頼性を実現するために、複数の異なる汎用DBMSを組み合わせ、透過的な問い合わせが可能なヘテロジニアス型時系列データべースとしてDiamondを開発した8。 具体的には、インメモリ分散KVS、オンSSD分散KVS、オンHDD分散KVSを組み合わせ、書き込みスループットとストレージのGB単価を最適化し、クラッシュリカバリのためのWrite-Ahead Log(WAL)9として分散メッセージブローカーを利用した。 プロビジョニングと故障からの復旧の容易さを低減するために、マネージドサービスを用いたサーバレスアーキテクチャ10を採用した。

2. アーキテクチャ

アーキテクチャ概要

アーキテクチャとしての達成要件は以下の5つである。

  • (a) 書き込みI/Oが増加したときに各コンポーネントがスケールアウト可能
  • (b) 高可用性: 特定のノードが故障しても処理を継続可能
  • (c) 書き込みI/Oとデータ保持の観点で、コンピューティングリソース費用効率が高いこと
  • (d) リソースのプロビジョニングおよび故障時の復旧の容易さ
  • (e) 検証および開発の容易さ

(a), (b)については、汎用DBMS方式を選択し、Dynamo-styleと呼ばれるようないわゆる分散データベース[2]を利用することにより達成できる。 Dynamo-styleのデータベースは、CassandraやDynamoDBのようにKVSの形をとることが多いため、以下ではKVSの利用を前提とする。 (c)については、書き込みI/OだけみればインメモリKVSが有利だが、データ保持の観点でみるとHDDベースのKVSが有利である。 そこで、複数のKVSを組み合わせたヘテロジニアス構成により、インメモリKVS書き込みI/Oを受け付け、参照頻度の低い古いタイムスタンプをもつデータポイントを低速なディスクに配置する。 さらに、インメモリKVSの耐久性を補うため、Apache Kafkaのような分散メッセージブローカーをWALとして利用し、クラッシュリカバリできるようにする。 しかし、複数のOSSのDBMS実装を用いつつ、(d),(e)を達成することは、困難であると考え、そこで、クラウド事業者が提供するマネージドサービスを用いたサーバレスアーキテクチャを採用する。

一方で、専用DBMS方式を選ばない理由として、次のの2点がある。 (a),(b),(c)を満たす専用DBMS方式を新規開発することは難易度が高いことと、汎用DBMS方式のほうが分散システムとして成熟した実装である傾向があるためだ。

ヘテロジニアス環境においては、一時的な故障の発生により、各KVS間のデータ整合性と耐久性を失いやすくなる。 そこで、故障時にリトライ可能にするために、べき等性をもつデータ構造が重要となる。 さらに、複数の異なる特性をもつKVS実装を利用するため、特殊な機能に依存しないシンプルな機能要件のみで、データ構造を実装できることが重要だ。 一方、伝統的なデータベースが備えるACID特性を満たすことは、サーバモニタリング向け時系列データベースのコアな要求ではない。

以下では、アーキテクチャの動作フロー、データ構造およびKVSの機能要件を説明する。

動作フロー

Diamondアーキテクチャの動作フローを以下に示す。

     write path
         |
         v
+----------------------+
| 分散メッセージブローカー |
+----------------------+
         |
         v
+-----------------+
|  writer node    |
+-----------------+
         |
         v
+-----------------+
| インメモリ分散KVS | -------------\
+-----------------+               \
         |  flush                  \
         v                          v
+-----------------+              +-------------+
| オンSS分散KVS  | -----------> | reader node | ---> read path
+-----------------+              +-------------+
         |  export                  ^
         v                         /
+-----------------+               /
| オンHDD分散KVS  | ------------/
+-----------------+

まず、書き込み経路を説明する。

  1. クライアントが分散メッセージブローカーに非同期書き込み
  2. 分散メッセージブローカーからのメッセージを購読しているwriter nodeがインメモリ分散KVSに書き込み
    1. において、メトリックごとのデータポイント数が所定の個数を超えていれば、SSDベース分散KVSに書き込み
  3. 書き込み経路とは異なるバックグラウンド処理により、一定期間より前のタイムスタンプをもつデータポイントをSSDベース分散KVSからHDDベース分散KVSへデータを移動させる

分散メッセージブローカーは、インメモリKVSのクラッシュリカバリのために利用する。 分散メッセージブローカーに残留するログを再適用することにより、インメモリKVS上のロストしたデータを復元できる。

次に、読み込み経路を説明する。

  1. クライアントがメトリック名とタイムスタンプのレンジをクエリとして、reader-nodeに問い合わせる
  2. reader nodeは、クエリに含まれるタイムスタンプのレンジに応じて、データが配置されているKVSコンポーネントからデータポイントを取得する
  3. reader nodeは、データポイントを取得したのちに、任意の集約操作を適用し、クライアントへデータポイント列を返却する

Diamondアーキテクチャでは、複数のKVS間のデータの整合性を保つために、各KVSへの書き込み処理がべき等であることが重要となる。 書き込み処理がべき等であれば、既に書き込んだデータを再度書き込んだとしても、データの一貫性が担保される。 したがって、クラッシュリカバリの発生またはいずれかのKVSの故障が発生したとしても、リトライによりデータの耐久性を確保できる。

書き込み処理をべき等にするためのデータ構造を以下に説明する。

データ構造

Diamondは、Graphiteプロジェクトの時系列データベースであるWhisper[^14]データ構造に近いモデルを採用している。 Diamondでは、Whisperデータ構造の特徴のうち、以下の2点に着目している。

  • 書き込み処理がべき等であること
  • クライアントの入力にかかわらずメトリック系列に含まれるデータポイント数の最大値を制御できること

まず、KVSに時系列データを格納するための最もシンプルなデータ構造を、以下の図に示す。

+------+        +-----------------------------------------+
| name |  --->  | {timestamp:value, timestamp:value, ...} |
+------+        +-----------------------------------------+

図中のnameはメトリック名を表す文字列、timestampはデータポイントのUNIX時間を表すint64型の数値、valueはデータポイントの値を表すfloat64型の数値である。 このデータ構造では、メトリック名(name)をキー、ハッシュマップをバリューとし、ハッシュマップのキーをタイムスタンプ(timestamp), ハッシュマップのバリューを値(value)とする。 グラフ表示などの読み込みワークロードを考慮すると、ある系列に含まれるデータポイント列をまとめて効率よく読み出す必要があるため、データポイント列を同じレコード内にまとめて格納する。 データポイント列を表すデータ構造が、リストではなくハッシュマップである理由は、同じタイムスタンプをもつデータポイントの上書きを許し、べき等性を確保するためだ。 タイムスタンプのレンジがクエリに含まれる場合は、バリューをすべて取得し、reader nodeがレンジ外のタイムスタンプをもつデータポイントを間引く必要がある。

このデータ構造は、データポイントが追記されるたびに、レコードサイズが増大していく。 KVSはレコードサイズに制限をもつことがあり、その場合制限を超えない程度のレコードサイズを維持しなければならない。 DynamoDB11は400KB、Cloud Bigtable12は256MBのハードリミットをそれぞれ持つ。 この問題を解決するために、メトリック系列を固定幅のタイムウィンドウに分割するデータ構造を以下の図に示す。

+------------------+        +-----------------------------------------+
| name, wtimestamp |  --->  | {timestamp:value, timestamp:value, ...} |
+------------------+        +-----------------------------------------+

wtimestampは、タイムウィンドウの開始時刻を表すUNIX時間であり、ウィンドウサイズは固定長とする。 例えば、ウィンドウサイズが3000秒とすると、wtimestampの値は1482700020, 1482703020, 1482706020...のように3000ずつ加算される。

しかし、ウィンドウ内のデータポイント数の最大値が不定であるため、レコードサイズのハードリミットを超える可能性がある。 時系列データベースとしてサポートするメトリックの解像度をステップと呼ぶことにすると、クライアントがステップより小さなインターバルでメトリックを書き込む場合、ウィンドウ内のデータポイント数は想定よりも大きな値となる。 そこで、データポイント数の最大値を固定するために、ステップの倍数にアラインメントする。 例えば、解像度を60秒とするならば、元のtimestampが1482700025であれば、60で割った剰余を差し引いた値である1482700020に変更したのちに、KVSに書き込む。 ステップよりも小さいインターバルで書き込まれると、アラインメントにより既に書き込まれたtimestampの領域を上書きすることになり、ウィンドウ内のデータポイント数は一定の個数以下となることが保証される。

以上により、Diamondデータ構造がべき等性をもちつつ、KVSの制限内で書き込み処理可能であることを示した。 次に、Diamondデータ構造を実現するためのKVSの機能要件を説明する。

KVSの機能要件

Whisperデータ構造をKVS上で実装するには、各KVSへ求める最低限の機能要件として以下を満たす必要がある。

  • キーを入力とし、キーに対応するバリューを出力できる
  • バリューのデータ型としてバイナリが実装されている

KVSとしてごく標準的な機能を満たせば実装可能であることが要点である。

ただし、書き込み処理効率の観点では、バリューのデータ構造としてハッシュマップが実装されていることが望ましい。 データポイントをレコードに追記する際に、レコード内に存在する既存のデータポイントをKVSの外へ読み出したのちに、新らたに書き込むデータポイントを結合し、レコードを更新する必要がある。 KVSがハッシュマップを実装していれば、KVS内で効率よくデータポイントを追記できる。

さらに、ACIDのうち隔離性の観点では、read-modify-write操作をatomicに実行できることが望ましい。 複数のクライアントが同じレコードを書き込もうとしたときに、クライアントAが既存のレコードを読み取ったあとに、クライアントBがレコードに書き込んだとき、クライアントBの書き込みが失われることがありえる。

3. 実装

実装概要

分散メッセージブローカーとしてAmazon Kinesis Streams13、インメモリ分散KVSとしてRedis14、SSDベース分散KVSとしてAmazon DynamoDB、HDDベース分散KVSとしてAmazon S315を採用した。 writer nodeとして、AWS Lambda16、DynamoDBからS3へのデータ移動についてはDynamoDB TTLとDynamoDB Triggerを利用し、TTLの期限切れイベントを契機にLambda functionを起動し、S3へ期限切れしたレコードを書き込む。

実装の構成図を以下に示す。

              +---------------------------------------------------------------+
              |                                                               |
write path -->|--> Kinesis Streams --> Lambda (writer) ------------------+    |
              |                                                          |    |
              |                                   |<- Redis Cluster <----+    |
read path  <--|<-- ALB <-- reader(golang) <-------|                      |    |
              |                                   |<- DynamoDB <---------+    |
              |                                   |      v                    |
              |                                   |   Lambda (exporter)       |
              |                                   |      v                    |
              |                                   +<---- S3                   |
              |                                                               |
              +---------------------------------------------------------------+

実装の詳細については、AWS Summit Tokyo 2017のプレゼンテーション17,18,19にて紹介している。

KVS間のデータ移動

まず、RedisからDynamoDBへの移動の実装を説明する。 バックグラウンドプロセスによるバッチ処理ではなく、writerがRedisへのデータポイント書き込み時に条件を満たした場合のみメトリック系列単位でDynamoDBに移動させる。 条件は、Redis上の該当メトリック系列に含まれるデータポイント数がN個以上である。 条件を満たすかをチェックしたのちに、Redisのレコードを読み出し、DynamoDBへ書き込み、最後にRedisの該当レコードを削除する。 データポイントが書き込まれなくなったメトリック系列は、DynamoDBに移動されず、Redis上にデータポイントが残り続けるため 定期的なバッチ処理により、DynamoDBに移動させる。 もし、一連の処理の流れの中で、一時的なエラーが発生した場合はLambda function実行のリトライにより、耐久性とRedisとDynamoDB間のデータ整合性が担保される。

次に、DynamoDBからS3への移動の実装を説明する。 TimeFuzeアーキテクチャ20により、TTLイベントを契機にDynamoDB Triggerを経由して、Lambda Functionを起動し、DynamoDBの期限切れレコードをS3に書き込む。 S3に書き込む際に、FacebookのGorillaにて実装されているdouble-delta-encodingとXOR encodingにより差分符号化した結果を書き込み、データポイントあたりのバイト数を削減している。 S3への書き込み時に一時的なエラーが発生しても、Lambda function実行のリトライにより、データの耐久性と整合性が保証される。

データ位置の解決

reader nodeは、受信したメトリック名とタイムスタンプレンジから、各KVSのうち必要なデータが存在するKVSからデータを取得する。 データが存在するKVSは、メトリックごとに記録せずに、静的に解決している。 タイムスタンプレンジの開始時刻をst、終端時刻をet、現在時刻をnow、Redisにデータが残留する最大経過秒数をn、DynamoDBにデータが残留する最大経過秒数をm、S3にデータが移動するまでの最短経過秒数をkとすると、Redisからのデータ取得条件は et > (now - n)、DynanoDBからのデータ取得条件は et > (now - m)、S3からのデータ取得条件は (now - k) > stとなる。 ただし、n < m とする。 n, m, kの値はヒューリスティックに決定される。

同じメトリック系列かつ同じタイムスタンプをもつデータポイントを異なるKVSからそれぞれ取得した場合、Redis、DynamoDB、S3の順に採用する。

費用特性

本実装において、書き込みスループットとデータ保持の観点で、3種類のKVSによる費用最適化が可能であることを確認する。

まず、サンプルとして、100,000データポイント/sの書き込みスループットにおける費用比較を以下の表に示す。 2018/05/05時点のap-northeast-1リージョンのオンデマンド費用を元にしている。

KVS I/O費用(USD/月)
Redis 1405.44
DynamoDB 54300.83
S3 1258848.00

RedisはEC2インスタンスのr4.large(0.176USD/時間)を使用し、各パーティションあたりのレプリカ数を2、つまり3ノード/パーティションとする。 1ノードあたりの書き込み上限を25,000reqs/secとする(要出典)。 したがって、r4.largeインスタンスが12台必要となる。 EC2インスタンス上のRedisは、書き込み元のwriterと異なるアベイラビリティゾーンに配置されている場合、別途GBあたりのゾーン間転送料金が発生する。 DynamoDBの料金構造21からアイテムサイズに比例してI/Oコストが大きくなる。今回は、最低単位の1KBで計算する。 S3は、標準ストレージを利用するもとすると、S3のPUT料金は1000リクエストあたり0.0047USDである。 S3はKVSではあるが、ファイルを格納するためのオブジェクトストレージであるため、高スループットかつ小さな書き込みには向いておらず、今回のワークロードでは上表のように高額な料金となる。

次に、データ保持費用における費用比較を以下の表に示す。

KVS データ保持費用(USD/GB/月)
Redis 21.96
DynamoDB 0.25
S3 0.025

r4.largeのRAMのサイズは16GBであり、GB単価は7.32USD/月、レプリカ数を2とすると上表の値となる。 S3は標準ストレージを利用するものとする。

以上より、I/O費用とデータ保持費用はトレードオフの関係にあり、高スループット書き込みをインメモリKVSで受け付け、蓄積したメトリックを低速なディスク指向KVSへ移動させる手法の有効性を確認できた。

4. 考察と今後の課題

Diamondの欠点

Diamondは、アーキテクチャレベルで以下の欠点をもつ。

  • (1) 最適化された専用DBMS方式と比べ、処理効率の観点で無駄が多い
  • (2) 各KVSにて実装されているデータ構造が異なるため、ある特定のKVSに実装されている特殊なデータ構造を使いづらい
  • (3) 構成が一見複雑にみえる

(1) については、アーキテクチャの章の概要で述べたように、他の要件の達成により欠点を補えると考えている。 (2) については、シンプルなKVSで表現できる時系列データ構造やインデックス構造の限界がどこにあるかを調査する必要がある。 (3) については、分散トレーシングなどの分散システムをモニタリングする技術の発達により、補えると考えている。

実装レベルでは、以下の欠点がある。

  • (1) S3に移動済みの古いタイムスタンプをもつデータポイントを書き込みしづらい
  • (2) データ位置の解決がヒューリスティックであり、データを正しいKVSから取得できることを完全には保証できない

(1)については、通常の書き込み経路とは異なる、過去の時系列データをまとめたファイルをバッチでアップロードする書き込み経路を実装することで解決できる。 (2)については、静的解決ではなくフォールバック方式またはインデックス方式による動的解決を考えている。

フォールバック方式とは、インメモリKVS、SSDベースKVS、HDDベースKVSの順にメトリック系列を参照することである。 タイムスタンプレンジの開始時刻のデータポイントが存在すれば、その時点でフォールバックを停止し、応答を返却する。 存在しなければ、引き続きフォールバックする。 欠点として、静的解決と比較して、低速なKVSに応答速度を律速されやすい可能性がある。

インデックス方式は、メトリック名とタイムスタンプレンジを入力として、該当するKVSを返却するインデックスにより動的解決することである。 インデックスはインメモリKVS上に保持しておき、データ移動が発生するたびに、インデックスを更新する。 欠点として、インデックス分のメモリ使用とI/Oが増加することがある。

将来機能

Diamondの時系列データモデルとして、現在のWhisperモデルに加えて、Prometheus22が提供するようなラベルによる多次元データモデルをサポートを考える。

前者の多次元データモデルの例を以下に示す。

requests_total{path="/status", method="GET", instance=”10.0.0.1:80”}
requests_total{path="/status", method="POST", instance=”10.0.0.3:80”}
requests_total{path="/", method="GET", instance=”10.0.0.2:80”}

メトリック名であるrequests_totalと、ラベルと呼ばれるキーバリューペアの組み合わせにより、メトリック系列を表現する。 Prometheusでは、V2ストレージではLevelDBによりラベルに対するインデックスを作成しており、V3ストレージでは転置インデックスに置き換えている23。 Prometheusのストレージはファイルシステム上に実装されている一方で、DiamondではKVS上に実装する必要がある。

そこで、V3ストレージエンジン同様に、次のように転置インデックスをKVS上に実装することを考えている。

  1. メトリック系列にユニークなIDを割り当て、データポイントを書き込む
  2. データポイントに紐づくラベルのキーペアをキー、ラベルに紐づくメトリックIDをバリューとして、別途用意したインデックス用KVSに書き込む。
  3. 参照時には、ラベルを入力として、メトリックIDを転置インデックスから取得し、メトリックIDをキーとして各KVSからデータポイント列を取得する。

5. まとめ

本稿では、時系列データに対する高い書き込みスケールアウト性、高可用性、費用効率、低い運用性、開発容易性を実現する時系列データベースアーキテクチャを提案し、実装を示した。 アーキテクチャの要点は、複数のKVSを組み合わせたヘテロジニアス構成、メッセージブローカーによるWALリプレイ、サーバレスアーキテクチャおよびべき等性とデータ幅を制御可能なデータ構造である。 考察では、アーキテクチャと実装のそれぞれの欠点と解決策、さらに多次元データ構造のサポートのアイデアを議論した。

今後の課題としては、以下の2点について、実験・調査したいと考えている。 まず、アーキテクチャ要件の(a)、(b)、(c)の有効性を示すために、先行実装であるGraphiteと比較実験する。 次に、アーキテクチャ要件の(d)、(e)の有効性を示すための文献を調査する。例えば、ソフトウェア開発論文のサーベイにより開発容易性やOSS実装の複雑性を示すヒントを発見する。

スライド

あとがき

@hirolovesbeerさんからの「ヘテロジニアス(heterogeneous)に込めた意味はなにか?」という質問に対してうまく答えられなかったが、この問いの答えがもともとあとがきで書こうと思っていたことだということに気づいたので、ここに記しておく。

ヘテロジニアスの対義語として、ホモジニアス(homogeneous)があり、ホモジニアスな時系列データベースとはここではDBMSの実装を一つだけ使ったものを指す。 それに対して、ここでのヘテロジニアスとは、異種混合データベースの上にデータベースエンジンを実装することを指しており、ヘテロジニアスであることがDiamondアーキテクチャのアイデアの要点となる。

Martin Kleppmann著、「Designing Data-Intensive Applications」24の12章 The Future Of Data Systemsにて、データシステムを構築する上で、すべての異なる状況に適した1つのソフトウェアは存在せず、異なるソフトウェアを組み合わせアプリケーションを開発していく必要があるという趣旨の意見が書かれている。 Diamondアーキテクチャは、この考え方の延長線上にあり、インデックス、WAL、メモリ上のデータ構造、ディスク上のデータ構造など、古典的なデータベースエンジンの構成要素を、ヘテロジニアス環境で再構築したものになる。 そして、サーバレスアーキテクチャにより、分散システム上での複数箇所にまたがるデータの更新、移動を、信頼性のあるビルディングブロックの上に構築しやすくなった。

実際に、ヘテロジニアスKVS環境でGraphiteを実装したもになるため、今度はヘテロジニアスKVS環境でPrometheusを実装することを考えるといろいろとアイデアがでてくる。

実はこれと似た話が身近にあり、例えば id:matsumoto_r さんの言葉を引用する。

今後はVMやコンテナの連携が今でいうOSとなる世界が来ると思っているので、古典的なOSの機能をいかにネットワークに通じたそれに置き換えていくかに挑戦 cgroupとLinux Capabilityの活用 - https://speakerdeck.com/matsumoto_r/rcon-and-capcon-internals-number-lxcjp

この言葉を、分散システム上でOSの構成要素を再構築していくということだと理解しており、これをデータベースに置き換えたような話をやろうとしている気がしてきている。

参考文献


  1. ウェブシステムの運用自律化に向けた構想 - 第3回ウェブサイエンス研究会 - ゆううきブログ

  2. B. Mitschang et al. “Survey and Comparison of Open Source Time Series Databases.”, In Proceedings of Database Systems for Business, Technology, and Web, 2017

  3. Florian Lautenschlager, et.al., “Chronix: Long Term Storage and Retrieval Technology for Anomaly Detection in Operational Data”, In Proceedings of 14th USENIX Conference on File and Storage Technologies (FAST), 2016.

  4. Jensen, Søren Kejser, et al. “Time Series Management Systems: A Survey.” IEEE Transactions on Knowledge and Data Engineering, vol.29, no.11, 2017, pp. 2581-2600.

  5. “Whisper”, https://github.com/graphite-project/whisper

  6. Pelkonen, Tuomas and Franklin, Scott and Teller, Justin and Cavallaro, Paul and Huang, Qi and Meza, Justin and Veeraraghavan, Kaushik, “Gorilla: a fast, scalable, in-memory time series database”, In Proceedings of the VLDB Endowment, 2015

  7. Michael P Andersen and David E. Culler, “BTrDB: Optimizing Storage System Design for Timeseries Processing”, In Proceedings of 14th USENIX Conference on File and Storage Technologies (FAST), 2016.

  8. Hatena Co., Ltd., “はてな、サーバー監視サービス「Mackerel」の時系列データベースを強化。1分間隔の時系列データ保持期間を460日に変更”, http://hatenacorp.jp/press/release/entry/2018/01/24/153000, 2018

  9. C. Mohan and Frank Levine, “ARIES/IM: An Efficient and High Concurrency Index Management Method Using Write-Ahead Logging”, at ACM International Conference on Management of Data (SIGMOD), June 1992.

  10. Sarah Allen, et al. “CNCF Serverless Whitepaper v1.0”, https://github.com/cncf/wg-serverless

  11. Amazon Web Services, Inc., “Amazon DynamoDB”, https://aws.amazon.com/dynamodb/, 2018

  12. Google, “CLOUD BIGTABLE - A high performance NoSQL database service for large analytical and operational workloads”, https://cloud.google.com/bigtable/, 2018

  13. Amazon Web Services, Inc., “Amazon Kinesis Data Streams”, https://aws.amazon.com/kinesis/data-streams/, 2018

  14. “Redis”, https://redis.io/

  15. Amazon Web Services, Inc., “Amazon S3”, https://aws.amazon.com/s3/, 2018

  16. Amazon Web Services, Inc., “AWS Lambda”, https://aws.amazon.com/lambda/, 2018

  17. 時系列データベースという概念をクラウドの技で再構築する - ゆううきブログ

  18. サーバレスアーキテクチャによる時系列データベースの構築と監視 / Serverlessconf Tokyo 2017 // Speaker Deck

  19. AWS で実現した Mackerel 時系列データ1分粒度長期保存の裏側 / Mackerel Meetup #11 Tokyo // Speaker Deck

  20. TimeFuzeアーキテクチャ構想 - 処理とデータとタイマーを一体化したデータパイプライン - ゆううきブログ

  21. DynamoDBのインフラコスト構造と削減策 - ゆううきブログ

  22. Prometheus Authors, “Prometheus”, https://prometheus.io/, 2018

  23. Fabian Reinartz, “Writing a Time Series Database from Scratch”, https://fabxc.org/, April 20 2017.

  24. Martin Kleppmann, “Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems”, O'Reilly Media, March 2017

DynamoDBのインフラコストの構造と削減策

Amazon DynamoDBは、RDSのようなインスタンスサイズによる課金モデルではなく、ストレージのデータ使用量とスループットを基にした課金モデルになっている。 インスタンスサイズによる課金モデルでないデータストア系サービスとして、他にはS3、Kinesisなどがある。 これらは、AWSの中でも、フルマネージドサービスと呼ばれる位置づけとなるサービスだ。 フルマネージドサービスは、ElastiCacheのようなそうでないものと比較し、AWSに最適化されていて、サービスとしてよくできていると感じている。

Mackerelの時系列データベースのスタックの一つとして、DynamoDBを採用している。 時系列データベースの開発は、コストとの戦いだったために、それなりにコスト知見が蓄積してきた。(時系列データベースという概念をクラウドの技で再構築する - ゆううきブログ)

(※ 以下は、2018年4月16日時点での情報を基にしている。)

DynamoDBのコスト構造

冒頭に書いたように、「ストレージのデータ使用量」と「スループット」を基にするため、インスタンスサイズモデルと比較して、よりアプリケーションロジックがダイレクトに反映されるコスト構造になっている。

正確なコスト定義は、公式ドキュメントを参照してほしいが、これを初見で理解するのはなかなか難しい。データ使用量のコストモデルはGB単価なため把握しやすい。しかし、スループットに関するキャパシティユニットの概念が難しい。 以下では、各要素について、メンタルモデルを説明する。 AWS Calculatorに適当な値をいれてみながらコストを見てみるとだいたいの感覚をつかめると思う。

ストレージのデータ使用量

ストレージのGB単価は、$0.25/GB/月であり、S3のStandardストレージクラスと比較して、およそ10倍程度となる。 こう聞くと割高に聞こえるが、画像やブログのテキストデータなどを格納しなければ*1、それほど高いというわけではない。 実際、1TBのデータ使用に対して、$300/月程度のコストとなる。

DynamoDBはSSD上に構築されている*2ようなので、安定して低レイテンシという性能特性があるため、大容量データをひたすら保持するというよりは、エンドユーザに同期的に応答するようなユースケースに向いている。

スループット

スループットによるコストには、「秒間のread数」と「秒間write数」と「対象のアイテムサイズ」が要素として含まれる。 S3のように月のAPIコール数の合計により課金されるわけではなく、予めどの程度のスループットとなるかを予測し、事前にキャパシティとして確保しておく必要がある。 したがって、見込んでいる最大のスループットにあわせてキャパシティ設定することになり、正味のリソース消費量よりも余分にコストがかかることに注意する。 ただし、後述するAuto Scalingにより、余分なコストを抑えられる可能性がある。

スループットによるコストは、大雑把には以下の特性をもつ。

  • 「秒間read数」または「秒間write数」に比例してコストが大きくなる
  • アイテムサイズを1KBに固定したときの「秒間write数」の単価は「秒間read数」の約5~6倍程度となる。読み取り整合性を結果整合性のある読み込みにすると、「秒間read数」のコストは1/2となる
  • readまたはwriteしたアイテムのサイズに比例してコストが大きくなる
  • 「アイテムサイズ」のコスト単位はreadとwriteで異なる。readは4KB単位で比例し、writeは1KB単位で比例する。
  • グローバルセカンダリインデックスおよびローカルセカンダリインデックスを利用する場合、writeする度に、内部的にインデックスの更新作業が走るため、writeコストが大きくなる。*3

2番目、4番目、5番目の特性がwriteスループットの大きいユースケースでDynamoDB利用が割高と言われる所以である。 同条件でのread(strong consitency)とwriteの差は、アイテムサイズが最小単位の場合では5~6倍程度となる。 さらに、アイテムサイズがより大きい場合では、readとwriteの差はより大きくなる。

複数アイテム操作

DynamoDBは、同時に複数のアイテムを操作するAPI(BatchGetItem、BatchWriteItem)があり、これらを用いてアプリケーションの性能を改善することができる。 しかし、基本的には、キャパシティユニットの消費を抑えられるわけではないため、これらのAPIを用いてコストが下がるわけではない。 ただし、Queryについては、単一の読み取りオペレーションとして扱われる*4。これについては最近まで見落としていた。 キャパシティーユニットの消費の詳細については、 https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/CapacityUnitCalculations.html に書かれている。

フィルタ式

フィルタ式を用いると、クライアントとDynamoDB間のネットワーク通信料を削減できる。 しかし、DynamoDB内部の読み取りオペレーションの数が減るわけではないため、キャパシティユニットの消費を抑えることはできない

コスト試算表

以下の表では、いくつかのケースで、スループットコストを試算している。実際には、readとwriteのワークロードは両方発生するため、readとwriteのコストの合計値となる。コストはap-northeast-1のもの。

アイテムサイズ(KB) read or write 秒間オペレーション数 読み取り整合性 コスト(USD)
1 read 1000 strong 100
1 read 5000 strong 540
4 read 5000 strong 540
40 read 5000 strong 5400
40 read 5000 eventually 2700
1 write 1000 - 500
4 write 1000 - 2000
4 write 5000 - 10800

表をみるとわかるように、writeのコストが大きくなりやすい。

ネットワーク

同一リージョンの他のAWSサービスとの間で転送されたデータは無料となる。S3と基本的に同じ。

ただし、プライベートサブネットにあるクライアントからDynamoDBへ転送されたデータには、VPC NAT Gateway での転送処理コストが発生する。 https://aws.amazon.com/jp/vpc/pricing/によると、TokyoリージョンではGB単価が$0.062であり、AZ間通信の$0.010/GBの6倍あるため、あなどれないコストになる。

コスト削減策

知る限りのDynamoDBのコスト削減策を書いてみる。ここにある方法以外の策がほしい場合は、そもそもDynamoDBに向いてないユースケースか、アーキテクチャレベルで再設計する必要があるかもしれない。

リザーブドキャパシティ

DynamoDBには、EC2のリザーブドインスタンスのように前払いにより、コストを下げるリザーブドキャパシティがある。 https://aws.amazon.com/jp/dynamodb/pricing/によると、前払いした分のキャパシティ消費が$0になるわけではなく、別途時間料金が設定されており難しい。 最大効率では、1年間前払いで、約50%程度の割引になり、3年間前払いでは、約75%程度の割引になるはず。

Auto Scaling

前述したように、DynamoDB Auto Scalingにより、固定的なキャパシティ割り当てから、実際に消費したキャパシティユニットにより近づけることで、コスト削減できる。 ただし、一日のキャパシティ削減回数には限りがあるため、インターバルの小さいキャパシティ増減には対応できない。

メモリキャッシュ & DAX

DynamoDBのシャーディング機構は、プライマリキーを内部ハッシュ関数への入力とし、内部的なノード配置を決定する。 したがって、特定プライマリキーにreadまたはwriteが集中すると、テーブルのキャパシティを増やしても、1つのノードの性能限界にあたってしまう。

readについては、前段にmemcachedのようなキャッシュを挟むことで対応できる。 もしくは、DynamoDBのインメモリキャッシュであるDynamoDB Acceleratorを使う。 DAXはDynamoDBのwrite throughキャッシュとして動作し、DynamoDBのストレージまで到達してから、DAX上のアイテムに書き込み、レスポンスを返す。 一方で、読み込みは、インメモリキャッシュのレスポンスを返すため、readが支配的なワークロードで効果を発揮する。 DAXの動作モデルについては、https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/DAX.consistency.htmlが詳しい。 DAXは自分で使ったことがないので、DAXそのもののコストについては理解が浅いが、インスタンスタイプベースのコストモデルになっている。

TTL

DynamoDBではアイテムの削除もwriteとして扱われる。 時間経過による自動削除を許すケースであれば、TTLにより、writeコストを削減できる。 TTLによる削除であれば、キャパシティユニットを消費しない。 TTLはアイテムごとに設定できるため、RedisやMemcachedと同じような感覚で扱える。 ただし、おそらく内部的にはコンパクションのタイミングで削除されるため、設定したTTLの時刻になった瞬間に削除されるわけではないことに注意する。

テーブルデータ構造

前述したようにDynamoDBは、writeが支配的なワークロードで、コストが大きくなりやすい。 しかし、メインDBとなるMySQLやPostgreSQLはwriteスケールアウトしづらいため、DynamoDBを使いたいケースにはwriteが支配的であることは多いように思う。 スループットコストの特性からみて、基本的には、writeの回数を減らすか、アイテムサイズを小さくすることで対処する。

writeの回数を減らすには、一つのアイテムに詰め込んで書き込むことになる。 しかし、単純に詰め込んでも、アイテムサイズに比例して、一回あたりの書き込みコストが増加してしまう。 そこで、例えば、1KBが最小単位なため、1KB未満のデータを書き込んでいる場合は、1KBぎりぎりのサイズになるように、データを詰め込んで書き込む。

アイテムサイズを小さくするには、なんらかの手段で圧縮し、バイナリとして書き込むという手段がある。 バイナリとして書き込む場合は、アイテムの追記が難しい。追記するには、一旦アイテムのデータを読み出してから、データを連結して書き込む必要があり、読み出しコストが余分にかかる。リスト型やマップ型の要素としてバイナリ型を使って意味があるケースであれば、素直に追記できる。

数値はおおよそのサイズが「(属性名の長さ)+(有効桁数 2 あたり 1 バイト)+(1 バイト)」と書かれており、桁数ベースなので、バイナリとして扱うほうがサイズ効率はよい。 https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/CapacityUnitCalculations.html

あとは、属性名の長さがアイテムサイズに含まれるので、長い属性名を付けている場合は、短くするとよい。

ネットワーク転送

NAT Gatewayのコストは、DynamoDB VPCエンドポイントにより回避できる。 S3とDynamoDBのVPCエンドポイントは、ゲートウェイVPCエンドポイントと呼ばれるタイプのエンドポイントで、プライベートなDNSエンドポイントが払い出されるわけではなく、VPCのルーティングテーブルを変更し、L3でルーティングする。 想像したものと違ったので、面食らったが、NAT Gatewayのコストは問題なく削減できる。

まとめ

DynamoDBのコスト構造と、自分が知るコスト削減手段を紹介した。 DynamoDBは、データモデルとコストモデルのための公式ドキュメントがもちろん揃っているのだが、計算式はそれなりに複雑になので、妥当な感覚を掴むまでに時間がかかった。 コスト見積もりし、サービスインしたのちに、実際の使用量を確認し、改善策を打つことで、徐々に理解が進んできた。 CPU、メモリなどのハードウェアベースのキャパシティプランニングとは異なり、アプリケーションロジックフレンドリーな計算モデルなため、アプリケーション開発者がコスト見積もりやスケーリング対応をしやすいサービスになっている。

ちなみにAWS Lambdaのコスト構造については、次のエントリ内で紹介している。コスト効率の悪いLambdaアプリケーションの性質に関する考察 - ゆううきブログ

*1:そもそも、DynamoDBはアイテムサイズが現在のところ400KBまでという制限がある https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Limits.html#limits-items

*2:https://aws.amazon.com/jp/dynamodb/

*3:プライマリキー単体と比較して、プライマリキー+ソートキーの複合キーの作成により、追加のwriteコストが発生することはないと認識しているが、ドキュメントを見つけられなかった。

*4:内部的にはソートキーでソートされたSSTableのような構造になっていて、Queryは、OSのファイルシステム上で連続領域に対するreadになり、1回か少ないI/Oで読み出せるためではないかと推測している

Webサービスをデータセンター移行するときに必要となる技術要素

クラウドへの移行を含むデータセンター(以下DC)移行事例を基に、WebサービスをDC移行するための基本的な技術要素を紹介します。具体的には移行手順、データベースのデータ移行、ネットワーク、DNSなどです。 最近、社内で大規模なDC移行を実施しつつあり、DC移行とはなにかをメンバーへ共有するための文章でもあります。 ちなみに、この記事はHosting Casual Talks #4の発表内容を書き下ろしたものです。

続きを読む

AnsibleとDockerによる1000台同時SSHオペレーション環境

1000台同時SSHオペレーション環境を構築するにあたって、手元のローカル環境の性能限界の問題を解決するために、オペレーションサーバをSSHクライアントとすることによりSSH実行を高速化した。実行環境としてDocker、レジストリとしてAmazon ECR(EC2 Container Registry)を用いて、ローカル環境とオペレーションサーバ環境を統一することにより、オペレーションサーバの構成管理の手間を削減した。

はじめに

3年前に Ansible + Mackerel APIによる1000台規模のサーバオペレーション - ゆううきブログ という記事を書いた。 この記事では、ホストインベントリとしてのMackerelと、並列SSH実行にすぐれたAnsibleを組み合わせ、オペレーション対象のホスト情報をプログラマブルに管理する手法を紹介した。また、工夫点として、並列SSH実行する上でのパフォーマンスチューニングやレガシーOSでの対応について紹介した。

しかし、並列度をあげるとforkするプロセスの個数が増えてローカル環境のリソースをくいつぶすという問題があった。加えて、並列度が小さいと実行終了まで待たされるという問題があった。 さらに、ローカル環境のOSやハードウェア性能が人によって異なるため、ローカル環境を統一して整備する手間があった。特に毎日利用する用途ではないため、利用頻度に対する整備コストが大きかった。

そこで、ローカルから対象ホスト群に接続するのではなく、オペレーションサーバをクライアントとして対象ホスト群に接続する仕組みに変更した。 これにより、スケールアップが容易になり、普段利用しているサーバ構成管理ツールを用いて、複数ユーザが同じ環境を利用できるようになった。 オペレーションサーバを対象ホスト群と同じデータセンター内に配置すれば、SSHクライアントと対象ホスト群とのレイテンシが小さくなるため、実行速度が向上する可能性があるというメリットもある。

しかし、playbookの開発にはオペレーションサーバではなくローカル環境を用いるため、ローカル環境とオペレーションサーバ環境の差異を小さくできるほうがよい。 そこで、Dockerを用いて、ローカルとオペレーションサーバ共通の環境を構築する。

アーキテクチャと実装

アーキテクチャ

アーキテクチャを図1に示す。 図1: アーキテクチャ

単一サーバから命令を各サーバへ送信するPull型のイベント送信モデルになる。 ローカル -> オペレーションサーバ -> 対象ホストの流れに沿ってSSHログインする。 オペレーションサーバ上では、並列SSHツール(Ansible)が起動し、記述したplaybookにしたがい、オペレーションを実行する。 対象ホスト一覧は、ホストインベントリ(Mackerel)のAPIから取得し、フィルタ(Ansibleのfilter)により、除外パターンを記述できる。 Ansibleそのものとplaybook、スクリプトなどが入ったDockerイメージをコンテナリポジトリ(ECR)にPUSHし、オペレーションサーバ上でPULLしておく。

ヘルパースクリプト

運用観点では、オペレーションサーバのホスト名、Dockerイメージ名、コンテナ名などを覚えてオペレーションはしたくない。 そこで、ヘルパースクリプト yuuki/ansible-operation-helper を参考のため公開している。これは社内事情を吸収するための層になるため、汎用的ではなく、そのまま動くわけではない。

  • Makefile: でDockerイメージのビルド、ECRへのプッシュ、オペレーションサーバへのデプロイ、オペレーションサーバが動作するかどうかチェックするテストのタスクを定義している。
  • bin/on_local_container: ローカルのDockerコンテナ上で引数指定したコマンドを実行する。
  • bin/on_remote: オペレーションサーバにSSHしつつ、引数指定したコマンド実行する。
  • bin/on_remote_container: オペレーションサーバ上のDockerコンテナにて、引数指定したコマンドを実行する。
  • libexec/mackerel.rb: Mackerel用のAnsible Dynamic Inventory。

工夫

オペレーションサーバ越しのroot権限実行

一斉にOSのパッケージを更新したいなど、コマンドをroot権限で実行したいことはケースはたくさんある。 Ansibleでは、Becomeにより、対象ホストにてコマンドをsudo/suを用いて、インタラクティブパスワード入力でroot権限実行できる。 しかし、たいていはsudoerの秘密鍵がローカルにあるため、オペレーションサーバ経由で対象ホストにsudoerとしてログインするにはひと工夫必要になる。 *1

オペレーションサーバ上には当然sudoerの秘密鍵を配置するわけにはいかないため、今回はagent forwardingを用いた。 agent forwardingにより、オペレーションサーバ上のssh-agentプロセスがUNIXドメインソケットを提供し、オペレーションサーバ上のSSHクライアントがそのソケットから認証情報を読み出し、対象ホストへのSSH接続を認証する。

Agent forwarding should be enabled with caution. Users with the ability to bypass file permissions on the remote host (for the agent's Unix-domain socket) can access the local agent through the forwarded connection. An attacker cannot obtain key material from the agent, however they can perform operations on the keys that enable them to authenticate using the identities loaded into the agent.

https://linux.die.net/man/1/ssh

agent forwardingは、セキュリティポリシー上、問題ないか確認した上で利用したほうがよいと考えている。 上記のssh(1)のmanにも書かれているように、攻撃者がagentにロードされた認証情報を使って、オペレーションすることができてしまう。*2 例えば、インターネットに公開された踏み台サーバ上でagent forwardingを用いることは好ましくない。

ヘルパーツールでは、agent forwardingをむやみに利用しないように、on_remoteラッパー実行時のみ、 forwardingを有効するために、-Aオプションを用いている。参考

rawモジュールとscriptモジュールのみの利用

本格的なサーバ構成管理をするわけではないため、シェルスクリプトを実行できれば十分だ。 Ansibleにはrawモジュールscriptモジュールがあり、シェルスクリプトを実行できる。 rawモジュールとscriptモジュールのメリットは、対象ホスト上のPython環境に左右されずにオペレーションできることだ。 例えば、Ansible 2.4からPython 2.4/2.5のサポートが切られた*3ため、CentOS 5ではepelからpython 2.6をインストールして使うなどの手間が増える。Ansible 2.4 upgrade and python 2.6 on CentOS 5

Ansibleの実行ログのGit保存

どのサーバに対してオペレーションしたかを記録するため、ログをとっておくことは重要だ。 CTO motemenさんの furoshiki2を用いて、Ansibleのコマンド実行ログをGit保存している。 作業ログと履歴をシンプルに共有できる furoshiki ってツールを書いた - 詩と創作・思索のひろば 前述の on_remote_container 内でansible-playbookの実行に対して、furo2コマンドでラップするだけで使える。

まとめと今後の課題

AnsibleとDockerを用いて、オペレーションサーバ経由で、大量のサーバに同時SSHオペレーションする環境の構築例を紹介した。 アーキテクチャと、アーキテクチャを実現するOSS、ヘルパーツールに加えて、3つの工夫として、agent forwardingによる権限エスカレーション、raw/scriptモジュールの利用、furoshiki2によるログのGit保存がある。

並列SSHすることが目的であれば、Ansibleはややオーバーテクノロジーといえるかもしれない。 具体的には、YAMLにより宣言的に記述されたplaybookや、各種Ansibleモジュールは今回の用途では不要であり、これらの存在は余計な学習コストを生む。 そこで、シンプルな並列SSHコマンド実行ツールとして、最近発見したorgalorgに着目している。 サーバとの接続に対してプロセスをforkするAnsibleと異なり、orgalorgはgoroutineを用いるため、より高速な動作を期待できる。 しかし、現時点では、パスワードありsudo実行、ssh agent forwardingに対応していない*4ことと、AnsibleのPatterns機能が、ホスト管理上非常に便利なため、今のところはAnsibleを利用している。

*1:LinuxユーザとSSH鍵の管理ポリシーにより、とりえる手段がかわってくるため注意

*2:鍵の中身そのものを取得はできないとのこと

*3:https://github.com/ansible/ansible/issues/33101#issuecomment-345802554

*4:これぐらいならコントリビュートできそう