8
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?

More than 3 years have passed since last update.

LaravelをAWS Lambdaで動作させてデータベースにはAuroraとRDS Proxyを使ってみた

Last updated at Posted at 2021-03-30

前回、データベースにServerless Frameworkを利用していたのをAuroraとRDS Proxyを利用する版となります。
LaravelをAWS Lambdaで動作させることができる素敵な時代になっていたので、Laravel SailとLaravel Breezeでログイン機

前提

こちらのGitHubリポジトリを利用してデプロイ環境を構築する手順です。
環境構築後の内容はこちらのブランチにおいています。

手順

Serverless Frameworkのインストール

セットアップスクリプトが提供されているのでそれを利用します。

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

> serverless -v
Framework Core: 2.30.3 (standalone)
Plugin: 4.5.1
SDK: 4.2.0
Components: 3.7.4

Serverless Getting Started Guide
https://www.serverless.com/framework/docs/getting-started/

Brefを利用してAWS Lambda用にLaravelを構成する

Brefというパッケージを利用するとAWS LambdaでLaravelを動作させる環境構築がとても簡単にできます。

Bref - Serverless PHP made simple
https://bref.sh/

brefphp/laravel-bridge: Package to use Laravel on AWS Lambda with Bref
https://github.com/brefphp/laravel-bridge

LaravelをコンテナにしてLambdaでデプロイするのが超簡単になった2021年 - Qiita
https://qiita.com/umihico/items/514cf792d30bf3706ef5

パッケージをインストールしてServerless Framework用の設定ファイルを生成します。

> ./vendor/bin/sail composer require bref/bref bref/laravel-bridge
> ./vendor/bin/sail artisan vendor:publish --tag=serverless-config

serverless.ymlの編集

