2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Next.jsのAPI RoutesをAmplify Hostingにデプロイした際の動作環境を調査してみた

Posted at

はじめに

先日、Amazon Bedrock AgentCoreとNext.jsを使って、LLMからの回答をWebUI上でストリーミング表示してみました。

本来は、Next.jsをAmplify Hostingにデプロイして実現したかったのですが、API Routesの部分が上手くいかず...

結局、Vercelにデプロイして動くようにしました。

結論としては、AWS公式に「ストリーミングはサポートしてない」と書かれていたのですが、せっかくなのでAmplify Hosting上でNext.jsのAPI Routesがどのような環境で動作しているのか調査してみることにしました。

背景

やりたかったこと

本来は、Amplify HostingにNext.jsをデプロイして、API Routes(下図のバックエンドAPI)とAgentCoreと連携させ、サーバー送信イベント(SSE)によるストリーム通信を実現したかったです。
image.png

AgentCoreはサーバー送信イベント(SSE)のストリーミングに対応
下記ドキュメントにもある通り、AgentCoreでストリーミングを実現するには、サーバー送信イベント(SSE)を使用する必要がありました。
image.png
参考:https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-service-contract.html

苦戦したところ

AgentCoreからバックエンドAPIへのレスポンスはストリーミングで返ってくるのですが、API Routesからフロントエンドへはストリーミングで返ってこないという問題にぶち当たりました。

image.png

Vercelにデプロイして解決

そこでAmplify Hostingを諦めて、Vercelにデプロイしたところ、期待通りストリーミングでレスポンスが返ってくることが確認に出来ました。
image.png

AWS Amplify Hostingのドキュメントを確認してみる

Amplify Hostingのドキュメントをよくよく見ると...
そもそもストリーミングがサポートされていませんでした。

image.png
参考:https://docs.aws.amazon.com/ja_jp/amplify/latest/userguide/ssr-amplify-support.html

解散!!
というのも、つまらないのでもう少し調査してみます。

API Routesの動作環境を調査してみる

先に結論

Amazon ClouFrontのオリジンに関数URLを付与したLambdaが設定されており、カスタムランタイム上のnode18でAPI Routesのコードが動作してそう。
これらのリソースはAWS管理のようで、マネジメントコンソール上などからは確認できない。

image.png

事前準備: Amplify Hostingにアプリをデプロイする

まずは環境を構築する必要があるため、Amplify Hostingに以前作ったNext.jsのアプリをデプロイします。

準備1. Amplify Hostingへのデプロイ用に設定ファイルを作成する

Amplify Hostingへデプロイするために、以下のamplify.ymlファイルを作成しました。コードはGitHubにPushしておきます。

amplify.ymlの内容はこちら
amplify.yml
version: 1
applications:
  - appRoot: nextapp
    backend:
      phases:
        build:
          commands:
            - npm ci --cache .npm --prefer-offline
            - npx ampx pipeline-deploy --branch $AWS_BRANCH --app-id $AWS_APP_ID
    frontend:
      phases:
        preBuild:
          commands:
            - npm ci
        build:
          commands:
            - touch .env.production
            - echo "AGENT_CORE_ENDPOINT=$AGENT_CORE_ENDPOINT" >> .env.production
            - export NEXT_TELEMETRY_DISABLED=1
            - npm run build
      artifacts:
        baseDirectory: .next
        files:
          - '**/*'
      cache:
        paths:
          - node_modules/**/*
          - .next/cache/**/*

Amplify Hostingでビルド設定をカスタマイズする
Amplify Hostingは、デプロイ時にアプリ内のpackage.jsonファイルから構成を読み取って、いい感じにデプロイしてくれるようです。

ただし、YAMLファイルを作成して、ビルド構成をカスタマイズすることもできます。

今回は、再現性という観点から一応YAMLファイルを作成して、ビルド設定を明示的に定義することにしました。

準備2. マネジメントコンソール上からデプロイする

コードの準備は整ったので、マネジメントコンソール上にアクセスして、Amplify Hostingでデプロイします。

詳細はこちら
  1. マネジメントコンソール上からAmplifyのページを開き、「新しいアプリを作成」を押します
    image.png

  2. ソースコードのプロバイダーにGitHubを選択して、「次へ」を押します
    image.png

  3. 該当のリポジトリとブランチを選択します

  4. また今回のアプリ構成はモノレポなので、「私のアプリケーションはモノレポです」にチェックを入れます

  5. Next.jsアプリのルートフォルダを指定して、「次へ」を押します
    image.png

  6. ビルドの設定は、先ほど作成したamplify.ymlが読み込まれているので、そのまま「次へ」を押します
    image.png

  7. 内容を確認して、「保存してデプロイ」を押します
    image.png

  8. あとは自動的にデプロイが実行されるので、完了するまで待ちます。
    image.png

  9. 忘れずに環境変数も設定しておきます
    image.png

  10. Amazon Bedrock AgentCoreのエンドポイントを設定します
    image.png

  11. 環境変数を設定したら、再デプロイします

