AWS CDK と Bref を用いた Laravel サーバーレス構成 ― Lambda + EFS + SQLite

はじめに

PHPでAWS Lambdaを利用したアプリケーションを構築するには、カスタムランタイムが必要になります。BrefはLambdaでPHPを実行するためのランタイムとLaravel、SymfonyなどのPHPフレームワークとの統合を提供しています。

Bref – Simple and scalable PHP with serverless
Bref is a framework to write and deploy serverless PHP applications on AWS Lambda.

本記事では、このBrefを使ったLaravelアプリケーションの環境構築方法を解説していきます。

プロジェクトの概要

コンセプト

基本的にBrefはServerless Frameworkを介して利用しますが、今回はAWS CDKにBref用の専用Constructが用意されているので、CDKを利用することにしました。コードベースでのインフラ管理や拡張の容易性もCDKを選定した理由になります。

また、個人のテスト環境として比較的低コストで構築できるように最小構成で実装していきます。

成果物

完成品は以下に配置してあります。

GitHub - ShotaroMuraoka/cdk-serverless-laravel: Laravel アプリケーションを Bref でサーバーレス化した CDK
Laravel アプリケーションを Bref でサーバーレス化した CDK. Contribute to ShotaroMuraoka/cdk-serverless-laravel development by creating an ac...

システム構成

システム構成は以下の通りです。

AWSの構成図。クライアントからはHTTPとaws lambda invokeの線が出ており、それぞれAPI Gateway, Lambda ConsoleFunctionと接続している。API GatewayはApiFunctionと接続している。ApiFunctionとConsoleFunctionはEFSに接続している。API GatewayはAWS Cloudの中、Lambda ApiFunctionとConsoleFunction, EFSはAWS Cloudの中のVPCの中のPrivate subnetの中に配置されている。

Lambda関数が2つデプロイされているのは、API Gatewayに統合されているWebアプリケーションと php artisan migrate などを実行するConsoleアプリケーションを分けているためです。EFSは後述しますが、データストレージの役割を担っています。

準備

Laravel

Laravel側の作業はBrefに必要なライブラリをインストールするだけです。

% composer require bref/bref bref/laravel-bridge --update-with-dependencies

CDK

CDK側では、BrefのConstructパッケージをインストールします。

% npm install @bref.sh/constructs

CDKの実装

CDK側の実装を見ていきます。

ネットワーク

EFSをマウントさせる都合上、VPC Lambdaである必要があります。コスト高になりがちなNAT Gatewayに関しては、今回はLambdaからインターネットに出ていく必要は無いので、作成をしない設定にしています。

const vpc = new ec2.Vpc(this, "BrefVpc", {
  maxAzs: 1,
  natGateways: 0,
  restrictDefaultSecurityGroup: true,
  subnetConfiguration: [
    {
      name: "BrefPrivate",
      subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
    },
  ],
});

データストレージ

RDBのマネージドサービスは高価ですので、Laravelの初期状態で使えるSQLiteを選定し、EFSに database.sqlite ファイルを配置するようにします。冗長化も不要なのでOneZoneを選択しています。

const fileSystem = new efs.FileSystem(this, "BrefEfs", {
  vpc: vpc,
  removalPolicy: cdk.RemovalPolicy.DESTROY,
  performanceMode: efs.PerformanceMode.GENERAL_PURPOSE,
  throughputMode: efs.ThroughputMode.BURSTING,
  oneZone: true,
});

const accessPoint = this.fileSystem.addAccessPoint("BrefAccessPoint", {
  path: "/bref"
  createAcl: {
    ownerUid: "1001",
    ownerGid: "1001",
    permissions: "750",
  },
  posixUser: {
    uid: "1001",
    gid: "1001",
  },
});

アプリケーション

システム構成の項で紹介した通り、WebアプリケーションとConsoleアプリケーションの2つを用意しています。

Webアプリケーション

Webアプリケーションでは、Lambda関数とHTTPからのアクセス用にAPI Gatewayを統合します。Lambda関数は @bref.sh/constructsPhpFpmFunction というBrefのレイヤーを追加したConstructがあるので、これを使用します。

const backendFn = new PhpFpmFunction(this, "BrefApiFunction", {
  functionName: "ApiFunction",
  handler: "public/index.php",
  code: packagePhpCode("../laravel/", {
    exclude: ["tests/**", "var/**"],
  }),
  timeout: cdk.Duration.seconds(28),
  memorySize: 1024,
  vpc: vpc,
  filesystem: lambda.FileSystem.fromEfsAccessPoint(
    accessPoint,
    "/mnt/efs",
  ),
  environment: {
    APP_ENV: "production",
    LOG_CHANNEL: "stderr",
    DB_CONNECTION: "sqlite",
    DB_DATABASE: "/mnt/efs/database.sqlite",
  },
  phpVersion: "8.4",
});

const api = new apigwv2.HttpApi(this, "BrefHttpApi", {
  defaultIntegration: new integrations.HttpLambdaIntegration(
    "Integration",
    backendFn,
  ),
});

new cdk.CfnOutput(this, "ApiUrl", {
  value: api.url!,
});

URLを CfnOutput で出力するとデプロイ後のアクセスが楽になります。

Consoleアプリケーション

Brefには、ConsoleFunction というConstructがあります。これは、artisanコマンドを実行するエンドポイントになります。

new ConsoleFunction(this, "BrefConsoleFunction", {
  functionName: "ConsoleFunction",
  handler: "artisan",
  code: packagePhpCode("../laravel/", {
    exclude: ["tests/**", "var/**"],
  }),
  timeout: cdk.Duration.seconds(28),
  memorySize: 512,
  vpc: vpc,
  filesystem: lambda.FileSystem.fromEfsAccessPoint(
    accessPoint,
    "/mnt/efs",
  ),
  environment: {
    APP_ENV: "production",
    LOG_CHANNEL: "stderr",
    DB_CONNECTION: "sqlite",
    DB_DATABASE: "/mnt/efs/database.sqlite",
  },
  phpVersion: "8.4",
});

artisanコマンドはaws cli経由で実行しますので、functionName を定義しておくと後々便利です。

デプロイ・動作確認

デプロイして動作確認してみます。

% cdk deploy

(略)

Outputs:
BrefStack.ApiUrl = https://example.execute-api.ap-northeast-1.amazonaws.com/

出力されたURLにアクセスしてみます。

Internal Server Error のタイトルで、Database file at path [/mnt/efs/database.sqlite] does not exist. のメッセージが出ている。

当然、migrationが終わっていないのでエラーが表示されます。ConsoleFunctionを直接実行してmigrationします。aws cliを利用して以下を実行します。

% aws lambda invoke \
     --function-name ConsoleFunction \
     --region ap-northeast-1 \
     --cli-binary-format raw-in-base64-out \
     --payload '"migrate --force"' \
     response.json
{
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST"
}

Lambdaの実行に問題がなければLaravelの画面が見られるようになるはずです。

Laravel 12 の初期画面。

まとめと感想

本記事では、BrefをCDKで利用してAWS Lambda上でLaravelを動かしてみました。また、低コストでの構築を目指し、最小構成の形を取りました。

BrefはLaravelアプリケーション側で意識することが少ないですので、かなり手軽にできるんじゃないかと思いました。デプロイパイプラインを整えれば、本番環境にも適用できると思います。

タイトルとURLをコピーしました