この記事では、Goで作成したTwitter風APIを、次の構成でAWSへデプロイします。
構成
Amazon ECS Fargate
Application Load Balancer
マルチAZ
Aurora PostgreSQL Serverless v2
ElastiCache for Redis
Amazon ECR
Secrets Manager
Amazon S3
Amazon SES
Route 53
AWS Certificate Manager
CloudWatch Logs
完成後はRoute 53で設定したURLで、APIへアクセスできる構成です。
1. 全体構成
通信の流れは次のようになります。
ユーザー
↓
Route 53
↓
ACM証明書
↓
Application Load Balancer
↓
ECS Fargate(2AZ・2タスク)
├─ Aurora PostgreSQL
├─ ElastiCache for Redis
├─ S3
├─ SES
└─ Secrets Manager
東京リージョンを使用します。
ap-northeast-1
マルチAZ構成として、以下の2つを使用します。
ap-northeast-1a
ap-northeast-1c
2. 事前準備
ローカル環境へ次をインストールします。
インストール
Docker Desktop
AWS CLI
Git
Go
Route 53で利用するドメイン
AWS CLIへログインできることを確認します。
aws sts get-caller-identity
アカウントIDとIAMユーザーのARNが表示されれば準備完了です。
3. アプリケーションをECS向けに準備する
ヘルスチェックAPI
ALBがアプリケーションの状態を確認できるように、認証不要のヘルスチェックAPIを用意します。
r.GET("/health", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "ok"})
})
ローカルで確認します。
curl http://localhost:8080/health
期待する結果は次のとおりです。
{"status":"ok"}
環境変数
アプリケーションは、最低限以下の環境変数を読み取れるようにします。
環境変数
DB_HOST
DB_PORT
DB_NAME
DB_USER
DB_PASSWORD
DB_SSLMODE
REDIS_ADDR
REDIS_PASSWORD
REDIS_TLS
AWS_REGION
S3_BUCKET
MAILER_BACKEND
SES_FROM
ACTIVATION_URL
SECURE_COOKIES
パスワードはコードやDockerイメージへ直接記述しません。
4. Dockerfileを作成する
Goアプリをマルチステージビルドします。
FROM golang:1.26.3-alpine AS build
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build \
-trimpath \
-ldflags="-s -w" \
-o /server .
FROM alpine:3.23
RUN apk add --no-cache ca-certificates \
&& adduser -D -H -u 10001 app
COPY --from=build /server /server
USER app
EXPOSE 8080
ENTRYPOINT ["/server"]
ECSタスクがARM64の場合は、イメージもARM64としてビルドします。
docker buildx build \
--platform linux/arm64 \
-t twitter-golang:arm64 \
.
アーキテクチャが一致しない場合、ECSタスクは起動できません。
5. VPCとサブネットを作成する
VPC
例として次のCIDRを使用します。
10.0.0.0/24
サブネット
2つ以上のAZにサブネットを作成します。
| 用途 | AZ | CIDR |
|---|---|---|
| アプリ/ALB | ap-northeast-1a | 10.0.0.0/28 |
| アプリ/ALB | ap-northeast-1c | 10.0.0.32/28 |
| DB | ap-northeast-1a | 10.0.0.16/28 |
| DB | ap-northeast-1c | 10.0.0.48/28 |
ALBを置くサブネットには、Internet Gatewayへのルートが必要です。
0.0.0.0/0 → Internet Gateway
ALBは、各サブネットに最低8個の空きIPアドレスを要求します。小さいサブネットを既存リソースと共有すると、ALB作成時に次のエラーが発生します。
Not enough IP space available
そのため、可能ならALB専用サブネットを用意します。
6. セキュリティグループを作成する
ALB用SG
受信ルール:
| ポート | 接続元 |
|---|---|
| 80 | 0.0.0.0/0 |
| 443 | 0.0.0.0/0 |
ECS用SG
受信ルール:
| ポート | 接続元 |
|---|---|
| 8080 | ALB用SG |
ECSの8080番ポートを、直接インターネットへ公開しないことが重要です。
RDS用SG
受信ルール:
| ポート | 接続元 |
|---|---|
| 5432 | ECS用SG |
Redis用SG
受信ルール:
| ポート | 接続元 |
|---|---|
| 6379 | ECS用SG |
IPアドレスではなく、セキュリティグループ同士で許可すると管理しやすくなります。
7. Aurora PostgreSQLを作成する
RDSコンソールから「データベースの作成」を選びます。
設定例:
エンジン: Aurora PostgreSQL
構成: 標準作成
インスタンス: Serverless v2
パブリックアクセス: なし
VPC: 作成したVPC
DBサブネットグループ: 2AZを含むグループ
セキュリティグループ: RDS用SG
マルチAZ化
ライターを1a、リーダーを1cに配置します。
database-instance-1: Writer / ap-northeast-1a
database-instance-2: Reader / ap-northeast-1c
8. ElastiCache for Redisを作成する
ElastiCacheでRedis OSSまたはValkey互換キャッシュを作成します。
設定例:
VPC: アプリと同じVPC
転送中の暗号化: 有効
認証トークン: 有効
ポート: 6379
セキュリティグループ: Redis用SG
ECSへは次のように設定します。
REDIS_ADDR=master.example.cache.amazonaws.com:6379
REDIS_TLS=true
RedisパスワードはSecrets Managerから渡します。
9. Secrets Managerへパスワードを保存する
次のようなシークレットを作成します。
twitter-golang/database
twitter-golang/redis
保存する内容:
twitter-golang/database → DBユーザーのパスワード
twitter-golang/redis → Redisの認証トークン
ECSタスク定義では、通常の環境変数ではなく「シークレット」として設定します。
{
"secrets": [
{
"name": "DB_PASSWORD",
"valueFrom": "DBシークレットのARN"
},
{
"name": "REDIS_PASSWORD",
"valueFrom": "RedisシークレットのARN"
}
]
}
ECSがシークレットを取得するには、タスク実行ロールへ次の権限が必要です。
{
"Effect": "Allow",
"Action": "secretsmanager:GetSecretValue",
"Resource": [
"DBシークレットのARN",
"RedisシークレットのARN"
]
}
シークレットを変更しただけでは、稼働中コンテナには反映されません。ECSサービスで「新しいデプロイの強制」が必要です。
10. 画像保存用S3バケットを作成する
S3バケットはグローバルで一意の名前にします。
twitter-golang-images-<AWSアカウントID>-ap-northeast-1
設定:
パブリックアクセスをすべてブロック
ACL無効
デフォルト暗号化を有効
署名付きURLでアップロード
オブジェクトキーを images/* に限定
CORS例:
{
"CORSRules": [
{
"AllowedOrigins": [
"https://example.com"
],
"AllowedMethods": [
"PUT"
],
"AllowedHeaders": [
"*"
],
"ExposeHeaders": [
"ETag"
],
"MaxAgeSeconds": 3600
}
]
}
タスクロールには最小限の権限だけを付与します。
{
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::バケット名/images/*"
}
署名付きURLを使えば、フロントエンドへAWS認証情報を渡さずにアップロードできます。
11. SESを設定する
SESでドメインIdentityを作成します。example.com
Route 53を利用している場合、SESが発行するDKIM用CNAMEをDNSへ登録します。さらに、必要に応じて以下を設定します。
- DKIM
- SPF
- DMARC
- カスタムMAIL FROM
アプリ側の設定例:
MAILER_BACKEND=ses
SES_FROM=noreply@example.com
AWS_REGION=ap-northeast-1
ACTIVATION_URL=https://example.com/activate
タスクロールにSES送信権限を追加します。
{
"Effect": "Allow",
"Action": [
"ses:SendEmail",
"ses:SendRawEmail"
],
"Resource": "arn:aws:ses:ap-northeast-1:<AWSアカウントID>:identity/example.com"
}
12. IAMロールを作成する
ECSでは、2種類のロールを分けます。
タスク実行ロール
ECS自体が使用します。
用途:
- ECRからイメージをpull
- CloudWatch Logsへ出力
- Secrets Managerから値を取得
タスクロール
アプリケーションが使用します。
用途:
- S3へ画像を保存
- SESでメールを送信
アプリケーションへAWSアクセスキーを環境変数で渡す必要はありません。AWS SDKはタスクロールの一時認証情報を自動的に使用します。
13. ECRへDockerイメージをpushする
リポジトリ作成
aws ecr create-repository \
--region ap-northeast-1 \
--repository-name golang-twitter
ECRへログイン
aws ecr get-login-password \
--region ap-northeast-1 |
docker login \
--username AWS \
--password-stdin \
<AWSアカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com
ARM64イメージをbuild・push
docker buildx build \
--platform linux/arm64 \
-t <AWSアカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/golang-twitter:arm64-v1 \
--push \
.
ECSタスク定義のCPUアーキテクチャもARM64にします。
{
"runtimePlatform": {
"cpuArchitecture": "ARM64",
"operatingSystemFamily": "LINUX"
}
}
14. ALBとターゲットグループを作成する
ターゲットグループ
設定例:
ターゲットタイプ: IP
プロトコル: HTTP
ポート: 8080
ヘルスチェックパス: /health
成功コード: 200
Fargateのawsvpcネットワークモードでは、ターゲットタイプをinstanceではなくipにします。
ALB
設定:
スキーム: Internet-facing
AZ: ap-northeast-1a / ap-northeast-1c
セキュリティグループ: ALB用SG
最初は80番リスナーを作り、ターゲットグループへ転送します。
15. ECSタスク定義を作成する
設定例:
起動タイプ: AWS Fargate
OS: Linux
CPUアーキテクチャ: ARM64
コンテナポート: 8080
ログ: awslogs
環境変数例:
DB_HOST=<Auroraクラスタエンドポイント>
DB_PORT=5432
DB_NAME=postgres
DB_USER=twitter_app
DB_SSLMODE=require
REDIS_ADDR=<Redisエンドポイント>:6379
REDIS_TLS=true
AWS_REGION=ap-northeast-1
S3_BUCKET=<S3バケット名>
MAILER_BACKEND=ses
SES_FROM=noreply@example.com
ACTIVATION_URL=https://example.com/activate
SECURE_COOKIES=true
シークレット:
DB_PASSWORD
REDIS_PASSWORD
CloudWatch Logsの例:
ロググループ: /ecs/twitter-golang-task
ストリームプレフィックス: ecs
HealthCheck(オプション)を追加
| 項目 | 設定値 |
|---|---|
| コマンド | CMD-SHELL,curl -fsS http://localhost:8080/health || exit 1 |
| 間隔 |
30秒 |
| タイムアウト |
5秒 |
| 開始期間 |
30秒 |
| 再試行 |
3回 |
それぞれの値には次の意味があります。
- command:コンテナ内部で実行する確認コマンド
- interval:ヘルスチェックを実行する間隔
- timeout:何秒応答しなければ失敗と判断するか
- retries:何回連続で失敗したらUNHEALTHYにするか
- startPeriod:起動直後の失敗をカウントしない猶予期間
-
curl -fは、HTTP 400や500などのエラーレスポンスを失敗として扱います。成功時の終了コードは0、失敗時は0以外になります。
16. ECSクラスターとサービスを作成する
クラスター
twitter-golang-cluster
インフラストラクチャはAWS Fargateを選びます。
サービス
設定例:
サービス名: twitter-golang-service
必要タスク数: 2
サブネット: ap-northeast-1a / ap-northeast-1c
パブリックIP: 有効
セキュリティグループ: ECS用SG
ロードバランサー: 作成したALB
ターゲットグループ: 作成したターゲットグループ
コンテナ: twitter-golang
ポート: 8080
低コスト構成では、Fargateを公開サブネットへ配置し、ECS用SGでALBからの通信だけを許可できます。
より安全な本番構成では、Fargateをプライベートサブネットへ配置し、NAT GatewayまたはVPC Endpointを利用します。
サービス作成後、タスクが2AZへ分散していることを確認します。
ap-northeast-1a: 1タスク
ap-northeast-1c: 1タスク
17. Route 53でドメインを設定する
Route 53でドメインを購入、またはホストゾーンを作成します。
例:
example.com
ALBを指すAレコードを作成します。
レコード名: example.com
タイプ: A
Alias: 有効
Alias先: Application Load Balancer
ALBへはIPアドレスではなくAliasレコードを設定します。
18. ACMでHTTPS証明書を作成する
ACMで証明書をリクエストします。
example.com
*.example.com
検証方法はDNS検証を選びます。
Route 53へ検証用CNAMEを登録し、証明書が次の状態になるまで待ちます。
Issued
ALBへ443番HTTPSリスナーを追加します。
HTTPS :443
証明書: ACM証明書
転送先: ECSターゲットグループ
80番リスナーはHTTPSへリダイレクトします。
HTTP :80 → HTTPS :443
Status: 301
19. 動作確認
HTTPS
curl -i https://example.com/health
期待する結果:
HTTP/2 200
HTTPリダイレクト
curl -I http://example.com
期待する結果:
HTTP/1.1 301 Moved Permanently
Location: https://example.com/
ALBターゲット
ターゲットグループで2台とも次の状態になっていることを確認します。
healthy
ECS
desiredCount: 2
runningCount: 2
pendingCount: 0
CloudWatch Logs
以下のログがあればアプリは起動しています。
Listening and serving HTTP on :8080
まとめ
今回のデプロイでは、単にコンテナをECSで起動するだけでなく、以下を組み合わせました。
ECS Fargateへデプロイする際にしたこと
Fargateによるコンテナ実行
ALBによる負荷分散
2AZへのタスク分散
Aurora Writer/Readerによる可用性
Secrets Managerによる認証情報管理
S3署名付きURLによる画像アップロード
SESによるメール送信
Route 53とACMによる独自ドメイン・HTTPS
CloudWatch Logsによる障害調査
