前回、データベースに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
と設定ファイルは下記のようになりました。
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
USER_NAME: root
PASSWORD: password
データベース名、バケット名は任意で指定します。
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
デプロイ後、エンドポイントにアクセスして以下のように表示されたら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
まとめ
データベースのリソース以外は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