TL;DR
- App RunnerでPHPが使えるようになったらしい
- 触った事ないので触ってみた
- どの場面で使うの?となった
はじめに
昨今お手軽にWEBアプリを動かす環境が増えて来ました。
一昔前はどんな小さなアプリでもある程度ポチポチカタカタsshなんてしていたものです。
便利になったものだ。
便利 = 面倒な事を抽象化してユーザに意識させない
という事なんですかね?
知らなくてもできる。
便利と怖さはセットなようです。
とりあえず、
「え?まだEC2とか使ってんの? んなもんRunner一発でエルボーっしょ」
って言いたいがためにApp Runner触ってみた話です.
やること
- symfonyで簡単なWebAppを作る
- DockerImageにする
- App Runnerに必要な一式をcdkで作る
- 動かしてみる
My環境
- PC: MAC Apple M1
- Node: 18.1.0
- PHP: 8.1
App Runner is 何
わからないのでITに詳しい私の親友に連絡してみます。
よしこの顔になったところで自分で調べます。最後に頼れるのは自分のみです。
https://aws.amazon.com/jp/apprunner/
AWS App Runner は、コンテナ化されたウェブアプリケーションや API を開発者が簡単かつ迅速にデプロイできるフルマネージド型サービスです。大規模に、しかも事前のインフラ経験を必要とせずにデプロイすることができます。ソースコードからでも、コンテナイメージからでも始められます。
あー、なるほどなるほど。(5%程理解したの意)
まあ色々みてみると、要はポイッとコードかイメージをpushすると、エイッとビルドしてデプロイしてくれるみたいです。
ざっくり構成図にしてみるとこんな感じです
App Runnerの知識も十分得た所で次に進みます。
・・。
このままじゃウチの社長にエルボーくらいそうなので、もう少し、得た知識を書きます。
特徴はざっくり以下です。
単一のイメージをスケールさせて起動できる
App Runnerは単一のイメージをスケールさせて稼働できるようです。
Appの元ネタは、github
or ECR
の2択です。
githubなら指定リポジトリの特定ブランチに更新が入るとビルド+デプロイが走る感じですね。
ECRならdockerImageの更新がトリガーです。今回は後者を使います。
スケーリング簡単に設定できる
同時リクエスト数、というメトリクスのみでオートスケールさせる事が可能なようです。
あくまで単一イメージなので、複数コンテナで1つのサービスを提供する形の場合は、ECSとか使わねばなりません。
ネットワーク周りを勝手に作ってくれる
オートスケールを簡単にしてくれるという事は、、、そうALBやVPCもよしなに作ってくれます。
隠蔽する形で裏っかわで作ってくれてる訳ですね。VPCとALB。
コンテナがスケールした時にそちらをよしなに、えいや、してくれてる訳です。
発展途上のサービスなので、開始直後はVPCにアクセスできなかった(←これほんとに何に使うんだよ)らしいです。
いまはVPCコネクタを用意して、既存のVPC内のサービス(RDSとか)にアクセスできるようになりました。
モロち..もちろん、SGも適用できます。
CD簡単に実現
デプロイ、という話をしましたが、コードorイメージをpushした時勝手にデプロイしてくれます。
App Runnerは実際は裏でfargateが動いてるみたいなので、結局codebuild&deployが走ってるわけですね。ALB+VPCと同じく、隠蔽(抽象化?)してくれているわけです。
改めて概念図
先程の図を改めます。
ヒゲのおじさんが隠してた部分はこんな感じという事です。
ではさっそくどれほどのものか、さわってみる事にします。
symfonyで簡単なWebAppを作る
まずはディレクトリ作ります.
mkdir sample-app-runner; cd $_
symfony new app --version="6.1.*" --webapp
cd app
symfony server:start -d
open -a "google chrome" https://127.0.0.1:8000
ok. 立ち上がりましたね。
テキトーなhtmlを当て込みます。
今回は ↓↓ から拝借した tpl_089.zip
のhtmlをあてます
https://f-tpl.com/category/HTML%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88/
unzip ~/Downloads/tpl_089.zip
mv ./tpl_089/index.html {path_to_sample-app-runner}/templates/index.html.twig
mv ./tpl_089/js {path_to_sample-app-runner}/public
mv ./tpl_089/css {path_to_sample-app-runner}/public
mv ./tpl_089/images {path_to_sample-app-runner}/public
次にコントローラーの雛形を作ります。
symfony console make:controller IndexController
> created: src/Controller/IndexController.php
> created: templates/index/index.html.twig ← こいつは不要ので消す
ちょいと修正します。
--- a/src/Controller/IndexController.php
+++ b/src/Controller/IndexController.php
class IndexController extends AbstractController
{
- #[Route('/index', name: 'app_index')]
+ #[Route('/', name: 'index')]
public function index(): Response
{
- return $this->render('index/index.html.twig', [
- 'controller_name' => 'IndexController',
- ]);
+ return $this->render('index.html.twig');
}
}
リロードすると。
はい、とりあえずWebApp(とは名ばかりの静的サイト)完成。
DockerImageにする
こいつを載せるイメージ作りましょう。
cd {path_to_sample-app-runner}
mkdir docker; cd $_
まずは、Dockerfile。
今回はPHP8.1 + apache のイメージを使います。
何もinstallするものがないので、ファイル配置してcomposer叩いてるだけです。
リポジトリルートからビルドする前提で書いてます。
# リポジトリルートからビルドする事!!
FROM php:8.1-apache
# confの配置
COPY ./docker/phpConf/php.ini /usr/local/etc/php/
COPY ./docker/apacheConf/000-default.conf /etc/apache2/sites-enabled/000-default.conf
# appの配置
COPY ./app /var/www/project
WORKDIR /var/www/project
# composer 持ってきて叩く
COPY --from=composer:2.3.5 /usr/bin/composer /usr/bin/composer
ENV COMPOSER_ALLOW_SUPERUSER 1
ENV COMPOSER_NO_INTERACTION 1
RUN composer install --no-dev --no-scripts --no-suggest --no-interaction
次にphpのconf。
タイムゾーンの変更だけです
[date]
date.timezone = "Asia/Tokyo"
そしてapache。
symfonyのDocに記載のやつをそのまま使ってます。
逆言うとこれに合わせてファイル配置しています。
VirtualHost切ってますが、 000-
と最初にロードされるようにする + servernameを指定しない事で、
どのdomainでアクセスしてもこのディレクティブに到達するようにしてます。
<VirtualHost *:80>
DocumentRoot /var/www/project/public
DirectoryIndex /index.php
<Directory /var/www/project/public>
AllowOverride None
Order Allow,Deny
Allow from All
FallbackResource /index.php
</Directory>
# uncomment the following lines if you install assets as symlinks
# or run into problems when compiling LESS/Sass/CoffeeScript assets
# <Directory /var/www/project>
# Options FollowSymlinks
# </Directory>
# optionally disable the fallback resource for the asset directories
# which will allow Apache to return a 404 error when files are
# not found instead of passing the request to Symfony
<Directory /var/www/project/public/bundles>
DirectoryIndex disabled
FallbackResource disabled
</Directory>
ErrorLog /var/log/apache2/project_error.log
CustomLog /var/log/apache2/project_access.log combined
# optionally set the value of the environment variables used in the application
#SetEnv APP_ENV prod
#SetEnv APP_SECRET <app-secret-id>
#SetEnv DATABASE_URL "mysql://db_user:db_pass@host:3306/db_name"
</VirtualHost>
ローカルで起動する
起動してみましょう。
appと同改装にdockerディレクトリを作ったので、リポジトリのルートからDockerfileを指定してビルドします。
(docker配下にapp持ってくればそんな事しなくてよかったのですが)
cd {path_to_sample-app-runner}
docker build -t sample-app-runner -f docker/Dockerfile .
docker run --publish 8001:80 -d --name sample-app-runner-test sample-app-runner
8001ポートにリンクしました。ブラウザで http://127.0.0.1:80001
を開いてみましょう。
okok,これでイメージは作成完了です。
今ディレクトリ構成はこんな感じ
sample-app-runner
├─ app (symfonyApp)
└─ docker
├─ phpConf
│ └─ php.ini
├─ apacheConf
│ └─ 000-default.conf
└─ Dockerfile
App Runnerに必要な一式をcdkで作る
せっかくなのでワンポチで環境つくれるようにcdkでApp Runner立ち上げます。
今回はtypescriptを使う事にします。
cd {path_to_sample-app-runner}
mkdir cdk; cd $_
npx cdk init --language typescript
入りました
> tree -L 1
.
├── README.md
├── bin
├── cdk.json
├── jest.config.js
├── lib
├── node_modules
├── package-lock.json
├── package.json
├── test
└── tsconfig.json
まずはイメージをpushする先、ECRを作成します。
作るのは2つだけ。
- ECR
- App Runner 本体
です。
本当はECRのリポジトリをfetchできるRoleをApp Runner用に作成してあげなければなりませんが、
ここは、CDK。勝手に作ってくれます。
--- a/cdk/lib/cdk-stack.ts
+++ b/cdk/lib/cdk-stack.ts
-import * as cdk from 'aws-cdk-lib';
+import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
+import * as Ecr from 'aws-cdk-lib/aws-ecr';
+import * as AppRunner from '@aws-cdk/aws-apprunner-alpha';
-export class ExampleAppRunnerStack extends cdk.Stack {
- constructor(scope: Construct, id: string, props?: cdk.StackProps) {
+export class ExampleAppRunnerStack extends Stack {
+ constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// ECRの作成
- const repository = new cdk.aws_ecr.Repository(this, 'ExampleAppRunnerRepository');
+ const Repository = new Ecr.Repository(this, 'ExampleAppRunnerRepository', {
+ repositoryName: 'app-runner'
+ })
+
+ // AppRunnerの作成
+ new AppRunner.Service(this, 'ExampleAppRunner', {
+ source: AppRunner.Source.fromEcr({
+ repository: Repository,
+ imageConfiguration: {
+ port: 80
+ }
+ })
+ })
}
}
初回であればbootstrap叩いて、
npx cdk bootstrap
さあ準備は整いました。
いざゆかん。
エンターを押す指を高らかに掲げ、
大きな声でせ〜の、
「よろしくおねがいしまーす!」 ッターン!!
npx cdk deploy
↓↓
クッそエラーでました。
ちょっと見てみると、
App Runnerが初回起動してECRからimageをpullしようとする → imgaeまだない → 待つ
みたいな感じでぐるぐるした後 CFnが根をあげて
Resource handler returned message: "null" (RequestToken: xxxxxxx, HandlerErrorCode: null)
となっている模様。
うーんなんか腑に落ちませんが、
ECR作ってイメージを用意してから、AppRunnerをデプロイしてみれば解決するように見える。
という事でスタックをわけます。
import {Stack, StackProps} from 'aws-cdk-lib';
import {Construct} from 'constructs';
import * as Ecr from 'aws-cdk-lib/aws-ecr';
export class EcrStack extends Stack {
public readonly repository: Ecr.Repository;
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// ECRの作成
this.repository = new Ecr.Repository(this, 'ExampleAppRunnerRepository', {
repositoryName: 'app-runner'
});
}
}
import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as AppRunner from '@aws-cdk/aws-apprunner-alpha';
import * as Ecr from 'aws-cdk-lib/aws-ecr';
interface AppRunnerStackProps extends StackProps {
repository: Ecr.Repository;
}
export class ExampleAppRunnerStack extends Stack {
constructor(scope: Construct, id: string, props: AppRunnerStackProps) {
super(scope, id, props);
// AppRunnerの作成
new AppRunner.Service(this, 'ExampleAppRunner', {
source: AppRunner.Source.fromEcr({
repository: props.repository,
imageConfiguration: {
port: 80
}
})
})
}
}
repositoryは専用のinterfaceを用意してコンストラクタで渡します。
ちなみにメインはこう
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { ExampleAppRunnerStack } from '../lib/apprunner-stack';
import {EcrStack} from "../lib/ecr-stack";
const app = new cdk.App();
const ecrStack = new EcrStack(app, 'EcrStack', {});
new ExampleAppRunnerStack(app, 'ExampleAppRunnerStack', {
repository: ecrStack.repository
});
もはやスタック管理する意味が皆無な形となりました。
分けたので、ECRだけ先に作成
npx cdk deploy EcrStack
いざイメージをpushします。
# ECRログイン
AWS_ACCOUNT_ID=$(aws sts get-caller-identity | jq -r .Account)
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com
# ビルド
docker build --platform amd64 -t app-runner -f docker/Dockerfile .
# タグづけ
docker tag app-runner:latest ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/app-runner:latest
# push
docker push ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/app-runner:latest
次にAppRunnerを作ります。
npx cdk deploy ExampleAppRunnerStack
ふむふむ、今度は行けそう。
いけました。
やはり色々リソース作ってるので遅い。
さて出来上がったのでブラウザでアクセスしてみます。
よし見える。
デプロイを試してみましょう。
トップの美女の画像をエガちゃんに変えます。
先程同様。ビルドし、pushします。
## 略 ##
docker push ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/app-runner:latest
しばらくするとAppRunnerが Operation in progress
に変わります。
5~6分経過して、Runningに変わりました。
モロち..もちろん、その間WebAppにはアクセス可能です。
どうやら、ローリングアップデートではなく、ブルーグリーンデプロイされるのだそう。
今回はタスク1つなので関係ないですが。
そしてブラウザをリロード
いい位置に文字が来てくれました。
無事にエガちゃんが見れた所で終了です。
さいごに
振り返ってみますが、まず料金。
コンテナ起動時間で課金され、 0.064 USD/vCPU 時
だそうです。
また、起動最小数として確保しておくメモリにも課金され 0.007 USD/GB 時
だそうです。
あと一応ビルドも分単位で料金がかかり 0.005 USD/ビルド時間 (分)
との事。
例でいくと 80リクエストさばけるような 1 vCPU、2 GB を 通常1インスタンスで構えていたとすると、
大体 月25.50USD くらいなわけです。
はい、そして大事な所、cloudfront & waf はさせません。 (2022/11/1現在)
静的サイトはS3+cloudfrontでいいよな。。
ちょっとしたお問い合わせとかもさばきたいなら、LightsailにGitflowとかが最安値を叩き出す気がします。
今このAppRunnerがドハマリする要件って何.....?
どの場面で使うの..? となった所でおしまいです。
参考