Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PHPer、Cloudflare に引っ越す

PHPer、Cloudflare に引っ越す

Avatar for Suguru Ohki

Suguru Ohki

April 24, 2026

More Decks by Suguru Ohki

Other Decks in Programming

Transcript

  1. 自己紹介 スー(@SuguruOoki) MOSH 株式会社 Laravel Live Japan Core Staff GoToMarket

    / HROps / SalesOps / 技術 広報 https://blog.sue-san.dev よろしくお願いします Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 2
  2. 半年前の私 「Cloudflare? それは JavaScript の世界。 PHPer が行く場所じゃない」 — そう思ってました。 でも、2026

    年のいま。 私の Laravel は 世界330都市で動いて います。 Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 3
  3. 今日壊す 3つの先入観 × 3つのアプローチ 先入観 解決アプローチ 本日の扱い PHPランタイム無いでしょ? A. php-wasm

    軽く触れる Laravel動かないでしょ? B. Containers + FrankenPHP 本命・デモあり 長時間処理できないでしょ? + Cloudflare Queues Real World で → 迷ったら B。今日の 20 分で、全部壊します Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 4
  4. A. php-wasm — PHP を Wasm 化して Worker で動かす Laravel

    Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 5
  5. A. アーキテクチャ HTTP Browser Cloudflare Worker V8 isolate + php-wasm

    D1 DB代替 R2 ファイル KV セッション PHP インタプリタを Wasm にコンパイル Workers の V8 isolate 上で実行 採用実績: WordPress.org 公式 Playground Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 6
  6. A. 書くのは PHP だけ・得手不得手も正直に <?php // public/index.php ← これ1ファイル header('Content-Type:

    application/json'); echo json_encode(['msg' => 'Hello from PHP', 'v' => PHP_VERSION]); wrangler deploy # → 世界330都市に即配布 得意: コールドスタートゼロ / 無料枠10万req/日 / WordPress 苦手: pdo_mysql 等の拡張 / FS揮発 / Laravel フルスタック → だから本命は B Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 7
  7. B. Containers — FrankenPHP で Laravel がそのまま動く Laravel Live Japan

    2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 8
  8. B. 最小 Dockerfile(これだけで Laravel が動く) FROM dunglas/frankenphp:php8.4-alpine RUN install-php-extensions \

    pdo_mysql pdo_sqlite redis intl bcmath pcntl WORKDIR /app COPY . . RUN composer install --no-dev --optimize-autoloader # Worker モード = Laravel Octane 相当 ENV FRANKENPHP_CONFIG="worker ./public/index.php" PHPer が既に書ける水準 = 学習コストほぼゼロ Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 9
  9. B. Laravel の .env 書き換えポイント 項目 変更内容 DB_CONNECTION mysql →

    sqlite (D1 プロキシ経由) FILESYSTEM_DISK local → s3 (R2 互換) SESSION_DRIVER file → database / redis (KV) CACHE_STORE file → database / redis (KV) QUEUE_CONNECTION database → cloudflare コード変更は .env だけで済むケースが多い Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 10
  10. Live Demo: 本当に Octane が動く # そのままコピペで叩けます URL=https://cloudflare-laravel-octane-demo.suguru-ohki.workers.dev/api/hello for i

    in {1..5}; do curl -s $URL | jq -c '{request_count_this_worker, worker_pid, laravel_version}' done {"request_count_this_worker":2,"worker_pid":524,"laravel_version":"13.6.0"} {"request_count_this_worker":3,"worker_pid":524,"laravel_version":"13.6.0"} {"request_count_this_worker":4,"worker_pid":524,"laravel_version":"13.6.0"} {"request_count_this_worker":5,"worker_pid":524,"laravel_version":"13.6.0"} {"request_count_this_worker":6,"worker_pid":524,"laravel_version":"13.6.0"} pid=524 不変 × カウンタが増加 = Octane ワーカーが常駐している証拠 Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 11
  11. B. ベンチ & コスト(月10万PV想定) レイテンシ EC2 t3.small (ap-northeast-1): TTFB 45ms

    CF Containers + FrankenPHP: TTFB 18ms 月額 項目 EC2構成 CF構成 コンピュート $15 $3 DB $12 (RDS) $2 (D1) 転送 $9 (S3 egress) $0 (R2) 合計 $36 $5 → 約 1/7 のコスト で動く Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 12
  12. C. Worker を "賢い CDN" として前段に Worker(エッジ): 認証 / キャッシュ

    / A/B / ジオルーティング 既存 Laravel(オリジン): 1 行も変えない // routes/api.php (Laravel 側) Route::middleware('auth:sanctum')->group(function () { Route::post('/orders', fn (Request $r) => Order::create([ 'user_id' => $r->user()->id, 'items' => $r->json('items'), ])); }); Sanctum / Passport がそのまま動く = 認証資産を捨てなくていい Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 14
  13. Yes No Yes No START Laravel 等 フルスタック? B: Containers

    + FrankenPHP 既存オリジン あり? C: ハイブリッド A: php-wasm A / B / C ここまでのまとめ 要件 A B C WordPress / 軽量API ◎ ◎ ◦ Laravel + Octane △ ◎ 実証済 ◎ 既存本番の高速化 ✗ △ ◎ コールドスタート ◎ ◦ ◎ 迷ったら B。新規小規模なら A、既存活 かすなら C Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 15
  14. Live Demo 2: 認証ありAPI + Cache-Control URL=https://cloudflare-laravel-octane-demo.suguru-ohki.workers.dev # 1. 認証なし

    → 401 curl -s -H "Accept: application/json" $URL/api/me # → {"message":"Unauthenticated."} # 2. ログイン → Sanctum トークン取得 TOKEN=$(curl -s $URL/api/login \ -H "Content-Type: application/json" -H "Accept: application/json" \ -d '{"email":"[email protected]","password":"demo-password"}' | jq -r .token) # 3. 認証あり → 200 + ユーザー情報 curl -s $URL/api/me -H "Authorization: Bearer $TOKEN" | jq # 4. レスポンスヘッダ → Laravel 側の防御が効いている証拠 curl -sI $URL/api/me -H "Authorization: Bearer $TOKEN" | grep -i cache-control # → Cache-Control: no-store, private Sanctum がそのまま動く × Cache-Control で CDN キャッシュ禁止 → でも もし これが無かったら? Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 16
  15. キャッシュが認証データを漏らす事故 絶対避けたい事故 Worker が GET /api/me を URL だけでキャッシュ User

    A の応答が User B に返る → 個人情報漏洩 / 認証バイパス 安全な3パターン 対象 戦略 公開 GET そのままキャッシュ OK 認証 GET Authorization / セッション あり → bypass 認証 GET を効かせたい稀ケース cache key に ユーザーID 混ぜる Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 18
  16. Laravel 側: Cache-Control で明示 return response()->json($data) ->header('Cache-Control', 'private, no-store'); 効き目

    private : CDN / プロキシのキャッシュ 禁止 no-store : あらゆるキャッシュ 禁止 Cloudflare は RFC 9111 準拠 なので、Laravel 側だけで全レイヤに効く 基本は これだけ で OK Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 19
  17. Email: Cloudflare Email Service (2025/10 Beta) // 既存の Laravel Mail

    はそのまま Mail::to($user->email)->send(new WelcomeMail($user)); 初回セットアップ(1回だけ) composer require symfony/http-client # Worker へ POST するクライアント カスタム Transport を app/Mail/Transports/ に配置(Appendix B0) .env で MAIL_MAILER=cloudflare に切り替え → 以降は既存 Mail クラスがそのまま Cloudflare 経由に Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 21
  18. Queue: dispatch() がそのまま流れる // .env: QUEUE_CONNECTION=cloudflare use App\Jobs\GenerateMonthlyReport; GenerateMonthlyReport::dispatch($userId) ->onQueue('php-heavy-jobs');

    実行枠 CPU 5分 / wall 15分 — HTTPの30秒制限を最大30倍に拡張 失敗時は 自動リトライ + DLQ Consumer の実装は Appendix A1-A2 参照 Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 22
  19. ハマりポイント TOP3(実体験・抜粋) 1. session_start() のファイルセッション → KV / DB へ

    2. storage/logs 揮発 → stdout + Logpush 3. pdo_mysql 前提 → D1 HTTP API / TiDB Serverless 初日の詰まりを先回りで消せる(残り2件は Appendix A7) Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 23
  20. 冒頭の3つの先入観、全部壊しました 先入観 解 PHP ランタイム無い php-wasm で動く Laravel 動かない Containers

    + FrankenPHP で実証済 長時間処理できない Queues で CPU 5分 / wall 15分 そして 半年後の私の Laravel は、世界330都市で動いている Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 24
  21. 今日持ち帰ってほしい3つ 1. PHP は Cloudflare で 動く 2. Laravel なら

    Containers + FrankenPHP が本命 3. 「無いパーツ」は Laravel 流儀のまま差し替えるだけ Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 25
  22. Laravel Live Japan のご紹介 日本唯一の Laravel 専門カンファレンス 2026年 5月26日(火)〜 27日(水)

    開催 Taylor Otwell も来日実績、海外コミッター登壇常連 Octane / Livewire / Inertia / Reverb の深掘り Day 関西 PHPer にこそ来てほしい3つの理由 1. 関西からの登壇枠が毎年複数 — 東京一極集中じゃない 2. 前夜祭で Laravel 本家コアコミッターから日本語で一次情報 3. Core Staff 募集中(関西リモート大歓迎 ) laravellive.jp / @LaravelLiveJP 関西 PHP 勉強会の延長線に、もう一段深い Laravel コミュニティがあります Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 26
  23. 今夜の 5 分で、あなたの Laravel が330都市に # 1. wrangler をインストール(初めての方) npm

    install -g wrangler wrangler login # 2. サンプルを clone してデプロイ git clone https://github.com/SuguruOoki/cloudflare-php-demo cd cloudflare-php-demo/apps/frankenphp-container wrangler deploy リソース GitHub: github.com/SuguruOoki/cloudflare-php-demo Live: cloudflare-laravel-octane-demo.suguru-ohki.workers.dev Docs: developers.cloudflare.com/containers Thank you! — ご質問をどうぞ Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 27
  24. A1: Queue 詳細 — batch_size / batch_timeout 配送の 2 つのダイヤル

    設定 意味 既定 / 最大 max_batch_size 1 回で受け取る最大件数 10 / 100 max_batch_timeout 満杯未満でも配送される秒数 5 / 60 配送ルール size に達する OR timeout 経過 のどちらか 先に来た方 でバッチ確定 Consumer 実行時間枠 CPU: 既定 30秒 → 最大 5分 Wall clock: 最大 15分 Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 29
  25. A2: Queue Consumer 実装(Laravel 側) // routes/web.php などに Worker からの

    POST を受ける口 Route::post('/process', function (Request $r) { $kind = $r->json('kind'); if ($kind === 'monthly-report') { GenerateMonthlyReport::dispatchSync($r->json('args.user_id')); } return response()->json(['status' => 'processed']); }); DLQ(Dead Letter Queue) Laravel failed_jobs テーブルと同じ役割。違いは DLQ 自体もキュー なので「失敗 ジョブ再実行」を Consumer 追加だけで実装できる。 Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 30
  26. A3: キャッシュ Cache-Control 仕様(RFC 9111) ディレクティブ 意味 誰が禁止される? private single-user

    向け CDN / プロキシのみ 禁止 no-store あらゆるキャッシュ禁止 全員(ブラウザ含む) public 共有キャッシュ OK — no-cache 保存OKだが毎回再検証 — Cloudflare は仕様に従うので、Laravel で private, no-store を書く だけで全レイヤに効く Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 31
  27. A4: Worker 側の二重防御(belt and suspenders) 契約が破られる稀なケース 1. Cache Rules で

    "Cache Everything" 強制(Enterprise設定ミス) 2. Worker コードで Cache-Control を剥がしてから caches.default.put() 3. 壊れた中間プロキシ Worker 側: 認証ヘッダを見たら即 bypass if (req.headers.has('Authorization')) return bypass(); if (/laravel_session|auth/i.test(cookie)) return bypass(); Laravel 側の private, no-store と Worker 側の shouldBypassCache() の両方揃 って初めて全レイヤで漏れない。 Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 32
  28. B0-①: Cloudflare Mail Transport — 準備と登録 1. 必要ライブラリ composer require

    symfony/http-client ※ Laravel 標準の Http:: ファサードが内部で利用 2. Transport 登録 // AppServiceProvider::boot Mail::extend('cloudflare', fn () => new CloudflareMailTransport( workerUrl: config('services.cloudflare.worker_url'), )); → 実装クラスは次ページ(B0-②) Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 33
  29. B0-②: Cloudflare Mail Transport — 実装クラス // app/Mail/Transports/CloudflareMailTransport.php namespace App\Mail\Transports;

    use Symfony\Component\Mailer\Transport\AbstractTransport; use Symfony\Component\Mailer\SentMessage; use Illuminate\Support\Facades\Http; class CloudflareMailTransport extends AbstractTransport { public function __construct(private string $workerUrl) {} protected function doSend(SentMessage $message): void { Http::post($this->workerUrl, [ 'raw' => $message->toString(), ])->throw(); } public function __toString(): string { return 'cloudflare'; } } Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 34
  30. A5: デバッグ・モニタリング ローカル開発 wrangler dev → Container も起動可(php artisan serve

    感覚) 本番 wrangler tail → リアルタイムログ(tail -f 感覚) Logpush → S3 / R2 / BigQuery へ構造化ログ排出 Analytics Engine → カスタムメトリクス エラートラッキング Sentry PHP SDK がそのまま動く Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 35
  31. A7: ハマりポイント(本編 TOP3 の残り) 4. ext-gd / ext-imagick → Dockerfile

    で install-php-extensions に追加 5. タイムゾーン → config/app.php timezone => 'Asia/Tokyo' or date_default_timezone_set() 本編 TOP3(再掲) 1. session_start() → KV / DB へ 2. storage/logs 揮発 → stdout + Logpush 3. pdo_mysql 前提 → D1 HTTP API / TiDB Serverless Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 36
  32. A6: 参考リンク 分類 リンク Cloudflare Docs developers.cloudflare.com/{containers, email-service, queues} Cache

    Control developers.cloudflare.com/cache/concepts/cache-control HTTP 標準 RFC 9111 HTTP Caching 関連 OSS frankenphp.dev / laravel.com/docs/octane 本日のサンプル github.com/SuguruOoki/cloudflare-php-demo Laravel Live Japan 2026 / PHPer、Cloudflare に引っ越す 2026 / @SuguruOoki 37