調査1. マネジメントコンソールのリソースを確認

まず初めにマネジメントコンソール上から作成されたリソースが無いか確認しました。

  • AWS Lambda
  • Amazon EC2
  • Amazon ECS

とりあえず主要なコンピューティングサービス周りを見てみましたが、特に作成されていませんでした。
よって、AWS管理のリソースが作成されると推察しました。

調査2. AWS Lambdaの環境変数を確認してみる

AWS Amplifyは、サーバレスリソースしか作成しないでしょう。ということで、AWS Lambdaと仮定します。

準備1. コードにログを仕込んで再デプロイする

API Routesのコードに下記のコードを追加して、ログを出力するようにします。
後述しますが、リクエストヘッダーも見たいので、ログに追加してます。

function logLambdaEnvInfo(request: NextRequest) {
  // Lambda環境変数の内容(キーと値のペア)をすべてログ出力
  console.log('[Lambda Env] All env:', process.env);

  // リクエストヘッダー全体をログ出力
  const headersObj = Object.fromEntries(request.headers.entries());
  console.log('[Lambda Env] Request headers:', headersObj);
}

準備2. CloudWatch からログを取集する

マネジメントコンソール上から「ホスティングしているコンピューティングログ」を開くと、Amazon CloudWatch のリンクがあるので、リンクをクリックしてログストリームを表示します。

image.png

あとはデプロイしたアプリを操作するとログが吐かれるので、そのログを収集します。

image.png

ログを確認する

それでは収集したログを確認していきます。まずは環境変数です。
以下に環境変数の抜粋しました。概要は生成AIにまとめてもらいました。

環境変数名 概要
AWS_LAMBDA_FUNCTION_NAME Compute-d2l408rai9nvda-655b54c79442f289d521d4cf93d289d8 実行中のLambda関数の名前。
AWS_LAMBDA_FUNCTION_VERSION $LATEST デプロイされた関数のバージョン。$LATESTは最新版を示す。
AWS_LAMBDA_RUNTIME_API 127.0.0.1:9001 ランタイムAPIのエンドポイント。カスタムランタイムがLambdaと通信するために使用。
AWS_EXECUTION_ENV AWS_Lambda_nodejs18.x 実行環境の識別子。ここではNode.js 18.xランタイムを使用していることを示す。
AWS_LAMBDA_INITIALIZATION_TYPE on-demand 初期化タイプ。on-demandはリクエスト時に初期化されることを意味する。
AWS_LAMBDA_EXEC_WRAPPER /opt/wrapper 関数実行時に使用されるラッパースクリプトのパス。通常は拡張機能やモニタリング用に使われる。

ポイント1. API RoutesはAWS Lambdaで実行されている

環境変数の取得が可能で、AWS Lambda関連の設定が確認できたので、API Routesは予想通りAWS Lambda上で実行されてるようです。

クライアント -> AWS Lambda

ポイント2. 実行環境は カスタムランタイムで実行されている

以下のとおり、AWS_LAMBDA_RUNTIME_APIという環境変数が確認できました。
つまり、実行環境ではランタイムAPIが使われており、ランタイムAPIが使われているということはカスタムランタイムで動作してそうです。

AWS_LAMBDA_RUNTIME_API: '127.0.0.1:9001'

ランタイムは、コードの実行環境のことであり、AWSが提供する標準のランタイムと自分で実行環境を構築するカスタムランタイムがあります。

ランタイムAPIは、AWS Lambdaのサービスと 実際にコードを動かす実行環境との橋渡しをするためものです。
image.png
参考:https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/runtimes-custom.html

標準のランタイムを使用する場合には、橋渡しを良しなにAWS側でやってくれますが、カスタムランタイムの場合は、自前でランタイムAPIを設定しないといけないようです。

また以下の環境変数からランタイムはnode.jsの18系を使用しています。

AWS_EXECUTION_ENV: 'AWS_Lambda_nodejs18.x'

ただし、AWSのドキュメントを確認すると、Node.js 18はLambdaの標準のランタイムでサポートされています。ただし、2025/9/1で廃止とあります。

じゃあ、標準のランタイムで動かせばいいじゃん!と思いましたが、こういったEOLを見越して、カスタムランタイムを使ってるのですかね...

image.png

ポイント3. オンデマンド実行なのでコールドスタートが発生しそう

AWS_LAMBDA_INITIALIZATION_TYPEを見るとon-demandとあります。

AWS_LAMBDA_INITIALIZATION_TYPE: on-demand

この設定だと、リクエストが来たタイミングでLambdaが起動されるため、一定時間アクセスがないと、次回のリクエスト時に初期化処理が走り、コールドスタートが発生します。

このあたりは、実際に運用するとパフォーマンス周りでハマりそうですね...

provisioned-concurrency を設定すれば、常にLambdaを起動状態に保つことができ、コールドスタートを回避できます(ただしコストは増加)。

