AWS再入門ブログリレー2022 AWS App Runner編

2022.03.07

当エントリは弊社コンサルティング部による『AWS 再入門ブログリレー 2022』の23日目のエントリです。

このブログリレーの企画は、普段 AWS サービスについて最新のネタ・深い/細かいテーマを主に書き連ねてきたメンバーの手によって、 今一度初心に返って、基本的な部分を見つめ直してみよう、解説してみようというコンセプトが含まれています。

AWS をこれから学ぼう!という方にとっては文字通りの入門記事として、またすでに AWS を活用されている方にとっても AWS サービスの再発見や 2022 年のサービスアップデートのキャッチアップの場となればと考えておりますので、ぜひ最後までお付合い頂ければ幸いです。

では、さっそくいってみましょう。今日のテーマは『AWS App Runner』です。

AWS App Runnerとは?

AWS App Runner(以後 App Runner)は公式ページを見ると以下の様に記述されています。

コンテナ化されたウェブアプリケーションや API を開発者が簡単かつ迅速にデプロイできるフルマネージド型サービスです。

とある様に、アプリケーションコンテナひとつ用意すればあとのインフラはAWSにおまかせでWEBアプリケーションを公開できる便利なサービスです。

AWSでコンテナを扱うサービスは他にAmazon ECSAmazon EKSがありますが、App Runnerはインフラ管理をよりマネージドにし利用者はアプリケーション開発に集中できる様になっています。
実際App Runnerの内部実装はAWS管理のAmazon ECS + AWS Fargateとなっています。

ちなみに他社サービスに馴染みのある方にとってはGoogle Cloud RunやAzure App Service(Linxu環境のWeb Apps)に近いものと考えるとわかりやすいかもしれません。

基本構成

App Runnerの基本構成は下図の通りです。

App Runnerでは公開するアプリケーションを「サービス」という形で登録し、サービス内部では単一イメージのコンテナアプリケーションが実行されます。
コンテナインスタンス数はアクセス量に応じてオートスケーリングされ、各種ログはCloudWatch Logsに出力されます。

コンテナイメージは自作のDockerイメージをECRリポジトリにPushして利用するか、GitHub Repositoryと連携してソースコードからAmazon LinuxベースのDockerイメージを自動生成するかの2パターン選ぶことができます。
GitHub連携の場合、本日時点で選択可能な言語は

  • Python 3.7 - 3.8
  • Node.js 12, 14
  • Java (corretto) 8, 11

となります。 詳細は以下のドキュメントをご覧ください。

利用可能リージョン・料金

本日時点でApp Runnerを利用可能なリージョンは

  • バージニア北部
  • オハイオ
  • オレゴン
  • アイルランド
  • 東京

となります。
残念ながらまだ大阪では使えませんが期待して待ちましょう。

そして利用料金はコンテナインスタンスのスペックと利用時間に応じた金額が請求されます。
詳細は料金ページを参照してください。

  • AWS App Runner の料金 (※東京リージョンの利用単価は但し書きの方に記載されているので注意)

App Runnerでできること (2022年3月時点)

App Runnerはインフラ管理をAWSにまるっと任せる代償にユーザーが細かくカスタマイズすることができない部分があります。
また比較的新しいサービスであるため日々機能改善されています。

このため利用時点においてApp Runnerでできること(逆にできないこと)を押さえておくのは重要です。
まずは本日時点においてできることを解説していきます。

1. オートスケーリング

App Runnerではリクエスト量に応じたオートスケーリングを行うことが可能です。
デフォルトでは「1コンテナインスタンスあたり100同時リクエスト」をしきい値に「1 - 25 インスタンスの間でインスタンス数が変動」します。

同時リクエストのしきい値やインスタンス数はカスタマイズできますが、同時リクエスト数以外の指標を使うことはできません。

2. 自動デプロイ

ECRに新しいイメージがPushされたときおよびGitHubの特定ブランチへPushされた際にアプリケーションの自動デプロイを行うことができます。
これによりCodeシリーズ等と連携してCI/CDを行うことができます。

