メインコンテンツまでスキップ

Cloudflare Workers — Azure Functions相当のエッジ関数

Cloudflare WorkersはAzure Functionsに相当するサーバーレス関数プラットフォームですが、実行モデルが根本的に異なります。

観点Azure FunctionsCloudflare Workers
実行場所特定リージョン(japaneast等)ユーザー最寄りのエッジPoP(300+拠点)
コールドスタートあり(Consumption planで数百ms〜秒単位)なし(V8 Isolateの事前起動)
スケールアウトインスタンスが複製される(stateful可)リクエストごとにIsolateが起動(stateless)
最大実行時間10分(Consumption)〜無制限(Premium/Dedicated)CPU時間:無料10ms / 有料30秒
ランタイム.NET / Node.js / Python / Java 等JavaScript / TypeScript / Python / Rust
課金実行時間 × メモリリクエスト数(無料10万回/日)
.NETについて

Cloudflare WorkersはC#/.NETをサポートしていません。 技術的にはWebAssemblyへのコンパイルで動作させる方法はありますが、.NET WASMバンドルサイズ(数十MB)がWorkersの上限(有料プランで10MB compressed)を大幅に超えるため、現実的ではありません。

Azure Functions + .NETを移行・並用する場合のアーキテクチャは後述を参照してください。


トリガーの対比​

Azure Functionsのトリガーに相当するWorkersのハンドラーです。

Azure Functions トリガーWorkers ハンドラー用途
HTTP TriggerfetchHTTPリクエスト処理
Timer TriggerscheduledCron定期実行
Queue Trigger (Service Bus)queueCloudflare Queuesからの消費
Blob Triggerなし(R2イベントは別途)—
Event Grid Triggerなし—
Email TriggeremailCloudflare Email Routing

バインディングの対比​

Azure Functionsの入出力バインディングに相当するWorkers Bindingsです。envオブジェクト経由でアクセスします。

Azure Functions バインディングWorkers Binding説明
Blob Storage (in/out)R2Bucketオブジェクトストレージ
Table StorageKVNamespaceキーバリューストア
Service BusQueueメッセージキュー
SQL DatabaseD1DatabaseSQLiteエッジDB
Cosmos DBKVNamespace / D1Database用途に応じて選択
SignalRDurableObjectWebSocket・リアルタイム
HTTP (output)Fetcher(Service Binding)別Workerの呼び出し

Workerの基本構造​

HTTPトリガー(Azure HTTP Trigger相当)​

// src/index.ts
export interface Env {
// wrangler.tomlで定義したバインディングを型で宣言
MY_KV: KVNamespace;
MY_BUCKET: R2Bucket;
MY_DB: D1Database;
API_KEY: string; // wrangler secret put API_KEY で設定した値
}

export default {
// Azure Functions の Run(HttpRequest req, ...) に相当
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const url = new URL(request.url);

// ルーティング(Azure Functions の function.json routeに相当)
if (url.pathname === '/api/hello' && request.method === 'GET') {
return Response.json({ message: 'Hello from the edge!' });
}

if (url.pathname === '/api/items' && request.method === 'POST') {
const body = await request.json<{ name: string }>();

// KVに保存(Azure Table Storage outputバインディング相当)
await env.MY_KV.put(`item:${crypto.randomUUID()}`, JSON.stringify(body));

return Response.json({ ok: true }, { status: 201 });
}

return new Response('Not Found', { status: 404 });
},
};

Timer Trigger(Cron定期実行)​

Azure FunctionsのTimerTriggerに相当します。wrangler.tomlでCronスケジュールを定義します。

# wrangler.toml
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2024-11-01"

# Azure Functions の TimerTrigger schedule に相当
[[triggers]]
crons = ["0 */6 * * *"] # 6時間ごと
export default {
async fetch(request: Request, env: Env): Promise<Response> {
return new Response('OK');
},

// Timer Trigger に相当
async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext): Promise<void> {
console.log(`Cron triggered: ${event.cron} at ${event.scheduledTime}`);

// D1 DBのクリーンアップ処理など
await env.MY_DB.prepare(
'DELETE FROM logs WHERE created_at < ?'
).bind(Date.now() - 7 * 24 * 60 * 60 * 1000).run();
},
};

Queue Consumer(Queue Trigger相当)​

Azure FunctionsのService Bus Queue Triggerに相当します。

