はじめに
こんにちは!AWSを学習中のrikutoです。
今回、ポートフォリオとして開発したXクローンをAWSにデプロイしました。
この記事では、構成の全体像と「何を選び、なぜそう判断したか」をまとめます。
対象読者は、これからECS Fargateでポートフォリオをデプロイする人です。
各サービスの基本的な使い方は省略して、判断した内容を中心にまとめています。
Xクローンのリポジトリは以下です。
この構成で実現していること
- ECS Fargateでデプロイ
- マルチAZ構成
- ALB使用
- 独自ドメイン + HTTPS化
- 画像をS3に保存
- メールをSESで送信
- ElastiCache for Redisの作成
構成図
ECS Fargate
マルチAZ + desired count 2
タスクの配置は次のようにしました。
- 配置先: 2つのAZ(1a / 1c)のプライベートサブネット
- サービスのdesired count: 2
これにより、各AZに1タスクずつ配置されます。
desired countを2にした理由は2つです。
- ALBの振り分け先が2つになり、負荷分散が実際に機能する
- 片方のAZに障害が起きても、もう片方でサービスを継続できる
料金は1タスクの2倍になりますが、本番運用を想定してこの構成にしました。
セキュリティグループはALBからのみ許可
外部からの入口はALBの1箇所に限定しています。
タスクのセキュリティグループでは、ALBのセキュリティグループをソースに指定して、8080番ポートだけを許可しました。
IPアドレス(CIDR)ではなくセキュリティグループを指定した理由は、ALBのIPが固定ではないからです。セキュリティグループで参照すれば、IPが変わってもルールは壊れません。
RDS(PostgreSQL)
プライベートサブネットに配置
RDSはプライベートサブネットに配置しました。
理由は、DBに個人情報が格納されるからです。
インターネットから構造的に到達できない場所に置くことで、外部からの攻撃経路を断っています。
マルチAZ構成にした
マルチAZ構成(Primary + Standby)を採用しました。
可用性と耐久性のためです。
- 障害発生時にStandbyへ自動で切り替わり、稼働を継続できる(可用性)
- Standbyへ同期レプリケーションされるため、切り替わってもコミット済みのデータを失わない(耐久性)
エンドポイントは1つのまま切り替わるため、アプリ側の変更は不要です。
DATABASE_URLはSecrets Manager経由で注入
DBの接続情報は、平文の環境変数では持たせていません。
Secrets Managerに保存し、タスク定義のValueFromで注入しています。
- Secrets Managerに接続URLを丸ごと1キー(
DATABASE_URL)で保存 - ECSのタスク実行ロールに、該当シークレットへの
GetSecretValueを許可 - タスク定義では
ARN:DATABASE_URL::の形式で参照
丸ごと1キーで保存した理由は、Goのコードが最初から接続URLを1本の環境変数で読む実装だったからです。
databaseUrl := os.Getenv("DATABASE_URL")
ElastiCache for Redis
Redisはログイン時のセッション管理に使っています。
構成は次のとおりです。
- ノードベース:
cache.t3.micro - マルチAZ + レプリカ1台
- 通信時暗号化: 有効
RedisもマルチAZにした理由
料金が2倍になっても、マルチAZを採用しました。
可用性のためです。
Redisが停止するとログイン機能ごとサービスが止まります。
停止中はセッションの発行も確認もできません。
セッションデータ自体は軽く、消えても全ユーザーが1回再ログインすれば回復します。
しかし「データの重さ」は軽くても、「稼働していることの重さ」はサービスの生死に直結すると判断しました。
TLS通信
通信時暗号化を有効にしています。
当初はElastiCache Serverlessで構築しており、ServerlessはTLS通信が必須です。
そのためGo側をTLS対応にしました。
その後、料金を抑えるためノードベースに移行しました。
通信が暗号化されている方が安全なため、有効のまま運用しています。
Go側がTLSに対応できておらず、エラーになったときの記事です。
SES + Secrets Manager
メール送信はSESを使い、アプリ → SES → ユーザーの流れで配信しています。
SMTP方式を採択した理由
SMTP方式を選びました。
SESの利用方法には、SDK(sesv2)方式とSMTP方式があります。
SMTPにした理由は2つです。
- 既存のGoコードが
net/smtpで実装済みのため、コード変更を最小化できる - SDK方式はAWSのライブラリにコードが依存するため、AWSへの依存度が高くなる
SMTPなら、接続先の環境変数を切り替えるだけです。
ローカル(Docker)と本番を同じコードで動かせます。
SDK方式について詳しく知りたい方は、公式ドキュメントを参照してください。
認証情報の管理
SMTP認証情報(実体はIAMユーザー)はSecrets Managerに保存しました。
Goのコードが環境変数 SMTP_USER と SMTP_PASS を必要としますが、DATABASE_URLと同様に、タスク定義のValueFromで注入しています。
平文の環境変数にしなかった理由は2つです。
- 平文で書いた値はタスク定義に露出する。閲覧権限があれば誰でも読めてしまうが、ValueFromならタスク定義に載るのはARNだけ
- SMTP認証情報が漏れると、第三者が自分のSES経由でメールを送信できてしまう。スパム送信に悪用されれば、課金の発生に加えて送信ドメインの信頼も下がる
まとめ
この構成での一番の学びは、「正解は状況で変わるからこそ、判断に理由が要る」ということでした。
マルチAZもプライベートサブネットも、可用性やセキュリティと引き換えに料金がかかります。
要件を満たすだけなら、もっと安い構成もあり得ました。
その中で「何のために、いくら払うのか」を一つひとつ言語化しながら選んだことが、単に動くものを作る以上の経験になりました。
同じようにポートフォリオをAWSにデプロイする方の参考になれば嬉しいです。
