Web アプリケーションを把握するためのコンソール

技術部開発基盤グループの鈴木 (id:eagletmt) です。 クックパッドではほとんどの Web アプリケーションが Amazon ECS 上で動く状態となり、またマイクロサービス化や新規サービスのリリースにより Web アプリケーションの数も増えていきました。 個々のアプリケーションでは Docker イメージを Jenkins でビルドして Amazon ECR にプッシュし、Rundeck から hako を用いて ECS にデプロイし、またその Web アプリケーションからは Amazon RDS、Amazon ElastiCache 等のマネージドサービスを活用しています。

このように多くの Web アプリケーションが存在し、また各アプリが別のアプリや AWS の様々なマネージドサービスを利用している状況では、どのアプリが何を使っているのかを把握することが困難になっていきます。 具体的には新しくチームに所属したメンバーが、どのアプリがどの GitHub リポジトリに存在するのか、どの Jenkins ジョブを使っているのか、どうデプロイするのかを把握することが難しかったり、また自分のアプリの調子が悪いときにどのデータベースのメトリクスを見ればいいのか、どの ELB のメトリクスを見ればいいのかが難しかったりします。 マイクロサービス化を推し進めて各チームに権限と責任を移譲していく上で、各チームが自分たちのアプリの状態をすばやく把握できる状況は不可欠です。 そういった課題を解決するために hako-console を昨年開発していたので、その話を書こうと思います。 なお今回の話は昨年11月に行われた Rails Developers Meetup #7 での発表と一部重複します。

https://speakerdeck.com/eagletmt/web-application-development-in-cookpad-2017

hako-console とは

hako-console はアプリケーション毎にそのアプリケーションに関連したシステムやメトリクス等の情報を閲覧することができる Web アプリケーションです。 たとえば

  • そのアプリの Docker イメージがビルドされている Jenkins ジョブがわかる
  • そのアプリのエラーログが蓄積されている Sentry プロジェクトがわかる
  • そのアプリが利用している RDS インスタンスがわかり、そのメトリクスも閲覧できる
  • そのアプリが stdout/stderr に出力したログを閲覧、検索できる

といった機能を持ちます。

hako-console app page hako-console ECS metrics page hako-console ELB metrics page

設計方針

Web アプリケーションの把握を手助けするための手段としてまず最初に挙がるのがドキュメンテーションだと思います。 しかしながら、人間が入力した情報は構成変更等があったときに必ず古くなり誤った記述になります。 README に書かれていたジョブを探したけど見つからなかった、ドキュメントに書かれていない別のデータベースにも実は接続していた、みたいな経験はよくあると思います。 これを解決するには「人間が入力しない」ということが重要だと考えていて、AWS のように API で情報を取得できるインフラを使っていたり、hako の定義ファイルのように機械的に読み込める情報があるので、これらの実際に使われている実態と乖離していない一次情報を自動的に集めることを念頭に置いて設計しました。

  • hako-console 自体は極力マスターとなるデータを持たない
    • 既に別の場所にあるデータやメトリクスを表示するだけにする
    • 人間が何かを入力することも極力しない
  • かわりに他のシステムからデータを取得して、それを機械的に処理する

アプリの状態を知るためのメトリクスは Zabbix、Amazon CloudWatch、Prometheus といったものに既に存在していたので、hako-console の役割は各アプリとメトリクスを繋ぐものにしようと考えました。 当初はサーバやマネージドサービスのメトリクスのようなものだけを考えていましたが、AWS X-Ray を使うようになって からは X-Ray のデータを使って通信があるアプリ間にリンクを表示する機能を追加する等 *1、そのアプリについて知るために役立つ情報を追加していきました。

開発に必要な情報を集める

クックパッドでの Web アプリケーションの主要な開発フローは以下のようになっています。

  1. GitHub Enterprise にリポジトリを作成し、そこで開発を行う
  2. 同リポジトリに Dockerfile を追加し、Jenkins ジョブでテストを実行し、docker build && docker push を実行する
  3. hako_apps という中央リポジトリにアプリケーション定義を追加する
    • たとえば example というアプリであれば example.jsonnet を追加する
  4. hako_apps リポジトリの webhook を通じて hako-console にアプリケーションが自動的に登録される
  5. hako-console から Rundeck にデプロイ・ロールバック用のジョブを作成する
    • デプロイ用のジョブは hako deploy example.jsonnet 、ロールバック用のジョブは hako rollback example.jsonnet を実行するように作成される
  6. ruboty deploy example と Slack で発言することで chatbot の ruboty を通じて Rundeck のデプロイジョブが起動され、ECS でアプリが動く状態になる

