AWSでクラウドネイティブアーキテクチャを使用したシステム構築

こんにちは。戦略推進室の荒海です。

ぐるなびでは、アドシステムやレコメンドの開発に携わったのち、今の部署ではデータ戦略として、データを使ったサービスのAPI提供などを開発しています。
現在、AWS上で動作する店舗情報提供APIの開発を行っています。リクエスト数が多いので、大量のリクエストに対して、それを捌く構成なども書こうと思います。
AWS上で動くAPIの開発を行おうとしているけど、どういう構成にするといいのか悩んでいる、とか、どういうサービス使えばいいのか全然わからない、という人の参考にもなればと思います。

その中でお伝えしたいトピックとして、クラウドネイティブで運用している部分を切り取ってみました。

目次

  • どうしてクラウドネイティブなのか
  • クラウドネイティブで良かった点
  • クラウドネイティブで大変だった点
  • これからの展望

どうしてクラウドネイティブなのか

オンプレからAWSに移行する流れがあった

弊社でオンプレミスのサーバからAWSのクラウドサーバへ移行する流れがありました。
どういう構成がいいのか、安い料金で運用する為にどうしたらいいのかを考えるいい機会になりました。

定額で料金がかかるものよりも従量課金の方が費用が抑えられそう

EC2などのサーバを多数払い出して、そこで様々なサービスを立ち上げるような従来のオンプレミスと同様の構成にすることも出来ましたが、フル稼働しているわけでもないサーバもあり、それだと動いていない時の料金がもったいないなと感じた為、従量課金のAWSサービスを使うことにしました。
例えば、今まではオンプレミスでMemcacheを使っていたのですが、それをDynamoDBに変更することで、サーバとしての定額課金を無くし、従量課金に切り替えました。 f:id:gnavi-araumi:20210312162009p:plain

料金について具体例として、EC2DynamoDBを比較してみます。
EC2
インスタンスタイプ: t3a.medium メモリ: 4GB 料金: 0.049USD/時間
f:id:gnavi-araumi:20210312162113p:plain

30日間動かし続けると
0.049 * 24 * 30 = 35.28 (USD)

DynamoDB
書き込み要求ユニット 100 万あたり 1.4269USD
読み出し要求ユニット 100 万あたり 0.285USD
f:id:gnavi-araumi:20210312162125p:plain

1時間当たり書き込みが1万回、読み出しが10万回だったとして
30日間続くと
1 * 24 * 30 = 720 -> 7.2 * 1.4269 = 10.27USD(書き込み)
10 * 24 * 30 = 7200 -> 72 * 0.285 = 20.52USD(読み出し)
合計 10.27 + 20.52 = 30.77USD

これで同じくらいの金額になりました。
EC2の場合は、フルに動かしてもちょっとだけ動かしてもこの値段なのに対し、
DynamoDBの場合は、リクエスト回数で課金されます。なので、提示した回数よりも少なくなればもっと安くなります。

今までやった事がなかったのでやってみたかった

AWS自体を使う機会はたくさんありましたが、クラウドネイティブでの運用経験は無かったので興味はありました。例えば、NoSQLのDynamoDBを使った事もなかったですし、デプロイでCodePipelineを使ったこともなかったです。その中でこういう経験が出来るというのが大変嬉しかったです。
APIの構成は図のようになりました。 f:id:gnavi-araumi:20210324094237p:plain ※実際にはCloudFrontが挟んであったりしますが、簡易的にイメージとしてこう書きました。

クラウドネイティブで良かった点

一度環境を整えると別のプロジェクトでも使いまわせる部分がある

AWSには便利なサービスがあります。
各サービスを画面からでなく、コードから立ち上げることの出来るCloudFormationというものが。
これの良いところは、テスト用の環境と本番環境を作らなければならないとなった時、まずテスト用の環境をCloudFormationで作れるようにしておけば、あとはいつでも本番環境もすぐに作れるようになります。もちろん名前の差異やネットワーク系のIDに違いはありますが、それもパラメータとして指定出来るようにしておけば、汎用的に使えるテンプレートが作れます。
f:id:gnavi-araumi:20210315095107p:plain ※画像はテンプレートファイルのごく一部分だけ抜粋しました。

APIでは以下のサービスを使っています。

  • ECS
  • DynamoDB
  • ELB
  • CloudFront
  • AmazonMQ

ECS Fargate上でAPIが動作する環境を作りました。
データをDynamoDBに配置して、そこから必要なデータを抜き出しています。
ELBを使い、複数のタスクを同一のドメイン名で受け持てるようにもしてあります。
さらには弊社のドメイン名も使い、CloudFrontでリダイレクトすることで、使う側がそこにアクセスすればデータを取れるという状況にもしてあります。
また、データ更新タイミングでAmazonMQを使うことで、複数のECSタスクに同時に命令を投げることが出来るようになってます。

管理画面では以下のサービスを使っています。

  • S3
  • APIGateway
  • Lambda
  • RDS
  • CloudFront

