AWS ECS FargateでNginxとアプリケーションを動かすための設定例についてメモ書きします。
今回はFrontend(Next.js)とNginxを同じタスク内で動作させる設定となります。
また、FargateではNetwork Mode
のbridge
が利用できないため、awsvpc
を利用した設定となります。
本記事ではECS タスク定義にはjsonファイルを利用します。
Network Mode解説:ネットワークモードの選択
Fargateではawsvpcの設定が必須:タスク定義パラメータ:ネットワークモード
背景
画面表示にNext.jsを利用しており、Next.jsからbackendのAPIを実行する際、部分的にNext.jsのプロキシ機能を利用していました。
Next.jsとbackendは長時間、通信を行う要件が追加で発生しましたのですが、Next.jsのAPI受付時間の上限は基本30秒であり、それを超えた場合通信エラーとなってしまいます。
上限を変更する方法もあったのですが、非推奨な方法であり、その設定を行うと何が起こっても知りませんよという警告が出てしまいました。
そこでNext.jsのプロキシ機能の代用としてNginxの利用を検討することとなりました。
実装
DockerfileはECRにpushしたものを利用します。
※ECRのリポジトリ作成やPush方法については説明を割愛
Dockerfile
FROM --platform=linux/amd64 nginx:1.25.3-alpine-slim
ADD ./nginx/dev/default.conf /etc/nginx/conf.d/default.conf
FROM --platform=linux/amd64
でどのCPUアーキテクチャでビルトするか明示的に指定しています。
(amd64はx86_64, x86と同じ意味)
利用イメージをnginx:latest
ではなく、nginx:1.25.3-alpine-slim
にしているのは2023年12月12日時点で重要度の高い脆弱性がnginx:latest
の方に出ていたからです。(ECRにイメージをpushすると確認できます)
ADD
でローカル環境の./nginx/dev
にあるdefault.conf
でNginx内の/etc/nginx/conf.d/default.conf
を上書きしています。
default.conf
Dockerfile
内で上書き配置しているdefault.conf
の内容です。
# TTLが5秒を超えるとAmazonDNSサーバーに名前解決しにいく設定。
# resolverの設定を利用するには下記で設定しているようにset <変数名> <ドメイン>で対象ドメインを変数にしなければならない
# 169.254.169.253はAWS内部からのみ接続可
resolver 169.254.169.253 valid=5s;
server {
# NGINXの受付ポート
listen 5000;
server_name nginx;
# 最大転送ファイルサイズ設定
client_max_body_size 200M;
# タイムアウト時間の設定(秒)
proxy_read_timeout 60;
proxy_connect_timeout 60;
proxy_send_timeout 60;
# ヘッダー情報設定
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
location / {
# localhost は 127.0.0.1 (IPv4), ::1 (IPv6) どちらともに名前解決されるので、明示的に127.0.0.1を設定する
proxy_pass http://127.0.0.1:3000;
}
# ドメインを変数backend_serverに入れる
set $backend_server hoge.api.jp
location /v1/login {
# resolverとsetを利用時、リクエストパラメータがある場合は${is_args}${args}を設定に入れる必要がある
proxy_pass proxy_pass https://${backend_server}/v1/login${is_args}${args};
}
location /v1/reset-password {
proxy_pass https://${backend_server}/v1/reset-password${is_args}${args};
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
listen 5000;
はECS(Nginx)の前にあるALB(アプリケーションロードバランサー)が受け付けた通信を5000番ポートに送信するように設定しているので5000としています。
location /
の設定をproxy_pass http://localhost:3000;
としているのは、FargateのNetwork Mode awsvpc
はタスク単位でプライベートIPアドレスが設定されているためです。
今回はFrontend(Next.js)とNginxを同じタスク内で動作させる設定となるため、Frontend(Next.js)とNginxの通信はlocalhostで行えることになります。
マニュアル:ネットワークモードの選択:awsvpc
よって、NginxからNext.jsに通信する際はhttp://localhost:3000
(Next.jsのデフォルトポートは3000)となります。
このあたりの設定に誤りがあるとECSのexit code:1がになってNginxが停止してしまいます。
# ECSログ
2023-12-11T16:07:00.477+09:00 TASK StoppedCode:TaskFailedToStart
2023-12-11T16:07:33.202+09:00 CONTAINER:frontend LastStatus:STOPPED HealthStatus:UNKNOWN (exit code: 143)
2023-12-11T16:07:33.202+09:00 CONTAINER:nginx LastStatus:STOPPED HealthStatus:UNKNOWN (exit code: 1)
2023-12-11T16:07:33.202+09:00 TASK LastStatus:DEPROVISIONING
ログの確認方法:https://rurukblog.com/post/essential-container-in-task-exited/
また特定のpassでの通信を受け取った際は、Next.jsではなく別タスクで動作しているbackendコンテナ(https://hoge.api.jp)に転送する設定をlocation /v1/login
とlocation /v1/reset-password
で行っています。
AWSのALBのIPアドレスは定期的に変更されるため、resolver
、set
等の設定を行い、名前解決したIPアドレスをキャッシュするのではなく、都度IPを確認するようにする必要があります。
※Nginxは一度名前解決したIPは何もしないとずっと同じ値でキャッシュされます。
詳細についてはこちらの記事に記載しています。
【AWS】フロントのECSにNginxを導入後、時間経過でログイン不可になる
ecs-task-def.json
ECSのタスク定義については次のようになります。
portMappings
でNext.js、Nginxそれぞれでのポート番号を利用するの設定しています。
{
"deploymentConfiguration": {
"maximumPercent": 200,
"minimumHealthyPercent": 100
},
"deploymentController": {
"type": "CODE_DEPLOY"
},
"desiredCount": 1,
"enableECSManagedTags": false,
"enableExecuteCommand": false,
"healthCheckGracePeriodSeconds": 0,
"launchType": "FARGATE",
"loadBalancers": [
{
"containerName": "nginx",
"containerPort": 5000,
"targetGroupArn": "arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxxxxxxx:targetgroup/xxxxxxx-blue/xxxxxxxxxxxxxxx"
}
],
"networkConfiguration": {
"awsvpcConfiguration": {
"assignPublicIp": "DISABLED",
"securityGroups": ["sg-xxxxxxxxxxxxxxxxxx"],
"subnets": [
"subnet-xxxxxxxxxxxxxxxxx",
"subnet-xxxxxxxxxxxxxxxxx
]
}
},
"placementConstraints": [],
"placementStrategy": [],
"platformFamily": "Linux",
"platformVersion": "1.4.0",
"schedulingStrategy": "REPLICA",
"serviceRegistries": [],
"tags": [
{
"key": "NAME",
"value": "hoge"
}
]
},
{
"containerDefinitions": [
{
"cpu": 0,
"essential": true,
"image": "xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/dev/frontend-nextjs:latest",
"linuxParameters": {
"initProcessEnabled": true
},
"name": "frontend",
"portMappings": [
{
"containerPort": 3000,
"hostPort": 3000,
"protocol": "tcp"
}
]
},
{
"cpu": 256,
"environment": [],
"essential": true,
"image": "xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/dev/nginx:latest",
"memoryReservation": 512,
"linuxParameters": {
"initProcessEnabled": true
},
"name": "nginx",
"portMappings": [
{
"containerPort": 5000,
"hostPort": 5000,
"protocol": "tcp"
}
]
}
],
"cpu": "512",
"executionRoleArn": "arn:aws:iam::xxxxxxxxxxxxxx:role/ecs-task-execution-role",
"family": "lottie-dev-admin-front-frontworker",
"memory": "2048",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"runtimePlatform": {
"cpuArchitecture": "X86_64",
"operatingSystemFamily": "LINUX"
},
"tags": [
{
"key": "NAME",
"value": "hoge"
},
],
"taskRoleArn": "arn:aws:iam::xxxxxxxxxxxxx:role/ecs-task-role"
}