Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

自己紹介 - Kohei Suzuki (@eagletmt) - クックパッド 技術部開発基盤グループ - Docker を使ってアプリケーションを動かす基盤を全面的に担当

Slide 3

Slide 3 text

アウトライン - 去年の時点での開発環境 - アプリケーションをデプロイするまでのフロー - Docker、ECS、Hako - 今年に入ってからやってきたこと - Web アプリケーションの統合管理コンソール - pull-request 単位のステージング環境 - 分散トレーシング - 次にやっていきたいこと

Slide 4

Slide 4 text

これまでのあらすじ - クックパッドではマイクロサービスを推進してきた - Web API のインターフェイスを決める Garage、GarageClient - 各アプリケーションから共通で使えるツールの整備 - 定期実行ジョブ管理システム Kuroko2 - 非同期ジョブキューシステム Barbeque - エラートラッカーとしては Sentry を採用 - アプリケーションの実行基盤としては Amazon ECS を採用

Slide 5

Slide 5 text

Web アプリケーションのデプロイフロー

Slide 6

Slide 6 text

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 に変わるだけ

Slide 7

Slide 7 text

Amazon ECS、Hako - ECS: EC2 インスタンス上で Docker コンテナを動かしてくれる AWS のマネージド サービス - Hako: ECS を利用してデーモンを動かしたりワンショットのタスクを実行するための 自作ツール - https://github.com/eagletmt/hako

Slide 8

Slide 8 text

Hako が作られた経緯 - 基盤チームがアプリ開発のボトルネックになりたくない - マイクロサービス化で新規に Web アプリを作成することが増えた - インフラ面で強い権限を持つチームに作業依頼をするフローをできるだけ減らす - サーバを立ててほしい、これを設定してほしい、 etc. - セルフサービス化 - 同じような作業の自動化 - Docker によりサーバのプロビジョニングは全アプリサーバで共通にできる - AWS と Docker でどんな Web アプリケーションもだいたい同じ構成になる - Route 53、ELB、RDS、ElastiCache、……

Slide 9

Slide 9 text

Hako - アプリケーションの定義を YAML で記述し、それを元に ECS 等の API を叩いて操 作する - Docker イメージ、環境変数といったタスク定義 - ECS に関連付ける ELB の設定 - デプロイ中に差し込む処理の指定 - Route 53 で指定した ELB にドメインを設定したり - Jenkins から最新の安定ビルドを取得してその Docker イメージのタグを設定したり - gem で拡張可能

Slide 10

Slide 10 text

秘密の値 - 環境変数の一部は秘密の値を含むため Git リポジトリにコミットしたくない - データベースのパスワード - API キー - などなど - デプロイ時に環境変数の一部を別のところから取得して埋め込みたい - 秘密の値のストレージとして HashiCorp Vault を採用

Slide 11

Slide 11 text

Vault による秘密の値の管理 - secret/hako/${team_name}/${app_name}/${var_name} のようなパス に秘密の値を書き込んでおく - secret/hako/${team_name} 以下にはそのチームのメンバーが読み書きで きるようにしておく - Hako で環境変数を定義するときに #{var_name} のような文法で Vault 内の値 を参照できるように

Slide 12

Slide 12 text

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 の設定

Slide 13

Slide 13 text

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 から秘密の値を 提供 秘密の値を埋め込み

Slide 14

Slide 14 text

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 で設定

Slide 15

Slide 15 text

新規 Web アプリケーションの開発フロー - アプリケーションを書く - rails new - ヘルスチェック用のエンドポイントを用意するために revision_plate gem を入れる - sentry-raven gem でエラーログを残せるようにする - 他アプリから使われる API は Garage で書く - Docker イメージを用意する - Dockerfile を書く - Jenkins で docker build して Amazon ECR に docker push

Slide 16

Slide 16 text

