13
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWS App Runnerで簡単にコンテナアプリを実装する

Last updated at Posted at 2025-11-13

本記事はBIPROGY /ユニアデックス社内AWSコミュニティ「BIPROGY AWS Ambassador」の定期投稿企画第7回目の記事です。他の定期投稿企画の記事は、#BIPROGY_AWS_Ambassador タグまたは Organizationページをご覧ください。

はじめに

AWSでコンテナと言えばECSやEKSを普段利用していますが、先日AWS App Runnerというサービスに触れる機会がありました。
本記事では、このサービスを使ったコンテナアプリケーション構築の方法を備忘も兼ねて紹介します。

AWS App Runnerとは

App Runnerを一言で表すと、「サーバレスでWebアプリケーションを自動デプロイし実行できるマネージドサービス」です。

主な特徴

  • サーバレス
    • ユーザはインフラの考慮が不要
    • VPC、サーバ、LB等は内部で稼働し、ユーザからは見えない
  • 自動スケーリング
    • トラフィックに応じた自動スケール
    • 閾値はカスタマイズ可能
  • 自動デプロイ
    • コードリポジトリ、あるいはECR(コンテナレジストリ)と連携
    • 新たなコードやイメージがプッシュされると自動でビルド・デプロイ

注意点・制約事項

一方で、以下に注意する必要があります。

  • 単一コンテナのみ対応
    • 複数コンテナの同時実行は不可
    • ECS・EKSなどと性質が全く異なる
  • 永続ストレージ非対応
    • 実行中は一時ストレージにデータを保存
    • コンテナ終了時にデータは消失
    • データの永続化には外部DBやストレージが必要

適用シーン

以上の特性から、例えば以下のようなユースケースに適しています。

  • 小規模なWebアプリケーションの公開
  • 外部アプリケーションのバックエンドAPIの実装
  • 開発中コンテナの検証

逆に複数のコンテナが連携するワークロードや、詳細なコンテナオーケストレーションが必要な環境では、ECSやEKSの方が適しています。

料金体系

料金はコンテナを実行するホストのリソース(vCPU、メモリ)の利用量に応じて課金される形式です。
アプリケーションがアイドル状態の場合と、アクティブ状態の場合とで料金の計算方法が異なり、それぞれメモリの使用量、vCPUの使用量+メモリの使用量で計算されます。詳細は公式ドキュメントをご覧ください。

コンテナアプリを実装してみる

実際にアプリケーションをApp Runner上にデプロイし、動作を確認します。

0. 事前準備

必要なAWSサービスを構築する前にアプリケーションとDockerfileを作成しておきます。
また本記事では手軽さの確認も踏まえ、IaCではなくマネジメントコンソールからAWSサービスを構築します。

0-1. アプリケーション作成

簡単なWeb掲示板を実装しました。外部DBやストレージを持たないシンプルな作りで、HTMLとCSSで画面を表示します。コード部分はほとんどGithub Copilotに作成してもらいました。

(参考)アプリケーションのコードは以下です。後述する自動デプロイの動作確認のためコードを一部変更しましたが、こちらは変更後のコードになります。

app.py
from flask import Flask, render_template, request, redirect, url_for
import sqlite3
import os

app = Flask(__name__)
DATABASE = 'bbs.db'

# DB初期化
if not os.path.exists(DATABASE):
    conn = sqlite3.connect(DATABASE)
    c = conn.cursor()
    c.execute('''CREATE TABLE threads (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL
    )''')
    c.execute('''CREATE TABLE posts (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        thread_id INTEGER,
        handle TEXT NOT NULL,
        content TEXT NOT NULL,
        created_at TEXT NOT NULL,
        FOREIGN KEY(thread_id) REFERENCES threads(id)
    )''')
    conn.commit()
    conn.close()

# トップページ: スレッド一覧
@app.route('/')
def index():
    conn = sqlite3.connect(DATABASE)
    c = conn.cursor()
    c.execute('''SELECT t.id, t.name, COUNT(p.id) FROM threads t LEFT JOIN posts p ON t.id = p.thread_id GROUP BY t.id''')
    threads = c.fetchall()
    conn.close()
    return render_template('index.html', threads=threads)

# スレッド作成
@app.route('/create', methods=['POST'])
def create_thread():
    name = request.form['name']
    if name:
        conn = sqlite3.connect(DATABASE)
        c = conn.cursor()
        c.execute('INSERT INTO threads (name) VALUES (?)', (name,))
        conn.commit()
        conn.close()
    return redirect(url_for('index'))

# スレッド詳細・書き込み一覧
@app.route('/thread/<int:thread_id>')
def thread(thread_id):
    conn = sqlite3.connect(DATABASE)
    c = conn.cursor()
    c.execute('SELECT name FROM threads WHERE id=?', (thread_id,))
    thread = c.fetchone()
    c.execute('SELECT id, handle, content, created_at FROM posts WHERE thread_id=? ORDER BY id ASC', (thread_id,))
    posts = c.fetchall()
    conn.close()
    return render_template('thread.html', thread=thread, posts=posts, thread_id=thread_id)

# 書き込み
@app.route('/thread/<int:thread_id>/post', methods=['POST'])
def post(thread_id):
    import datetime
    content = request.form['content']
    handle = request.form.get('handle', '').strip()
    if not handle:
        handle = '名無し'
    if content:
        conn = sqlite3.connect(DATABASE)
        c = conn.cursor()
        now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        c.execute('INSERT INTO posts (thread_id, handle, content, created_at) VALUES (?, ?, ?, ?)', (thread_id, handle, content, now))
        conn.commit()
        conn.close()
    return redirect(url_for('thread', thread_id=thread_id))

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080, debug=True)

0-2. Dockerfile作成

