Rails Developers Meetup #7 東京会場 https://techplay.jp/event/631428
クックパッドでの Webアプリケーション開発 2017Kohei Suzuki (@eagletmt)
View Slide
自己紹介- Kohei Suzuki (@eagletmt)- クックパッド 技術部開発基盤グループ- Docker を使ってアプリケーションを動かす基盤を全面的に担当
アウトライン- 去年の時点での開発環境- アプリケーションをデプロイするまでのフロー- Docker、ECS、Hako- 今年に入ってからやってきたこと- Web アプリケーションの統合管理コンソール- pull-request 単位のステージング環境- 分散トレーシング- 次にやっていきたいこと
これまでのあらすじ- クックパッドではマイクロサービスを推進してきた- Web API のインターフェイスを決める Garage、GarageClient- 各アプリケーションから共通で使えるツールの整備- 定期実行ジョブ管理システム Kuroko2- 非同期ジョブキューシステム Barbeque- エラートラッカーとしては Sentry を採用- アプリケーションの実行基盤としては Amazon ECS を採用
Web アプリケーションのデプロイフロー
Web アプリケーションのデプロイフロー- GitHub Enterprise- アプリケーションと Dockerfile を書く- hako_apps という1つのリポジトリに Hako の定義ファイル (YAML) を書く- ビルドパイプライン- Jenkins でテストを実行し docker build して Amazon ECR に docker push- デプロイ実行- Slack から Ruboty (chatbot) を経由して Rundeck で hako deploy コマンドを実行- Amazon ECS で Docker コンテナ化された Web アプリケーションを動かす- バッチ実行- Rundeck が Kuroko2 や Barbeque に変わるだけ
Amazon ECS、Hako- ECS: EC2 インスタンス上で Docker コンテナを動かしてくれる AWS のマネージドサービス- Hako: ECS を利用してデーモンを動かしたりワンショットのタスクを実行するための自作ツール- https://github.com/eagletmt/hako
Hako が作られた経緯- 基盤チームがアプリ開発のボトルネックになりたくない- マイクロサービス化で新規に Web アプリを作成することが増えた- インフラ面で強い権限を持つチームに作業依頼をするフローをできるだけ減らす- サーバを立ててほしい、これを設定してほしい、 etc.- セルフサービス化- 同じような作業の自動化- Docker によりサーバのプロビジョニングは全アプリサーバで共通にできる- AWS と Docker でどんな Web アプリケーションもだいたい同じ構成になる- Route 53、ELB、RDS、ElastiCache、……
Hako- アプリケーションの定義を YAML で記述し、それを元に ECS 等の API を叩いて操作する- Docker イメージ、環境変数といったタスク定義- ECS に関連付ける ELB の設定- デプロイ中に差し込む処理の指定- Route 53 で指定した ELB にドメインを設定したり- Jenkins から最新の安定ビルドを取得してその Docker イメージのタグを設定したり- gem で拡張可能
秘密の値- 環境変数の一部は秘密の値を含むため Git リポジトリにコミットしたくない- データベースのパスワード- API キー- などなど- デプロイ時に環境変数の一部を別のところから取得して埋め込みたい- 秘密の値のストレージとして HashiCorp Vault を採用
Vault による秘密の値の管理- secret/hako/${team_name}/${app_name}/${var_name} のようなパスに秘密の値を書き込んでおく- secret/hako/${team_name} 以下にはそのチームのメンバーが読み書きできるようにしておく- Hako で環境変数を定義するときに #{var_name} のような文法で Vault 内の値を参照できるように
Hakoscheduler:type: ecsregion: ap-northeast-1cluster: hako-productionrole: ecsServiceRoletask_role_arn: arn:aws:iam::xxxx:role/EcsAwesomeAppelb_v2:vpc_id: vpc-01234567listeners:- port: 80protocol: HTTP- port: 443protocol: HTTPScertificate_arn:arn:aws:acm:ap-northeast-1:xxxx:certificate/yyyy……ECS の設定IAM ロールELB の設定
ECS / Docker へのパラメータHako……app:image: 012345678901.dkr.ecr.ap-northeast-1.amazonaws.com/awesome-appcpu: 3072memory: 2048env:$providers:- type: vaultaddr: https://vault:8200directory: hako/dev-infra/awesome-appDATABASE_URL: mysql2://user:#{mysql_password}@awesome-app.rds.amazonaws.com/mainRAILS_ENV: production……Docker イメージVault から秘密の値を提供秘密の値を埋め込み
Hako- これらの script は gem で拡張可能- 社内の事情にあわせて独自の script を用意……scripts:- type: jenkins_tagjob: docker-awesome-app- type: route53_subdomainhosted_zone: ZABCDEFGHIJKLJenkins の docker-awesome-app ジョブから最新の安定ビルドのリビジョンを取得して Docker イメージタグを書き換えelb_v2 で指定された ELB を指すawesome-app.example.com を Route 53 で設定
新規 Web アプリケーションの開発フロー- アプリケーションを書く- rails new- ヘルスチェック用のエンドポイントを用意するために revision_plate gem を入れる- sentry-raven gem でエラーログを残せるようにする- 他アプリから使われる API は Garage で書く- Docker イメージを用意する- Dockerfile を書く- Jenkins で docker build して Amazon ECR に docker push
新規 Web アプリケーションの開発フロー- Hako の定義ファイルを書く- hako_apps リポジトリに pull-request- Web アプリやワーカのようなデーモンの場合- Rundeck にデプロイ、ロールバック用のジョブを登録して Ruboty を使って Slack からデプロイ- オフラインジョブの場合- 定期実行するときは Kuroko2 に登録- 非同期実行したい場合は barbeque_client gem を導入し Barbeque に登録- モニタリングは Amazon CloudWatch や Datadog で
課題感- 様々なツールを使っているため1つのアプリに関する情報を把握しきるのが困難- 1つの GitHub リポジトリを見るだけでは終わらない- Jenkins ジョブ、Hako の定義ファイル、Sentry のプロジェクト、CloudWatch、……- README や社内 Wiki に毎回同じようなリンク集を書くことに- 1つの環境を作るときにやることが多い- 新機能を別ブランチで開発して普段とは別のステージング環境で動かしたい- しかしそのためには似たような Hako の定義や Jenkins ジョブ等を書かなければならない
統合コンソールの開発- hako-console という社内 Web アプリを開発することにした- アプリケーション毎に関連するページへのリンク集を自動的に表示- よく見る CloudWatch メトリクスはその場で表示- 典型的な開発での作業を一部自動化- 1つの pull-request に紐付いたステージング環境作成を支援
hako-console
hako-consoleHako の定義ファイル アプリのリポジトリJenkins ジョブ Sentry プロジェクトRundeck ジョブ使っている ELB使っている RDS
hako-console動いてるインスタンスECS のメトリクス (CloudWatch)コンテナ毎のメトリクス (Datadog)
hako-consoleELB の情報どのアプリが使っているかELB のメトリクス (CloudWatch)
hako-console のポリシー- 手動で入力する箇所をなくし、可能な限り自動でページを作る- 手動メンテは必ず情報が古くなる- 実際の状態を API で取得したり、実際に使われている設定ファイルを元にすべき
情報収集手段- Hako の定義ファイルでの指定をデータソースにする- ELB の名前や ECS のサービス名から CloudWatch のデータを表示- 規約 (Convention) を元に探す- Jenkins や Rundeck のジョブ一覧を API で取得して、名前で寄せる- Hako の定義ファイルに書かれた環境変数を元にする- DATABASE_URL を見て使ってる DB インスタンスを特定- rds.amazonaws.com や cache.amazonaws.com という文字列から Amazon RDS や AmazonElastiCache のインスタンスを特定- Sentry DSN っぽい文字列から Sentry のプロジェクトを特定
開発フローの変化- モニタリングはまずは hako-console を見ればよくなった- どの DB を使ってるか分かる- その DB の負荷状況の概要も分かる- どのインスタンスで動いているか分かる- どの ELB を使っているかが分かる- その ELB のアクセス数やエラーレートの概要も分かる- 開発に必要なものも分かる- GitHub リポジトリはどこにあるのか分かる- Docker イメージを作ってる Jenkins ジョブはどれなのか分かる- 新規に参加する開発者が把握しやすい
pull-request staging
pull-request staging- 開発中の機能をステージング環境で動かしたい- 本番と同じようなデータや複数人で使ってる状況で動かしたい- ディレクターや他のスタッフに使ってみてもらいたい- 普段とは別のステージング環境の作成を楽にしたい- 機能開発は pull-request で進む- pull-request と同じライフサイクルの環境を作ればいいのではないか
pull-request staging- hako-console に GitHub の pull-request に関するイベントを受けとる webhook を用意- “make staging please” とコメントするとその pull-request のブランチから専用のステージング環境が自動的に作成される- pull-request がクローズ (マージ含む) されたらそのステージング環境は破棄
pull-request stagingmake staging please
pull-request stagingmerged
pull-request staging- 作成時- Jenkins ジョブを複製して push する Docker イメージの名前を置換- YAML 内で指定している Docker イメージを↑で置換したものに変更- Rundeck ジョブを複製して↑で push した YAML ファイルでデプロイするように変更- ここで作った Jenkins ジョブや YAML ファイルや Rundeck ジョブを DB に保存- 削除時- hako remove で ECS からアプリケーションを削除- DB に保存されたジョブや YAML ファイルを削除
分散トレーシング
マイクロサービス化の課題- マイクロサービス化により、システム全体を把握することが難しくなる- 障害発生時の原因究明が難しい、システム全体でのパフォーマンスの分析が難しい- アプリも DB も負荷は高くなってないのに何故遅くなった ?- マイクロサービス化にあたって必ず出てくる問題
分散トレーシング- システム外からのあるリクエスト (例: エンドユーザ) を起因としたシステム内のサービスへのリクエストを追跡できるようにするもの- どのサービスに何回リクエストしているのか- 各サービスへのリクエストにどれくらい時間がかかってるのか- 最初にエラーを返したサービスはどれなのか
分散トレーシング- 最初のリクエストを受けたときに「トレース ID」を発行し、他サービスへリクエストするときのヘッダにトレース ID を付加する- トレース ID つきのリクエスト、レスポンスのデータを分散トレーシングシステムに送信app-Aapp-Bapp-C app-Dトレーシングシステムエンドユーザトレース ID を発行trace-Xtrace-Xtrace-Xトレースデータ- トレース ID- リクエスト- レスポンス
AWS X-Ray- 分散トレーシングシステムとして AWS X-Ray を採用- トレースデータを送信する SDK で Ruby の公式サポートは現時点では無し- @taiki45 が非公式の gem を作成 https://github.com/taiki45/aws-xray
EAWS X-Ray + ECS- SDK から送信されてくるデータを AWS X-Ray に送信するデーモンを一緒に起動する- このデーモンは公式のものが用意されているのでそれを Docker イメージへ- アプリには aws-xray gem を導入して一緒に起動している xray コンテナにデータを送信するaws-xray gemappcontainerxray daemonxraycontainerAWS X-RayECS task
AWS X-Ray + ECSサービスマップ
AWS X-Ray + ECS障害時 ダメそうなサービスが分かる
AWS X-Ray + ECSトレースの詳細
AWS X-Ray + hako-consoleX-Ray のデータを使ってどのアプリとどのアプリが通信しているのかを表示
今後やっていきたいこと
今後やっていきたいこと- サービスメッシュ- 1つのアプリが複数の別のアプリの Web API を使っている状況- 各アプリがどれくらいリクエストしているのかメトリクスを知りたい- うまく接続したい- 接続先が過負荷でダウンしたときは接続を止めたい (サーキットブレーカー )- 適切にリトライしたい- タイムアウトの値を調整したい- 接続先をうまく管理したい (サービスディスカバリ )- @taiki45 が構築中
今後やっていきたいこと- RPC- Garage による REST API の限界- スキーマがほしい- どんな JSON が返ってくるのか、各フィールドの型は何なのか- REST へのマッピングが困難- GET とか POST とか考えずにメソッドのようなエンドポイント- クライアントのコード生成- gRPC が候補か?