S3に配置したフロントエンドからAPIGatewayを通ってLambda関数を実行し、データを取得、書き換えを行っています。
管理画面からはRDSにデータベースを用意し、そこに追加や変更を入れています。
管理画面もAPI同様に、CloudFrontでリダイレクトすることで、APIと同様のドメイン名で扱えるようになりました。

デプロイ環境として以下のサービスを使っています。

  • CodeCommit
  • CodeBuild
  • CodeDeploy
  • CodePipeline

APIでは、バージョンの管理も必要になっており、バージョンが上がった時に、今まで使えてた機能が使えなくなるなどが発生しないように設計されています。
そのため、ECSELBなどがバージョン2として作成出来るように、CloudFormationテンプレートを整え、バージョンが変わるほどの機能追加なども行えるようにプロジェクト構成を作りました。

これらの構成をCloudFormationのテンプレートで作成し、テスト環境、本番環境共に同じ構成になるようにしました。

全部自分で面倒見れるのでコードとインフラ両面からのアプローチがかけられる

今回のプロジェクトを進めていく中で、キャッシュを更新するタイミングをECSの各タスクに通知したいとなりました。RedisのPub/Subの様な機能が欲しかったのですが、このためにElestiCacheを使うのも違うと思い、SQSSNSを試し、最終的にAmazonMQに辿り着きました。様々なサービスを、時間をかけずに試すことができて納得の行く環境をつくれるのが、とても良いポイントだと思いました。
f:id:gnavi-araumi:20210312162228p:plain

コストが見れるので対策が考えやすい

AWSを使い、例えばテスト環境を作って動かしていると、料金を見たときに何にいくらかかっているのかわかります。
ひと月で合計1万円の課金がされていたら、それをずっと続けると一年後には12万円かかるというのがわかります。
どのサービスにどのくらいの金額がかかっているのかも見えるので、そのサービスの使い方は合っているのか、プロジェクトに適しているのか、または、コストがかかりすぎていると思ったら他のサービスで代替できないか、など自分達で考えられます。そういう少しずつの改善もしやすいのが、クラウドで運用する上でのメリットにもなっていると思うので、これから先も改善点が見つかれば色々改善していきたいと思います。
f:id:gnavi-araumi:20210312162247p:plain ※このサービスって意外と料金かかるんだ!という発見もありました

クラウドネイティブで大変だった点

サービスが多くて知るだけで大変

サービスの数が多く、覚えるのが大変でした。
しかし、プロジェクトをAWS上に構築して色々やっていくうちに、どんどん覚えていけるので、まずはAWSに慣れることも大事だと思います。
弊社では、インフラの部署が予めテンプレートや環境の雛形を作ってくれていたので、ある程度目星がついていたのは大変有り難かったです。
f:id:gnavi-araumi:20210312162309p:plain ※たくさんありますが、全部理解しているわけではなく、少しずつ理解していっています。

APIのバージョン管理で構成を丸々もう一つ作らなければならなかった

バージョン管理について考えたときに、一つのプログラムで運用するのは煩雑になってしまうなと考えていました。
そこで、バージョン毎にECSのサービスを丸々新しく作ってしまおうと思ったのですが、構成やデプロイなど、全部に手を入れなければならず、大変な作業になってしまいました。
しかし、最終的に出来上がった構成自体はとてもスッキリしており、良いものになったのではないかと思っています。
f:id:gnavi-araumi:20210324085811p:plain

AWSでのクラウドネイティブにすると他のクラウドに移行するのが容易ではなくなるため、AWSのサービス部分とアプリケーション部分で疎にする必要があった

例えば、オンプレミスでMemcachedやRedisのKVSを使っていたとしたら、AWSでは、EC2にそれらのサービスをインストールして使うこともできますし、ElastiCacheというKVSに特化したサービスを使うこともできますし、我々が採択したようにDynamoDBを使うという選択肢もあると思います。
どのサービスを使うのかは、費用対効果に基づいて決定するのが良いと思いますので、最初からどれが良いという話はなく、特に開発初期ではどのサービスを使うことになっても大丈夫なつくりにしなければならなかったです。
そこで、どのサービスになっても少ない変更で済むように、処理自体をインターフェイスで定義して、その内部の実装はそのサービスに特化したもので書くが、使う側はそれを意識することがない作りとしました。いわゆるクリーンアーキテクチャです。
AWSのサービスの処理部分とアプリケーション部分が疎な関係になり、切り替えが容易に出来るようになりました。
この作りにすることで、AWSからGCP等に移行する時も、そのサービスに密に関わっている部分だけを変更すれば良くなるので、他のクラウドへの移行も容易になります。

これからの展望

運用していく上で、実際にかかる費用も見えるようになっているので、出来るだけ費用を抑えられるような環境を作っていければいいなと思います。
コストを気にしつつ、環境を考えていきたいです。

荒海
ゲーム業界でコンシューマゲーム等を作っていたが、2016年にぐるなびに中途入社。ぐるなびでは主にAPI開発を担当し、社内でGoのコミュニティを作ったりしてGoの布教活動をしている。趣味は発声とゲーム。