AWS App Runnerの正体を探る

はじめに

お久しぶりです。 本ブログは 「AWS App Runner Advent Calendar 2022 17日目の投稿」となります。

このアドベントカレンダーでは、2022年のAWS App Runnerアップデート振り返りやAWS App Runner好きのエンジニアによる「動かしてみた」系の内容がたくさん投稿されており、筆者としても楽しく読ませてもらっています。

僕もAWS App Runner好きの一員として折角エントリーしてみたので、敢えて少し毛色の違ったトピックをご紹介します。

そのテーマはズバリ、「AWS App Runnerの正体を探る」です。 ちょっと何言っているのかわからない方も多いかも(?)しれませんが、しばしお付き合いください。

AWS App Runnerは何者なのか ~ 仮説を立てる

AWSには「Building Block」という、ブロックを組み合わせながらサービスを構築していく、という思想があります。 2019年当時のAWS CEO である Andy Jassy が re:Invent 2019 のlive chatにて、「Building Block 」という言葉を多用しています。

www.youtube.com

AWSが提供する各種サービスにおいてもまた、既存のAWSサービスが組み合わされ、新たなサービスとして提供されているケースが多数あります。 例を挙げると、AWS Elastic BeanstalkはAmazon EC2Amazon RDS、ELBなどがAWSCloudFormationテンプレートとしてサービス提供されています。 また、CloudWatch SyntheticsはWebアプリケーションやAPIエンドポイントに対して正常性監視ができるサービスです。有効化すると、実体として裏ではLambdaが作成されて監視されます。

このように、一部のAWSサービスは、複数のプリミティブなAWSサービスにより機能が提供されています。 この前提を考慮すると、「AWS App Runnerも既存のプリミティブなAWSサービスを組み合わせてサービス提供されているのではないか?」となんとなく想像ができそうですね。

ここで初心に立ち返り、AWS App Runnerのサービスページを見てみると、「フルマネージド型のコンテナアプリケーションサービス」と記載されています。

aws.amazon.com

ここまでの内容を読んだ読者の方々は、App Runnerがどのようなサービスで構成されていそうなのか、なんとなく察しがつくのではないでしょうか? 「マネージドかつコンテナサービス」といえば・・・そう、 「Amazon ECS & AWS Fargate」です。

改めて、今回のブログのテーマは、「AWS App RunnerはAmazon ECS & AWS Fargateの組み合わせとしてサービス提供されているのか?」を探索していきます。

正体を突き止めるための下準備

単純に自分たちのアプリケーションコンテナをApp Runner上で起動するだけでは、その正体を探ることはできません。 少々短絡的な考えですが、正体を突き止めるためには、コンテナの中から情報を探って特定するのが良さそうです。 そこで、App Runner上で稼働させたアプリケーション内にログインする方法を検討します。

ところで、以前、筆者はとあるmeet-upでこんなLTをしました。

speakerdeck.com

この内容を踏まえると、「App Runnerで稼働させるコンテナ内にSSMエージェントを仕込んでAWS System Manager - Session Managerを経由することでログインできそうかも」と予想できそうですね。 つまり、次のような構成です。

一点注意すべき点として、App Runnerは「WebアプリケーションやAPIの稼働」を前提としたサービスです。 つまり、アプリケーション作成時にヘルスチェック機能が自動的に組み込まれます。 このヘルスチェックに正常応答するようにエンドポイントを構築しないと、App Runnerのインスタンスが停止してしまいます。

そのため、SSMエージェントを起動しつつも、Webアプリケーションを起動する、という多重プロセスなコンテナを用意する必要があります。 今回用途向けですが、Go製のWebアプリケーションでヘルスチェック応答しつつ、SSMエージェントが起動されるようなDockerfileをサクッと作成しました。

興味のある方は以下リポジトリを参照してみてください。

github.com

App Runnerの内部にログイン

準備が終わったところで、以下の流れでApp Runner上にアプリケーションを起動させましょう。

  1. SSMエージェントのインストール&起動とGoのWebアプリケーションを起動するコンテナをビルド
  2. Amazon ECRを作成して、ビルド済みコンテナイメージをプッシュ
  3. AWS App RunnerからECRイメージを参照して起動