社内では https://gist.github.com/eagletmt/f66150364d2f88daa20da7c1ab84ea13 のような hako の script を使っており、example.jsonnet の scripts に jenkins_tag を追加すると hako deploy -t jenkins example.jsonnet で最新の安定ビルドのリビジョンを Docker イメージのタグに指定できるようになっています。 そのため example.jsonnet を読めば example というアプリがどの Jenkins ジョブを使っているかが分かります。 Rundeck ジョブは ruboty から使われるため、hako-console を作成する前から Rundeck ジョブ名はアプリ名にすることが習慣となっていました。そのため、Rundeck のジョブを API で取得しアプリ名で寄せることで、どの Rundeck ジョブを使っているかが分かります。 hako-console ができてからは、この習慣に従って Rundeck ジョブが hako-console によって半自動的に作成されるようになっています。

hako-console はこのような形で情報を集めて、アプリケーション毎にリンクを表示しています。

運用に必要な情報を集める

Web アプリケーションが使っている ELB は、hako が hako-${app_id} という固定の命名規則で ELB を作成するため、それで見つけることができます。 たとえば example.jsonnet というアプリケーション定義であれば、hako-example という名前のロードバランサーやターゲットグループが対応します。

RDS インスタンスや ElastiCache インスタンスはどうでしょうか。 Docker コンテナで動かすアプリの場合、接続先の MySQL や memcached のエンドポイントといった設定値は環境変数で渡すことが多く、環境変数は hako のアプリケーション定義に書かれます。 したがって example.jsonnet の環境変数定義を調べて、その中に RDS のエンドポイントっぽい文字列 (つまり /\b(?<identifier>[^.]+)\.[^.]+\.(?<region>[^.]+)\.rds\.amazonaws\.com/ にマッチするような文字列) であったり、ElastiCache のエンドポイントっぽい文字列を探すことで、多くの場合うまくいきます。 こうすることで、各アプリケーションが接続している RDS インスタンスや ElastiCache インスタンスを見つけることができ、また逆に各 RDS インスタンスや ElastiCache インスタンスを使っているアプリケーションを知ることもできます。

hako-console RDS and ElastiCache list page hako-console RDS page

Sentry のプロジェクトについても同様で、Sentry 用の各種 SDK は SENTRY_DSN という環境変数で DSN を設定できるようになっており、API を通じてプロジェクトの一覧を取得できるので、 example.jsonnet に書かれた SENTRY_DSN と API の結果を突き合わせることで Sentry プロジェクトを見つけることができます。

ログの閲覧、検索

ECS で動かしている Docker コンテナのログは、log driver として fluentd を指定し fluent-plugin-s3 を使って Amazon S3 に送信するようにしています。 S3 に送信されたログはそのままでは閲覧しにくいので、hako-console 上でアプリ毎やタスク毎に閲覧や検索できるようにしています。

hako-console log list page hako-console log page

検索には Amazon Athena を使っており、そのためのテーブル定義は AWS Glue のデータカタログに作成しています。 fluentd が ${アプリ名}/${コンテナ名}/${日付}/ のようなプレフィックスで jsonl を gzip で圧縮したものを保存し、日次のバッチジョブがログの中身にあわせたパーティションを Glue のテーブル定義に追加していくことで、S3 にログが送信されてからすぐに検索対象になるようにしています。

まとめ

内製している hako-console について紹介しました。 Web アプリケーションを把握するためのコンソールを作ること、またそのようなコンソールを作成できるようなツールやインフラにすることは重要だと考えています。 hako-console は社内のインフラ事情と密接に関係しているため OSS ではありませんが、それぞれの環境にあわせてこのようなコンソールを作成することに意味があるんじゃないかなと思います。

*1:ちなみに、この「どのアプリとどのアプリが通信するのか」という情報はトレーシングではなくサービスメッシュによって達成しようと動いています https://speakerdeck.com/taiki45/observability-service-mesh-and-microservices

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/