Brefが進化してたのでサーバーレスLaravel環境作ってみた

## はじめに
この記事は、[PHP Advent Calendar 2019](https://qiita.com/advent-calendar/2019/php) の16日目の記事です。
[PHPerKaigi 2019](https://phperkaigi.jp/2019/)で『[サーバーレスPHP](https://speakerdeck.com/ippey/sabaresuphp)』というお話をさせていただいたのですが、その際に紹介した『[Bref](https://bref.sh/)』が進化してたので紹介したいと思います。

## Brefとは
[Bref](https://bref.sh/)とはAWS Lambda上でPHPを動作させるためのもろもろの設定を楽にしてくれるCLIです。Brefを利用するためには、AWSアカウント、[aws-cli](https://aws.amazon.com/jp/cli/)、[serverless framework](https://serverless.com/)が必要となります。

## いままでのBrefとの変更点
### 相性の良いサービス
PHPerKaigiでは『DBの絡むものには向いていない』と発表したのですが、現在の[公式ドキュメント](https://bref.sh/docs/)には

![matrix.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9882/376eb2a0-e199-a607-1659-eaa7f4462226.png)
※公式ドキュメントより

上記のように、DBの絡むサービスが相性が良いものに変わっていました。
以前はLambdaからVPCに接続する時のコールドスタートがネックとなるので、相性が悪いサービスとしてDBが絡むものが挙げられてましたが、[Lambdaの機能改善](https://aws.amazon.com/jp/blogs/news/announcing-improved-vpc-networking-for-aws-lambda-functions/)により、かなり早くDBにアクセスできるようになりました。よって、RDSを使ったサービスであれば実用に耐えうるものになったようです。

### インストール
春先のBrefはこうでした。

```
composer require mnapoli/bref
```

が、今のBrefはこう変わっています。

```
composer require bref/bref
```

プロジェクト名がわかりやすくなってますね。1点注意ですが、新しいBrefはPHP7.2以上必須です。PHP7.1以下の方はPHPのバージョンアップして再度インストールしてください。

### 設定ファイル
BrefはデプロイにCloud Fomationを利用するため、Cloud Fomation用の設定ファイルを用意する必要がありました。
例えばこんな感じ。

```yaml:template.yml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Globals:
  Function:
    Environment:
      Variables:
        APP_ENV: prod

Resources:
  Website:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: 'bref-symfony-website'
      CodeUri: .
      Handler: public/index.php
      Timeout: 30 # in seconds (API Gateway has a timeout of 30 seconds)
      MemorySize: 1024
      Runtime: provided
      Layers:
        - 'arn:aws:lambda:ap-northeast-1:209497400698:layer:php-73-fpm:1'
      Events:
        HttpRoot:
          Type: Api
          Properties:
            Path: /
            Method: ANY
        HttpSubPaths:
          Type: Api
          Properties:
            Path: /{proxy+}
            Method: ANY

  Console:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: 'bref-symfony-console'
      CodeUri: .
      Handler: bin/console
      Timeout: 120 # in seconds
      Runtime: provided
      Layers:
        - 'arn:aws:lambda:ap-northeast-1:209497400698:layer:php-73:1' # PHP
        - 'arn:aws:lambda:ap-northeast-1:209497400698:layer:console:1' # The "console" layer

Outputs:
  DemoApi:
    Description: 'URL of our function in the *Prod* environment'
    Value: !Sub 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/'
```

これは、PHPerKaigiで紹介したBrefを利用して、サーバーレスなSymfony環境を用意するためのCloud Fomation用設定ファイルです。
これでもサンプルがドキュメントに用意してあたので、そんなに問題なく用意できましたが、ぱっと見わかりにくい印象です。
これが、新しいBrefだとこんな感じになります。


```yaml:serverless.yml
service: bref-symfony-website

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

plugins:
    - ./vendor/bref/bref

package:
    exclude:
        - node_modules/**
        - tests/**

functions:
    website:
        handler: public/index.php
        timeout: 28 # in seconds (API Gateway has a timeout of 29 seconds)
        layers:
            - ${bref:layer.php-73-fpm}
        events:
            -   http: 'ANY /'
            -   http: 'ANY /{proxy+}'
    console:
        handler: bin/console
        timeout: 120 # in seconds
        layers:
            - ${bref:layer.php-73} # PHP
            - ${bref:layer.console} # The "console" layer
```

まず、template.ymlというファイル名からserverless.ymlというファイル名に変わり、Cloud Fomation用の設定ファイルでなくserverlessの設定ファイルを用意する形に変更になりました。Cloud Fomationよりもシンプルになったので、かなり書きやすくなったと思います。また、前回同様、公式サイトのドキュメントにサンプルがあるので、それを利用すれば、さくっと設定ファイルが用意できます。

### デプロイ
春先では、デプロイは以下のコマンドを入力する必要がありました。

```
sam package --output-template-file .stack.yaml --s3-bucket [S3バケット名]

sam deploy --template-file .stack.yaml --capabilities CAPABILITY_IAM --stack-name [スタック名]
```

```sam package```で、プロジェクトをS3にアップロードし、```sam deploy```でLambdaにデプロイ、API Gatewayに設定していました。
これが新しいBrefでは

```
serverless deploy
```

serverlessのコマンド1つでデプロイできるようになりました。書いてて薄々きづいてきましたが、Brefの進化は、Serverless Frameworkの進化が大きく影響を与えているようです。


## Laravel環境をつくってみる。
では、Laravelの環境をセットアップしてみましょう。上記の通りですが、作業をまとめてみると非常にシンプルなのがよくわかります。

### インストール
コマンドでインストールしていきます。

```
composer create-project laravel/laravel serverless-laravel --prefer-dist
cd serverless-laravel
composer require bref/bref
vendor/bin/bref init

 What kind of lambda do you want to create? (you will be able to add more functions later by editing `serverless.yml`) [PHP function]:
  [0] PHP function
  [1] HTTP application
  [2] Console application
 > 1 # どれを選んでもあとで設定を変えるので特に問題ないですが、HTTP applicationを選びました。

Creating serverless.yml
Creating index.php

                                                                                                                        
 [OK] Project initialized and ready to test or deploy.                                                                  
                                                                                                                        

```

自動生成されたプロジェクト直下のindex.phpは削除しても問題ありません。

### serverless設定
そして、serverlessの設定を行います。

```yaml:serverless.yml
service: serverless-laravel

provider:
  name: aws
  region: ap-northeast-1
  runtime: provided
  environment:
    # Laravel environment variables
    APP_STORAGE: '/tmp'

plugins:
  - ./vendor/bref/bref

functions:
  website:
    handler: public/index.php
    timeout: 28 # in seconds (API Gateway has a timeout of 29 seconds)
    layers:
      - ${bref:layer.php-73-fpm}
    events:
      -   http: 'ANY /'
      -   http: 'ANY /{proxy+}'
  artisan:
    handler: artisan
    timeout: 120 # in seconds
    layers:
      - ${bref:layer.php-73} # PHP
      - ${bref:layer.console} # The "console" layer
```

regionですが、us-east-1からap-northeast-1に変更しています。この設定はwebsiteとartisanを用意しています。websiteはHTTPアクセス用の設定、artisanはコンソール（後述）の設定です。

### Laravel微調整

Lambdaのカスタムランタイム上で動作しますが、/tmpディレクトリをのぞきファイルを保存することができません。そこで、少しLaravelを調整します。
以下のとおり、ファイルを修正します。


```diff:bootstrap.php
$app = new Illuminate\Foundation\Application(
    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);

+ $app->useStoragePath($_ENV['APP_STORAGE'] ?? $app->storagePath());
```

```diff:.env
+ VIEW_COMPILED_PATH=/tmp/storage/framework/views


+ SESSION_DRIVER=array
- SESSION_DRIVER=file


+ LOG_CHANNEL=stderr
- LOG_CHANNEL=stack
```

```diff:app/Providers/AppServiceProvider.php
    public function boot()
    {
+        if (! is_dir(config('view.compiled'))) {
+            mkdir(config('view.compiled'), 0755, true);
        }
    }
```

### デプロイ
ここまで設定したら、デプロイするだけです。

```
serverless deploy

Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.... 中略 ....
Serverless: Stack update finished...
Service Information
service: serverless-laravel
stage: dev
region: ap-northeast-1
stack: serverless-laravel-dev
resources: 15
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:
  website: serverless-laravel-dev-website
  artisan: serverless-laravel-dev-artisan
layers:
  None
Serverless: Run the "serverless" command to setup monitoring, troubleshooting and testing.
```

endpointsに記載されてるURLにアクセスしてみましょう。

![laravel.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9882/cbf23f93-de4c-9869-28ee-cbcbc24a0fb3.png)

無事、表示されました。
コンソールも実行できるか確認してみましょう。

```
vendor/bin/bref cli --region ap-northeast-1  serverless-laravel-dev-artisan -- inspire
Simplicity is the essence of happiness. - Cedric Bledsoe
```

こちらも動きました。コンソールの設定をすることで、ローカル環境からLambda上のLaravel（artisan）を実行します。serverless.ymlの設定でregionをap-northeast-1に変更したので、bref cliの実行オプションに``` --region ap-northeast-1```を付与します。付与しないとエラーとなるので注意しましょう。

## まとめ
PHPerKaigiから約半年ですが、春先よりも随分と設定がシンプルになった印象がしました。前よりも環境が準備しやすいので試しやすく、いろいろなサーバーレスPHPの実例が出てくるような気がします。
来年も[PHPerKaigi](https://phperkaigi.jp/2020/)に参加する予定なので、なにか実例ありましたら是非当日おしえてくださいー！