本記事はFIXERが提供する「cloud.config Tech Blog」に掲載された「【AWS】VPCってなんだっけ?というレベルから本番環境を構築するまでの学びを共有します」を再編集したものです。
皆さんこんにちは。FIXER4年目の藤野です。今回は12月のアドカレ19日目の記事となっています。アドカレが始まると年末が来たな、て感じがしますね!!と同僚に話したところ、「いや、そうでもないやろ」と言われました。そうでもないかー。。。
皆さん今年はいかがだったでしょうか。寒い冬を乗り越える意味も兼ねて、暖かい気持ちで見てくれたら幸いです。この記事には一部生成AIを利用しています。
はじめに
大分寒くなりましたね。今年の夏は暑すぎて北極の氷が溶けてしまうかと思ったのですが(?)、きちんと寒い冬が来て何よりです。ていうか寒すぎです勘弁してください。
...さて、今回は弊社であまり知見のないウェブアプリとモバイルアプリのインフラ設計を担当する機会がありましたので、備忘録の意味も兼ねて記事にしてみようと思います。
完成図
最初に完成図を示します。
アプリの概要?...あーほら、生成AIを活用したモバイルアプリをiOSとAndroidのユーザーに提供して、それを一部の管理者ユーザーがウェブアプリからあーだこーだする、みたいな感じです。...なんですかその目は。アプリの概要知ったってしょうがないでしょ!!あーもうほら、インフラ構成図いくよ!!
各リソースやサービスについて、順番に説明していけたらと思います。
VPC、リージョン等の準備
まず、VPCやリージョン等を準備しました。
正直「VPCってなんだっけ?」というレベルからのスタートだったので、Claudeに聞いてみたところ、
「VPC(Virtual Private Cloud)とは、AWS上に作成する仮想的なプライベートネットワークのことです。VPCについては、ビルに例えると分かりやすいです。VPCをフロア全体とすると、パブリックサブネットは受付フロア、プライベートサブネットは執務室です。それでいうと、Internet Gatewayは正面玄関口と言えるでしょう。」
...Claude、お前は本当にすごいな。もう全部説明してくれ。
サブネット、エンドポイント等の作成
次に、サブネット、エンドポイント等を作成します。サブネットはVPC内のネットワークを分割するために作成します。VPCという一つのフロアに大きな区画を作るイメージですね。
ここでVPC Endpointも作成しておきます。VPC EndpointはAWSのリソースであってもVPCに属していないリソースにアクセスする為に利用します。ビルの例えで言うならば、ビル内から特定の配送業者(AWSサービス)に直接つながる専用トンネルのようなものでしょう。トラフィックがインターネットを通らない分、よりセキュアに扱えるという感じです。
IAMの設定
IAMユーザーを作成します。IAMはユーザーごとの権限を設定します。先ほどのビルのたとえに則るなら、IAMは首からぶら下げる社員証でしょう。また、本番環境など重要な環境に対しては、Identity Centerを利用して特定のユーザーに対しては必要な時にのみ権限を付与できるようになっていれば良さそうです。
EC2、RDSの作成
EC2、RDSを作成します。EC2はローカルでバックエンドやDBツールを動かす際に、データベースにアクセスするための踏み台サーバーとして利用します。開発環境だけでなく、本番環境やその他の環境のDBマイグレーションを行う際にも利用します。
RDSは冗長構成とするためにマルチAZとして設定し、リージョン内でどちらのDBが更新されても自動的に同期するようにしておきます。
また、ウェブ・モバイルのバックエンド・データベースを分けるか、という議論がありましたが、要件的に特に分ける必要もないのと、管理・コストの観点から一つで運用するよう統一しました。
そして、こういった各サービスを作成する際には、セキュリティグループの作成も必要になるでしょう。SG(セキュリティグループ)にはインバウンドルール・アウトバウンドルールがあり、それぞれどんなIPなどのリソースのアクセスを許可するのか、どこへの通信を許可するのかを設定します。サブネットをフロアとするなら、EC2などの各リソースがフロア内に部屋として存在し、SGはその部屋のドアといえるでしょうね。
その他リソースの作成
バックエンド本体となるECSを作成する前にその接続先となるその他のリソースを作成していきます。
S3、SQS、SES、Cognito、Certificate Manager を作成します。各リソースについて説明してもいいですが、長くなりそうなのと表形式でもわかりやすそうなので以下に示します。また、SQSはメール以外でも利用しますが分かりやすくメールをやりとりする場合の表記にしています。
ビルの本物証明書発行所って何?て感じですが、私たちからするとどこに自分の会社のビルがあるのか、それが本物なのかは疑う余地がありません。ただしインターネットという地理が存在しない場所では、クライアントからすると「これって本当にリクエストを送り届ける場所で合ってる?」と確認したくなるのは当然でしょう。そう考えると本物証明書が必要、というのもしっくりきます。
【コラム】認証・認可の話 〜ソシャゲはどうやって自動でログインしているのか〜
Cognitoの設定をしていて、ふと疑問に思ったことがあります。
「ソシャゲって、毎回ログインしなくても勝手にログインされてるよな...あれ、どうやってるんだ?普段はWebアプリを開発している自分からすると、まずログイン画面があって、ログイン時に入力された情報をもとにデータを取得して、て感じだけど...」
調べてみると、これにはデバイス識別子という仕組みが大きく関わっているようです。
デバイス識別子とは
モバイル端末には、それぞれ固有の識別子があります:
・iOS:Identifier for Vendor(IDFV)
・Android:Android ID や Firebase Installation ID
ソシャゲでは、この端末固有のIDとユーザーアカウントを紐づけておくことで、「この端末からのアクセス = このユーザー」と判断できます。だから、毎回ログイン画面を出さなくても、アプリを開いた瞬間に「あ、いつもの端末だな」と認識できるわけです。
初回ログイン時:
[端末] → 「ユーザーID + パスワード + デバイスID」 → [サーバー]
[サーバー] → 「このユーザーはこのデバイスIDと紐づけておこう」
2回目以降のアプリ起動時:
[端末] → 「デバイスID」 → [サーバー]
[サーバー] → 「このデバイスIDは〇〇さんの端末だな、トークンベースで認証して、ログインOK」
トークンによる認証維持
とはいえ、デバイスIDだけでこの端末からのリクエストが正当なものかを毎回検証するのは大変です。そこで、トークンという仕組みも併用されています。
ログイン時にサーバーから発行されるトークンは主に3種類:
・アクセストークン:APIを叩くための入場券(有効期限:短い、通常1時間程度)
・IDトークン:ユーザー情報が入った身分証明書
・リフレッシュトークン:新しいトークンを発行してもらうための引換券(有効期限:長い、最大30日など)
アクセストークンが期限切れになっても、リフレッシュトークンが生きていれば自動的に新しいアクセストークンを取得できます。
つまり、デバイスIDで「誰の端末か」を識別し、トークンで「正当なリクエストか」を検証する、という二段構えになっているわけです。
ゲストユーザーの仕組み
また、ゲストユーザー(未登録ユーザー)の扱いも興味深いです。Cognitoには「未認証ID」という機能があり、ユーザー登録していなくても一時的なIDを発行できます。
このとき、デバイス識別子が重要な役割を果たします。ゲストユーザーはメールアドレスもパスワードも持っていませんが、デバイスIDを使って「この端末のゲストさん」として識別できるんです。
ゲストユーザーのデータ保持:
[端末A] → デバイスID: abc123 → ゲストID: guest_abc123 → ゲームデータ
[端末B] → デバイスID: xyz789 → ゲストID: guest_xyz789 → ゲームデータ
ちなみに、ゲストユーザーが後からアカウント登録すると、デバイスIDで紐づいていたゲームデータを正式なアカウントに引き継げます。機種変更の際に「引き継ぎコード」が必要になるのは、新しい端末ではデバイスIDが変わってしまうからなんですね。
ALB
次に、ALB(Application Load Balancer)を作成します。ALBは外部からのリクエストを受け付け、適切なターゲット(ECSタスクなど)に振り分ける役割を担います。ビルの例えでいうと、正面玄関(Internet Gateway)から入ってきた来訪者を「あなたはこちらの窓口へどうぞ」と案内する総合案内係のようなものです。
ここで重要なのが、ALBにACMで作成したSSL証明書を設定することです。これにより、HTTPS通信が可能になります。HTTPでのアクセスは自動的にHTTPSにリダイレクトする設定も入れておきました。
また、ウェブ・モバイル・バックエンドのアクセスルーティングをURLのプレフィックスで設定しました。これにより問題の切り分けが容易になります。ウェブのプレフィックスをadmin としたのは、文字通りウェブサイトを管理者用のサイトとして扱うためです。
・ウェブフロントエンド→xxxx.com(プレフィックスなし)
・モバイルバックエンド→xxxx.com/v1/api(apiプレフィックス)
・ウェブバックエンド→xxxx.com/v1/api/admin(api + adminプレフィックス)
ECS
いよいよ、アプリケーション本体ともいえるフロント・バックエンドのECS(Elastic Container Service)を作成します。ECSはDockerコンテナを実行するためのサービスです。ECSにはウェブのフロント、ウェブ・モバイルのバックエンドをデプロイします。モバイルのフロントエンドはApple、Googleのデプロイサーバーにデプロイします。今回はFargateを採用し、サーバーの管理を AWS に任せる構成にしました。
バックエンドAPIをコンテナ化し、ECR(Elastic Container Registry)にイメージをプッシュ。ECSタスク定義でそのイメージを指定し、ECSサービスとして常時稼働させています。また、画像生成処理用のワーカーも別のECSサービスとして動かしています。こちらはSQSからメッセージを受け取って処理するバッチ的な役割ですね。
また、画像生成にはGPT-Image-1を採用しました。当初Amazon SageMaker を採用することも検討されましたが、検証の結果サービスのコストが高く、学習コストも高かったことから不採用となりました。その点GPT-Image-1はAPIで利用する際もスムーズにバックエンドと連携でき、コストも比較的安価でした(OpenAI APIはインターネットを経由しています)。
【コラム】使用する言語選定について
バックエンド(ウェブ・モバイル両方)・ウェブフロントエンド・モバイルフロントエンドの三つの言語を選定する必要がありました。バックは弊社お得意のC#/.NET...?ウェブはTS/Nuxt...?モバイルはFlutterやReact Nativeというものがあるらしい...結果、選ばれたのはJS体系でした。
・バックエンド→JS/Node
・ウェブフロント→TS/Nuxt
・モバイル→JS/React Native
TypeScriptはJavaScriptを静的に型付けできるようにしたものなので、JSとシナジーがあります。また、React NativeよりDart/Flutterの方がUIをきれいにできる、という意見もありましたが、前者でも問題ないくらいのUIを実装できそうと判断し、採用しました。
この構成にすることで、学習コストを抑え、既存のウェブアプリの知見を活かすことができました。レビューもある程度ならバック・ウェブ・モバイルの担当が違っても行えるようになりました。
とはいえ、ウェブのコンポーネント設計だったりとか、バックエンドのクリーンアーキテクチャ/DDDの設計については知見が必要なので、結局そこは担当者に聞いてみないと分からないんですけどね。
もっとも、最近はAIコーディング・レビューが主流の場合もあるはずですので、ある程度コーディングの経験があれば、使用言語がバラバラでも学習コストを抑えられるのかもしれません。
【コラム】CI/CDについて
CI/CDはGitHub Actionsを利用しました。環境変数の設定の仕方はymlファイルでParameter Storeの値を読み取り、Parameter Storeの値にはSecrets ManagerのARN(Secrets Managerの値を示すURLのようなもの)を入れて、そのSecrets Managerに環境変数の値を入れるようにしました。こうすることで、以下のようなメリットがありました。
・環境変数の値を修正する時、Secrets Managerの値を修正するだけで済む
・GitHubに機密情報が洩れることがない
・本来であれば環境ごとにパイプラインのymlファイルを作成する形になるが、開発・本番環境などがあっても、一つのパイプライン・ymlファイルで済むようになる(例:aws ssm get-parameter --name "${ENV}-id")
特にCI/CDはこちらの記事でも記載したことがあるとおり、懐かしさで胸が張り裂けそうでした。
自分のPCでひっそりと息を潜めていたRancher desktopを見つけた藤野「おまっ、Rancher!!生きとったんかワレェ!!!Docker Desktopと食い合わせが悪いでお馴染みのお前ワレぇ!!」
自分のPCでひっそりと息を潜めていたRancher desktop「うぅ、藤野っピ、、、僕を起動しないと、docker buildは使えないでゲス、、、あとドッカーデーモンをドッカーだえもん、とあえて読むのは恥ずかしいからやめるでゲス、、、」
Lambda
Lambdaを作成します。今回は、Cognitoのトリガー処理(ユーザー登録時の処理など)にLambdaを使用しています。こうすることで、CognitoのユーザープールとRDS両方にユーザーデータを登録できるようになります。
実は、最初の設計ではバックエンドAPIもLambdaで構築する予定でした。サーバーレスでスケーラブル、使った分だけ課金、管理も楽...いいことづくめじゃないですか。
しかし、有識者にレビューしてもらったところ、こんなフィードバックをもらいました:
「画像生成処理をLambdaでやると、タイムアウトとの戦いになるよ」
Lambdaには最大15分という実行時間の制限があります。画像生成のような重い処理は、この制限に引っかかる可能性が高い。また、メモリも最大10GBまでという制限があり、大きな画像を扱う場合は厳しいかもしれない。(今となっては画像生成の時間は1分以下であり、このような懸念はありませんが、↓が致命的でした。)
さらに、コールドスタートの問題もあります。Lambdaは使われていない時間が続くとインスタンスが破棄され、次のリクエスト時に再起動が必要になります。この初回起動に数秒〜数十秒かかることがあり、ユーザー体験に影響します。特にVPC内のLambdaは起動が遅くなりがちです。ECSなら常時起動しているので、この問題がありません。
「それなら、メインの処理はECSにして、Lambdaはイベント駆動の軽い処理に使うのがいいんじゃない?」
なるほど、適材適所ということですね。結果的に、この構成にして正解だったと思います。Lambdaの制限に悩まされることなく、画像生成処理を安定して動かせています。有識者に感謝。
セキュリティ設定
次に、冗長構成とセキュリティ関連のサービスを設定します。
まず、RDSをマルチAZ構成にしました。これにより、一つのアベイラビリティゾーン(AZ)で障害が発生しても、自動的に別のAZにフェイルオーバーします。寝ている間にDBが落ちても、自動で復旧してくれるのは心強いですね。
CloudTrailは、AWSアカウント内で「誰が」「いつ」「何をしたか」を記録するサービスです。ビルの例えでいうと、入退室記録や監視カメラの映像を保存しているようなものです。何か問題が起きたときに「犯人捜し」ができます。
CloudWatchは、各リソースのメトリクス(CPU使用率、メモリ使用率など)を監視し、アラートを設定できるサービスです。ビルでいうと、各部屋の温度や電力使用量を監視するセンサーとダッシュボードですね。「CPU使用率が80%を超えたらSlackに通知」といった設定ができます。
GuardDutyは、AWS環境への脅威を検知するサービスです。不審なAPIコール、マルウェアの通信先への接続、異常なログインパターンなどを機械学習で検知します。ビルでいうと、AIを搭載した警備システムでしょうか。「この人、普段と違う動きをしているぞ」と自動で検知してくれます。
Secrets Managerは、データベースのパスワードやAPIキーなどの機密情報を安全に保管するサービスです。ビルでいうと、金庫室ですね。CI/CDパイプラインからも参照でき、環境変数にシークレットを直接書かなくて済みます。Parameter Storeと併用して、機密度に応じて使い分けています。
【コラム】セキュリティ・リクエスト制御について
セキュリティはまず、モバイルアプリのアクセスに対してどのようにプロテクトをかけるか?という疑問がありました。というのも、ウェブのフロント・バックエンド、モバイルのバックエンドはAWSで管理していますが、モバイルのフロントエンドはAppleとGoogleのサーバーにデプロイされる認識なので、そこからのアクセスに対してどのようにセキュリティを担保するのか?が明確ではありませんでした。
有識者に質問したり調査した結果、以下の対策で十分とのことでした:
・同一IPから評価期間内に制限数を超えるリクエストが送信された場合にブロック(レート制限)
・XSS・LFI等の一般的な攻撃対策
・既知の悪意ある入力パターンのブロック
また、以下のセキュリティ設定を行いました。(一部省略しています。)
ALBへのインターネットからの直接アクセスはブロックし、CloudFront経由のみ許可するようにしました。
モバイルアプリは全世界からのリクエストを想定しており、特定のIPを防ぐ等の設定は必要なさそうとのことでした。ウェブに関しては一部の管理者ユーザーが決まったIPからアクセスする想定だったので、そちらの画面とAPIにはIP制限をかけ、特定のIP以外からはアクセスできないようにしました。
Terraformを利用したコード管理
また、Terraformを利用して各アカウントリソース差分をコード管理できるようになっているといいでしょう。コードとサービスとの差分はterraform planで確認できますし、コード側の変更をサービスに反映する場合もterraform applyで簡単にできます。
まとめ
正直、これまで「リクエストが来て、なんかサーバーで処理されて、レスポンスが返る」くらいの解像度でしか理解していませんでした。
今回、実際にインフラを一から構築してみて、データがどのように流れているのかが鮮明に見えるようになりました。
さいごに
本来ならだいたい2年ぶりのテックブログなんだから、せっかくなので少しでも面白くしようとする自分と、真面目に書こうとする自分がいました。
ここはちょっとふざけすぎか、、、?いやいやもうすこしぐらいなら、、、みたいな。
多重人格なんですよ自分。笑っちゃってください。、、、おい何笑ってんだ、ぶっとばすぞ!!!
あーでも、テックブログ書いたのでレッドブルもらえますか、、、あざすあざす。。。
今回省いてしまった部分や描ききれなかった部分は、また気が向いたら記事を書こうと思います。
とあるゲームの終章が始まったことに嬉しさと切なさのハーモニーを感じつつも、それを背中で一身に受け止めて前に進む藤野でした。
藤野 元規/FIXER
2022年度からFIXERに入社しました。


この連載の記事
-
TECH
AWS CDKとGitHubを使ったIaC=インフラ構成管理の基本 -
TECH
CX視点で興味深かった「AWS Summit Japan 2024」のセッション -
TECH
IAMユーザーのアクセスキーを使わず「IAMロール」を使うべき理由 -
TECH
Amazon BedrockからWeb上のコンテンツを参照する新機能「Web Crawler」 -
TECH
Amazon SESでEメールの送信機能/受信機能を作る手順 -
TECH
Terraform:変数の値が未代入でもインタラクティブな入力を回避する方法 -
TECH
ノーコードで生成AI連携! SlackからAmazon Bedrockのエージェントに質問 -
TECH
AWS CDKでGuardDutyのRDS保護を有効化しよう(として詰みかけた話) -
TECH
同世代エンジニアに刺激を受けた!JAWS-UG「若手エンジニア応援LT会」参加レポート -
TECH
AWS EC2でkubeadmを用いたKubernetesクラスターを作ってみたのでメモ -
TECH
初心者向け:RDSスナップショットを別のAWSアカウントで復元する手順 - この連載の一覧へ
















