Help us understand the problem. What is going on with this article?

Brefを使って完全サーバレスな、Symfony + Vue.js + Aurora Serverless製TODOリストを作る

SymfonyとVue Routerを使ってTODOリストを作ってみました。せっかくなのでBrefを使って完全サーバレスにしてみました。

Brefとは

BrefとはAWS Lambda上でPHPを動作させるためのもろもろの設定を楽にしてくれるCLIです。Brefを利用するためには、AWSアカウント、aws-cliserverless frameworkが必要となります。

構成

構成.001.png

サービス 内容
Lambda PHPを実行します。Symfonyで実装しました
API Gateway 指定のURLでLambdaを実行します
S3 JSやCSSを配置します。Vueで実装しました
Cloudfront CDN、API GatewayとS3を同一ドメインとして扱います
Aurora Serverless DB、今回はRDSのAurora Serverlessを使います
Cloudformation プロビジョニング、デプロイ、serverlessが実行します
serverless デプロイツール、Brefが実行します
Bref 上記のサーバレスアプリケーションをビルド&デプロイします

BrefはLambda用のPHP Runtimeを提供しているので、これを利用してSymfonyで実装したアプリケーションを実行する関数を作成します。PHPからDBにアクセスするので、RDSのAurora Serverlessを用意します。Lambda関数にはAPI Gatewayを利用してURLを割り当てます。LambdaにはJSやCS、画像などを用意しても動作しないので、S3に配置します。このままではドメインが分かれてしまうので、Cloudfrontを利用してそれぞれ同一ドメインでアクセスできるようにします。

追加インストール

AWS CLIインストール

AWS CLIはOSによってインストール方法が異なります。詳しくはAWSのドキュメントをご確認ください。

AWS CLIをインストールしたら、利用できるように設定を行います。

aws configure
AWS Access Key ID [None]:アクセスキー
AWS Secret Access Key [None]: シークレットアクセスキー
Default region name [None]: 利用するリージョン # 例:ap-northeast-1
Default output format [None]: json

アクセスキー、シークレットアクセスキーはIAMでユーザ追加し、追加後に表示されたキーを設定します。これでAWS CLIは完了です。

serverlessインストール

つづいてserverlessをインストールします。

curl -o- -L https://slss.io/install | bash
serverless upgrade

# もしくは
npm install -g serverless

インストール後に、aws cliと同様に設定します。

serverless config credentials --provider aws --key アクセスキー --secret シークレットアクセスキー

AWS CLIと同じアカウントのアクセスキー、シークレットアクセスキーを設定します。

Brefインストール

Brefはcomposerでインストールします。

cd path/to/project
composer require bref/bref

Serverless AWS Pseudo Parameters インストール

Serverless AWS Pseudo ParametersはAWSの擬似パラメータをserverlessで容易に設定できるためのライブラリです。Cloudfrontの設定をserverlessで行うために利用します。

yarn add serverless-pseudo-parameters

設定

インストールが完了したら、インストールを行うための設定を行います。

vendor/bin/bref init

実行するとserverless.ymlがルートディレクトリに作成されます。
それをさくっと以下のようにします。

serverless.yml
service: todo

provider:
    name: aws
    region: ap-northeast-1
    runtime: provided
    environment:
        APP_ENV: prod

plugins:
    - ./vendor/bref/bref
    - serverless-pseudo-parameters

functions:
    console:
        handler: bin/console
        timeout: 120 # in seconds
        layers:
            - ${bref:layer.php-73} # PHP
            - ${bref:layer.console} # The "console" layer
        vpc:
            securityGroupIds:
                - sg-*****
            subnetIds:
                - subnet-*****1
                - subnet-*****2
                - subnet-*****3
    api:
        handler: public/index.php
        description: ''
        timeout: 28 # in seconds (API Gateway has a timeout of 29 seconds)
        layers:
            - ${bref:layer.php-73-fpm}
        events:
            -   http: 'ANY /'
            -   http: 'ANY /{proxy+}'
        vpc:
            securityGroupIds:
                - sg-*****
            subnetIds:
                - subnet-******1
                - subnet-******2
                - subnet-******3

# Exclude files from deployment
package:
    exclude:
        - 'node_modules/**'
        - 'tests/**'
        - '.env.local'