# wrangler.toml
[[queues.consumers]]
queue = "my-queue"
max_batch_size = 10
max_batch_timeout = 30
export default {
async fetch(request: Request, env: Env): Promise<Response> {
return new Response('OK');
},

// Azure Functions の ServiceBusTrigger に相当
async queue(batch: MessageBatch<{ orderId: string }>, env: Env): Promise<void> {
for (const message of batch.messages) {
try {
await processOrder(message.body.orderId, env);
message.ack(); // 処理成功
} catch (err) {
message.retry(); // リトライキューに戻す
}
}
},
};

リソースバインディングの使い方​

KV(キーバリューストア)​

// 書き込み(TTL付き)
await env.MY_KV.put('session:abc123', JSON.stringify({ userId: 1 }), {
expirationTtl: 3600, // 1時間後に自動削除
});

// 読み取り
const raw = await env.MY_KV.get('session:abc123');
const session = raw ? JSON.parse(raw) : null;

// 削除
await env.MY_KV.delete('session:abc123');

// プレフィックスで一覧取得
const { keys } = await env.MY_KV.list({ prefix: 'session:' });

R2(オブジェクトストレージ)​

// アップロード(Azure Blob Storage output bindingに相当)
await env.MY_BUCKET.put('uploads/image.png', request.body, {
httpMetadata: { contentType: 'image/png' },
customMetadata: { uploadedBy: 'user123' },
});

// ダウンロード
const object = await env.MY_BUCKET.get('uploads/image.png');
if (!object) return new Response('Not Found', { status: 404 });

return new Response(object.body, {
headers: { 'Content-Type': object.httpMetadata?.contentType ?? 'application/octet-stream' },
});

// 削除
await env.MY_BUCKET.delete('uploads/image.png');

D1(SQLiteエッジDB)​

// SELECT
const result = await env.MY_DB.prepare(
'SELECT * FROM users WHERE id = ?'
).bind(userId).first<{ id: number; name: string; email: string }>();

// INSERT
await env.MY_DB.prepare(
'INSERT INTO users (name, email) VALUES (?, ?)'
).bind('山田太郎', 'yamada@example.com').run();

// バッチ実行(トランザクション相当)
await env.MY_DB.batch([
env.MY_DB.prepare('UPDATE orders SET status = ? WHERE id = ?').bind('shipped', orderId),
env.MY_DB.prepare('INSERT INTO order_logs (order_id, event) VALUES (?, ?)').bind(orderId, 'shipped'),
]);

Service Binding(Worker間呼び出し)​

別のWorkerをHTTPなしで内部呼び出しできます。Azure FunctionsのDurable Functions CallActivityに近いです。

# wrangler.toml
[[services]]
binding = "AUTH_WORKER"
service = "auth-worker"
// 別Workerを内部呼び出し(HTTPオーバーヘッドなし)
const authResponse = await env.AUTH_WORKER.fetch(
new Request('https://internal/verify', {
method: 'POST',
body: JSON.stringify({ token }),
})
);
const { valid, userId } = await authResponse.json<{ valid: boolean; userId: string }>();

デプロイ方法​

1. Wrangler CLIで手動デプロイ​

# 本番にデプロイ
wrangler deploy

# 環境指定デプロイ(wrangler.tomlの[env.staging]を使用)
wrangler deploy --env staging

2. GitHub Actionsで自動デプロイ​

# .github/workflows/deploy.yml
name: Deploy to Cloudflare Workers

on:
push:
branches: [main]

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'

- name: Install dependencies
run: npm ci

- name: Run tests
run: npm test

- name: Deploy to Cloudflare Workers
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --env production

3. Terraformでデプロイ(Infrastructure as Code)​

Azure ARM Templates / BicepのようにIaCでWorkerを管理できます。

# main.tf
terraform {
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 4.0"
}
}
}

provider "cloudflare" {
api_token = var.cloudflare_api_token
}

# Workerスクリプトのデプロイ
resource "cloudflare_worker_script" "api" {
account_id = var.account_id
name = "my-api-worker"
content = file("dist/index.js")

# 環境変数(非機密)
plain_text_binding {
name = "API_BASE_URL"
text = "https://api.example.com"
}

# KVバインディング
kv_namespace_binding {
name = "MY_KV"
namespace_id = cloudflare_workers_kv_namespace.sessions.id
}

# R2バインディング
r2_bucket_binding {
name = "MY_BUCKET"
bucket_name = cloudflare_r2_bucket.uploads.name
}
}

