最近 Web アプリを開発するときは専ら Cloudflare Workers を使っている。
Cloudflare Workers は早いとか、安いと、まことしやかに囁かれており1、
私もそれをモチベーションに使っている。しかし、それがどのような理屈で動いたサービスゆえの評価なのか当然知らなかったため、
Cloudflare Workers の背景・モチベーション・仕組みなどを調べてみた。
(Cloudflare Workers の機能に関しては公式ドキュメントを読んだ方が早いので、ここでは書かない。)
Workerとは
Cloudflare Workers は、Cloudflareのグローバルネットワーク全体でアプリケーションを構築、デプロイ、スケーリングするためのサーバーレスプラットフォーム 2。 Worker 上で Node.js の HTTP サーバーを実行 したり、 Workerの一部として静的ファイルをアップロード・配信したり、 その他にもコードを実行することで色々なことができる。
エッジコンピューティング?
Cloudflare Workers は 「CDN ベースのエッジコンピューティング (の代表格)」という印象があったが、 意外にも、公式ドキュメント内で “edge” の言及がほぼない。Workers のサイトトップでも Cloudflare’s global networkという表現を使っている。
そもそもエッジコンピューティングとはデータソース3の近くで演算するネットワークの考え方を指している。 「近い」計算場所は
- ユーザーのコンピューター
- IoT デバイス
- エッジサーバー 4
など色々である。
エッジコンピューティングとは、遅延と帯域幅の消費を低減するために、演算処理を可能な限りデータのソースに近づけることに焦点を当てたネットワークの基本方針です。(…)ネットワークのエッジにコンピューティング機能を移動すると、クライアントとサーバー間で発生する長距離通信の量が最小限に抑えられます。
ref: https://www.cloudflare.com/ja-jp/learning/serverless/glossary/what-is-edge-computing/
つまり “エッジコンピューティング” という言葉は意味が広すぎるため、数千台のマシンが分散配置されていることから、 global network と言っている (多分)。
Service Workers との関係
Cloudflare Workers の “Workers” は W3C 標準 API である Service Workers の “Workers” であることが Cloudflare Workers 公開直後のブログで紹介されている。
Cloudflare Workersの名前はWeb Workersに由来しており、より具体的には、Webブラウザのバックグラウンドで動作してHTTPリクエストをインターセプトするスクリプトのW3C標準APIであるService Workersに由来しています。
ref: https://blog.cloudflare.com/ja-jp/cloudflare-workers-unleashed/#whats-a-worker
2017年時点の Cloudflare のモチベーションは「Cloudflareのエッジ環境でコードを実行できるようにすること」5。 その結果、V8 で構築された環境で Service Workers API を用いた JavaScript のコードが実行できる Worker が提供された。
Worker は 2 つの実装形式をサポートしているが、 Service Workers 形式は非推奨になっており、いろいろメリットがある Module Workers が推奨されている。
// Service Workers (非推奨) addEventListener("fetch", (event) => { event.respondWith(handler(event.request)); });// Module Workers (推奨) export default { fetch(request) { // ... }, };
Cloudflare Workers 登場当初からある Service Worker 形式はまさに Web 標準に従ったものである。
addEventListener("fetch", (event) => {});
V8 isolate
ユーザーの worker 関数は以下のような構造で配置・実行される。
- 分散配置された数千台のマシンによるグローバルネットワーク
- マシン内でホストされた Workers ランタイム
- Workers ランタイム内で実行される数千ものユーザー定義アプリケーション
Workers functions run on Cloudflare’s global network - a growing global network of thousands of machines distributed across hundreds of locations.
Each of these machines hosts an instance of the Workers runtime, and each of those runtimes is capable of running thousands of user-defined applications. This guide will review some of those differences.
ref: https://developers.cloudflare.com/workers/reference/how-workers-works/
Cloudflare Workers は内部で V8 (JavaScript エンジン) の isolate という仕組みを使うことで、単一のプロセス6上で数百〜数千のユーザーのコードを同時に実行している。
a single process can run hundreds or thousands of Isolates, seamlessly switching between them. They make it possible to run untrusted code from many different customers within a single operating system process.
V8 isolate とは 独自のメモリ領域をもつ V8エンジンのインスタンス のこと 7。 isolate はそれぞれが完全に分離されており、オブジェクトを共有することが出来ない。
Cloudflare Workers は VM でもなく、 コンテナでもなく、 この V8 isolate を使って、
- コンテナ化されたプロセスの起動が不要なため、瞬時に起動すること
- 1つの JavaScript ランタイムのオーバーヘッドだけでほぼ無限のスクリプトを実行できるため、メモリ消費が少ないこと
- 1つのプロセス内で全てのコードを実行するため、コンテキストスイッチのコストがないこと
- マルチテナントを考慮した設計である V8 を用いることで、単一のプロセス内で分離された環境でコードを実行できること
を実現している。
Workers におけるサンドボックス
Cloudflare Workers のコードサンドボックスは “安全な分離” と “API デザイン” で構成されている。
There are two fundamental parts of designing a code sandbox: secure isolation and API design.
安全な分離
安全に分離された環境とは、コードが本来アクセスすべきでないものにアクセスできないような実行環境のこと。 Cloudflare はこれを実現するために V8 isolate・プロセスレベルの分離・信頼レベル・Layer 2 sandbox を組み合わせている。
V8 isolate
独自のメモリ領域を持つ V8 isolate を使うことで、コードが isolate 外のメモリにアクセスできない。 これによって同じプロセス内でも複数のテナントのコードを互いに隔離する。
プロセスレベルの分離
Worker を独自のプライベートプロセスでスケジュールする場合がある。 例えば開発者ツールを使って worker を検査する場合、検査対象の worker を別のプロセスに移動させる。 V8 のインスペクタープロトコルはセキュリティ上の精査が少なく、使用者が信頼できるとも限らないため、別プロセスに隔離することでリスクを低減している。
信頼レベル
Worker に信頼レベルを割り当て、異なる信頼レベルの Worker は別のプロセスで実行される。 例えば Free プランの Worker と Enterprise の Worker が同じプロセスでスケジュールされることはない。
Layer 2 sandbox
V8 isolate のサンドボックスに加えて、プロセス全体レベルのサンドボックス層 (Layer 2 sandbox) も設けている。
ここでは Linux 名前空間と seccomp を使ってファイルシステムやネットワークへのアクセスを完全に遮断している。
ネットワークへの直接アクセスはできず、UNIX domain socket 経由で同じシステム上の他のプロセスとのみ通信できる。
外部世界との通信はサンドボックス外のローカルプロセスが仲介する。
API デザイン
サンドボックスによって、完全に分離された環境は逆に役に立たない。 何らかの有用な処理を行うためには少なくともリクエストを受信して応答できる必要がある。
サンドボックス文脈における API デザインの責任として、Cloudflare API は worker が出来ること・出来ないことを明確に定義している。
- ファイルシステム API を非公開にすることでローカルファイルシステムへのアクセスを禁止している 8。
- Worker からの outbound HTTP リクエストは UNIX domain socket 経由でローカルのプロキシサービスに送られる。パブリックなインターネットまたはその Worker のゾーンの origin server 宛のリクエストのみ外部に転送される。内部ネットワークへのアクセスは禁止されている。
- inbound HTTP リクエストは直接 Workers ランタイムに届かず、プロキシサービスを経由する。TLS の終端処理や実行する Worker スクリプトの特定を行った上で、UNIX domain socket を介して sandbox プロセスに渡される。
また、Worker のコードや設定の取得は supervisor プロセスが担っている。supervisor はディスクや内部サービスから Worker のコードと設定を取得し、sandbox が実行すべき Worker に関係のない設定にアクセスできないよう制御している。
Footnotes
-
ツイッターで見た。ってやつ ↩
-
「データソース」は DB などと繋がっているオリジンサーバーじゃないの?ってふと思ってしまったが、正確には計算するデータ発生源であるブラウザなどを指す。 ↩
-
エッジサーバーはネットワークの論理的な端または「エッジ」に存在するコンピューターのこと(URL)。多くのデバイスの接続・通信で構成されるネットワークにおいて、「エッジ」という表現は少々曖昧 (URL)。 ↩
-
Cloudflare 自身による機能開発に限界があるため、ユーザーが Cloudflare のサーバー上で直接コードを書けるようにする…という発想が凄すぎる。 ↩
-
Node.js compatibility として virtual filesystem にアクセスするための
fsが今はあるけど…。 ↩