【テクニカル・上級編】PulumiでCloudflare WorkersとD1データベースを統合管理するサーバーレス構成の完全ガイド – インフラ構成管理(IaC)活用バイブル

エッジの支配者へ:Pulumiで築くCloudflare Workers & D1 サーバーレス要塞の完全自動構築

インフラストラクチャ・作為(IaC)の歴史は、長らく「リージョン」の縛りとの戦いだった。AWSの`ap-northeast-1`、GCPの`asia-northeast1`。我々は常に、物理的なデータセンターの座標にインフラをアンカーしてきた。

だが、パラダイムは変わった。Cloudflare WorkersとD1(Serverless SQLite)の組み合わせは、地球全土の数百年単位のPoP(Point of Presence)そのものを実行基盤に変える。リクエストはユーザーの数ミリ秒隣で処理され、データはエッジで完結する。

この「境界なきコンピューティング」を、手動のポータルクリックや、アドホックなWranglerコマンドの連打で構築するなど、SREの冒涜に他ならない。すべてのリソースはコードで定義され、一糸乱れぬ冪等性のもとにリコンサイルされなければならない。

今回は、PulumiのTypeScriptプロバイダを極限まで駆使し、Cloudflare Workers、D1データベース、そしてDNSレコード群を単一のコードベースで完全自動プロビジョニングする要塞の構築法を解説する。机上の空論ではない。本番の戦場で生き抜くための、骨の髄まで実用的な知見を授けよう。

—

1. エッジIaCの哲学とCloudflareプロバイダの深淵

なぜTerraformではなくPulumiなのか?

Cloudflareのエッジエコシステムは高速に進化している。新しいAPIエンドポイント、Workersのバインディング仕様の変更、D1のスキーママイグレーションの統合など、HCL(HashiCorp Configuration Language)の静的な宣言的構文では、複雑な条件分岐や動的なリソース生成(例えば、動的な環境変数マップや、ルーティングの網羅的生成)で限界を迎える。

Pulumiは、TypeScript、Python、Goといった本物のプログラミング言語を使える。これにより、インフラストラクチャを単なる設定ファイルの羅列ではなく、「アプリケーションコードの延長線上にあるソフトウェア」として扱える。

Cloudflareプロバイダの内部挙動をハックする

PulumiのCloudflareプロバイダは、CloudflareのREST APIを直接叩いている。ここで重要なのは、「APIのレートリミット(Rate Limiting)」と「リソース間の暗黙の依存関係」の制御だ。

Workers ScriptとD1データベース、そしてRoutesを同時に作成する場合、API側でリソースの結びつき(Binding)が完了する前にルートが有効化されると、一時的な500エラー(エッジの伝播遅延)が発生する。Pulumiの `dependsOn` や `output` の遅延評価メカニズムを完璧に理解し、エッジの eventualmente consistent(結果整合性)をコードレベルで調停する必要がある。

—

2. 構築実践:Workers、D1、DNSの完全統合コード

以下のディレクトリ構成を想定する。

.
├── Pulumi.yaml
Pulumi.dev.yaml
├── index.ts # インフラストラクチャの定義
└── worker/
├── index.ts # WorkersのTypeScriptコード
└── wrangler.toml # ローカル開発用

ステップ1: プロジェクトの初期化と構成

`Pulumi.yaml` と設定ファイルを用意し、CloudflareのAPIトークンとアカウントIDをコンフィグにバインドする。

Pulumi.yaml
name: cloudflare-edge-fortress
runtime: nodejs
description: Production-grade serverless architecture on Cloudflare Workers and D1 using Pulumi.

ステップ2: インフラストラクチャのコード実装 (`index.ts`)

ここが本記事の核心だ。D1データベースのプロビジョニング、Workersスクリプトのビルド&デプロイ、そしてDNSとルートの設定をひとつのグラフとして構築する。

import as pulumi from “@pulumi/pulumi”;
import as cloudflare from “@pulumi/cloudflare”;
import as fs from “fs”;
import as path from “path”;

// コンフィグの読み込み
const config = new pulumi.Config();
const accountId = config.require(“cloudflareAccountId”);
const zoneId = config.require(“cloudflareZoneId”);
const domainName = config.require(“domainName”); // 例: api.example.com

// 1. D1データベースのプロビジョニング
// エッジ全体に分散配置されるSQLiteデータベースのインスタンスを作成
const database = new cloudflare.D1Database(“app-database”, {
accountId: accountId,
name: “production-core-db”,
});

// 2. Workersスクリプトのビルド成果物の準備
// 本番ではここでesbuild等を用いてコードをバンドルする前提とする
const workerScriptPath = path.join(__dirname, “worker/index.ts”);
// 簡易的にコードを読み込み(実際のパイプラインではビルドステップを挟むこと)
const workerScriptContent = fs.readFileSync(workerScriptPath, “utf-8”);

// 3. Workers Scriptリソースの定義
const workerScript = new cloudflare.WorkerScript(“app-worker”, {
accountId: accountId,
name: “edge-api-processor”,
content: workerScriptContent,
// D1データベースを Workers からバインドする(極めて重要)
d1Databases: [{
binding: “DB”,
databaseId: database.id,
}],
// 互換性日付の固定(エッジランタイムの破壊的変更を防ぐSREの鉄則)
compatibilityDate: “2024-03-01”,
});

// 4. カスタムドメインへのルーティング設定 (Workers Route)
const workerRoute = new cloudflare.WorkerRoute(“app-route”, {
zoneId: zoneId,
pattern: `${domainName}/`,
scriptName: workerScript.name,
}, { dependsOn: [workerScript] });

