Laravelのサーバレス環境構築入門

LaravelのAPIとバッチをサーバレスに構築したく下記３つを使って
APIGatewayとLambda環境を簡単に構築してみます。

- PHPをLambdaで動かす為のサポートをしてくれるbref
- LaravelをLambda用に動作するようにしてくれるlaravel-bridge
- AWSにサーバレス環境を自動構築してくれるnpm serverless


## Lambdaとは

- Lambdaとは
    - インフラの構築・管理を気にしなくてよく、コードをアップすればLambdaが動かしてくれる
    - 自動でスケーリングしてくれます
    - 処理した時間だけ課金（個人レベルの利用であれば無料枠を超えることはほぼない
- httpリクエストをトリガーにLambdaを使うためにAPIGatewayを使います
- デメリット
    - Lambdaに合わせた実装が必要になる
        - 最大15分の処理時間に合わせる
        - httpリクエストだと29秒
        - 容量512MBまでなのでそれに合わせる
        - リクエストとレスポンスのペイロードが6MBまでといった細かな制約も
    - 関数インスタンスの起動が遅い
        - 数百ミリ秒の遅延が発生する
        - 設定によって起動したインスタンスを再利用することで改善できるようです
            - 料金は別途かかる
            - プロビジョニングされた同時実行
- Lambda+RDS使うときの注意→RDSProxyを使う
    - LambdaからRDSへの同時接続数に上限があるのでアクセス多いとエラーになる（db.t2.micro/メモリ1GBだと85）
    - Lambdaは全てのプロセスが独立してしまうので、コネクションが使い回せず同時接続数がどんどん増えます
    - RDSProxyで既存のコネクションを再利用することができるので、同時接続数をこれでセーブする

### 詳細

- リクエストを処理している関数のインスタンスは、リサイクルされるまで数時間アクティブのままになる
- 関数は、/tmpディレクトリ内のローカルストレージにアクセス可

## Laravelでの構築の仕方

#### 1.(npm)serverlessのインストール

awsの認証情報もインストール後設定します。

```bash
$ npm install -g serverless
$ serverless config credentials --provider aws --key <key> --secret <secret>
```

### 2.(php)brefとlaravel-bridgeインストール

PHPをLambdaで動かす為のサポートをしてくれるbrefと、LaravelをLambda用に動作するようにしてくれる
laravel-bridgeをインストールします。

```bash
$ composer require bref/bref bref/laravel-bridge
```

### 3.serverless.ymlを生成

下記コマンドでserverless.ymlを生成します。
serverless.ymlはどんなAPIGateway・Lambdaを作るかの設計図になります。

```bash
$ php artisan vendor:publish --tag=serverless-config
```

### 4.serverless.ymlを自分の環境に合わせる

ほぼ修正する必要なく利用できますが、下記自分の環境に合わせて修正します。

- regionの変更
- timeoutの設定(デフォルト120秒。最大15分(900秒))
- cronの設定(events > schedules)
    - artisanコマンドの場合、inputで「"」で括る（'"video:channel"'）

```yml

functions:
    # This function runs the Laravel website/API
    web:
        handler: public/index.php
        timeout: 28 # in seconds (API Gateway has a timeout of 29 seconds)
        layers:
            - ${bref:layer.php-74-fpm}
        events:
            -   httpApi: '*'
    # This function lets us run artisan commands in Lambda
    artisan:
        handler: artisan
        timeout: 120 # in seconds
        layers:
            - ${bref:layer.php-74} # PHP
            - ${bref:layer.console} # The "console" layer
        events:
          - schedule:
              rate: cron(0 0 * * ? *) # GMT
              input: '"video:channel"'
          - schedule:
              rate: cron(15 0 * * ? *) # GMT
              input: '"video:videolist"'
```

#### 5.AWSデプロイ

下記コマンド実行するとLambda+APIGatewayが自動構築されます。

```bash
$ php artisan config:clear
$ serverless deploy
```

ファンクション単位でのデプロイ

```bash
$ serverless deploy function -f <yourfunction>
```
https://serverless.co.jp/blog/25/


### 6.自動デプロイ


