オリジンを環境変数で指定、汎用のリバースプロキシとして動作する Nginxのコンテナイメージを Amazon Linux 2023 で作成し、 AWS App Runner を リバースプロキシの実行環境として利用する機会がありましたので、 紹介させて頂きます。
作業環境(EC2)
AMIは Amazon Linux 2023 (x86_64)。 Dockerは 標準リポジトリのパッケージを利用しました。
ECR、App Runnerを AWS CLIで操作するため、EC2ロールとして AdministratorAccessポリシーを付与したものを利用しました。
インストール
sudo yum install docker jq -y
sudo systemctl start docker
sudo systemctl enable docker
sudo usermod -a -G docker ec2-user
sudo su - ec2-user
バージョン情報
$ docker --version
Docker version 20.10.23, build 7155243
$ cat /etc/os-release
NAME="Amazon Linux"
VERSION="2023"
ID="amzn"
ID_LIKE="fedora"
VERSION_ID="2023"
PLATFORM_ID="platform:al2023"
PRETTY_NAME="Amazon Linux 2023"
ANSI_COLOR="0;33"
CPE_NAME="cpe:2.3:o:amazon:amazon_linux:2023"
HOME_URL="https://aws.amazon.com/linux/"
BUG_REPORT_URL="https://github.com/amazonlinux/amazon-linux-2023"
SUPPORT_END="2028-03-01"
コンテナイメージ作成
Docker設定
IMAGE='docker-nginx'
mkdir -p ~/${IMAGE}
cd ~/${IMAGE}
## Dockerfile
cat << "EoL" > Dockerfile
FROM nginx:latest
COPY ./default.conf.template /etc/nginx/templates/default.conf.template
COPY ./healthcheck.html /var/www/healthcheck.html
EoL
## default.conf.template
cat << "EoL" > default.conf.template
server {
listen 8080 default_server;
server_name localhost;
set $backend ${PROXY_HOST};
resolver 169.254.169.253 valid=5s;
location = /healthcheck.html {
access_log off;
root /var/www;
}
location / {
proxy_ssl_server_name on;
proxy_set_header Host $backend;
proxy_pass https://$backend;
}
}
EoL
## healthcheck.html
cat << "EoL" > healthcheck.html
<html>ok</html>
EoL
Nginx設定
環境変数対応
環境変数をNginx設定に反映するため、Nginx 1.19 よりデフォルトサポートされた envsubst を利用しました。
環境変数を利用する設定項目、${PROXY_HOST}
をとテンプレートファイルを /etc/nginx/templates/default.conf.template
に配置。
テンプレートファイル中の ${PROXY_HOST}
を、環境変数で指定した値に置換したファイルを、Nginxの設定ファイルとして利用しました。
Using environment variables in nginx configuration (new in 1.19)
By default, this function reads template files in /etc/nginx/templates/*.template and outputs the result of executing envsubst to /etc/nginx/conf.d.
名前解決
オリジン(リバースプロキシ先)のIPアドレスが変更される場合に備え、
Nginxのワークアラウンドとして知られる、proxy_pass
を変数で定義する設定を行いました。
resolver
には Route 53 Resolver のIPv4アドレス (169.254.169.25) を指定、 一定頻度 (TTL5秒)でオリジンの名前解決が行われる設定としました。
ヘルスチェック
- App Runnerのヘルスチェック用にローカルファイルを配置しました。
- ヘルスチェックのアクセスログ出力は抑制、CloudWatch Logs 費用の節約を図りました。
SNI
- HTTPS接続 に SNIを利用する CloudFront、API Gateway などを オリジンで利用可能とするため、
proxy_ssl_server_name on
を設定しました。
コンテナイメージ作成
IMAGE='docker-nginx'
cd ~/${IMAGE}
docker build -t ${IMAGE} .
起動
- 環境変数で オリジンとなるFQDN、弊社コーポレートサイト(CloudFront)を指定して、イメージを起動しました。
IMAGE='docker-nginx'
cd ~/${IMAGE}
## run
docker run --name ${IMAGE} --rm -p 8080:8080 --env "PROXY_HOST=classmethod.jp" -d ${IMAGE}
環境変数確認
Nginxの設定ファイルに指定したオリジンのFQDNが反映されている事を確認しました。
$ docker exec -it ${IMAGE} cat /etc/nginx/conf.d/default.conf | grep 'set $backend'
set $backend classmethod.jp;
疎通確認
curlコマンドを利用した疎通確認を試みました。
$ curl -v localhost:8080/healthcheck.html
* Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /healthcheck.html HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.88.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.25.0
< Date: Tue, 06 Jun 2023 02:24:59 GMT
< Content-Type: text/html
< Content-Length: 16
< Last-Modified: Tue, 06 Jun 2023 02:16:17 GMT
< Connection: keep-alive
< ETag: "647e96f1-10"
< Accept-Ranges: bytes
$ curl -s localhost:8080/healthcheck.html
<html>ok</html>
$ curl -s localhost:8080/ | head -n 4
<!doctype html>
<html lang="ja">
<head>
<script>
イメージPush
ECRにプライベートリポジトリを作成、「latest 」タグを付与したイメージを Pushしました。
ECRのURL要素に含まれるアカウントIDはSTSの「get-caller-identity」、 リージョンはメタデータ(IMDSv2)より取得しました
## Docker Image Name
IMAGE='docker-nginx'
## Account ID
ACCOUNT=`aws sts get-caller-identity | jq -r .Account`
## REGION
TOKEN_IMDSv2=`curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"`
REGION=`curl -s -H "X-aws-ec2-metadata-token: $TOKEN_IMDSv2" http://169.254.169.254/latest/meta-data/placement/availability-zone | sed -e 's/.$//'`
## Create Repository
aws ecr create-repository --repository-name ${IMAGE} --region ${REGION}
## ECR URI
ECR_URI="${ACCOUNT}.dkr.ecr.${REGION}.amazonaws.com/${IMAGE}"
echo "ECR_URI: ${ECR_URI}"
docker tag ${IMAGE}:latest ${ECR_URI}
aws --region ${REGION} ecr get-login-password | docker login --username AWS --password-stdin "${ECR_URI}:latest"
docker push ${ECR_URI}
App Runner環境作成
設定ファイル作成
今回、CLIで App Runner 環境を作成を試みました。
Nginx設定用の環境変数や、スペック、ヘルスチェックで設定などを反映したJSONファイルを用意しました。
cat << EoL > app-runner.json
{
"ServiceName": "nginx-app",
"SourceConfiguration": {
"ImageRepository": {
"ImageIdentifier": "${ACCOUNT}.dkr.ecr.${REGION}.amazonaws.com/${IMAGE}:latest",
"ImageRepositoryType": "ECR",
"ImageConfiguration": {
"RuntimeEnvironmentVariables": {
"PROXY_HOST": "classmethod.jp"
},
"Port": "8080"
}
},
"AutoDeploymentsEnabled": false,
"AuthenticationConfiguration": {
"AccessRoleArn": "arn:aws:iam::${ACCOUNT}:role/service-role/AppRunnerECRAccessRole"
}
},
"InstanceConfiguration": {
"Cpu": "0.25 vCPU",
"Memory": "0.5 GB"
},
"HealthCheckConfiguration": {
"Protocol": "HTTP",
"Path": "/healthcheck.html",
"Interval": 20,
"Timeout": 2,
"HealthyThreshold": 1,
"UnhealthyThreshold": 5
}
}
EoL
cat app-runner.json | jq .
$ aws apprunner create-service --cli-input-json file://app-runner.json
{
"Service": {
"ServiceName": "nginx-app",
"ServiceId": "00000000000000000000000000000000",
"ServiceArn": "arn:aws:apprunner:ap-northeast-1:000000000000:service/nginx-app/000000000000",
"ServiceUrl": "0000000000.ap-northeast-1.awsapprunner.com",
"CreatedAt": "2023-05-27T15:42:27.605000+00:00",
"UpdatedAt": "2023-05-27T15:42:27.605000+00:00",
"Status": "OPERATION_IN_PROGRESS",
"SourceConfiguration": {
"ImageRepository": {
"ImageIdentifier": "000000000000.dkr.ecr.ap-northeast-1.amazonaws.com/docker-nginx:latest",
"ImageConfiguration": {
"RuntimeEnvironmentVariables": {
"PROXY_HOST": "classmethod.jp"
},
"Port": "8080"
},
"ImageRepositoryType": "ECR"
},
"AutoDeploymentsEnabled": false,
"AuthenticationConfiguration": {
"AccessRoleArn": "arn:aws:iam::000000000000:role/service-role/AppRunnerECRAccessRole"
}
},
"InstanceConfiguration": {
"Cpu": "256",
"Memory": "1024"
},
"HealthCheckConfiguration": {
"Protocol": "HTTP",
"Path": "/healthcheck.html",
"Interval": 20,
"Timeout": 2,
"HealthyThreshold": 1,
"UnhealthyThreshold": 5
},
"AutoScalingConfigurationSummary": {
"AutoScalingConfigurationArn": "arn:aws:apprunner:ap-northeast-1:000000000000:autoscalingconfiguration/DefaultConfiguration/1/000000000000",
"AutoScalingConfigurationName": "DefaultConfiguration",
"AutoScalingConfigurationRevision": 1
},
"NetworkConfiguration": {
"EgressConfiguration": {
"EgressType": "DEFAULT"
},
"IngressConfiguration": {
"IsPubliclyAccessible": true
}
}
},
"OperationId": "000000000000"
}
動作確認
App RunnerのサービスURLを取得、curlコマンドで App Runner の リバースプロキシを経由して、オリジンのコンテンツを表示できる事を確認しました。
APP_URL="https://`aws apprunner list-services --output text --query 'ServiceSummaryList[?ServiceName==\`nginx-app\`].ServiceUrl'`"
echo ${APP_URL}
curl -s ${APP_URL}
- 実行結果
$ curl -s ${APP_URL} | head -n 3
<!doctype html>
<html lang="ja">
<head>
利用例
App Runner
Node.jsのコンテナ実行環境 として App Runnerを利用する場合、 前段 に Nginxコンテナの App Runnerを配置する事で、Webサーバ経由で Node.jsの実行環境を公開する利用が可能です。
キャッシュ用ヘッダをNginxで設定、CloudFrontのキャッシュ制御などが可能となります。
Nginxでルーティング、特定パスを別サーバなどに転送するパスルーティングなどが可能になります。
アプリケーション実行環境のApp Runnerをプライベートアクセス用に設定し、 VPC Endpoint 経由で Nginx に公開する事で、Nginxを用いた認証利用も可能になります。
※VPCエンドポイントを利用する場合、AZ毎の維持、通信費用が発生する点にはご注意ください。
CloudFront切替
CloudFront、ディストリビューション間のCNAME重複が禁止されているため、 無停止でCloudFrontディストリビューションを交換する場合、AWSサポートへの依頼や、同一アカウントであれば AssociateAlias APIを利用する必要がありました。
CloudFront ディストリビューション のCNAME交換作業中、 Nginxを公開環境として利用する事で、任意の時間帯にサービスダウンの無いメンテナンスが可能となります。
ただし、CloudFrontの前段に Nginxを配置する利用は、CDNの配信性能としては大きく低下します。 Nginx が ボトルネックとならない事や、性能低下が許容できるレベルである事を事前に確認頂いた上で、 極力短期間の利用に留める事をお勧めします。
- 移行前
- 移行中
-
移行後
まとめ
App Runner、CPU負荷が発生しない状態であれば、プロビジョニングされたコンテナインスタンス 0.007 USD/GBのメモリ費用に応じた費用で利用できます。
北米リージョンで、メモリ割り当て設定を最小の「0.5GB」とした場合、App Runnerの月額費用は 2.5ドル (0.007 USD x 0.5(GB) x 24(h) x 30(d)) の維持費と、CPU負荷に応じて発生する従量課金で利用できます。
Nginxの実行環境として、AWSではFargate(ECS)、EC2なども利用可能ですが、 HTTPS提供を必要とする場合、Let's Encrypt の設定や、ELBを利用する場合そのコストが発生します。
シンプルな要件、スモールスタートで Nginxを利用する場合、App Runner もお試しください。