// 5. DNSレコードの自動構成 (Proxy有効化 = CloudflareのCDN/WAFを通す)
const dnsRecord = new cloudflare.Record(“app-dns”, {
zoneId: zoneId,
name: domainName,
type: “CNAME”,
value: “ghs.hosted.cloudflare.com”, // または Workers.dev のプレースホルダ、あるいはProxiedなプレースホルダ
proxied: true, // Cloudflareのオレンジ色の雲を有効化
}, { dependsOn: [workerRoute] });

// エクスポート:デプロイ完了後にアクセス先URLを出力
export const endpoint = `https://${domainName}`;
export const d1DatabaseId = database.id;

ステップ3: エッジWorkers側のコード (`worker/index.ts`)

D1データベースを叩く最小限かつ堅牢なWorkersコードの実装例。

// 型定義(@cloudflare/workers-typesを想定)
export interface Env {
DB: D1Database;
}

export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise {
try {
// D1に対するクエリ実行(ゼロレイテンシに近いエッジSQLiteアクセス)
const { results } = await env.DB.prepare(
“SELECT datetime(‘now’) as current_time”
).all();

return new Response(JSON.stringify({
status: “success”,
message: “Hello from Cloudflare Workers & D1 Edge Fortress”,
serverTime: results[0],
rayId: request.headers.get(“cf-ray”),
}), {
headers: { “Content-Type”: “application/json” },
});
} catch (e: any) {
return new Response(JSON.stringify({ error: e.message }), {
status: 500,
headers: { “Content-Type”: “application/json” },
});
}
},
};

—

3. ローカル開発から本番デプロイまでのシームレスなパイプライン設計

真のSREは、ローカル環境と本番環境の乖離を許さない。Wrangler(Cloudflare公式CLI)とPulumiを美しく協調させるワークフローを構築する。

1. ローカル開発時のエミュレーション

D1のローカル開発には `wrangler d1 execute` や Miniflareベースのローカルランタイムを使用する。
`wrangler.toml` はローカルテスト専用として割り切り、本番のインフラ構成(環境変数、D1バインディング名)は完全にPulumi側でソースオブトゥルース(真実のソース)として管理する。

2. CI/CDパイプライン(GitHub Actionsの極限最適化)

GitHub Actions上でPulumiを実行し、インフラの差分適用とWorkersのデプロイを自動化する。

name: Edge Deploy Pipeline

on:
push:
branches: [ main ]

jobs:
deploy:
runs-on: ubuntu-latest
steps:

  • name: Checkout Repository

uses: actions/checkout@v4

  • name: Setup Node.js

uses: actions/setup-node@v4
with:
node-version: ’20’
cache: ‘npm’

  • name: Install Dependencies

run: npm ci

  • name: Setup Pulumi

uses: pulumi/action-get-pulumi@v3

  • name: Run Pulumi Preview & Up

uses: pulumi/action-pulumi@v3
with:
command: up
stack-name: production
cloud-url: ${{ secrets.PULUMI_ACCESS_TOKEN }}
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

—

4. エキスパート向け最適化ハック:メモリ、コールドスタート、スキーマ管理

ここからは、一般的なドキュメントには載っていない、エッジコンピューティング特有の「罠」と「最適化ハック」を伝授する。

1. D1データベースのマイグレーション問題

D1はリレーショナルデータベースでありながら、サーバーレスゆえに「いつスキーマを適用するか」という問題が生じる。
PulumiでD1インスタンス自体は作成できるが、`CREATE TABLE` などのDDL実行をIaCツールだけで綺麗にハンドリングするのは難しい(プロバイダの制限がある)。

解決策:
Pulumiの `Command` リソース(`pulumi-command` パッケージ)を利用し、インフラプロビジョニングの直後に `wrangler d1 execute` を安全にフックする。

import as command from “@pulumi/command”;

// データベースが作成された後に、自動でマイグレーションスクリプトを適用する
const migration = new command.local.Command(“d1-migration”, {
create: pulumi.interpolate`npx wrangler d1 execute production-core-db –remote –file=./db/schema.sql`,
}, { dependsOn: [database] });

これで、インフラの構築からテーブルの初期化までが、完全な冪等性を持ってワンストップで完了する。

2. Workersのコールドスタートとメモリ消費の最適化

Cloudflare WorkersはV8 Isolatesで動作するため、AWS LambdaのようなOSコンテナの起動オーバーヘッド(コールドスタート)は存在しない。しかし、巨大なバンドルサイズやモジュールの初期化時の重い処理は、CPUタイムリミット(Standardプランでは10ms〜、Workers Paidでは無限等、プラン依存)に抵触する。

  • 知見: `esbuild` などのバンドラーを通す際、ツリーシェイキングを極限まで効かせ、D1を操作するクエリビルダー(KyselyやDrizzle ORMなど)を採用する場合は、軽量なdialectを選ぶこと。重厚長大なORMはエッジのメモリ制限(通常128MB)を圧迫し、予期せぬCPU消費を引き起こす。
  • D1のコネクション管理: D1はHTTPベースで背後でSQLiteを操作するため、従来のRDBのような「コネクションプールの枯渇」を気にする必要はない。むしろ、リクエストごとにクエリをプリペアドステートメント(`db.prepare()`)で安全にキャッシュし、SQLインジェクションを完全に防ぐ設計を徹底すること。

—

結び:インフラストラクチャの未来へ

Cloudflare WorkersとD1、そしてPulumiによる構成管理。このスタックを使いこなした瞬間、あなたは「サーバーをどこに置くか」という旧世紀の悩みから解放される。

インフラはコードであり、コードは地球を覆うエッジの神経網そのものとなる。
この圧倒的な速度と、IaCがもたらす絶対的な秩序を武器に、君だけの要塞をエッジの深淵に築き上げてほしい。

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