ローカルでコマンドを打つのも手間なのでgit pushしたら自動で「serverless deploy」が走るようにします。
github actionを用います。

- 1.S3のバケットに.envをアップロードする
    - .envはgit管理対象外にしたい
    - S3に.envアップロードしデプロイ時に利用する
- 1.GithubのActions secretsを設定します。
    - AWS_ACCESS_KEY_ID
    - AWS_SECRET_ACCESS_KEY
    - S3_DOWNLOAD_BUCKET(.envファイル用
- 2.workflowを作成

https://github.com/serverless/github-action
https://github.com/aws-actions/configure-aws-credentials

```
name: Deploy master branch

on:
  push:
    branches:
      - develop
defaults:
  run:
    working-directory: ./src
jobs:
  deploy:
    name: deploy
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [14.x]
    steps:
    - uses: actions/checkout@v2
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ap-northeast-1

    - name: Download file from S3
      env: 
        S3_DOWNLOAD_BUCKET: ${{ secrets.S3_DOWNLOAD_BUCKET }}
      run: |
        aws s3 cp s3://$S3_DOWNLOAD_BUCKET/.env ./  --quiet

    - name: Output file contents
      run: |
        cat .env
    - name: Install Dependencies
      run: composer install --prefer-dist --optimize-autoloader --no-dev
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v1
      with:
        node-version: ${{ matrix.node-version }}
    - run: npm ci
    - name: serverless deploy
      run: |
        npm i -g serverless@2.x
        serverless deploy
      env:
        AWS_REGION: ap-northeast-1
        AWS_VERSION: latest
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
```

あとはgit pushすればデプロイされます。

### ローカル

#### 開発環境

下記参考に実装しました。
/tmp以外ではコマンドからファイル生成できないので、やりづらい。(readonlyになる為)
https://bref.sh/docs/web-apps/local-development.html

```yml
version: "3"
services:
  db:
    image: mysql:5.7
    volumes:
      - ./db:/var/lib/mysql
      - ./initdb.d:/docker-entrypoint-initdb.d
      - ./mysql/my.cnf:/etc/mysql/conf.d/my.cnf
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: vtuber
      TZ: "Asia/Tokyo"
    ports: ["3306:3306"]
  web:
      image: bref/fpm-dev-gateway
      ports:
          - '8000:80'
      volumes:
          - ../src:/var/task
      depends_on:
          - php
      environment:
          HANDLER: public/index.php
          DOCUMENT_ROOT: public
  php:
      image: bref/php-74-fpm-dev
      volumes:
          - ../src:/var/task:ro
          - ./tmp:/tmp
```

#### バッチ

ローカルからLambdaのバッチを実行する

```bash
# AWS_DEFAULT_REGION=ap-northeast-1 AWS_ACCESS_KEY_ID=[access_key_id] AWS_SECRET_ACCESS_KEY=[secret_access_key] vendor/bin/bref cli laravel-dev-artisan video:channel
```

php artisan migrateしたいときは下記です。
「--force」つけないと本番環境で「本当に実行しますか？」と入力待ち状態になってしまう為です。
https://mrkmyki.com/%E9%80%86%E5%BC%95%E3%81%8Dlaravel/laravel%E3%81%A7%E5%BC%B7%E5%88%B6%E7%9A%84%E3%81%ABmigration%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%99%E3%82%8B

```
vendor/bin/bref cli laravel-dev-artisan -- migrate --force
```

### AWS CloudWatchLogsの見方

```
REPORT RequestId: 3595eefc-ec9c-4021-a826-28453c0f5afd	Duration: 654.51 ms	Billed Duration: 980 ms	Memory Size: 1024 MB	Max Memory Used: 102 MB	Init Duration: 325.39 ms
```

- Duration: 654.51 ms / 関数のハンドラーメソッドがイベントの処理に費やした時間
- Billed Duration: 980 ms / 呼び出しの課金対象の時間
- Init Duration: 325.39 ms / 最初に処理されたリクエスト（コールドスタート時）についてハンドラーメソッド外で関数をロードしてコードを実行するためにランタイムにかかった時間

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/nodejs-logging.html