アプリケーションをDockerコンテナ化するため、Dockerfileを作成します。こちらもGithub Copilotに作成してもらいました。最近の生成系AIはかなり便利です。

(参考)Dockerfileは以下です。

dockerfile
# ベースイメージ
FROM python:3.11-slim

WORKDIR /app

# 必要パッケージインストール
COPY app.py ./
COPY templates ./templates
COPY static ./static
RUN pip install flask

EXPOSE 8080

CMD ["python", "app.py"]

1. ECR(リポジトリ)作成

コンテナイメージを格納するリポジトリを作成します。
特に注意するべきパラメータはありません。

スクリーンショット (383).png

2. コンテナイメージのビルド、プッシュ

ローカルで以下のコマンドを実行し、コンテナイメージをビルド、ECRへのプッシュを行います。

# イメージのビルド
docker build -t apprunner_test .

# ECRログイン
aws ecr get-login-password --region us-east-1 --profile <profile> | docker login --username AWS --password-stdin <accountid>.dkr.ecr.us-east-1.amazonaws.com

# タグ付与
docker tag apprunner_test:latest <accountid>.dkr.ecr.us-east-1.amazonaws.com/ynag_apprunner_ecr:latest

# ECRにプッシュ
docker push <accountid>.dkr.ecr.us-east-1.amazonaws.com/ynag_apprunner_ecr:latest

先ほど作成したリポジトリにイメージを格納できました。

スクリーンショット (385).png

3. App Runner構築

次にいよいよApp Runnerの構築を行います。

3-1. ソースおよびデプロイ

スタートページから「App Runnerサービスの作成」をクリックすると、まずはソースの指定とデプロイに関する設定を行います。

スクリーンショット (387).png

ここではコンテナイメージをECRに格納しているので、ソースにはECRを選択し、先ほどプッシュしたイメージのURIを指定します。
また、デプロイトリガーは自動を選択します。これにより、App Runnerは最初の構築時と上で指定したコンテナイメージの更新時に自動でデプロイを行います。

3-2. サービスを設定

次のページに進むとサービスの設定画面に移ります。
App Runnerではアプリケーションを「サービス」という単位で実行します。こちらでは画像の通りサービスを立ち上げるためにアプリケーションの稼働に関する様々な設定を行います。

  • サービス設定
    使用するリソース、公開するポートの設定などを行います。
    スクリーンショット (438)_01.png

  • Auto Scaling
    自動スケールに関する設定を行います。
    スクリーンショット (388)_01.png

  • ヘルスチェック
    ヘルスチェックの実行に関する設定を行います。
    スクリーンショット (389)_01.png

  • セキュリティ
    インスタンスロールやKMSキーなどの設定を行います。実行するアプリケーションにロールを付与する場合はここで指定します。
    スクリーンショット (390)_01.png

  • ネットワーキング
    公開方法(パブリック/プライベート)の設定、アウトバウンド通信方法の設定を行います。
    スクリーンショット (391)_01.png

  • 可観測性
    トレースデータの可視化を行うX-Rayとの統合設定を行います。
    スクリーンショット (439)_01.png

その他、タグの設定が可能です。
本構築ではサービス名とポートのみ変更し、その他はデフォルト設定としました。

3-3. 確認および作成

次のページに進み、設定内容を確認します。
「作成とデプロイ」をクリックするとApp Runnerの構築とアプリケーションのデプロイが開始します。

デプロイが開始されると以下のようにサービスの画面に遷移します。
タイミングにもよるかと思いますがデプロイには数分かかります。デプロイの進捗は画面下にあるデプロイログから確認できます。

スクリーンショット (392).png

4. 動作確認

デプロイが完了し、アプリケーションにアクセスすると正常に動作していることが確認できました。掲示板にスレッドを立てて書き込みを行っています。

スクリーンショット (395).png

マネジメントコンソールでイベントログやアプリケーションログを確認可能です。何かあった際にはこれらを参考にデバッグが行えます。

また、App Runnerは「アクション」メニューからアプリケーションの一時停止が可能です。停止中はアプリケーションへのアクセスができなくなります。

スクリーンショット (393).png

スクリーンショット (396).png

以上で、App Runnerを構築して簡単にコンテナアプリを実装することができました。

5. 自動デプロイ確認

自動デプロイを行うよう設定したので、コンテナイメージを更新しその内容がアプリケーションに反映されることを確認します。

ソースコードを一部更新し、再度コンテナイメージのビルドとECRへのプッシュを行います。

スクリーンショット (404).png

少しすると、App Runnerが自動的にアプリケーションの再デプロイを開始します。

スクリーンショット (405).png

デプロイが完了し、アプリケーションにアクセスすると、更新内容が反映されていることを確認できます。
なお、本記事で構築したアプリケーションは外部ストレージを持たないため、再デプロイに伴いデータは初期化されています。

スクリーンショット (406).png

スクリーンショット (407).png

以上で自動デプロイの様子も確認することができました。
反映までには若干のタイムラグがありますが、手動でデプロイが不要な点は非常に便利です。

まとめ

本記事ではApp Runnerでコンテナアプリケーションの実装と動作確認を行いました。
デプロイまでの手順は単純で、手っ取り早く実装するにはうってつけのサービスだと思います。
またインフラ部分を考慮する必要がないところも開発者にとって嬉しいポイントでしょう。
ただ、外部DBやストレージを利用したい場合などは別途設計が必要なため、適用シーンによってはそこまで簡単ではないというのも事実です。
ECSやEKSに並ぶコンテナアプリケーション実装の選択肢の1つとして活用できればと考えています。

本記事ではアプリケーションをApp Runner内で完結させましたが、今後はVPCとの接続やRDS、S3との併用などについても検証する予定です。
最後までお読みいただきありがとうございました!

We Are Hiring!

13
6
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
13
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?