Nginx リバースプロキシの実行環境としてApp Runnerを利用してみた
オリジンを環境変数で指定、汎用のリバースプロキシとして動作する 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 もお試しください。