クックパッドでの Webアプリケーション開発 2017 / Web application development in Cookpad 2017

Eec11c7d221770d15a4b16104f3cf07e?s=47 Kohei Suzuki
November 16, 2017

クックパッドでの Webアプリケーション開発 2017 / Web application development in Cookpad 2017

Rails Developers Meetup #7 東京会場
https://techplay.jp/event/631428

Eec11c7d221770d15a4b16104f3cf07e?s=128

Kohei Suzuki

November 16, 2017
Tweet

Transcript

  1. クックパッドでの Webアプリ ケーション開発 2017 Kohei Suzuki (@eagletmt)

  2. 自己紹介 - Kohei Suzuki (@eagletmt) - クックパッド 技術部開発基盤グループ - Docker

    を使ってアプリケーションを動かす基盤を全面的に担当
  3. アウトライン - 去年の時点での開発環境 - アプリケーションをデプロイするまでのフロー - Docker、ECS、Hako - 今年に入ってからやってきたこと -

    Web アプリケーションの統合管理コンソール - pull-request 単位のステージング環境 - 分散トレーシング - 次にやっていきたいこと
  4. これまでのあらすじ - クックパッドではマイクロサービスを推進してきた - Web API のインターフェイスを決める Garage、GarageClient - 各アプリケーションから共通で使えるツールの整備

    - 定期実行ジョブ管理システム Kuroko2 - 非同期ジョブキューシステム Barbeque - エラートラッカーとしては Sentry を採用 - アプリケーションの実行基盤としては Amazon ECS を採用
  5. Web アプリケーションのデプロイフロー

  6. 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 に変わるだけ
  7. Amazon ECS、Hako - ECS: EC2 インスタンス上で Docker コンテナを動かしてくれる AWS のマネージド

    サービス - Hako: ECS を利用してデーモンを動かしたりワンショットのタスクを実行するための 自作ツール - https://github.com/eagletmt/hako
  8. Hako が作られた経緯 - 基盤チームがアプリ開発のボトルネックになりたくない - マイクロサービス化で新規に Web アプリを作成することが増えた - インフラ面で強い権限を持つチームに作業依頼をするフローをできるだけ減らす

    - サーバを立ててほしい、これを設定してほしい、 etc. - セルフサービス化 - 同じような作業の自動化 - Docker によりサーバのプロビジョニングは全アプリサーバで共通にできる - AWS と Docker でどんな Web アプリケーションもだいたい同じ構成になる - Route 53、ELB、RDS、ElastiCache、……
  9. Hako - アプリケーションの定義を YAML で記述し、それを元に ECS 等の API を叩いて操 作する

    - Docker イメージ、環境変数といったタスク定義 - ECS に関連付ける ELB の設定 - デプロイ中に差し込む処理の指定 - Route 53 で指定した ELB にドメインを設定したり - Jenkins から最新の安定ビルドを取得してその Docker イメージのタグを設定したり - gem で拡張可能
  10. 秘密の値 - 環境変数の一部は秘密の値を含むため Git リポジトリにコミットしたくない - データベースのパスワード - API キー

    - などなど - デプロイ時に環境変数の一部を別のところから取得して埋め込みたい - 秘密の値のストレージとして HashiCorp Vault を採用
  11. Vault による秘密の値の管理 - secret/hako/${team_name}/${app_name}/${var_name} のようなパス に秘密の値を書き込んでおく - secret/hako/${team_name} 以下にはそのチームのメンバーが読み書きで きるようにしておく

    - Hako で環境変数を定義するときに #{var_name} のような文法で Vault 内の値 を参照できるように
  12. Hako scheduler: type: ecs region: ap-northeast-1 cluster: hako-production role: ecsServiceRole

    task_role_arn: arn:aws:iam::xxxx:role/EcsAwesomeApp elb_v2: vpc_id: vpc-01234567 listeners: - port: 80 protocol: HTTP - port: 443 protocol: HTTPS certificate_arn: arn:aws:acm:ap-northeast-1:xxxx:certificate/yyyy …… ECS の設定 IAM ロール ELB の設定
  13. ECS / Docker へのパラメータ Hako …… app: image: 012345678901.dkr.ecr.ap-northeast-1.amazonaws.com/awesome-app cpu:

    3072 memory: 2048 env: $providers: - type: vault addr: https://vault:8200 directory: hako/dev-infra/awesome-app DATABASE_URL: mysql2://user:#{mysql_password}@awesome-app.rds.amazonaws.com/main RAILS_ENV: production …… Docker イメージ Vault から秘密の値を 提供 秘密の値を埋め込み
  14. Hako - これらの script は gem で拡張可能 - 社内の事情にあわせて独自の script

    を用意 …… scripts: - type: jenkins_tag job: docker-awesome-app - type: route53_subdomain hosted_zone: ZABCDEFGHIJKL Jenkins の docker-awesome-app ジョブから最新の安定ビルドのリ ビジョンを取得して Docker イメージタグを書き換え elb_v2 で指定された ELB を指す awesome-app.example.com を Route 53 で設定
  15. 新規 Web アプリケーションの開発フロー - アプリケーションを書く - rails new - ヘルスチェック用のエンドポイントを用意するために

    revision_plate gem を入れる - sentry-raven gem でエラーログを残せるようにする - 他アプリから使われる API は Garage で書く - Docker イメージを用意する - Dockerfile を書く - Jenkins で docker build して Amazon ECR に docker push
  16. 新規 Web アプリケーションの開発フロー - Hako の定義ファイルを書く - hako_apps リポジトリに pull-request

    - Web アプリやワーカのようなデーモンの場合 - Rundeck にデプロイ、ロールバック用のジョブを登録して Ruboty を使って Slack からデプロイ - オフラインジョブの場合 - 定期実行するときは Kuroko2 に登録 - 非同期実行したい場合は barbeque_client gem を導入し Barbeque に登録 - モニタリングは Amazon CloudWatch や Datadog で
  17. 課題感 - 様々なツールを使っているため1つのアプリに関する情報を把握しきるのが困難 - 1つの GitHub リポジトリを見るだけでは終わらない - Jenkins ジョブ、Hako

    の定義ファイル、Sentry のプロジェクト、CloudWatch、…… - README や社内 Wiki に毎回同じようなリンク集を書くことに - 1つの環境を作るときにやることが多い - 新機能を別ブランチで開発して普段とは別のステージング環境で動かしたい - しかしそのためには似たような Hako の定義や Jenkins ジョブ等を書かなければならない
  18. 統合コンソールの開発 - hako-console という社内 Web アプリを開発することにした - アプリケーション毎に関連するページへのリンク集を自動的に表示 - よく見る

    CloudWatch メトリクスはその場で表示 - 典型的な開発での作業を一部自動化 - 1つの pull-request に紐付いたステージング環境作成を支援
  19. hako-console

  20. hako-console

  21. hako-console

  22. hako-console

  23. hako-console Hako の定義ファイル アプリのリポジトリ Jenkins ジョブ Sentry プロジェクト Rundeck ジョブ

    使っている ELB 使っている RDS
  24. hako-console 動いてるインスタンス ECS のメトリクス (CloudWatch) コンテナ毎のメトリクス (Datadog)

  25. hako-console ELB の情報 どのアプリが使っているか ELB のメトリクス (CloudWatch)

  26. hako-console のポリシー - 手動で入力する箇所をなくし、可能な限り自動でページを作る - 手動メンテは必ず情報が古くなる - 実際の状態を API で取得したり、実際に使われている設定ファイルを元にすべき

  27. 情報収集手段 - Hako の定義ファイルでの指定をデータソースにする - ELB の名前や ECS のサービス名から CloudWatch

    のデータを表示 - 規約 (Convention) を元に探す - Jenkins や Rundeck のジョブ一覧を API で取得して、名前で寄せる - Hako の定義ファイルに書かれた環境変数を元にする - DATABASE_URL を見て使ってる DB インスタンスを特定 - rds.amazonaws.com や cache.amazonaws.com という文字列から Amazon RDS や Amazon ElastiCache のインスタンスを特定 - Sentry DSN っぽい文字列から Sentry のプロジェクトを特定
  28. 開発フローの変化 - モニタリングはまずは hako-console を見ればよくなった - どの DB を使ってるか分かる -

    その DB の負荷状況の概要も分かる - どのインスタンスで動いているか分かる - どの ELB を使っているかが分かる - その ELB のアクセス数やエラーレートの概要も分かる - 開発に必要なものも分かる - GitHub リポジトリはどこにあるのか分かる - Docker イメージを作ってる Jenkins ジョブはどれなのか分かる - 新規に参加する開発者が把握しやすい
  29. pull-request staging

  30. pull-request staging - 開発中の機能をステージング環境で動かしたい - 本番と同じようなデータや複数人で使ってる状況で動かしたい - ディレクターや他のスタッフに使ってみてもらいたい - 普段とは別のステージング環境の作成を楽にしたい

    - 機能開発は pull-request で進む - pull-request と同じライフサイクルの環境を作ればいいのではないか
  31. pull-request staging - hako-console に GitHub の pull-request に関するイベントを受けとる webhook

    を 用意 - “make staging please” とコメントするとその pull-request のブランチから専用のステージング環境 が自動的に作成される - pull-request がクローズ (マージ含む) されたらそのステージング環境は破棄
  32. pull-request staging make staging please

  33. pull-request staging merged

  34. pull-request staging - 作成時 - Jenkins ジョブを複製して push する Docker

    イメージの名前を置換 - YAML 内で指定している Docker イメージを↑で置換したものに変更 - Rundeck ジョブを複製して↑で push した YAML ファイルでデプロイするように変更 - ここで作った Jenkins ジョブや YAML ファイルや Rundeck ジョブを DB に保存 - 削除時 - hako remove で ECS からアプリケーションを削除 - DB に保存されたジョブや YAML ファイルを削除
  35. 分散トレーシング

  36. マイクロサービス化の課題 - マイクロサービス化により、システム全体を把握することが難しくなる - 障害発生時の原因究明が難しい、システム全体でのパフォーマンスの分析が難しい - アプリも DB も負荷は高くなってないのに何故遅くなった ?

    - マイクロサービス化にあたって必ず出てくる問題
  37. 分散トレーシング - システム外からのあるリクエスト (例: エンドユーザ) を起因としたシステム内のサー ビスへのリクエストを追跡できるようにするもの - どのサービスに何回リクエストしているのか -

    各サービスへのリクエストにどれくらい時間がかかってるのか - 最初にエラーを返したサービスはどれなのか
  38. 分散トレーシング - 最初のリクエストを受けたときに「トレース ID」を発行し、他サービスへリクエストす るときのヘッダにトレース ID を付加する - トレース ID

    つきのリクエスト、レスポンスのデータを分散トレーシングシステムに送信 app-A app-B app-C app-D トレーシング システム エンドユーザ トレース ID を発行 trace-X trace-X trace-X トレースデータ - トレース ID - リクエスト - レスポンス
  39. AWS X-Ray - 分散トレーシングシステムとして AWS X-Ray を採用 - トレースデータを送信する SDK

    で Ruby の公式サポートは現時点では無し - @taiki45 が非公式の gem を作成 https://github.com/taiki45/aws-xray
  40. E AWS X-Ray + ECS - SDK から送信されてくるデータを AWS X-Ray

    に送信するデーモンを一緒に起動 する - このデーモンは公式のものが用意されているのでそれを Docker イメージへ - アプリには aws-xray gem を導入して一緒に起動している xray コンテナにデータ を送信する aws-xray gem app container xray daemon xray container AWS X-Ray ECS task
  41. AWS X-Ray + ECS サービスマップ

  42. AWS X-Ray + ECS 障害時 ダメそうなサービスが分かる

  43. AWS X-Ray + ECS トレースの詳細

  44. AWS X-Ray + hako-console X-Ray のデータを使ってどのアプリとどのアプリが通信しているのかを表示

  45. 今後やっていきたいこと

  46. 今後やっていきたいこと - サービスメッシュ - 1つのアプリが複数の別のアプリの Web API を使っている状況 - 各アプリがどれくらいリクエストしているのかメトリクスを知りたい

    - うまく接続したい - 接続先が過負荷でダウンしたときは接続を止めたい (サーキットブレーカー ) - 適切にリトライしたい - タイムアウトの値を調整したい - 接続先をうまく管理したい (サービスディスカバリ ) - @taiki45 が構築中
  47. 今後やっていきたいこと - RPC - Garage による REST API の限界 -

    スキーマがほしい - どんな JSON が返ってくるのか、各フィールドの型は何なのか - REST へのマッピングが困難 - GET とか POST とか考えずにメソッドのようなエンドポイント - クライアントのコード生成 - gRPC が候補か?