ちなみにデプロイ方式を特定することはできませんでした...ローリングアップグレードな気がしますが違うかもしれません。
デプロイ方式に関して、先日参加したイベントによればBlue/Greenデプロイとのことでした。
新バージョンのデプロイが完了しヘルスチェックに成功した時点でトラフィックを切り替えているそうです。
ECSの様に再ルーティングに関する設定は無く完全にApp Runnerにおまかせする形です。

3. カスタムドメイン

App Runnerのサービスはデフォルトで[ランダムID].[リージョン名].awsapprunner.comというドメイン名が割り当てられます。
ユーザー保有の独自ドメインを紐づけACMをつかったサーバー証明書の自動発行を行うことができます。

App Runnerでできないこと (2022年3月時点)

逆に本日時点でできないことは以下となります。

1. 複数イメージ管理

App Runnerは単一コンテナイメージを使い単一のサービスを作るものです。
ECSタスクの様に複数のコンテナイメージを組み合わせて利用するといった事は出来ません。

サービスの根幹に関わる部分ですのでこれが変わることは無いでしょう。
複数イメージを利用するほどサービスが巨大化・複雑化する場合はApp RunnerでなくECSを使うべきです。

2. AWS WAF連携

App Runnerのサービスに直接AWS WAFを紐づけることはできません。
代わりにApp Runnerの前段にCloudFrontを立てWAFを紐づけることであれば可能ですが、App Runnerの接続元をCloudFrontに制限することはできないので迂回路が残る形となります。

3. シークレット管理

ECSの様にサービス組み込みでSecrets Managerと連携する機能はありません。
App RunnerでSecrets Manager(およびその他シークレット管理機能)を扱いたい場合はアプリケーション内で自前実装する必要があります。

補足 : 開発ロードマップ

ちなみにGitHub上でロードマップが公開されており、現在できないことに対する機能リクエストやその対応状況を確認することができます。

気になる点が出た場合はこちらを参照すると良いでしょう。
本記事ではこれ以上触れませんが個人的にはこの辺のIssueが気になっています。

待望のVPCサポート

最初に示した様にApp Runnerの基本構成にVPCは一切登場しません。

この点がデータストアを考える際に大きな制約になり、たとえばDynamoDBの様なVPCに依存しないサービスやPublicに公開したRDS(および類似の公開RDBMSサービス)を選ばざるを得ない状況がしばらく続いていました。

App RunnerからVPCリソースへのアクセスはサービス提供開始当初からリクエストされていた機能であり、つい先月この機能が提供されました。

現在はApp Runnerに新しく「VPC Connector」というリソースを追加可能になり、指定のサブネットにENIを生やす形でVPCとApp Runnerサービスを紐づけVPC内のPrivateなリソースにアクセスできる様になります。

そしてVPC Connectorを設定した場合、すべてのアウトバウンド通信 *1はVPC Connectorから行われる様になります。
このためインターネットアクセスしたい場合はInternet GatewayやNAT Gatewayといったリソース *2が必要となります。

この機能の詳細については以下のAWS Blogが参考になりますので併せてご覧ください。

試してみた

それでは実際にApp Runnerを試していきます。
今回は私の検証用アカウントの東京リージョンで検証していきます。

0. 検証アプリケーション

今回動作確認のために簡単なアプリケーションを用意しました。

こちらはFlask + gunicornを使ったPythonアプリケーションでDynamoDBやRDS(for PostgreSQL)への接続確認もできる様にしています。

(ローカルデバッグ時のスクリーンショット)

このアプリケーションをPrivateなECRリポジトリに登録してApp Runnerを構築してきます。

1. ECRリポジトリへのイメージ登録

最初にアプリケーションをECRリポジトリに登録します。

ソースコード一式をダウンロードし、Dockerfileのあるディレクトリに移動しdocker buildコマンドでイメージを作成します。

# PowerShell 7.2.1 環境で実施

# Dockerイメージの作成
cd <Dockerfileのあるディレクトリ>
docker build -t my-flask-app .

エラー無くコマンドが終了しmy-flask-appという名前のイメージができていればOKです。

次にECRリポジトリを新規に作成しこのイメージをPushします。
ここは今回の本題ではないのでAWS CLIでサクッとやってしまいます。

# AWS CLI on PowerShell 7.2.1 環境で実施

# ECRリポジトリの作成
$env:AWS_PROFILE="<your AWS profile>"
aws ecr create-repository --repository-name my-flask-app

# 作成済みDockerイメージをPush
#  * 自身のアカウントIDとリージョン名を取得
$accountId = aws sts get-caller-identity --query "Account" --output text
$region = aws ec2 describe-availability-zones --output text --query 'AvailabilityZones[0].[RegionName]'
#  * docker login から docker push まで
aws ecr get-login-password --region $region | docker login --username AWS --password-stdin "$accountId.dkr.ecr.$region.amazonaws.com"
docker tag my-flask-app:latest "$accountId.dkr.ecr.$region.amazonaws.com/my-flask-app:latest"
docker push "$accountId.dkr.ecr.$region.amazonaws.com/my-flask-app:latest"

最終的に下図の様にECRリポジトリmy-flask-applatestタグのついたイメージが登録されていればOKです。

2. App Runner Serviceの登録

次にApp Runnerの管理画面に移動し「App Runnerサービスを作成」します。

サービス作成ウィザードが開始されるので必要な設定を記載してきます。

「ソース」は先ほど作成したECRリポジトリのURIを指定します。
そして「デプロイ設定」は手動デプロイ、自動デプロイのどちらでも構いませんが今回は手動にしておきます。
今回はPrivateなECRリポジトリをソースにしているためApp Runnerのサービスがリポジトリにアクセスするための権限(サービスロール)を新規作成する様にしておき、「次へ」をクリックします。

続けてサービス設定を行います。
サービス名はmy-first-app-runnerとしておきます。 今回のアプリケーション(Dockerイメージ)はTCP 5000番ポートを公開する設定となっているので「ポート」欄は5000に変えます。

オートスケーリングおよびヘルスチェックの設定はデフォルトのままにしておきます。

セキュリティ設定もデフォルトにしておきます。
ちなみに、この「インスタンスロール」はApp RunnerのコンテナインスタンスがAWSサービスにアクセスする場合の権限を付与するものとなります。

ネットワーク設定は「パブリックアクセス」にします。
前述のVPC Connectorを使う場合は「カスタムVPC」を選びます。

「次へ」をクリックすると確認画面に遷移するので、内容に間違いが無いことを確認し「作成とデプロイ」をクリックします。

App Runnerサービスの作成が開始されるのでしばらく待ちます。

サービスの作成が完了するとステータスが「Running」になりサイトにアクセス可能になります。

今回はhttps://meiyy25y7i.ap-northeast-1.awsapprunner.com/というデフォルトURLが与えられ、アクセスするといい感じにサイトが表示されます。

これでサービス作成は完了です。

補足1 : DynamoDBにアクセスしてみる

ここまでで最低限App Runnerサービスを構築することができましたが、補足としてDynamoDBからデータを取得するための設定も施してみます。

今回のサンプルアプリケーションではDyanmoDBのEmployeeテーブルからデータを取得するコードが含まれているのですが、ここまでの手順だけだとApp RunnerのコンテナインスタンスのDynamoDBに対するアクセス権限を持っていないため実行時エラーとなります。

このため専用のIAMロールを作り「インスタンスロール」に割り当ててやる必要があります。

今回IAMロールはmy-flask-app-task-roleという名前でAWS CLIからサクッと作っておきます。

# AWS CLI on PowerShell 7.2.1 環境で実施
$accountId = aws sts get-caller-identity --query "Account" --output text
$region = aws ec2 describe-availability-zones --output text --query 'AvailabilityZones[0].[RegionName]'

# App Runnerのコンテナインスタンス(tasks.apprunner.amazonaws.com)に割り当てるIAMロールを作成
$policyDocument = @"
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "tasks.apprunner.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
"@ -replace '"','\"'
aws iam create-role --role-name 'my-flask-app-task-role' --assume-role-policy-document $policyDocument

