2分で(説明)できる(かもしれない)ハッカソン向けインフラ
先日、ハッカソンに参加したkyoya0819(Twitter, GitHub)です。
2日半という短い期間で、環境構築にかける時間はできる限り最小限にしつつも、セキュリティ等に配慮したアーキテクチャを目指しました。結果として、案外良い構成になったので共有したいと思います。
誤りがありましたら、ZennのコメントやTwitterのDM等でご指摘いただけますと幸いです。
発言は個人の経験, 知見に基づくものであり、いかなる所属, 関係組織を代表するものではありません。
デプロイしたアプリケーション
バックエンドがLaravel、フロントエンドがNext.js(Pages Router)で構成されています。
サブドメインでアクセスを分けており、今回の説明ではapi.example.com
をバックエンド、example.com
をフロントエンドとします。
この構成における一つのポイントは、Laravel SanctumのSPAモードを利用することでexample.com
からのAPIリクエストをステートフルに行えるようにし、認証認可を可能にしています。
Next.jsに統一したほうが楽じゃない?
確かにそうかもしれませんが、今回は以下の理由で意図的に全く別のFWを採用しています。
LaravelかHonoかで最後まで迷ったのですが、最終的にはLaravelを採用しました。
(理由は↓↓↓↓↓
DB, JobQueue, AuthといったLaravelの恩恵を受けたかった
今回、アプリケーションの仕様上、複数の重い処理がありリクエストのたびに行うのはUX的に避けたいため、JobQueueを使う需要が高かったです。
AWSであればSQS + Lambdaであったり、SQS + EC2 & AWS SDKを用いれば同様のことが可能だと思いますが、後述するDBの恩恵も合わせるとあまりメリットがないと感じてしまいました。
Eloquentが最強ってのはみなさんご存知だと思うので。。。ね?
(ただ、laravel-ide-helperのバグにハマってしまい、TypeScriptへの憧れを感じてました。
また、LaravelにはAuthファサードという認証機能が存在したため、そちらを活用したかったという背景もあります。
(実際に認証関係は全てAuthファサードを利用しました
同じFWを同時にいじることによる事故を減らしたかった
ハッカソンになると、同じプロジェクトのメンバーが同時に少数のファイルに変更を加えます。
この時、Next.jsのみを採用するとAPI Routesに実装箇所を分けない限りgetServerSideProps
に少なからずロジックを書くこととなり、コンフリクトであったり予期せない挙動が起きる可能性があると考えました。
API Routesを導入するくらいなら、前述のようなLaravelのメリットを享受するためにフロント・バックを完全に分離した方が楽だと考えました。
単体で開発・テストしたかった
フロントエンドとバックエンドで同時に開発をしていくため、フロントエンドはフロントエンドでモックアップとして、バックエンドはバックエンドでPostmanなどを使ってテストをし、最終的にバックエンドとフロントエンドを結合するというプロセスを選択しました。
これにより、どちらかの開発を待つ時間をできる限り無くしたいという思惑を見事実現することができました。
(最悪動かなかった時にモックアップとしてデモできるので。。。
構成図
余談ですが、AWSのサービス画面のロゴって別に最新のロゴってわけじゃないんですね。。。
調べてて知りました。
ロゴ, アイコンは公式「AWS シンプルアイコン - AWS アーキテクチャーセンター | AWS」より
※構成図は全ての構成を詳しく書いてるわけではないです(書いてみたらカオスになった
ざっくり解説
基本的な指針は、
- 自分でサービスを立ち上げることなく、AWSのマネージメントサービスをそのまま利用する
- ELBを使うことでなるべくPrivate Subnet内にリソースを配置する
の2点です。
なぜマネージメントサービスを使うのか?
確かに、EC2やLightsailなどのVPS, VPSライクなサービスを使えば確かに初動の構築は楽かもしれません。しかし、以下のようなデメリットがあると「私は」考えています。
- さまざまな物を1つのサーバー入れるため、性能の見極めが難しい
- コンソール上からインスタンス停止 -> ストレージ切り離し -> 性能変更後再アタッチでできるけど...
- 標準の
dnf
やapt get
でインストール可能なものが最新であるとは限らない- 現にAL2023の標準だとPostgreSQLが15までしか入らない(JSON型ネイティブサポートは16から
- 他AMIでもEPELやらRemiを追加するくらいなら、最初からマネージメントサービスで最新バージョンを動かした方が早い
Public SubnetにEC2建ててIPv4公開すればいいじゃん
これには主に以下の理由があります。
- セキュリティの観点で、EC2にPublic IPv4アドレスを割り当て80ポート, 443ポートを公開していい思いをあまりしたことがない
- ドメイン運用の都合で、EC2に自己証明書を入れなければならず非常に
めんどくさい大変 - ELB(ALB)を利用するため、EC2をインターネット上に公開する必要がなかった
それでは、なぜELBを使うのか。
それはELBでEC2をターゲットグループとして設定することで、
- リスナールールの条件を設定することで、リクエスト情報をもとにしてアクセスを分岐させることができる
- ターゲットグループのポートを設定することでポートマッピングができる
という恩恵を受けることが可能だからです。
ELBでアクセス分岐を行う
今回、ELBでは以下のようなリスナールールを設定しています。
名前タグ | 優先度 | 条件 | アクション |
---|---|---|---|
App | 100 | HTTPホストヘッダが example.com | app-tg |
Api | 101 | HTTPホストヘッダが api.example.com | api-tg |
デフォルト | 最後 | 固定レスポンスを返す(レスポンスコード:404) |
これにより、example.com
へのアクセスはapp-tg
へ、api.example.com
へのアクセスはapi-tg
へ振り分けを行っています。
ターゲットグループを活用しポートマッピングを行う
例えば、3000ポートで動いているアプリケーションを80あるいは443で動作させるためには、Nginxでリバースプロキシを構築することが一般的だと思います。
しかし、同じEC2上でLaravelとNext.jsが動作しているのでNginxの構築が少しめんどくさい大変なこと、NginxのリバースプロキシとELBではELBの構築の方が簡単なことから、上記の方法は採用しませんでした。
(もちろんここは人それぞれ考え方が違うと思いますが。。。。
具体的には、以下のようなターゲットグループを構築しています。
app-tg
インスタンス ID | 名前 | ポート | ゾーン |
---|---|---|---|
i-hogepiyo | hackathon-server | 3000 | ap-northeast-1 |
api-tg
インスタンス ID | 名前 | ポート | ゾーン |
---|---|---|---|
i-hogepiyo | hackathon-server | 80 | ap-northeast-1 |
EC2へのSSHはどうするん?
今回、AWS Systems ManagerのSession Manager経由でSSHを行なっています。
IAM Identity Centerを導入しているので、AWS CLI v2を用いることでSSOで認証情報を取得できるので便利です。
また、Private SubnetにあるEC2のSSM AgentがSystems Managerエンドポイントに到達するためには、VPCエンドポイント or NATゲートウェイが必要なため、もしも似たような構成で失敗する場合はVPC周りの設定を確認してみてください。
その他
NAT GatewayかVPCエンドポイント
今回、EC2から外部のPackagistやnpm等のリソースにアクセスする必要があったため、VPCエンドポイントではなくNAT Gatewayを使っています。
またNAT Gatewayの場合、転送がなくても固定費がかかるのと、AWSからのデータアウトが多い場合料金が高額になります。
今回の場合、ハッカソン期間中の運用だったのと、AWSからのデータアウトはほぼ発生しない計画だったためNAT Gatewayを選択しました。
NAT GatewayとPrivate Subnet
RDSとElastiCacheは、NAT Gatewayに繋がるPrivate Subnetである必要はないです。
そのため、別のPrivate Subnetを用意したほうが良いのかもしれませんが、時間的制約から今回はSecurity Groupでアウトバンドルールを全て拒否しているので問題ないとして一回パスしました。
なぜus-east-1のBedrockにアクセス?
開発初期段階で、複数のAI言語モデルを比較するため、より多くのモデルが利用可能なus-east-1
リージョンを選択しました。
最終的にはClaude 3 Haikuを使用することになり、ap-northeast-1
リージョンでも利用可能でした。しかし、時間的制約のため、既に申請, 設定済みのus-east-1
リージョンでデプロイを進めることにしました。
ちなみに、EC2にアタッチしたロールには以下のようなInline Policyを定義しています。
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "bedrock:InvokeModel",
"Resource": "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-haiku-20240307-v1:0",
"Effect": "Allow"
}
]
}
最後に
昨年, 一昨年とハッカソンの運営側にはなったことがあるのですが、参加者として参加するのは3年ぶり?くらいで、とても楽しい3日間でした。
IaC(CDK)をもっと勉強して、すんなりコードベースでインフラを構築できるようになりたいです。
2分じゃ絶対説明できなかったですね。
Discussion