# KV Namespace
resource "cloudflare_workers_kv_namespace" "sessions" {
account_id = var.account_id
title = "my-sessions-kv"
}

# R2 Bucket
resource "cloudflare_r2_bucket" "uploads" {
account_id = var.account_id
name = "my-uploads"
location = "APAC"
}

# HTTPルート(カスタムドメイン)
resource "cloudflare_worker_route" "api" {
zone_id = var.zone_id
pattern = "api.example.com/*"
script_name = cloudflare_worker_script.api.name
}

# Cronトリガー
resource "cloudflare_worker_cron_trigger" "cleanup" {
account_id = var.account_id
script_name = cloudflare_worker_script.api.name
schedules = ["0 */6 * * *"]
}

D1マイグレーション管理​

D1はバージョン管理されたマイグレーション機構を備えています。連番のSQLファイルを順番に適用し、適用済みの状態を d1_migrations テーブルで追跡します。

Entity Framework Migrationsとの違い(よくある誤解)

EF Core MigrationsはAzure SQL Databaseの機能ではなく、Entity Framework Core(.NETのORM)が提供する機能です。SQL Server / Azure SQL / PostgreSQL / SQLiteなど複数のデータベースで動作します。「Azure SQL DatabaseのMigrations」という固有機能が存在するわけではありません。

設計思想も異なります。

  • D1マイグレーション:手書きのSQLファイルを順番に適用する**SQLスクリプトベース(DBファースト)**の方式。.NET/Azureの世界では DbUp / Flyway や SQL Database Project(DACPAC / sqlpackage)に近い。
  • EF Core Migrations:C#のモデルクラスの差分からマイグレーションコード(Up/Down)を自動生成するコードファーストの方式。

「バージョン管理されたマイグレーションを順次適用する」という点は共通ですが、両者を単純に同一視するのは正確ではありません。

# マイグレーションファイルを作成
wrangler d1 migrations create my-database add_users_table
# → migrations/0001_add_users_table.sql が生成される
-- migrations/0001_add_users_table.sql
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
created_at INTEGER NOT NULL DEFAULT (unixepoch())
);

CREATE INDEX idx_users_email ON users(email);
# ローカルDBに適用
wrangler d1 migrations apply my-database --local

# 本番DBに適用
wrangler d1 migrations apply my-database

# 適用状況確認
wrangler d1 migrations list my-database

.NET開発者のための移行戦略​

WorkersはC#をサポートしないため、.NETのワークロードを完全にWorkersへ移行することは現実的ではありません。以下のハイブリッドアーキテクチャが推奨です。

ユーザー
│
▼
[Cloudflare Workers — TypeScript]
├── 認証・セッション検証(JWT検証、KVでセッション管理)
├── レートリミット・Bot判定
├── 静的アセット配信(R2)
├── 軽量なCRUD(D1)
└── 重い処理はAzureへプロキシ
│
▼
[Azure Functions — .NET 10]
├── 複雑なビジネスロジック
├── Azure SQL / Cosmos DB 操作
├── Azure Service Bus 処理
└── 外部API統合

役割分担の目安​

処理Workers (TypeScript)Azure Functions (.NET)
JWT検証✅ 向いている(エッジで即時検証)—
静的ファイル配信✅ R2 + CDN—
軽量なKVキャッシュ✅ Workers KV—
簡単なCRUD (SQLite規模)✅ D1—
複雑なビジネスロジック—✅ 向いている
Azure SQL / Cosmos DB❌ 直接接続できない✅ ネイティブバインディング
既存の.NETライブラリ利用❌ 非対応✅
バッチ・長時間処理❌ CPU時間上限あり✅

制限事項​

項目無料プラン有料プラン (Workers Paid)
リクエスト数10万/日1,000万/月(超過分 $0.30/100万)
CPU時間/リクエスト10ms30秒
メモリ128MB128MB
スクリプトサイズ3MB(gzip後)10MB(gzip後)
KVデータサイズ(値)25MB25MB
R2オブジェクトサイズ5GB5GB
D1データベースサイズ500MB2GB
Workers実行数(同時)制限なし(Isolateによる分離)制限なし
CPU時間 vs 実行時間

WorkersのCPU時間は「実際にCPUを使った時間」のみカウントされます。外部APIへのfetch待機中はカウントされないため、Azure Functionsの実行時間とは異なる概念です。I/Oバウンドな処理であれば30秒の制限はほとんど影響しません。


参考リンク​