操作手順の様子は割愛しますが、AWS App Runnerが起動すると、アプリケーションのログ上に次のような内容が出力されます。

ログ上に表示されている「mi-xxxxxxxxxxxxxxxxx」ですが、これはセッションマネージャにてインスタンスに接続する際のIDです。

次にAWS Systems Manager セッションマネージャの画面に移動しましょう。 すると、先程表示された「mi-xxxxxxxxxxxxxxxxx」がエントリされています。

AWS App Runner上でSSMエージェントがインストールされ、セッションマネージャのマネージドインスタンスとしてしっかり登録されていることがわかりますね。 早速実行してみると・・・・

無事AWS App Runnerのインスタンス内にログインすることができました🚀

Amazon ECSのメタデータエンドポイントから探る

はたして、App RunnerはAmazon ECSにて管理しているのか?その手がかりはなにか?

ここで、Amazon ECSのオンラインドキュメントをざっと眺めてみると、次のような記載があります。

docs.aws.amazon.com

Fargateのプラットフォームバージョン1.4.0以降、ECS_CONTAINER_METADATA_URI_V4 という名前の環境変数がタスク内の各コンテナに挿入されます。タスクメタデータエンドポイントバージョン 4 に対してクエリを実行すると、さまざまなタスクメタデータおよび Docker 統計 をタスクで利用できます。

つまり逆説的に、「もし環境変数ECS_CONTAINER_METADATA_URI_V4」が存在していれば、Amazon ECS/AWS Fargate(v1.4.0以降)として動作しているのでは?」とも考えられます。(必要十分な条件としては明記されていないので、若干雑な発想ですが・・・)

Session Manager経由ログインしたインスタンスのコンソール上でenvコマンドを実行し、環境変数を一覧表示してみると・・・

HOSTNAME=ip-10-0-61-172.ap-northeast-1.compute.internal
TERM=xterm-256color
AWS_CONTAINER_CREDENTIALS_RELATIVE_URI=/v2/credentials/a8b0d93f-699c-46a7-89c7-93e3f3030113
AWS_EXECUTION_ENV=AWS_ECS_FARGATE
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin
_=/usr/bin/env
AWS_DEFAULT_REGION=ap-northeast-1
PWD=/
ECS_AGENT_URI=http://169.254.170.2/api/51c5952d8baa4c7eab612e293d951090-193386898
LANG=C.UTF-8
AWS_REGION=ap-northeast-1
HOME=/home/ssm-user
SHLVL=2
GOPATH=/usr/local/go
PORT=8080
ECS_CONTAINER_METADATA_URI=http://169.254.170.2/v3/51c5952d8baa4c7eab612e293d951090-193386898
ECS_CONTAINER_METADATA_URI_V4=http://169.254.170.2/v4/51c5952d8baa4c7eab612e293d951090-193386898

しっかりとECS_CONTAINER_METADATA_URL_V4が定義されていますね! この環境変数curlコマンドで実行し、出力結果をjqで整形してみましょう。すると・・・

sh-4.2$ curl -s ${ECS_CONTAINER_METADATA_URI_V4} | jq