resources:
    Resources:
        # The S3 bucket that stores the assets
        Assets:
            Type: AWS::S3::Bucket
            Properties:
                BucketName: vue-router
                CorsConfiguration:
                    CorsRules:
                        -   AllowedHeaders: ["*"]
                            AllowedMethods: [GET]
                            AllowedOrigins: ["*"]
        # The policy that makes the bucket publicly readable
        AssetsBucketPolicy:
            Type: AWS::S3::BucketPolicy
            Properties:
                Bucket: !Ref Assets # References the bucket we defined above
                PolicyDocument:
                    Statement:
                        -   Effect: Allow
                            Principal: '*' # everyone
                            Action: 's3:GetObject' # to read
                            Resource: 'arn:aws:s3:::vue-router/*' # things in the bucket

        WebsiteCDN:
            Type: AWS::CloudFront::Distribution
            Properties:
                DistributionConfig:
                    Enabled: true
                    # Cheapest option by default (https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_DistributionConfig.html)
                    PriceClass: PriceClass_200
                    # Enable http2 transfer for better performances
                    HttpVersion: http2
                    # Origins are where CloudFront fetches content
                    Origins:
                        # The website (AWS Lambda)
                        -   Id: Website
                            DomainName: '#{ApiGatewayRestApi}.execute-api.#{AWS::Region}.amazonaws.com'
                            # This is the stage, if you are using another one (e.g. prod), you will need to change it here too
                            OriginPath: '/dev'
                            CustomOriginConfig:
                                OriginProtocolPolicy: 'https-only' # API Gateway only supports HTTPS
                        # The assets (S3)
                        -   Id: Assets
                            # Use s3-website URLs instead if you host a static website (https://stackoverflow.com/questions/15309113/amazon-cloudfront-doesnt-respect-my-s3-website-buckets-index-html-rules#15528757)
                            DomainName: '#{Assets}.s3.amazonaws.com'
                            CustomOriginConfig:
                                OriginProtocolPolicy: 'http-only' # S3 websites only support HTTP
                    # The default behavior is to send everything to AWS Lambda
                    DefaultCacheBehavior:
                        AllowedMethods: [GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE]
                        TargetOriginId: Website # the PHP application
                        # Disable caching for the PHP application https://aws.amazon.com/premiumsupport/knowledge-center/prevent-cloudfront-from-caching-files/
                        DefaultTTL: 0
                        MinTTL: 0
                        MaxTTL: 0
                        # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-forwardedvalues.html
                        ForwardedValues:
                            QueryString: true
                            Cookies:
                                Forward: all # Forward cookies to use them in PHP
                            # We must *not* forward the `Host` header else it messes up API Gateway
                            Headers:
                                - 'Accept'
                                - 'Accept-Language'
                                - 'Origin'
                                - 'Referer'
                        ViewerProtocolPolicy: redirect-to-https
                    CacheBehaviors:
                        # Assets will be served under the `/assets/` prefix
                        -   PathPattern: 'build/*'
                            TargetOriginId: Assets # the static files on S3
                            AllowedMethods: [GET, HEAD]
                            ForwardedValues:
                                # No need for all that with assets
                                QueryString: 'false'
                                Cookies:
                                    Forward: none
                            ViewerProtocolPolicy: redirect-to-https
                            Compress: true # Serve files with gzip for browsers that support it (https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/ServingCompressedFiles.html)
                    CustomErrorResponses:
                        # Do not cache HTTP errors
                        -   ErrorCode: 500
                            ErrorCachingMinTTL: 0
                        -   ErrorCode: 504
                            ErrorCachingMinTTL: 0

いくつかポイントに分けて解説します。

Console

Lambda上のSymfony Consoleを実行できるように設定します。

functions:
    console:
        handler: bin/console
        timeout: 120 # in seconds
        layers:
            - ${bref:layer.php-73} # PHP
            - ${bref:layer.console} # The "console" layer
        vpc:
            securityGroupIds:
                - sg-*****
            subnetIds:
                - subnet-*****1
                - subnet-*****2
                - subnet-*****3

handlerはbin/consoleとし、Symfony Consoleを実行するようにします。layerにconsoleレイヤーも設定します。また、AuroraにアクセスするためにVPCの設定を行っています。(後述)

API

API Gatewayを通じてLambda上のSymfonyを実行できるように設定します。

    api:
        handler: public/index.php
        description: ''
        timeout: 28 # in seconds (API Gateway has a timeout of 29 seconds)
        layers:
            - ${bref:layer.php-73-fpm}
        events:
            -   http: 'ANY /'
            -   http: 'ANY /{proxy+}'
        vpc:
            securityGroupIds:
                - sg-*****
            subnetIds:
                - subnet-******1
                - subnet-******2
                - subnet-******3

handlerにはpublic/index.phpを設定し、Webアプリケーションとして 実行させます。eventsは全てのURIで起動させるようにします。VPCはconsoleと同様の設定です。

Resource

S3とCloudfrontの設定です。

resources:
    Resources:
        # The S3 bucket that stores the assets
        Assets:
            Type: AWS::S3::Bucket
            Properties:
                BucketName: vue-router # S3のバケット名
                CorsConfiguration:
                    CorsRules:
                        -   AllowedHeaders: ["*"]
                            AllowedMethods: [GET]
                            AllowedOrigins: ["*"]
        # The policy that makes the bucket publicly readable
        AssetsBucketPolicy:
            Type: AWS::S3::BucketPolicy
            Properties:
                Bucket: !Ref Assets # References the bucket we defined above
                PolicyDocument:
                    Statement:
                        -   Effect: Allow
                            Principal: '*' # everyone
                            Action: 's3:GetObject' # to read
                            Resource: 'arn:aws:s3:::vue-router/*' # S3のバケット名

        WebsiteCDN:

#中略

                    CacheBehaviors:
                        # Assets will be served under the `/assets/` prefix
                        -   PathPattern: 'build/*' # 
                            TargetOriginId: Assets # the static files on S3
                            AllowedMethods: [GET, HEAD]
                            ForwardedValues:
                                # No need for all that with assets
                                QueryString: 'false'
                                Cookies:
                                    Forward: none
                            ViewerProtocolPolicy: redirect-to-https
                            Compress: true # Serve files with gzip for browsers that support it (https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/ServingCompressedFiles.html)
                    CustomErrorResponses:
                        # Do not cache HTTP errors
                        -   ErrorCode: 500
                            ErrorCachingMinTTL: 0
                        -   ErrorCode: 504
                            ErrorCachingMinTTL: 0

S3の指定のバケットをassetsとして利用するための設定です。S3のアクセス権や利用するパスのパターンを設定します。今回はbuildに全てのassetsがあるので、build/*をPath Patternに設定しました。

Aurora Serverless作成

設定が終わったらDBを作成します。AWS ConsoleからRDSにアクセスし、『新しくDBを作成する』をクリックし、DBを作ります。Aurora Serverlessにするには、画像の『サーバーレス』部分をクリックします。

Screen Shot 2020-03-06 at 15.35.00.png

DBを作成してしばらくすると、DB情報が表示されるようになります。DB情報内の、セキュリティグループ(sg-ではじまる文字列)とサブネット(subnet-ではじまる文字列)をserverless.ymlのvpc部分に設定します。

Screen Shot 2020-03-06 at 15.51.21.png

ひきつづき、DB情報内のエンドポイントとDB作成時に設定したユーザID,パスワードで.envにDBアクセス情報を作成します。

DATABASE_URL=mysql://[ユーザID]:[パスワード]@[エンドポイント]/[DB名]

なお、デフォルトだとAurora Serverlessは起動しっぱなしです。設定で『数分間アイドル状態のままの場合コンピューティング性能を一時停止する』を有効にすると、指定した時間アクセスがない場合は一時停止します。(ただし、指定した時間をすぎてアクセスすると起動に30秒くらいかかります。)

Symfony微調整

サーバーレスで利用するPHPレイヤーイメージには、ファイルの書き込みが/tmpのみ許されています。デフォルトだと、Symfonyはプロジェクト内のvarディレクトリにキャッシュやログを出力するので、この出力ディレクトリを変更するために、以下のメソッドを追記します。

src/Kernel.php
    public function getLogDir()
    {
        // When on the lambda only /tmp is writeable
        if (getenv('LAMBDA_TASK_ROOT') !== false) {
            return '/tmp/log/';
        }

        return parent::getLogDir();
    }

    public function getCacheDir()
    {
        // When on the lambda only /tmp is writeable
        if (getenv('LAMBDA_TASK_ROOT') !== false) {
            return '/tmp/cache/' . $this->environment;
        }

        return parent::getCacheDir();
    }

デプロイ

デプロイできる準備が整ったので、デプロイしていきます。

最適化&ビルド

PHPのパッケージを最適化した後、Vueのビルドを行います。

composer install --prefer-dist --optimize-autoloader --no-dev
yarn encore production

S3にアップロード

現在は、S3のみ手動でアップロードする必要があります。

aws s3 sync public/build s3://[バケット名]/build --delete

Symfonyではpublicがドキュメントルートになるので、S3にはpublic内のコンテンツをアップロードします。

デプロイ

serverless deploy
...中略...
Service Information
service: todo
stage: dev
region: ap-northeast-1
stack: todo-dev
resources: **
api keys:
  None
endpoints:
  ANY - https://******.execute-api.ap-northeast-1.amazonaws.com/dev
  ANY - https://******.execute-api.ap-northeast-1.amazonaws.com/dev/{proxy+}
functions:
  console: todo-dev-console # bref cliで利用するfunction名
  api: todo-dev-api
layers:
  None
Serverless: Removing old service artifacts from S3...
Serverless: Run the "serverless" command to setup monitoring, troubleshooting and testing.

で、やっとデプロイです。成功すると、上記のようなメッセージが出力されます。普段はここに記載されているエンドポイントでアクセスしますが、今回はCloudformationを利用しているのでこのエンドポイントは使いません。

DB作成

Aurora ServerlessにDBとテーブルを設定します。

vendor/bin/bref cli --region=ap-northeast-1 -- todo-dev-console doctrine:database:create
vendor/bin/bref cli --region=ap-northeast-1 -- todo-dev-console doctrine:schema:update --force

デプロイ時のconsoleに記載されたファンクション名を使ってbref cliを実行します。ファンクション名はSymfony Console用のサブコマンド・オプションになります。

アクセス

AWS ConsoleのCloudfrontに新しい設定が追加されているのでこのドメインでアクセスします。

https://********.cloudfront.net/tasks

Screen Shot 2020-03-06 at 16.57.01.png

まとめ

Brefを使うことで比較的簡単にサーバーレスなアプリケーションをデプロイでき、AuroraやCloudfront周りとも連携できます。S3にアップロードの部分だけちょっとめんどくさいですが、VPCまわりのコールドスタート問題が解消されたおかげで、結構実用に耐えうるくらいのスピードが出るようになっんじゃないかなと思います。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした