🙆

入門Cloudflare Workers

2023/04/12に公開

はじめに

この記事はCloudflare Workersの入門記事です。

  • 名前は聞いたことがあるけれどCloudflare Workersが何者か知らない方
  • 「Cloudflare Workersはサーバーレス・エッジコンピューティングサービスだよ」と説明されて日本語でOKと感じた方
  • AWSのLambdaやGCPのCloud Runと似たコンセプトのサービスだろうと認識されている方

上記に当てはまる方のお役に立てるはずです。

開発環境の構築

まずは開発環境を構築しましょう。といっても、最新のnode.jsをインストールするだけです。

インストールできたらバージョンを確認しておきましょう。

$ node --version
v18.17.0

補足

  • v16.13.0より新しいバージョンのnodeが必要になります。記事を読み進めて不具合が発生した場合はnodeのバージョンを確認してください。
  • Windowsの動作検証はしていません。ここから先の手順は問題なく動作するはずですが、不具合が発生した場合はWSLなどのLinux環境を利用してください。

サインアップ

それではCloudflare Workersを試してみましょう。なんと無料で使えるFreeプランが用意されています。太っ腹ですね。

もちろんクレジットカードの登録は不要です。気づかない間に課金されてクラウド破産することはありません。ご安心ください。

トップページのリンクは以下になります。Sign upをクリックしてサインアップしてください。

サブドメインの設定

サインアップが完了したらCloudflareのトップページを開いてLoginをクリックしてください。ダッシュボードが表示されます。

Cloudflare WorkersはプロジェクトごとにURLとしてhttps://プロジェクト名.サブドメイン.workers.devを発行してくれます;。サブドメインは自由に設定できます。

ダッシュボードのWorkersをクリックするとサブドメインの入力が求められます。お好みのサブドメインを設定してください。

以降はサブドメインとしてexampleを設定したものと仮定して説明を進めます。

CLIツール

Cloudflare Workersはダッシュボードから操作できます。しかし、ダッシュボードの見た目は変更されるかもしれません。スクリーンショットを貼り付けた説明は変更に追従するのが難しいのです。

そこで、CLIツールを利用して説明することにします。一度コマンドを覚えれば作業効率もアップします。

プロジェクトの初期化とデプロイ

それではプロジェクトの初期化とデプロイを行いましょう。

ここではプロジェクト名として「hello」を設定することにします。以下のコマンドを実行してください。

$ npm create cloudflare@latest

コマンドを実行すると、いくつか質問されます。以下のように回答してください。

  1. In which directory do you want to create your application? also used as application name
  • アプリケーションを作成するディレクトリを指定します。プロジェクトの名前を兼ねます。
  • ここではhelloと入力してください。
  1. What type of application do you want to create?
  • 作成するアプリケーションの種類を選びます。上下カーソルキーで選択できます。
  • ここでは「"Hello World" Worker」を選んでください。
  1. Do you want to use TypeScript? Yes / No
  • 実装言語としてTypeScriptを利用するか選びます。
  • TypeScriptは必須ではありません。ただし、今回は説明を進める上で都合が良いためTypeScriptを選ぶことにします。
  • ここでは「y」と入力してください。「y」を入力した段階で次のステップに進むため「yes」の3文字を入力する必要はありません。
  1. Do you want to deploy your application? Yes / No
  • 今すぐアプリケーションをデプロイするか選びます。
  • ここでは「y」と入力してください。
  • 自動的にブラウザが起動して認証画面が表示されます。URLが間違いなくcloudflare.comであることを確認した後、Allowボタンを押してください。

以上でプロジェクトの初期化とデプロイは完了です。

コマンドの実行が成功すると、以下のメッセージが出力されます。それと同時にhelloディレクトリが作成されます。

$ npm create cloudflare@latest
...省略...
├ Selecting Cloudflare account retrieving accounts
│ account アカウント名's Account
│
├ Deploying your application
│ deployed via `npm run deploy`
│
├  SUCCESS  View your deployed application at https://hello.example.workers.dev

動作確認

すでにWorkerはデプロイされて、インターネットからアクセス可能になっています。以下のコマンドを実行して確認してみましょう。

$ curl https://hello.example.workers.dev
Hello World!

おめでとうございます、Workerは元気に稼働中です!

※レスポンスに改行文字「\n」を含めていないため、実際には「Hello, World!$ 」のようにプロンプトの文字「$」が続けて表示されるはずです。

生成されたファイルの確認

続いて生成されたファイルを確認しましょう。helloディレクトリ内のsrc/worker.tsを開いてください。

/**
 * Welcome to Cloudflare Workers! This is your first worker.
 *
 * - Run `npm run dev` in your terminal to start a development server
 * - Open a browser tab at http://localhost:8787/ to see your worker in action
 * - Run `npm run deploy` to publish your worker
 *
 * Learn more at https://developers.cloudflare.com/workers/
 */

export interface Env {
	// Example binding to KV. Learn more at https://developers.cloudflare.com/workers/runtime-apis/kv/
	// MY_KV_NAMESPACE: KVNamespace;
	//
	// Example binding to Durable Object. Learn more at https://developers.cloudflare.com/workers/runtime-apis/durable-objects/
	// MY_DURABLE_OBJECT: DurableObjectNamespace;
	//
	// Example binding to R2. Learn more at https://developers.cloudflare.com/workers/runtime-apis/r2/
	// MY_BUCKET: R2Bucket;
	//
	// Example binding to a Service. Learn more at https://developers.cloudflare.com/workers/runtime-apis/service-bindings/
	// MY_SERVICE: Fetcher;
	//
	// Example binding to a Queue. Learn more at https://developers.cloudflare.com/queues/javascript-apis/
	// MY_QUEUE: Queue;
}

export default {
	async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
		return new Response('Hello World!');
	},
};

src/worker.tsの解説

生成されたsrc/worker.tsについて説明します。

まず、冒頭のブロックコメントはCloudflare Workersの紹介文です。読み飛ばして構いません。

次はEnvインターフェースの定義です。Cloudflareは様々なサービスを提供しており、それらとWorkerを連携する際に必要になります。発展的な内容になるため、ひとまず読み飛ばしましょう。

最後は関数定義です。async fetchと書かれている部分に注目してください。これがWorkerのエントリーポイントになります。

ライフサイクル

fetch関数で実行される処理について、流れを説明します。

  1. HTTPリクエストを受け取る
  2. 何らかの処理を行う
  3. HTTPレスポンスを返す

これだけです。

Workerはfetch関数の実行単位です。Cloudflareのサーバーにリクエストが到着すると、その都度Workerが作成されてfetch関数が実行されます。fetch関数の実行が完了するとWorkerは削除されます。

Cloudflare WorkersはWorkerのホスティングサービスです。サーバーを構築することなくサーバーサイドの処理を実装できるため「サーバーレス」とよばれています。

リクエストとレスポンスの型

改めてfetch関数に注目してください。引数のRequestと戻り値のResponseはJavaScript標準APIで定義されたインターフェースに準拠しています。

例えばレスポンスのヘッダーをカスタマイズするには、Responseコンストラクタの第2引数にHeadersのインスタンスを渡します。

// Content-Lengthヘッダーは自動的に設定されます。手作業で設定する必要はありません。
const headers = new Headers({
	"Content-Type": "application/json",
});

return new Response("{values: [1, 2, 3]}", { headers });

リクエストとレスポンスの構造については各自でMDNのドキュメントを参照してください。

なおfetch関数には第3の引数としてctx: ExecutionContextが定義されています。こちらもEnvインターフェースと同様に発展的な内容になるため、説明をスキップします。

ローカル環境の動作確認

いきなりデプロイしてしまいましたが、Workerはローカル環境で動作確認することもできます。以下のコマンドを実行してください。

$ npx wrangler dev

デフォルトではhttp://localhost:8787でリクエストを待ち受けます。curlコマンドを試してみましょう。

$ curl http://localhost:8787
Hello World!

コードの変更をデプロイする

おもしろいのはここからです。src/worker.tsのレスポンスのメッセージを「Hello Worker!」に変更してください。

return new Response('Hello Worker!');

この変更をデプロイしましょう。以下のコマンドを実行してください。

$ npx wrangler deploy

デプロイが完了したら、curlコマンドを実行してみてください。

$ curl https://hello.example.workers.dev
Hello Worker!

素晴らしい!

通常、デプロイは数秒で完了します。レスポンスのメッセージをあれこれ書き換えてnpx wrangler deployを実行してみてください。この手軽さがCloudflare Workersの魅力です。

用途の検討

ここからはCloudflare Workersがどのような場面で利用できるか検討します。

ロジックの隠蔽

まず考えられるのが、ロジックの隠蔽です。例えば、以下のような処理をWorkerで実行するとします。

export interface Env {
	// ...
}

const omikuji = ['大吉', '中吉', '小吉'];

export default {
	async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
		const i = Math.floor(Math.random() * omikuji.length);

		return new Response(omikuji[i]);
	},
};

おみくじを引くWorkerです。リクエストごとにランダムな結果が帰ってきます。

$ curl https://hello.example.workers.dev
大吉

$ curl https://hello.example.workers.dev
中吉

$ curl https://hello.example.workers.dev
中吉

どれだけ難読化してもクライアントサイドのJavaScriptは解析される恐れがあります。そこで、解析されて困るロジックをWorkerで実行することで隠蔽します。

パケットがインターネットを往復することになるため数十msのレイテンシーは避けられませんが、ちょっとした処理を隠蔽する用途としては最適です。

cfプロパティを用いた動的なコンテンツ配信

Requestインターフェースに注目してください。JavaScript標準APIに準拠すると同時に、実はCloudflareが独自に定義したcfプロパティが追加されています。

多くのcfプロパティが用意されているのですが、ここでは以下をピックアップします。

  • country: 国コード
  • asn: AS番号
  • asOrganization: AS組織名

上記のcfプロパティを返す実装例を示します。

export interface Env {
	// ...
}

export default {
	async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
		return Response.json({
			country: request.cf.country,
			asn: request.cf.asn,
			asOrganization: request.cf.asOrganization,
		});
	},
};

以下のような結果が得られます。

$ curl https://hello.example.workers.dev
{
  "country": "JP",
  "asn": 9605,
  "asOrganization": "NTT Docomo"
}

ここでは例としてdocomo回線のスマホにテザリングした状態でcurlコマンドを実行しました。リクエスト送信元のネットワークが正しく判定されています。

cfプロパティを利用すれば動的なコンテンツの配信が簡単に実装できます。例えばニュースフィードを実装するとして、countryの値でユーザーが居住している国ごとにコンテンツを出汁分けることができます。

Cloudflare Workersを深く知る

ここからはCloudflare Workersを使いこなすために把握しておくべき項目について説明します。

Workerは世界中にデプロイされる

ここまで何度かnpx wrangler deployを実行しました。その際、デプロイ先のサーバーは指定しませんでした。

デプロイしたコードはどこに設置されたサーバーで実行されるのでしょうか。日本でしょうか、あるいは米国でしょうか。

答えは世界中です。

coloに格納される値の検証

確かめてみましょう。cfプロパティにはリクエストが到着したデータセンターを識別するcoloが用意されています。

colo string

The three-letter IATA airport code of the data center that the request hit, for example, "DFW".

(引用元)https://developers.cloudflare.com/workers/runtime-apis/request/#incomingrequestcfproperties

coloに格納される値はデータセンター最寄りの空港を示すIATAコードです。

以下のコードをnpx wrangler deployでデプロイしてください。

export interface Env {
	// ...
}

export default {
	async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
		return Response.json({
			colo: request.cf.colo,
			country: request.cf.country,
			asn: request.cf.asn,
			asOrganization: request.cf.asOrganization,
		});
	},
};

以下、AWSのEC2からCloudflare Workersに向けてリクエストを送信したときの結果を示します。検証を行ったリージョンは米国のバージニア北部(us-east-1a)と日本の東京(ap-northeast-1a)です。末尾に1aとあるように、Availability Zoneはそれぞれ1aを選んでいます。

リクエストの送信元 country colo asn asOrganization
us-east-1a US IAD 14618 Amazon.com
ap-northeast-1a JP NRT 16509 Amazon.com

ご覧のとおり、coloの値が変化しています。リクエストの送信元に最も近い地域のデータセンターにリクエストが届いたことを確認できました。

このように、ユーザーに最も近い場所でサービスを提供する形態をエッジコンピューティングとよびます。加えて、私たちはサーバーを運用する必要がないため、Cloudflare Workersはサーバーレス・エッジコンピューティングとよばれています。

レイテンシーの測定

疑い深い方のために、さらに追加で検証してみましょう。以下のようにtimeコマンドを用いてレスポンスが返ってくるまでの時間を測定します。

$ time curl https://hello.example.workers.dev

以下、us-east-1aとap-northeast-1aの実行結果を示します。

リージョン 1回目の測定結果 2回目の測定結果 3回目の測定結果 4回目の測定結果 5回目の測定結果
us-east-1a 44 ms 32 ms 226 ms (※1) 38 ms 34 ms
ap-northeast-1a 48 ms 36 ms 39 ms 35 ms 37 ms

仮にCloudflare Workersのデプロイ先が米国であれば、日本からリクエストを送信してレスポンスを受信するまでに太平洋を往復することになります。しかし、リクエストの送信元に関わらずレイテンシーは40 ms前後でほぼ均一です。

この結果から、リクエストの送信元に最も近いデータセンターが選択されており、それらのデータセンターで同じコードが動作していることが確認できます。

補足

  1. us-east-1aからCloudflare Workersにリクエストを送信すると、まれに200 ms程度のレイテンシーが発生しました。どのような条件下でレイテンシーが増えるのかは特定できませんでしたが、リクエストがbotによるDDoS攻撃かどうか判定しているのかもしれません。

IATA空港コードについて

余談になりますが、Cloudflareのドキュメントによると日本国内のCloudflareサーバーは東京・大阪・福岡・那覇の4都市に配置されているそうです。

先ほどの検証結果を見てみると、coloの値がHND(羽田空港)ではなくNRT(成田空港)と表示されています。Cloudflareは千葉を東京圏内と判定しているのか、あるいはcoloの割り振りは大雑把なのか、その点はよくわかりません。

ランタイム

ここまでランタイムについては触れていませんでした。サーバーレスとはいえ、仕組みがどうなっているか把握しておくことは必要です。

まず、Cloudflare WorkersのJavaScriptランタイムにはV8が使われています。V8はnode.jsやChromiumブラウザで採用されているJavaScriptランタイムです。別の言い方をすれば、CloudflareのサーバーでホストされているV8インスタンスをCloudflare Workersと命名したのだと考えて差し支えないでしょう。

V8は関数を実行するためのサンドボックスを提供します。サンドボックスはリクエストごとに作成されるため、Worker同士は隔離されます。Cloudflare WorkersはVMやコンテナの代わりにV8を採用することで実質的な仮想化を実現していると言えます。

ブラウザでタブを開いた状況を想像してください。例えば、github.comを開いたタブとzenn.devを開いたタブがあるとします。それぞれのタブは隔離されているため、互いの通信を盗み見たり勝手にスクリプトを操作したりできません。V8は仮想化ソフトウェアではありませんが、仮想化と同等の機能を実現していると言えます。

従って、Cloudflare Workersはプロセス間の通信を行えません。また、異なるWorker同士で処理の結果を共有する場合はCloudflare KV、処理の結果を永続化する場合はCloudflare R2などの別サービスと連携する必要があります。

グローバル変数

ところで、サンドボックスの話に関連して注意が必要な箇所があります。グローバル変数です。以下のコードをみてください。

export interface Env {
	// ...
}

// 説明を簡単にするためcount変数のロックは考慮していません。
var count = 0;

export default {
	async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
		count += 1;

		return new Response(count);
	},
}

上記はアクセスカウンターです。リクエストを受け取る旅にグローバル変数countがインクリメントされます。

紛らわしいのですが、サンドボックスで隔離されるのはWorker単位です。例えばhttps://aaa.example.workers.devhttps://bbb.example.workers.devがあるとします。当然お互いのグローバル変数にはアクセスできません。

しかし、同じWorkerインスタンスであれば実行時の環境は引き継がれるためグローバル変数が参照できます。そのため、npx wrangler devを実行してローカルでWorkerを動作させる場合と、CloudflareのサーバーでWorkerを動作させる場合を比べると、挙動が異なるように見える場合があります。

ローカルでWorkerを動作させる場合の実行例

$ curl http://localhost:8787
1

$ curl http://localhost:8787
2

$ curl http://localhost:8787
3

CloudflareのサーバーでWorkerを動作させる場合の実行例

$ curl https://hello.example.workers.dev
1

$ curl https://hello.example.workers.dev
1

$ curl https://hello.example.workers.dev
1

CloudflareのサーバーでWorkerを動作させると、レスポンスとして常に1が返されます。これは正しい挙動です。Cloudflareのサーバーで実行されるWorkerは冗長化されています。つまり、上記の例で1番目・2番目・3番目のリクエストを受け取ったWorkerインスタンスはそれぞれ異なります。

一見すると正しくインクリメントされていないように見えますが、それぞれのWorkerインスタンスでは正しくインクリメントされています。そのため、偶然に同じWorkerインスタンスが使い回されるとレスポンスとして2や3が返ってくる可能性はあります。

何にせよ、グローバル変数に依存した処理を実装するべきではありません。CloudflareのドキュメントにもWorkerインスタンスがどのタイミングで破棄されるのか言及がありません。基本的には、リクエストのたびに作成されて、処理の完了と同時に破棄されると考えて処理を実装するべきです。

リソースの制限

あんなこといいな、できたらいいなと検討中の皆様に残念なお知らせです。Cloudflare Workersには厳しいリソース制限が設けられています。

Worker単位の制限

項目 無料プラン 有料プラン
CPUの利用時間 10 ms 50 ms
メモリ容量 128 MB 128 MB
環境変数の個数 64個 128個
環境変数の容量 5 KB 5 KB
Worker自体の容量 1 MB 5 MB

CPU利用時間の制限

注目していただきたいのがCPUの利用時間です。数値の単位はms(ミリ秒)です。「無料プランは10ミリ秒!?これじゃあHello, World!の文字列を返すくらいしか使い道がないぞ?」と思われるかもしれません。

しかし、悲しむ必要はありません。以下のコードを実行してみてください。

export interface Env {
	// ...
}

async function sleep(duration: number): Promise<string> {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			resolve(`I slept for ${duration} ms.`);
		}, duration);
	});
}

export default {
	async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
		const url = new URL(request.url);
		const duration = url.searchParams.get('duration');
		const message = await sleep(Number.parseInt(duration) || 1000);

		return new Response(message);
	},
};

上記はクエリパラメータdurationで指定された時間スリープするコードです。

それでは試してみましょう。例として5000ミリ秒スリープさせてみます。

$ curl 'https://hello.example.workers.dev?duration=5000'
I slept for 5000 ms.

curlコマンドを実行してから5秒後に上記のメッセージが表示されます。CPUの利用時間は10 msに制限されているのに、なぜ実行できるのでしょうか。

そうです、カウントされるのはリクエストを受け取ってからレスポンスを返すまでの所要時間ではなく、CPUの利用時間です。

上記のコードの場合、await sleep(duration)が実行された段階で処理は一時停止して実行キューに積まれます。その後、5秒経過するとスケジューラが処理を再開してレスポンスを返します。setTimeoutでスリープしている間の5秒間はCPU利用時間としてカウントされません。

他に時間のかかりそうな処理と言えば、URLのパースと数値型への変換くらいのものです。細かく見ればメッセージを組み立てるのに文字列テンプレートを利用しているため、その辺りもCPU利用時間を消費しそうです。とはいえ合計しても数百マイクロ秒か、多く見積もっても数ミリ秒のオーダーで処理は完了するはずです。

従って、JavaScriptの非同期処理をうまく利用すれば、10 msあるいは50 msの制限の中でもある程度複雑な処理が行えます。

例えばWorkerからHTTPリクエストを送信することが可能です。なぜなら実行時間の内訳はそのほとんどがレスポンスが帰ってくるまでの待ち時間だからです。CPUの利用時間はごくわずかです。

所要時間の制限

Cloudflareのドキュメントによると、所要時間に制限はありません。ただし、実行から30秒が経過すると強制的にタイムアウトされる可能性があります。

There is no hard limit for duration. However, after 30 seconds, there is a higher chance of eviction.

(引用元)Limits - Cloudflare Workers docs

試してみましょう。先ほどのコードを使って60秒間スリープさせてみます。

$ curl 'https://hello.example.workers.dev?duration=60000'

以下のようにタイムアウトした旨のメッセージが表示されました。

$ curl 'https://hello.example.workers.dev?duration=60000'
<html>
<head><title>504 Gateway Time-out</title></head>
<body>
...

なお、上記のメッセージが表示されたのはcurlコマンドを実行してから90秒ほどが経過した時点でした。60秒のスリープから復帰した時点ですぐにタイムアウトされるわけではありません。また、30秒の制限に到達した瞬間にタイムアウトするわけでもありません。

おそらく、30秒の制限に達した段階でスケジューラーはWorkerの優先度を下げるのでしょう。その後、サーバーの混雑状況を考慮して適当なタイミングでタイムアウトした旨のレスポンスを返すようです。

メモリ容量の制限

CPU利用時間の制限とは異なり、メモリ容量128 MBの制限は工夫でどうにかする余地がありません。また、無料プランと有料プランでメモリ容量には差がないため、課金すれば解決する問題でもありません。

メモリの浪費を防ぐ工夫

メモリの制限については工夫の余地がないと説明しましたが、浪費を防ぐ工夫はできます。以下のコードをみてください。

export interface Env {
	// ...
}

export default {
	async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
		const a = new Uint8Array(100 * 1024 * 1024);
		const b = new Uint8Array(100 * 1024 * 1024);
		const c = new Uint8Array(100 * 1024 * 1024);

		return new Response('Done!');
	},
};

上記のコードは100 MBのUint8Arrayを3つ作成するため、合計300 MBのメモリを確保することになります。当然、メモリ容量の制限に達してエラーが発生します。

$ curl https://hello.example.workers.dev
<!DOCTYPE html>
<head>
<title>Worker exceeded resource limits | memory.example.workers.dev | Cloudflare</title>
<meta charset="UTF-8" />
...

それでは、以下のコードはどうでしょうか?

export interface Env {
	// ...
}

export default {
	async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
		for (let i = 0; i < 3; i++) {
			const a = new Uint8Array(100 * 1024 * 1024);
		}

		return new Response('Done!');
	},
};

実行してみると、エラーは発生しません。

$ curl https://hello.example.workers.dev
Done!

上記のコードはforループの中で100 MBのUint8Arrayを作成しています。ループは3回実行されるため、合計300 MBのメモリが確保されそうに見えます。

V8の実装を確認したわけではないので推測になりますが、各ループのスコープから抜けるたびに変数aのリファレンスカウントは0になるはずです。そうすると誰からも参照されていないのですから、その時点でメモリは未使用と見なされます。

GCに回収されるまで本当の意味でメモリが解放されたとは言えないかもしれませんが、使用中と見なされるメモリの容量が128 MBを下回っていれば処理は継続できるようです。

AWS LambdaやGCP Cloud Runとの比較

Cloudflare Workersと似たコンセプトのサービスとしてAWS LambdaやGCP Cloud Runがあります。これらのサービスとの違いを比較します。

コンテナ仮想化ではない

LambdaやCloud Runはコンテナ仮想化を利用しています。一方、JavaScriptランタイムのV8を実質的な仮想化ソフトウェアとして利用しているのがCloudflare Workersの特徴です。

Cloudflare Workersの場合はコンテナの起動を待つ必要がありません。従って、コールドスタートとホットスタートの概念はありません。言うならばCloudflare Workersは常にホットな状態です。

加えて、Cloudflare Workersは公式に「0 ms Cold Starts」をうたっています。V8による仮想化が軽量とはいえ0 msは誇張表現と思われるかもしれません。それを実現するための技術的な背景についてまとめられた記事がありましたので、参考にしてください。

厳しいリソース制限

LambdaやCloud Runは目的に応じて性能を選ぶことができます。また、ほとんどの場合、性能不足に陥ったとしても課金することで解決できます。

一方、Cloudflare WorkersはCPU利用時間とメモリ容量に厳しい制限が課されています。さらに、記事執筆時点ではメモリ容量128 MBの制限は無料プランと有料プランで同一です。いくら課金しても性能不足を解決できません。

Cloudflare Workersは軽いタスクを素早く終わらせるのに適しています。従って重量級のタスク、例えば画像処理の実行基盤としては不向きです。

この点はCloudflareも承知しているようです。例えば画像のリサイズと最適化を行うフルマネージドなサービスとしてCloudflare Imagesが提供されています。

少ない資源をやりくりするのは腕の見せ所です。しかし、要件にフィットするなら素直にマネージドサービスを利用する方がお手軽かつ安上がりかもしれません。

言語のサポート

LambdaやCloud Runはコンテナ仮想化を採用しています。そのため、カスタムイメージのコンテナさえビルドできれば利用する言語は問いません。広く利用されているかはともかく、RustやC++を選択しても構いません。

一方、Cloudflare WorkersはランタイムにV8を採用しています。従って、基本的にはJavaScriptで実装することになります。あるいはTypeScriptなど、JavaScriptにトランスパイル可能な言語を選ぶことになります。

なお実験的な機能ではありますが、Cloudflare WorkersはWASI(Web Assembly System Interface)をサポートしています。興味のある方はチュートリアルが公開されているので試してみてください。

ただし、WASIが正式にサポートされたとしても任意の言語が活かせるとは限りません。

例えば先ほどのThe Cloudflare Blogでは利用できる言語としてGoやSwiftが紹介されています。Goコンパイラは直接WASIバイナリを出力できますし、SwiftはLLVMを経由してWASIバイナリを出力できます。

しかし、Goの魅力は高速なランタイムにあります。SwiftであればApple製品をターゲットにした高効率なフレームワークが用意されているのが魅力です。

Goの場合、コードはWASIランタイムで実行されることになるため、せっかくのGoランタイムが行かせません。Swiftの場合はAppleが提供しているフレームワークが利用できるとは限りません。

従って、WASIバイナリが出力できると言ってもメリットは任意の言語のシンタックスでコードが書けるだけになりかねません。そうすると、それぞれの言語が持つメリットが行かせません。

その点、RustはWASIとの相性が抜群です。強力な型システムに加えて、借用とライフタイムの概念によりメモリ安全なコードを実装できます。これあの言語機能はコンパイル時に適用されるため、ターゲットのアーキテクチャに依存しません。

まとめると、Cloudflare Workersの厳しいリソース制限を踏まえるとWASIが活用できる言語は限られます。とはいえ私自身はWASIに詳しくないため、今後状況は変化するかもしれません。

CDNとCloudflare Workersの関係

話が発散しそうなので意図的にCDNの説明は避けていたのですが、CDNとCloudflare Workersの関係について説明されている記事を発見しました。参考になる記事ですので、ぜひ読んでみてください。

おわりに

CloudflareのR2やD1が話題になっていることは知っていたのですが、Workersが何者かは知りませんでした。R2やD1を操作するための踏み台的なサービスなのかと想像していたのですが、調べてみるとWorkersそれ自体もかなり強力なサービスであることを知りました。

今後、個人でなにかサービスを開発するときはプラットフォームとしてCloudflare Workersが第一候補になりそうです。

この記事がお役に立てたなら幸いです。もし内容や私の理解に誤りがある場合は知らせていただけると助かります。

ところで私は視覚に障害があります。文章の有力は音声読み上げに頼っているため、頻繁に漢字の変換ミスをします。音声で読み上げると気がつかないこともあるので、記事の誤字脱字の指摘も歓迎です。

参考資料

  1. Get started guide - Cloudflare Workers docs
  2. Building a To-Do List with Workers and KV - The Cloudflare Blog
  3. Request - Cloudflare Workers docs
  4. Response - Cloudflare Workers docs
  5. How Workers works - Cloudflare Workers docs
  6. Cloudflare Global Network - Data Center Locations - Cloudflare
  7. Limits - Cloudflare Workers docs
  8. Pricing - Cloudflare Workers docs
  9. Eliminating cold starts with Cloudflare Workers - The Cloudflare Blog
  10. Announcing support for WASI on Cloudflare Workers - The Cloudflare Blog

参考文献

  1. ハンズオンWebAssembly - O'Reilly Japan
  2. JavaScript 第7版 O'Reilly Japan
  3. ハイパフォーマンスブラウザネットワーキング - O'Reilly Japan

Discussion