{
  "DockerId": "51c5952d8baa4c7eab612e293d951090-193386898",
  "Name": "instance",
  "DockerName": "instance",
  "Image": "009138453688.dkr.ecr.ap-northeast-1.amazonaws.com/image-repo-7593d4a37e734182a7dcf1467b800d76@sha256:0e08d52b30ccafe95fb57143df8dde9ed89ddf14e64938e173139fb2dee28e23",
  "ImageID": "sha256:0e08d52b30ccafe95fb57143df8dde9ed89ddf14e64938e173139fb2dee28e23",
  "Labels": {
    "com.amazonaws.ecs.cluster": "arn:aws:ecs:ap-northeast-1:060632019862:cluster/bullet-srv-123456789012",
    "com.amazonaws.ecs.container-name": "instance",
    "com.amazonaws.ecs.task-arn": "arn:aws:ecs:ap-northeast-1:060632019862:task/bullet-srv-123456789012/51c5952d8baa4c7eab612e293d951090",
    "com.amazonaws.ecs.task-definition-family": "bullet-td-7593d4a37e734182a7dcf1467b800d76-8164d2f209d94654b65c062d098496c6",
    "com.amazonaws.ecs.task-definition-version": "1"
  },
  "DesiredStatus": "RUNNING",
  "KnownStatus": "RUNNING",
  "Limits": {
    "CPU": 960
  },
  "CreatedAt": "2022-12-15T15:40:15.825835593Z",
  "StartedAt": "2022-12-15T15:40:15.825835593Z",
  "Type": "NORMAL",
  "Networks": [
    {
      "NetworkMode": "awsvpc",
      "IPv4Addresses": [
        "10.0.61.172"
      ],
      "AttachmentIndex": 0,
      "MACAddress": "06:ce:e9:f1:21:1d",
      "IPv4SubnetCIDRBlock": "10.0.32.0/19",
      "DomainNameServers": [
        "10.0.0.2"
      ],
      "DomainNameSearchList": [
        "ap-northeast-1.compute.internal"
      ],
      "PrivateDNSName": "ip-10-0-61-172.ap-northeast-1.compute.internal",
      "SubnetGatewayIpv4Address": "10.0.32.1/19"
    },
    {
      "NetworkMode": "awsvpc",
      "AttachmentIndex": 2,
      "DomainNameServers": [
        "10.0.0.2"
      ],
      "DomainNameSearchList": [
        "ap-northeast-1.compute.internal"
      ]
    }
  ],
  "ContainerARN": "arn:aws:ecs:ap-northeast-1:060632019862:container/bullet-srv-123456789012/51c5952d8baa4c7eab612e293d951090/dbd0524f-02d0-4654-88e1-fb106ac5d046",
  "LogOptions": {
    "awslogs-create-group": "true",
    "awslogs-group": "/customer/123456789012/apprunner-bastion/7593d4a37e734182a7dcf1467b800d76/application",
    "awslogs-region": "ap-northeast-1",
    "awslogs-stream": "application/instance/51c5952d8baa4c7eab612e293d951090"
  },
  "LogDriver": "awslogs"
}

出力結果からECSクラスターやタスクの情報が表示されました!

ECSクラスターのARNですがarn:aws:ecs:ap-northeast-1:060632019862:cluster/bullet-srv-123456789012となっており、「060632019862」と見慣れないAWSアカウントが表示されています。おそらく、これはAWS側で管理されているAWSアカウントでしょう。

またImage URIに関しても009138453688.dkr.ecr.ap-northeast-1.amazonaws.com/image-repo-759...となっています。 「009138453688」と、こちらも見慣れないAWSアカウントが表示されています。 もともと、ビルドしたコンテナイメージは自分たちのAWSアカウント上のECRに保存されていますが、AWS App Runner上で起動する際は、(おそらくAWS管理の)Amazon ECRを一旦挟んで起動している様子が垣間見れます。

以上の情報より、AWS App Runnerの正体はAmazon ECS/AWS Fargateであることがほぼ突き止められました。

補足ですが、AWS App Runnerではアプリケーションのソース元として、Amazon ECRによるコンテナイメージ取得、もしくはGitHubからのアプリケーションソースコード取得のいずれかを選択できます。 今回の検証では前者のパターンでご紹介しました。 なお、サンプルコードのアプリケーションでは、/infoエンドポイントを実行すると、環境変数ECS_CONTAINER_METADATA_URI_V4に対してcurlコマンドを実行した結果を表示できます。 試していただけるとわかるのですが、アプリケーションのソース元をGitHubとし、/infoに対してHTTPリクエストを実行することで、同様にECSクラスターやECSタスクの情報が得られます。

つまり結論として、アプリケーションのソース元の選択によらず、AWS App RunnerはAmazon ECS/AWS Fargateが裏で活躍している、と言えそうですね。

さいごに

最後まで読んでいただきありがとうございました。

今回のブログでは、SSMエージェントやECSメタデータエンドポイントの情報を基に、AWS App Runnerの正体を探ってみました。 公式にAWSが情報公開していないものの、裏ではAmazon ECS/AWS Fargateで稼働していることがファクトベースで実証できました。

本ブログ内容は誰得ネタですが、一つの面白い読み物として楽しんでいただけたのであれば幸いです。

ではまた!