生成されたserverless.ymlファイルにAurora Serverlessに必要となるリソースを追加します。下記の記事を参考にさせてもらいました。(感謝

Serverless FrameworkでAurora Postgres + VPC Lambda + RDS Proxyをデプロイする | DevelopersIO
https://dev.classmethod.jp/articles/rds-proxy-deploy-with-serverless-framework/

Aurora Serverless をCloudFormationを利用して設置してみた | DevelopersIO
https://dev.classmethod.jp/articles/aurora-serverless-by-cfn/

Brefを使って完全サーバレスな、Symfony + Vue.js + Aurora Serverless製TODOリストを作る - Qiita
https://qiita.com/ippey_s/items/bbb4e329c0a919d1a8b5

serverless.ymlと設定ファイルは下記のようになりました。

serverless.yml
service: laravel

provider:
  name: aws
  # The AWS region in which to deploy (us-east-1 is the default)
  region: ap-northeast-1
  # The stage of the application, e.g. dev, production, staging… ('dev' is the default)
  stage: dev
  runtime: provided.al2

custom:
  defaultStage: dev
  profiles:
    dev: sls-itg
    stg: sls-stg
    prd: sls-prd
  environments: ${file(./config/config.${opt:stage, self:custom.defaultStage}.yml)}
  secret: ${file(./config/secret/.secret.${opt:stage, self:custom.defaultStage}.yml)}

package:
  # Directories to exclude from deployment
  exclude:
    - node_modules/**
    - public/storage
    - resources/assets/**
    - storage/**
    - tests/**

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
    vpc:
      securityGroupIds:
        - !Ref LambdaSecurityGroup
      subnetIds:
        - !Ref PrivateSubnetA
        - !Ref PrivateSubnetC
    environment:
      DB_PORT: ${self:custom.environments.DB_PORT}
      DB_HOST: !GetAtt RDSProxy.Endpoint
      DB_PASSWORD: ${self:custom.secret.PASSWORD}

  artisan:
    handler: artisan
    timeout: 120 # in seconds
    layers:
      - ${bref:layer.php-74} # PHP
      - ${bref:layer.console} # The "console" layer
    vpc:
      securityGroupIds:
        - !Ref LambdaSecurityGroup
      subnetIds:
        - !Ref PrivateSubnetA
        - !Ref PrivateSubnetC
    environment:
      DB_PORT: ${self:custom.environments.DB_PORT}
      DB_HOST: !GetAtt RDSProxy.Endpoint
      DB_PASSWORD: ${self:custom.secret.PASSWORD}

plugins:
  # We need to include the Bref plugin
  - ./vendor/bref/bref
  - serverless-pseudo-parameters

resources:
  Resources:
    # The S3 bucket that stores the assets
    Assets:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: ${self:custom.environments.AWS_BUCKET}
    # 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: !Join ["/", [!GetAtt Assets.Arn, "*"]] # things in the bucket
              # alternatively you can write out Resource: 'arn:aws:s3:::<bucket-name>/*'
    ## VPC Resource
    VPC:
      Type: AWS::EC2::VPC
      Properties:
        CidrBlock: 10.0.0.0/24
        Tags:
          - { Key: Name, Value: Sample VPC }
    PrivateSubnetA:
      Type: AWS::EC2::Subnet
      Properties:
        VpcId: !Ref VPC
        CidrBlock: 10.0.0.0/25
        AvailabilityZone: ap-northeast-1a
        Tags:
          - { Key: Name, Value: Sample Private A }
    PrivateSubnetC:
      Type: AWS::EC2::Subnet
      Properties:
        VpcId: !Ref VPC
        CidrBlock: 10.0.0.128/25
        AvailabilityZone: ap-northeast-1c
        Tags:
          - { Key: Name, Value: Sample Private C }
    LambdaSecurityGroup:
      Type: AWS::EC2::SecurityGroup
      Properties:
        GroupDescription: SecurityGroup for Lambda Functions
        VpcId: !Ref VPC
        Tags:
          - Key: "Name"
            Value: "LambdaSecurityGroup"
    AuroraSecurityGroup:
      Type: AWS::EC2::SecurityGroup
      Properties:
        GroupDescription: SecurityGroup for Aurora
        VpcId: !Ref VPC
        SecurityGroupIngress:
          - IpProtocol: tcp
            FromPort: ${self:custom.environments.DB_PORT}
            ToPort: ${self:custom.environments.DB_PORT}
            CidrIp: 10.0.0.0/24
        Tags:
          - Key: "Name"
            Value: "AuroraSecurityGroup"
      DependsOn: VPC
    ## RDS Resource
    DBSubnetGroup:
      Type: AWS::RDS::DBSubnetGroup
      Properties:
        DBSubnetGroupDescription: "SampleDB subnet group"
        DBSubnetGroupName: sampledb-subnet-group
        SubnetIds:
          - !Ref PrivateSubnetA
          - !Ref PrivateSubnetC
    DBCluster:
      Type: AWS::RDS::DBCluster
      Properties:
        DatabaseName: ${self:custom.environments.DB_DATABASE}
        Engine: aurora-mysql
        # EngineMode: serverless
        MasterUsername: ${self:custom.secret.USER_NAME}
        MasterUserPassword: ${self:custom.secret.PASSWORD}
        DBClusterParameterGroupName: !Ref DBClusterParameterGroup
        DBSubnetGroupName: !Ref DBSubnetGroup
        VpcSecurityGroupIds:
          - !Ref AuroraSecurityGroup
      DependsOn: DBSubnetGroup
    DBClusterParameterGroup:
      Type: AWS::RDS::DBClusterParameterGroup
      Properties:
        Description: A parameter group for aurora
        Family: aurora-mysql5.7
        Parameters:
          time_zone: "Asia/Tokyo"
          character_set_client: "utf8"
          character_set_connection: "utf8"
          character_set_database: "utf8"
          character_set_results: "utf8"
          character_set_server: "utf8"
    # Aurora Serverlessの場合は不要
    DBInstance1:
      Type: AWS::RDS::DBInstance
      Properties:
        DBClusterIdentifier: !Ref DBCluster
        DBSubnetGroupName: !Ref DBSubnetGroup
        Engine: aurora-mysql
        DBInstanceClass: db.t3.medium
      DependsOn: DBCluster
    AuroraSecret:
      Type: AWS::SecretsManager::Secret
      Properties:
        Name: Sample/aurora
        SecretString: '{"username":"${self:custom.secret.USER_NAME}", "password":"${self:custom.secret.PASSWORD}"}'
    SecretTargetAttachment:
      Type: AWS::SecretsManager::SecretTargetAttachment
      Properties:
        SecretId: !Ref AuroraSecret
        TargetId: !Ref DBCluster
        TargetType: "AWS::RDS::DBCluster"
      DependsOn: DBCluster
    # Aurora ServerlessだとRDSProxyは使えない
    # https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/AuroraUserGuide/rds-proxy.html#rds-proxy.limitations
    ProxyRole:
      Type: AWS::IAM::Role
      Properties:
        RoleName: sample-proxy-role
        AssumeRolePolicyDocument:
          Version: "2012-10-17"
          Statement:
            - Effect: Allow
              Principal:
                Service:
                  - "rds.amazonaws.com"
              Action:
                - "sts:AssumeRole"
        Path: /
      DependsOn: DBCluster
    ProxyRolePolicies:
      Type: "AWS::IAM::Policy"
      Properties:
        PolicyName: RdsProxyPolicy
        PolicyDocument:
          Version: "2012-10-17"
          Statement:
            - Effect: Allow
              Action:
                - "secretsmanager:GetResourcePolicy"
                - "secretsmanager:GetSecretValue"
                - "secretsmanager:DescribeSecret"
                - "secretsmanager:ListSecretVersionIds"
              Resource:
                - !Ref AuroraSecret
            - Effect: Allow
              Action:
                - "kms:Decrypt"
              Resource: "arn:aws:kms:${self:provider.region}:#{AWS::AccountId}:key/*"
              Condition:
                StringEquals:
                  kms:ViaService: "secretsmanager.${self:provider.region}.amazonaws.com"
        Roles:
          - Ref: ProxyRole
      DependsOn: AuroraSecret
    RDSProxy:
      Type: AWS::RDS::DBProxy
      Properties:
        DBProxyName: SampleAuroraProxy
        Auth:
          - SecretArn: !Ref AuroraSecret
        VpcSecurityGroupIds:
          - !Ref AuroraSecurityGroup
        VpcSubnetIds:
          - !Ref PrivateSubnetA
          - !Ref PrivateSubnetC
        EngineFamily: MYSQL
        RequireTLS: false
        RoleArn: !GetAtt ProxyRole.Arn
      DependsOn: AuroraSecret
    DBProxyTargetGroup:
      Type: AWS::RDS::DBProxyTargetGroup
      Properties:
        TargetGroupName: default
        DBProxyName: !Ref RDSProxy
        DBClusterIdentifiers:
          - !Ref DBCluster
      DependsOn: RDSProxy
config/secret/.secret.dev.yml
USER_NAME: root
PASSWORD: password

データベース名、バケット名は任意で指定します。

config/config.dev.yml
DB_PORT: 3306
DB_DATABASE: hoge_app
AWS_BUCKET: kai-laravel-test

Lambda関数に環境変数を追加する

Brefが自動生成してくれるLambda関数のリソースにRDS Proxyへアクセスするための環境変数を設定します。RDSProxy.Endpointでエンドポイントを取得します。

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
    vpc:
      securityGroupIds:
        - !Ref LambdaSecurityGroup
      subnetIds:
        - !Ref PrivateSubnetA
        - !Ref PrivateSubnetC
    environment:
      DB_PORT: ${self:custom.environments.DB_PORT}
      DB_HOST: !GetAtt RDSProxy.Endpoint
      DB_PASSWORD: ${self:custom.secret.PASSWORD}

  artisan:
    handler: artisan
    timeout: 120 # in seconds
    layers:
      - ${bref:layer.php-74} # PHP
      - ${bref:layer.console} # The "console" layer
    vpc:
      securityGroupIds:
        - !Ref LambdaSecurityGroup
      subnetIds:
        - !Ref PrivateSubnetA
        - !Ref PrivateSubnetC
    environment:
      DB_PORT: ${self:custom.environments.DB_PORT}
      DB_HOST: !GetAtt RDSProxy.Endpoint
      DB_PASSWORD: ${self:custom.secret.PASSWORD}

クラスターの設定

AWS::RDS::DBCluster - AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbcluster.html#cfn-rds-dbcluster-enginemode

    DBCluster:
      Type: AWS::RDS::DBCluster
      Properties:
        DatabaseName: ${self:custom.environments.DB_DATABASE}
        Engine: aurora-mysql
        EngineMode: serverless
        MasterUsername: ${self:custom.secret.USER_NAME}
        MasterUserPassword: ${self:custom.secret.PASSWORD}
        DBClusterParameterGroupName: !Ref DBClusterParameterGroup
        DBSubnetGroupName: !Ref DBSubnetGroup
        VpcSecurityGroupIds:
          - !Ref AuroraSecurityGroup
      DependsOn: DBSubnetGroup

DBClusterParameterGroupでcharacter_setを指定していますが、これはMySQL5.7.7未満だと標準のcharasetがutf8mb4となり、unique制約をつけたカラムで文字数オーバーになるのを回避するためです。
詳細は下記が詳しいです。

Laravel5.4以上、MySQL5.7.7未満 でusersテーブルのマイグレーションを実行すると Syntax error が発生する - Qiita
https://qiita.com/beer_geek/items/6e4264db142745ea666f

    DBClusterParameterGroup:
      Type: AWS::RDS::DBClusterParameterGroup
      Properties:
        Description: A parameter group for aurora
        Family: aurora-mysql5.7
        Parameters:
          time_zone: "Asia/Tokyo"
          character_set_client: "utf8"
          character_set_connection: "utf8"
          character_set_database: "utf8"
          character_set_results: "utf8"
          character_set_server: "utf8"

デプロイ

serverless deployコマンドでデプロイします。時間がかかるのでデプロイ中はお茶でものんでまったりと待ちます。

> ./vendor/bin/sail npm run prod
> serverless deploy

image.png

デプロイ後、エンドポイントにアクセスして以下のように表示されたらOKです。
image.png

静的ファイルの配置

S3バケットにプロジェクトのpublicディレクトリをコピーします。これはAWS Lambda関数へのアクセスだけだと、public/css/app.cssなどへアクセスできないので、デプロイで作成した、アセット用のS3バケットへファイルを配置するためです。
ファイルがないとブラウザでアクセスした場合、ログイン画面へアクセスすると以下のようになります。
image.png

> aws s3 sync public s3://kai-laravel-test/public --delete

ファイルコピー後、下記のようになっていたらOKです。
image.png

マイグレーション

Brefにはcliコマンドが提供されているので、それを利用してAWS Lambdaのlaravel-dev-artisan関数を利用してマイグレーションします。
cliコマンドはAWSのクレデンシャルファイルを参照するので、sailコマンドを挟まないほうが手っ取り早いです。

> ./vendor/bin/bref cli --region=ap-northeast-1 laravel-dev-artisan -- migrate

image.png

動作確認

実際にデプロイした環境にアクセスしています。
image.png
image.png

今回はメール送信については対応していないので、パスワード変更のためのメール送信はエラーとなります。
image.png
image.png

リソースの削除

動作確認ができてリソースが不要であれば削除します。S3バケットにファイルがあるまま削除処理を実行するとエラーになるので、ファイルを削除しておきます。

> aws s3 rm --recursive s3://kai-laravel-test/
> serverless remove

まとめ

データベースのリソース以外はAurora Serverlessとほぼほぼ同じとなりました。
本来ならAurora ServerlessとRDS Proxyの組み合わせを試したかったのですが、Aurora Serverlessには対応していなかったのが、残念ポイントでした。

Amazon RDS Proxy による接続の管理 - Amazon Aurora
https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/AuroraUserGuide/rds-proxy.html#rds-proxy.limitations

Aurora サーバーレスクラスターでは RDS Proxy を使用できません。

参考

Serverless Getting Started Guide
https://www.serverless.com/framework/docs/getting-started/

Bref - Serverless PHP made simple
https://bref.sh/

brefphp/laravel-bridge: Package to use Laravel on AWS Lambda with Bref
https://github.com/brefphp/laravel-bridge

LaravelをコンテナにしてLambdaでデプロイするのが超簡単になった2021年 - Qiita
https://qiita.com/umihico/items/514cf792d30bf3706ef5

Serverless FrameworkでAurora Postgres + VPC Lambda + RDS Proxyをデプロイする | DevelopersIO
https://dev.classmethod.jp/articles/rds-proxy-deploy-with-serverless-framework/

Aurora Serverless をCloudFormationを利用して設置してみた | DevelopersIO
https://dev.classmethod.jp/articles/aurora-serverless-by-cfn/

Brefを使って完全サーバレスな、Symfony + Vue.js + Aurora Serverless製TODOリストを作る - Qiita
https://qiita.com/ippey_s/items/bbb4e329c0a919d1a8b5

AWS::RDS::DBCluster - AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbcluster.html#cfn-rds-dbcluster-enginemode

Laravel5.4以上、MySQL5.7.7未満 でusersテーブルのマイグレーションを実行すると Syntax error が発生する - Qiita
https://qiita.com/beer_geek/items/6e4264db142745ea666f

CreateDBClusterParameterGroup-Amazon Relational Database Service
https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_CreateDBClusterParameterGroup.html

AWS::RDS::DBInstance - AWS CloudFormation
https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-rds-database-instance.html#cfn-rds-dbinstance-masterusername

エラー: The policy failed legacy parsing - Qiita
https://qiita.com/sot528/items/f248db0b69c7e1d34220

Cloudformationでエラー: Has prohibited field Resource - Qiita
https://qiita.com/sot528/items/2263fab0dbb55d6ce032

Aurora Serverlessの導入時に気をつけるべきこと | DevelopersIO
https://dev.classmethod.jp/articles/lessons-learned-from-up-and-running-aurora-serverless/

Console commands - Bref
https://bref.sh/docs/runtimes/console.html

Serverless Frameworkで環境変数を外部ファイルから読み込み、環境毎に自動で切り替えてみる | DevelopersIO
https://dev.classmethod.jp/articles/serverless-framework-conf-change/

データベースの使用-Bref
https://bref.sh/docs/environment/database.html

Brefが進化してたのでサーバーレスLaravel環境作ってみた - Qiita
https://qiita.com/ippey_s/items/25129dde8c7fe85479e4

Creating serverless PHP websites - Bref
https://bref.sh/docs/websites.html#architectures

Amazon RDS Proxy による接続の管理 - Amazon Aurora
https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/AuroraUserGuide/rds-proxy.html#rds-proxy.limitations

8
6
2

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
8
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?