LaravelをAWS Lambdaで動作させることができる素敵な時代になっていたので、Laravel SailとLaravel Breezeでログイン機能を実装したプロジェクトをServerless Frameworkを利用してデプロイしてみました。
前提
こちらの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
と設定ファイルは下記のようになりました。
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 DBCluster.Endpoint.Address
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 DBCluster.Endpoint.Address
DB_PASSWORD: ${self:custom.secret.PASSWORD}
plugins:
# We need to include the Bref plugin
- ./vendor/bref/bref
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"
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
USER_NAME: root
PASSWORD: password
データベース名、バケット名は任意で指定します。
DB_PORT: 3306
DB_DATABASE: hoge_app
AWS_BUCKET: kai-laravel-test
Lambda関数に環境変数を追加する
Brefが自動生成してくれるLambda関数のリソースにAurora Serverlessへアクセスするための環境変数を設定します。Aurora Serverlessの場合、インスタンスは自動で生成されるので、DBCluster.Endpoint.Address
でエンドポイントを取得します。
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 DBCluster.Endpoint.Address
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 DBCluster.Endpoint.Address
DB_PASSWORD: ${self:custom.secret.PASSWORD}
クラスターの設定
EngineMode: serverless
と設定するとAurora Serverlessとしてクラスター作成できます。
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
デプロイ後、エンドポイントにアクセスして以下のように表示されたらOKです。
静的ファイルの配置
S3バケットにプロジェクトのpublicディレクトリをコピーします。これはAWS Lambda関数へのアクセスだけだと、public/css/app.css
などへアクセスできないので、デプロイで作成した、アセット用のS3バケットへファイルを配置するためです。
ファイルがないとブラウザでアクセスした場合、ログイン画面へアクセスすると以下のようになります。
> aws s3 sync public s3://kai-laravel-test/public --delete
マイグレーション
Brefにはcli
コマンドが提供されているので、それを利用してAWS Lambdaのlaravel-dev-artisan
関数を利用してマイグレーションします。
cli
コマンドはAWSのクレデンシャルファイルを参照するので、sail
コマンドを挟まないほうが手っ取り早いです。
> ./vendor/bin/bref cli --region=ap-northeast-1 laravel-dev-artisan -- migrate
動作確認
実際にデプロイした環境にアクセスしています。
今回はメール送信については対応していないので、パスワード変更のためのメール送信はエラーとなります。
リソースの削除
動作確認ができてリソースが不要であれば削除します。S3バケットにファイルがあるまま削除処理を実行するとエラーになるので、ファイルを削除しておきます。
> aws s3 rm --recursive s3://kai-laravel-test/
> serverless remove
まとめ
実用するにはまだまだ対応するべきことがありますが、ひとまずはやりたいことが実現できました。
やったぜ
参考
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