# DynamoDB(Employeeテーブル)への読み取り権限をもつインラインポリシーを作ってアタッチ
$inlinePolicy = @"
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DynamoDB",
      "Effect": "Allow",
      "Action": [
        "dynamodb:BatchGetItem",
        "dynamodb:GetShardIterator",
        "dynamodb:GetItem",
        "dynamodb:Scan",
        "dynamodb:Query",
        "dynamodb:GetRecords"
      ],
      "Resource": "arn:aws:dynamodb:${region}:${accountId}:table/Employee"
    }
  ]
}
"@ -replace '"','\"'
aws iam put-role-policy --role-name 'my-flask-app-task-role' --policy-name 'my-flask-app-task-policy' --policy-document $inlinePolicy

できあがったIAMロールはこんな感じ。

そしてこのロールを「設定」から「インスタンスロール」に割り当てサービスを更新します。

併せてDyanamoDB側にデータ投入しておきます。
(NoSQL WorkBenchの従業員データモデルのサンプルテーブルを用意してください)

これでApp RunnerサービスからDynamoDBのデータが取得できます。

ちなみに当該部分のソースコードはこんな感じです。

import os
import boto3

def get_dynamodb_employees():
    """
    DynamoDBからサンプルデータを取得
    """
    dynamodb = boto3.resource('dynamodb', region_name=os.getenv('AWS_DEFAULT_REGION', 'ap-northeast-1'))
    # NoSQL WorkBenchのサンプルデータを取得
    #  * https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/workbench.SampleModels.html#workbench.SampleModels.EmployeeDataModel
    table = dynamodb.Table('Employee')
    return table.scan()['Items']

補足2 : RDS for PostgreSQLにアクセスしてみる

最後にVPC Connectorを有効にしてVPC内部に用意したRDS for PostgreSQLからデータ取得してみます。

サービスの「設定」から「ネットワーキング」を選びVPCコネクタの新規作成を行います。

追加ダイアログが表示されるので接続したいVPCとENIを生やしたいサブネットとENIにアタッチするセキュリティグループ(要事前作成)を選び「追加」をクリックします。

追加すると紐づけ可能になるので選択しサービスを更新してください。

VPC Connectorを作成すると下図の様にVPC内にENIが生えApp Runnerからのアウトバウンド通信がここから行われる様になります。

(今回の場合は10.0.21.15010.0.22.146がENIのIP)

あとはRDS環境を用意しApp Runnerに接続情報を設定(このアプリケーションではPOSTGRES_URL環境変数で設定)してやればいい感じにRDSのデータを取得してくれます。

(RDSへの接続情報POSTGRES_URL環境変数を追加)

接続結果はこんな感じで表示されます。

図の一番最後の行がアプリケーションのセッションなのですが、接続元IP(client_addr)を見ると10.0.22.146とENIのIPになっていることが見て取れますね。

そして当該部分のソースコードはこんな感じです。

import os
import psycopg2
import psycopg2.extras

def get_postgres_connection():
    """
    RDS for PostgreSQLへ接続
    """
    return psycopg2.connect(os.getenv('POSTGRES_URL', ''))


def get_pg_stat_activity():
    """
    RDS for PostgreSQLのデータを取得
    """
    with get_postgres_connection() as connection:
        with connection.cursor(cursor_factory=psycopg2.extras.DictCursor) as cursor:
            # pg_stat_activity の内容を取得
            cursor.execute(
                r"""SELECT pid, backend_type, datname, application_name, client_addr, client_port, state
                      FROM pg_stat_activity
                     ORDER BY pid
                """)
            return cursor.fetchall()

終わりに

以上、『AWS 再入門ブログリレー 2022』の23日目のエントリ『AWS App Runner』編でした。
次回、3/8(火)は弊社青柳による「Amazon FSx for Windows File Server編」の予定です!

脚注

  1. 厳密にはECRからのイメージPullおよびCloudWatch Logsのログ出力以外すべてのアウトバウンド通信
  2. とENIのあるサブネットのルートテーブル設定