Laravel Vaporで実現するサーバーレスデプロイ:AWS Lambda前提の設計・設定・運用を一通り揃える

Laravel Vaporで実現するサーバーレスデプロイ:AWS Lambda前提の設計・設定・運用を一通り揃える

Laravel Vaporは、LaravelアプリをAWSのサーバーレス基盤へ載せるための実運用向けプラットフォーム。サーバ管理を減らせる一方で、Lambda特有の制約(コールドスタート、ローカルディスクの扱い、長時間処理、ネットワーク、ログの見え方など)を前提に“アプリ側の作法”を整えないと、デプロイはできても運用で詰まりやすい。この記事は、Vapor導入の前提整理から vapor.yml の基本、キュー/スケジューラ/ストレージ、よくあるハマりどころまでを、サーバーレス前提でまとめる。

全体像:Vaporで何が起きているか(サーバーレス前提の理解)

VaporのWebは主にAWS Lambda上で動き、オートスケールの単位は“コンテナ(実行環境)の増減”になる。常時起動のVMとは違い、アクセスが無い時間帯は縮退しやすく、アクセス急増時は並列実行が増える。結果として「アプリはステートレス(状態を持たない)」を徹底し、状態はDB/Redis/S3など外部へ出す設計が基本になる。

導入前チェック:Vaporが向くケース・注意が必要なケース

向くケース:トラフィック変動が大きい、運用工数を減らしたい、短期でスケールさせたい、ゼロダウンタイムデプロイを標準化したい。
注意が必要:常時高負荷で“常に最大”に近い、長時間の同期処理が多い、ローカルディスクへ巨大ファイルを書き続ける、TCPセッションを長く維持する(WebSocketなどは設計次第)、インフラ制約を吸収する体制が無い。

準備:AWSアカウント・権限・リージョンの決め方

最低限の考え方は「本番AWSは分離」「Vaporに必要な権限は最小化」「リージョンはユーザーに近い場所」。
・AWSアカウント分離(本番/検証)で事故を閉じ込める
・権限は“デプロイに必要な操作”へ絞る(最初から管理者権限で固定しない)
・リージョンはレイテンシと運用性(社内アクセス、監査、利用サービス)で決める

Vapor CLI導入:プロジェクトに“デプロイ定義”を置く

Vaporは「CLIでデプロイ」「vapor.ymlで環境定義」が中心になる。まずCLIを入れ、プロジェクト直下に vapor.yml を置いて、環境ごとの設定(staging/production)をコード管理するのが基本。

# CLIの導入(例)
composer require laravel/vapor-cli --dev

# Vaporへログイン(例)
php vendor/bin/vapor login

vapor.ymlの基本:環境ごとの“Web/Queue/ビルド”を分ける

まずは「staging と production を分ける」「runtime と memory を明示」「buildでキャッシュ系を作る」を固定すると、デプロイが安定しやすい。

# vapor.yml(雛形イメージ)
id: 12345
name: my-laravel-app

environments:
  staging:
    runtime: 'php-8.4:al2'
    memory: 1024
    build:
      - 'COMPOSER_MIRROR_PATH_REPOS=1 composer install --no-dev'
      - 'php artisan config:cache'
      - 'php artisan route:cache'
      - 'php artisan event:cache'

  production:
    runtime: 'php-8.4:al2'
    memory: 1024
    build:
      - 'COMPOSER_MIRROR_PATH_REPOS=1 composer install --no-dev'
      - 'php artisan config:cache'
      - 'php artisan route:cache'
      - 'php artisan event:cache'

※ runtime の指定例はVapor側で用意された形式に合わせる。

環境変数とシークレット:.envを“置かない”運用に寄せる

サーバーレスでは、ローカルに .env を置いてSSHで編集する運用ができない前提になる。環境変数はVapor管理へ寄せ、アプリ側は「設定は環境変数から入る」ことを前提にする。
・APP_KEYやDB接続情報は“環境ごと”に管理
・外部APIキーは“ロール/権限”もセットで設計
・環境差(stagingだけデバッグONなど)をvapor.ymlとVapor環境設定で明確化

静的アセット:CDNキャッシュ前提で“更新が反映されない”を潰す

