はじめに
こんにちは。クックパッドレシピサービス開発部の宮崎(HN:どや)です。 私は2021年新卒としてクックパッドに入社し、そろそろ1年が経とうとしています。時の流れははやいですね。
さて、表題にも記しましたが、去年の末に新しくサーバーサイドのアプリケーションを作成しました。
クックパッドではサービスメッシュを用いたマイクロサービスアーキテクチャを採用しており、ドメインやチームに応じてアプリケーションが小さく分割されています。今回、クックパッドで利用されるマイクロサービスの1つとして、新しいアプリケーションサーバーを立ち上げました。
社内のマイクロサービスの状況については以下の記事が詳しいので、ぜひ読んでみてください。
「アプリケーションサーバーを立ち上げる」と一口に言っても、新しいアプリケーションを作るのは意外と大変です。そもそも、新しいアプリケーションをリリースできる状態に持っていくには、考慮すべき点や手を動かす内容が結構あります。
たとえば、アプリケーションを動かすインフラを用意するのは当然のこと、ログの適切な保存も必要ですし、マイクロサービスアーキテクチャを採用していれば他のサービスとの協調なども考えなければなりません。加えて、クックパッドのサービスの裏側に新しくアプリケーションを追加するとなれば大きな負荷にも対応する必要があります。習熟した人間であればいさ知らず、未習熟の人間がやろうとすれば非常に大きな工数が必要となるでしょう。
本記事では、アプリケーションの立ち上げの話を通じてクックパッドの強力な基盤を紹介します。クックパッドの基盤パワーは恐ろしく、rails new すらまともにしたことが無かった新卒が学習を進めながら2ヶ月足らずでアプリケーションの作成を完遂できました。
アプリケーション作成の経緯
今回作成したアプリケーションはクックパッドのアプリにおける「買い物機能」のドメインにまつわるロジックやリソースを置くサーバーです。
買い物機能は、クックパッドアプリ上で Cookpad Mart のプラットフォームを利用して食材などを購入できる機能です。Cookpad Martというのは、クックパッドが提供している生鮮 EC のサービスで、クックパッドとは全く別のアプリとして Android/iOS でリリースされています。
ややこしいのですが、Cookpad Mart という独立した生鮮 EC サービスがあり、そのプラットフォームをクックパッドアプリからも利用できるようにしたのが買い物機能*1です。
さて、買い物機能は比較的新しく開発が始まった機能で、元々そのドメイン固有の API サーバーを持っていませんでした。購入や決済などの基本的な機能は既に Cookpad Mart のアプリケーションサーバーにあったため、そこに相乗りする形で開発をしていました。
すなわち、「Cookpad Mart では利用しないが、買い物機能では利用したい」API などがあった場合は Cookpad Mart のアプリケーションサーバーに強引に乗せるか、BFF などのオーケストレーション層に乗せるほかない状況でした。
このような決定をした理由としては以下のようなものが挙げられます。
- 初期は Cookpad Mart の既存機能しか使わないため、相乗りする形でも大きく困らなかった。
- 開発チーム内でサーバーサイドエンジニアが不足していた。
- 新規事業であり撤退する可能性もあったので大きな工数を取らなかった。
特に2つ目が大きく、アプリケーションは開発するだけでなく保守運用なども発生します。当時、サーバーサイドエンジニアが新卒1人しかいない状況で、新しくアプリケーションを作成するのは難しいという判断がなされました。
しかし、買い物機能の機能や体験の拡充に伴い、以下のような問題が発生してきました。
- どうしても既存のアプリケーションサーバーに置くには難しいデータやロジックなどが出てきた。
- 買い物機能は大きく体験を変える新規機能であることから開発チーム自体も Cookpad アプリ本体の開発チームとは分かれていたため、Cookpad Mart のアプリケーションを触る際に Cookpad Mart の開発チームとのコミュニケーションコストが増大していた。
こうした課題が無視できなくなってきたため、「アプリケーション分割」という形で、買い物機能用のアプリケーションサーバーを新しく作成する決定をしました。チームのサーバーサイドエンジニアが増えたのも、この意思決定を後押ししています。
このアプリケーション分割において、入ってまだ半年も経たない新卒が旗振りをすることになります。もちろん押し付けられたとかではなく、私が経験も知識もないのに「やりたい!」と言ったらやらせてもらえました。新卒がこういったことをやらせてもらえるのもクックパッドの良いところですね(宣伝)。
逆に言えば、未熟な新卒1人でもアプリケーション分割が許容可能な工数で実現できるほどに、基盤やベストプラクティスが整っているとも言えます。今回、買い物機能の開発速度を落とさないままにアプリケーション作成を行わなければならないため、1、2ヶ月ほどで少ない人数でアプリケーション分割を完了させる必要がありました。もし、後述する開発基盤が整っていなければ必要な工数はもっと大きくなっており、分割をするコストを捻出できず相乗りしたままつらい状況を続けていたかもしれません。
しかしながら、クックパッドの基盤を用いた開発によって工数や複雑性を大幅に縮小でき、許容可能な工数に収めることができました。ここからは、アプリケーション分割の道筋を述べながら、そんなクックパッドの便利な基盤の一端に触れていただきたいと思います。
アプリケーション作成までの道筋
具体的にアプリケーション分割をどのような手順で行なっていったかを順番に見ていきます。その中で、クックパッドの開発を支える優れた基盤を紹介します。
API のインターフェース決定
アプリケーションサーバーを作成するにあたって、要求を元にアプリケーションの要件を定義しました。今回定義したアプリケーションの要件は機能的にも非機能的にも比較的シンプルでした。
今回はまずアプリケーションのフレームワークとして社内で多くの採用実績がある Ruby on Rails を採用しました。クックパッドでは多くのケースでサーバーの実装に Ruby on Rails が採用されており、社内にも大量の知見やライブラリなどの資産があることが決定の主な理由です。
また、新規作成するアプリケーションの API の方式として、以下の2つを検討しました。
- RESTful
- Garageなる Restful API をサポートする gem がある。
- 認証を設定すればクライアントから直接叩くことができる。
- gRPC
- Griffinなる自前の gRPC サーバー実装がある。
- Schema を BFF 層などと共有できる。
それぞれ、Garage や Griffin といった社内資産があり、1から自分で構築をする必要もなく Rails の手軽さと合わせてシュッと API サーバーを作ることができました。
今回は BFF 層の裏に回すユースケースが多そうだったことから、社内のスタンダードにもなっている gRPC を採用しました。クックパッドではスマートフォン向けの BFF 層として Orchaというものが存在しており、これは Java で書かれています。gRPC を採用したことで Schema から Java 向けの型定義を生成できるのも、gRPC を選択した1つの理由でした。
gRPC サーバーの立ち上げを既存の gem などを使って自前で実装しようとすると、マルチプロセス対応やインターセプターの導入など考慮することが多くありますが、Griffin や既存のインターセプター実装である griffin-interceptorsのおかげで手軽に作成が完了しました。
インフラ・ミドルウェアなどの準備
クックパッドではアプリケーションにまつわるインフラの管理は主に以下の2つを通して行なっています。
- Terraform
- RDS、ElastiCache、VPC、CloudWatch などのリソースの管理・デプロイを行う。
- Hako
- 主に実装したアプリケーションの管理・デプロイなどを行う。
この Terraform や Hako といった基盤が既にあるため、レビュープロセスを経ながら安心かつ手軽に各リソースを準備することができました。
社内の Hako と ECS を組み合わせたエコシステムは強力で、開発者はタスク設定を記述するだけでこのエコシステムに乗ることができます。設定ファイルを書いておくことでサイドカーコンテナの定義や環境変数の注入、ALB との紐付け、Autoscale 設定など ECS の設定 + α が簡単に実現でき、後述する運用面の力強いサポートも受けることができます。これにより EC2 インスタンスを用意して、Ruby をインストールして、ミドルウェアも入れて……といったような煩雑かつ工数の多い操作が不要なため、簡単にアプリケーションの追加・修正が行えます。
サービスメッシュでアプリケーション間通信できるようにする
今回新規作成したアプリケーションは、BFF の裏側にあります。つまり、BFF との疎通を考える必要があり、またマイクロサービスにおけるサービス間通信はエラー時の対応など色々と考えることが多く大変です。クックパッドでは Envoy を採用してサービスメッシュを実現することでこの課題を解決しています。
サービスメッシュの設定を記述する共通のレポジトリがあり、そこに設定を追加するだけで簡単にサービスをサービスメッシュに乗せることができます。新規アプリケーションを upstream として登録し、BFF の設定に upstream のアプリケーション名を追加するだけで、Control Plane である Itachoが設定を読み出してよしなにサービス間通信を制御してくれます。
いざデプロイ
CI/CD の実現のための基盤も色々と整っています。
CI については、クックパッドは Jenkins と AWS CodeBuild を採用しています。
PR に対してテストを実行するであったり、ブランチがマージされたタイミングで CI を回すであったりといった設定も、Jenkins のテンプレート設定を元にして簡単に作成できます。
デプロイは Rundeck でデプロイ用のスクリプトを呼び出しています。この Rundeck のジョブを起動すると、デプロイが走り ECS の task や service が作成・更新されます。あとは ECS がよしなにやってくれるので、デプロイ完了です。Rundeck のジョブの起動も Slack や後述する hako-console 上から簡単に実行できます。
リリースに向けて
デプロイが完了したからと言ってすぐさまリリースができるわけではありません。たとえば、以下のような観点は考慮しておくべきでしょう。
- 監視などのためにログやメトリクスが適切に取られているか。
- リリース後に負荷に耐え切れるのか。
これについても問題ありません。
まず、ログについては ECS タスクが出力するものは Hako のエコシステムが自動で処理しており、hako-console と S3 で閲覧や検索が可能になっています。hako-console はアプリケーションのメトリクスやシステムについての情報を一覧することができる社内向けの便利なコンソールツールです。
アプリケーションレベルのログは fluentd を利用して送信することもでき、Hako のエコシステムを活用すれば fluentd の設定も簡便に行えます。
また、メトリクス については ECS や ALB から CloudWatch に出力されたものや、自前で運用している Prometheus、cadvisor 由来のものがあります。これらのアプリケーションで収集された rps やレイテンシーといったメトリクスは Grafana Dashboard で閲覧することができ、このダッシュボードについても hako-console によってサービスごとに自動で提供されます。
また、リリース後の負荷についても、負荷試験の基盤が整っています。シナリオを作成して、Web コンソール上から負荷試験を実施することができます。
ログやメトリクスの収集や可視化は、保守運用の上でとても大切です。またリリース時に負荷に耐え切れるのか負荷試験を行なってシステムを検証することも大規模サービスでは必要になります。しかし、これらの設定や実施のための手順が煩雑だと、後手に回ってしまう可能性があります。上述したようにそのハードルを下げることで、保守運用や安全なリリースのために必要なことを簡単に実現できるようにしています。
こうしてログ・メトリクスの整備や負荷試験も行い、無事にアプリケーションをリリースすることができました。 当日のリリース時にも監視を行なっていましたが、上で用意したメトリクスやログを元に容易に監視が行え、特に障害等は発生せずリリースが完了しました。
まとめ
クックパッドの基盤をフル活用して新卒でもアプリケーションを作成、デプロイ、リリースまで持っていけたよという事例を紹介しました。作業の流れを通じて、クックパッド社内の基盤を紹介しました。
クックパッドには数多くの基盤があり、生産性の向上やサービス品質の安定に一役買っています。今回紹介したのはその一部で、他にも色々な基盤が存在しています。それらは過去のクックパッドの歴史の中で試行錯誤の中で生み出されてきた知識の結晶とも呼べ、いまのクックパッドの開発の大きな支えとなっています。
特に、複雑なアーキテクチャや高い負荷などが見込まれる中で、サービスのために必要な開発が比較的低コストかつ安全に行えるというのは、非常に恵まれていると感じました。
さて、クックパッドでは「毎日の料理を楽しみにする」というミッションを実現するため、一緒にチャレンジする仲間を募集しています。 本記事で紹介した基盤は日々改善が重ねられています。本記事を読んでクックパッドの基盤開発やそれを活かしたサービス開発に興味を持った方は、ぜひカジュアル面談などで一度お話ししましょう!
*1:買い物機能の詳細や技術的な挑戦は以下の記事も参考にしてください。 https://techlife.cookpad.com/entry/2021/01/18/kaimono-swift-ui