新規 Web アプリケーションの開発フロー - Hako の定義ファイルを書く - hako_apps リポジトリに pull-request - Web アプリやワーカのようなデーモンの場合 - Rundeck にデプロイ、ロールバック用のジョブを登録して Ruboty を使って Slack からデプロイ - オフラインジョブの場合 - 定期実行するときは Kuroko2 に登録 - 非同期実行したい場合は barbeque_client gem を導入し Barbeque に登録 - モニタリングは Amazon CloudWatch や Datadog で

Slide 17

Slide 17 text

課題感 - 様々なツールを使っているため1つのアプリに関する情報を把握しきるのが困難 - 1つの GitHub リポジトリを見るだけでは終わらない - Jenkins ジョブ、Hako の定義ファイル、Sentry のプロジェクト、CloudWatch、…… - README や社内 Wiki に毎回同じようなリンク集を書くことに - 1つの環境を作るときにやることが多い - 新機能を別ブランチで開発して普段とは別のステージング環境で動かしたい - しかしそのためには似たような Hako の定義や Jenkins ジョブ等を書かなければならない

Slide 18

Slide 18 text

統合コンソールの開発 - hako-console という社内 Web アプリを開発することにした - アプリケーション毎に関連するページへのリンク集を自動的に表示 - よく見る CloudWatch メトリクスはその場で表示 - 典型的な開発での作業を一部自動化 - 1つの pull-request に紐付いたステージング環境作成を支援

Slide 19

Slide 19 text

hako-console

Slide 20

Slide 20 text

hako-console

Slide 21

Slide 21 text

hako-console

Slide 22

Slide 22 text

hako-console

Slide 23

Slide 23 text

hako-console Hako の定義ファイル アプリのリポジトリ Jenkins ジョブ Sentry プロジェクト Rundeck ジョブ 使っている ELB 使っている RDS

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

情報収集手段 - 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 のプロジェクトを特定

Slide 28

Slide 28 text

開発フローの変化 - モニタリングはまずは hako-console を見ればよくなった - どの DB を使ってるか分かる - その DB の負荷状況の概要も分かる - どのインスタンスで動いているか分かる - どの ELB を使っているかが分かる - その ELB のアクセス数やエラーレートの概要も分かる - 開発に必要なものも分かる - GitHub リポジトリはどこにあるのか分かる - Docker イメージを作ってる Jenkins ジョブはどれなのか分かる - 新規に参加する開発者が把握しやすい

Slide 29

Slide 29 text

pull-request staging

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

pull-request staging make staging please

Slide 33

Slide 33 text

pull-request staging merged

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

分散トレーシング

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

分散トレーシング - システム外からのあるリクエスト (例: エンドユーザ) を起因としたシステム内のサー ビスへのリクエストを追跡できるようにするもの - どのサービスに何回リクエストしているのか - 各サービスへのリクエストにどれくらい時間がかかってるのか - 最初にエラーを返したサービスはどれなのか

Slide 38

Slide 38 text

分散トレーシング - 最初のリクエストを受けたときに「トレース ID」を発行し、他サービスへリクエストす るときのヘッダにトレース ID を付加する - トレース ID つきのリクエスト、レスポンスのデータを分散トレーシングシステムに送信 app-A app-B app-C app-D トレーシング システム エンドユーザ トレース ID を発行 trace-X trace-X trace-X トレースデータ - トレース ID - リクエスト - レスポンス

Slide 39

Slide 39 text

AWS X-Ray - 分散トレーシングシステムとして AWS X-Ray を採用 - トレースデータを送信する SDK で Ruby の公式サポートは現時点では無し - @taiki45 が非公式の gem を作成 https://github.com/taiki45/aws-xray

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

AWS X-Ray + ECS サービスマップ

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

今後やっていきたいこと

Slide 46

Slide 46 text

今後やっていきたいこと - サービスメッシュ - 1つのアプリが複数の別のアプリの Web API を使っている状況 - 各アプリがどれくらいリクエストしているのかメトリクスを知りたい - うまく接続したい - 接続先が過負荷でダウンしたときは接続を止めたい (サーキットブレーカー ) - 適切にリトライしたい - タイムアウトの値を調整したい - 接続先をうまく管理したい (サービスディスカバリ ) - @taiki45 が構築中

Slide 47

Slide 47 text

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