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

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秒の制限はほとんど影響しません。


参考リンク