調査3. リクエストヘッダーを確認する

リクエストヘッダーも確認してみます。生成AIにまとめてもらいました。

Amazon CloudFront系のヘッダー

ヘッダー名 概要
cloudfront-forwarded-proto https CloudFrontが使用したプロトコル。
cloudfront-viewer-http-version 3.0 使用されたHTTPバージョン。
cloudfront-viewer-tls TLSv1.3:TLS_AES_128_GCM_SHA256:connectionReused TLSバージョンと暗号スイート。
cloudfront-is-android-viewer false Android端末かどうか。
cloudfront-is-desktop-viewer true デスクトップ端末かどうか。
cloudfront-is-ios-viewer false iOS端末かどうか。
cloudfront-is-mobile-viewer false モバイル端末かどうか。
cloudfront-is-smarttv-viewer false スマートTVかどうか。
cloudfront-is-tablet-viewer false タブレット端末かどうか。
cloudfront-viewer-asn 2518 Autonomous System Number(ISP識別子)。
cloudfront-viewer-country JP 国コード(ISO 3166-1 alpha-2)。
cloudfront-viewer-country-name Japan 国名。
cloudfront-viewer-country-region 01 地域コード。
cloudfront-viewer-time-zone Asia/Tokyo タイムゾーン。

※クライアントの位置情報など含まれるので、以下のヘッダーは除外しました。

  • cloudfront-viewer-address: クライアントのIPアドレスとポート
  • cloudfront-viewer-country-region-name: 地域名
  • cloudfront-viewer-city: 推定される都市名
  • cloudfront-viewer-postal-code: 郵便番号
  • cloudfront-viewer-latitude: 推定緯度
  • cloudfront-viewer-longitude: 推定経度
そのほかヘッダー
ヘッダー名 概要
accept-encoding gzip, deflate, br, zstd クライアントが対応する圧縮方式。
accept-language ja,en-US;q=0.9,en;q=0.8 クライアントが優先する言語。
content-length 31 リクエストボディのサイズ(バイト)。
host amplify-hosting-debug.1234fsdwf211as.amplifyapp.com リクエスト先のホスト名。
origin https://amplify-hosting-debug.1234fsdwf211as.amplifyapp.com リクエスト発信元のオリジン。
priority u=1, i HTTP/3の優先度情報。
referer https://amplify-hosting-debug.1234fsdwf211as.amplifyapp.com/ リクエスト元のページURL。
sec-ch-ua "Not;A=Brand";v="99", "Google Chrome";v="139", "Chromium";v="139" ブラウザのブランド情報(User-Agent Client Hints)。
sec-ch-ua-mobile ?0 モバイル端末かどうか(User-Agent Client Hints)。
sec-ch-ua-platform "Windows" プラットフォーム情報(User-Agent Client Hints)。
sec-fetch-dest empty リクエストの目的(例:script, imageなど)。
sec-fetch-mode cors リクエストのモード(CORSなど)。
sec-fetch-site same-origin リクエスト元と送信先の関係。
user-agent Mozilla/5.0 (Windows NT 10.0; Win64; x64)... クライアントのブラウザとOS情報。
via 3.0 ...cloudfront.net (CloudFront) 経由したプロキシ情報。
x-amz-cf-id qAbvTiLFmi4Wr3Nb2fAI9oRSAfNt_3wto5zNAwBV70Ofk0FKx7GaIQ== CloudFrontのリクエストID。
x-amzn-trace-id Root=1-68b2dada-... AWS X-RayのトレースID。
x-forwarded-for 118.108.78.0, 64.252.67.70 クライアントの元IPアドレス。
x-forwarded-host amplify-hosting-debug.1234fsdwf211as.amplifyapp.com 元のホスト名。
x-forwarded-port 443 元のポート番号。

ポイント1. AWS Lambdaの前段にはAmazon CloudFrontがいる

前述した通り、リクエストヘッダーにcloudfront-xxxが含まれていました。
これらはAmazon CloudFrontが付与するものなので、AWS Lambdaの前段にAmazon CloudFrontがいると思われます。

クライアント -> Amazon CloudFront -> AWS Lambda

ポイント2. AWS Lambdaには関数URLが設定されている

Amazon CloudFrontのオリジンに設定できるのは、関数URLを設定したAWS Lambdaになります。
そのため、今回のLambda 関数には、関数URLが設定されていると考えられます。

感想

今回は、私自身あまりAmplify Hostingについて詳しくないですが、自分なりに調べてみました。

やはりAmplifyを使うと楽ちんにアプリを構築できる反面、裏の仕組みをしっかり理解していない、運用中にハマりそうだなと思いました...
(何かあったら、裏側の動作仕様を検証するところからね...)

とはいえ、小規模なアプリに使う分にはサクッと構築できて重宝すると思います。
今後、もう少しAmplifyと仲良くなれるように、色々と使っていきたいと思います。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?