Vaporはデプロイ時に静的アセットをCDNへ配置する導線が用意されているため、ブラウザ/CDNのキャッシュを前提に設計する。:contentReference[oaicite:2]{index=2}
発生しがちな問題:JS/CSS更新後にユーザー側へ反映されない。
対策の考え方:
・ファイル名にハッシュ(バージョニング)を入れる(例:Vite/Mixのmanifest運用)
・HTML側はmanifest経由で参照し、同名ファイルの上書きを避ける
・CDNのTTL任せにせず“キャッシュ破壊”をビルドで担保する

ストレージ:ローカルディスク前提を捨ててS3へ寄せる

Lambdaはローカルディスクが永続前提ではない。アップロードや生成ファイルはS3へ置くのが基本。
・ユーザーアップロードはS3へ直行(署名付きURLなど)
・生成物(PDF/CSV/Excelなど)もS3へ
・/tmpは“短命・容量制約”の前提で、最小限だけ使う
発生条件:storage/app へ保存し続ける実装、複数インスタンスでファイルを共有したい実装。
結果:片方の実行環境には存在するが、別の実行環境には存在しない、という不整合が起きる。

DBとRedis:コネクション数・タイムアウトをサーバーレス前提で調整する

サーバーレスは並列実行が増えるため、DB接続数が跳ねやすい。
・コネクション数上限を意識(必要なら接続プール/Proxyを検討)
・タイムアウトを短めに(無限待ちで全体が詰まるのを避ける)
・重い集計はキューへ逃がす(同期HTTPでやらない)
発生条件:トラフィック増でLambdaが増える→同時にDB接続も増える→上限に当たりエラーが出る、という連鎖。

キューと非同期処理:WebでやらずQueueへ逃がすのが基本

メール送信、外部API連携、ファイル生成、重い集計などはWebリクエストで完結させず、キューで処理する。VaporはWeb/Queueを分けたインフラ運用を前提にできるため、設計としても分ける。:contentReference[oaicite:3]{index=3}
発生条件:Webがタイムアウト、レスポンスが遅い、瞬間的に高負荷になる。
対策:
・ジョブを細かく分割(1ジョブで全部やらない)
・冪等性(同じジョブが複数回動いても壊れない)を徹底
・外部APIはリトライ戦略を固定(回数・間隔)

スケジューラ:EventBridge前提で“runInBackground”を避ける

Vaporはタスクスケジューラをサーバーレス前提で扱う。背景実行(runInBackground)は環境の寿命と噛み合わず、予定通り回らない原因になりやすい。
また、サブ分単位のスケジューリングも設定で扱える。

# vapor.yml(例:サブ分タスクを有効にするイメージ)
environments:
  production:
    scheduler: sub-minute
    cli-timeout: 120

デプロイ運用:ゼロダウンタイムとロールバックを“手順化”する

サーバーレスでも、移行(migration)・キャッシュ生成・キューの整合は事故りやすい。
実務で固めたい手順:
・デプロイ前:メンテ不要の変更か、破壊的変更かを判定
・移行は“追加→移行→削除”の順(1回でやり切らない)
・ロールバック時に壊れる変更(カラム削除など)は分割して段階的に
・デプロイ後:ヘルスチェック、ログ監視、主要導線のE2E確認

パフォーマンス:コールドスタート・キャッシュ・Octaneの考え方

サーバーレスは「初回起動が遅い(コールドスタート)」が課題になりやすい。対策の方向性は、(1) 起動時処理を減らす、(2) キャッシュを活用する、(3) アプリ/依存の肥大を抑える。
・config/route/eventのキャッシュをビルドで作る
・外部I/O(起動時に外部APIを叩く等)を避ける
・重い初期化を遅延させる(必要なときだけ行う)
・選択肢としてOctane系の運用も視野に入る(ただし運用設計が増える)

よくある失敗と対処:Vaporで詰まりやすいポイント集

・ファイル保存:storage配下に永続保存→S3へ寄せる
・反映されない:CDNキャッシュ前提が無い→ファイル名ハッシュ/manifest運用
・タイムアウト:重い処理を同期で実施→キュー化、分割、cli-timeout調整
・DB接続上限:並列増で接続爆増→上限設計、処理分割、接続戦略の見直し
・スケジューラ不安定:runInBackground多用→避ける
・ログ追跡が辛い:リクエストIDが無い→相関IDを全ログへ付与

# デプロイ(例)
php vendor/bin/vapor deploy production