この記事はNikkei Advent Calendar 2022の 12 日目の記事です。
こんにちは。林仁(Shinyaigeek) と申します。今年 2022 年に日本経済新聞社に新卒入社して、日経電子版 Web やその周辺事業を担当し Web をやっています。
昨今 Web な勉強会を見ると Edge Computing が盛り上がっていますね。今年開催された JSConf 2022 ででも、Edge Runtime について言及しているセッションがちらほら見かけられました。
Edge Computing という技術に対して皆さんの期待するところはさまざまだと思います。例えば一つのユースケースとしては、CDN Edge で認可やルーティングを行うのに今までは Fastly Varnish が一般的に使われていましたが、それを Fastly VCL でなくプログラミング言語でその処理を記述でき、ローカル環境でのデバッグやテストもできる基盤が整う、というものがあります。また TypeScript で記述しそれを JavaScript へと compile したり npm modules を bundle したり、といった面倒なフローなしに扱える FaaS としての使い道を期待している方もいらっしゃるのではないでしょうか。
この他に、Edge Computing のユースケースとして Edge 上で処理を完結させてしまう、ある種 Origin Server のように振る舞う Runtime を CDN Edge へと移せる、といったものも挙げられるでしょう。現に Yamagoya Traverse 2022 という Fastly の開催するテックイベントでは、 "Fastly Anywhere" というセッションで "Your data lives on a central cloud, logic runs on the edge" という話をされていました。(Yamagoya 2022 はアーカイブを申し込むことでそれを視聴できますし、興味深いセッションばかりでしたので興味のある方は是非...!)
実際に Fastly 社の提供している Compute@Edge は Rust や JavaScript、Go、AssemblyScript といった言語で記述できるため、ユーザー端末から送信される HTTP Request をそのクライアント環境から地理的により近いサーバーで、短い起動時間で処理を実行するアプリケーションを動かす新たなプラットフォームとして捉えることもできるでしょう。
ちなみに去年のアドベントカレンダーでは「Compute@Edge は GraphQL Server の夢を見るか」というタイトルで、Compute@Edge で GraphQL サーバーを打ち立てる検証記事を書いていました。ご興味があればこちらも是非読んでくださると幸いです。
本稿では筆者の夢想している Cloudflare Workers 上のランタイムである、workerd についてちょっとした尖った使い方を紹介します。
workerd とは
workerd とは Cloudflare 社の公開している、JavaScript/Wasm Runtime です。Cloudflare Workers の Runtime として用いられており、Cloudflare Workers で動く script をそのままテスト/デバッグ、そしてアプリケーションサーバーのランタイムとして動かすことが可能です。
https://github.com/cloudflare/workerd
以下は 公式の example からの抜粋ですが、Cloudflare Workers のコードがそのまま動くことが確認できます。
addEventListener("fetch", event => {
event.respondWith(new Response("Hello World"));
});
using Workerd = import "/workerd/workerd.capnp";
const config :Workerd.Config = (
services = [
(name = "main", worker = .mainWorker),
],
sockets = [
# Serve HTTP on port 8080.
( name = "http",
address = "*:8080",
http = (),
service = "main"
),
]
);
const mainWorker :Workerd.Worker = (
serviceWorkerScript = embed "hello.js",
compatibilityDate = "2022-09-16",
);
また特筆すべきは Nanoservices のためのランタイムとしての側面なども挙げられるでしょう。記事の本題から逸れるためあまり深入りはしませんが、アプリケーションを機能単位で独立したデプロイメントが行え、それらは通常の関数実行などと遜色のないパフォーマンスで実行することができます。
本稿では workerd の「Cloudflare Workers で動かす処理を動かせるアプリケーションランタイム」としての側面に着目します。
日経電子版Webでの Fastly VCL の活用と課題
日経電子版 Web では Fastly Varnish を Edge Proxy としてフルに利用しています。CDN で HTTP Request を受け取った際に、認可や A/B テストの振り分け、Feature Flag の適用を行ってしまい、それらの結果を Origin Server へと HTTP Request を送る際に Header Field へ詰め込んでいます。例として、
allow-read-paid-contents: 1
feature-a-applied: 0
といった具合に「この記事を読む権限があるのか」、や「どんな Flag が割り当てられるか」といった情報を Origin Server への HTTP Request Header へと詰め込み、Origin Server からは Vary
Header でそれら allow-read-paid-contents
や feature-a-applied
を指定しユーザーに応じたコンテンツの詰まった HTTP Response も CDN 上で Cache できるようにすることでなるべくユーザーからの HTTP Request を CDN で捌いてしまうことで、 Origin Server の負荷軽減や、マスメディアという事業特性上生じうる "バズ" などによる急激なアクセス数のスパイクへの対応を実現しています。
本稿では説明の簡略化のため、このような CDN Edge 上で実行する Reverse Proxy のような処理を Edge Proxy と言葉を定義して話を進めます。
Fastly VCL で Edge Proxy を書いている場合、Fastly Varnish 以外のランタイムでこの Edge Proxy を実行するのはかなり厳しく、Fastly Varnish の元種となる Varnish で動かすことを考えても、それぞれ文法や機能に差異があるため双方の VCL を吐き出せる機構を用意するなりをしてあげる必要があり、更にほぼ内容が同じ VCL を二重管理し動作チェックもそれぞれの環境で行う必要があることを考えると、開発コスト、運用コスト的にはあまり現実的ではありません。しかしだからと言って Fastly VCL でのみ Edge Proxy を書いてしまっていると Fastly で障害が生じてしまうと他の CDN や Origin Server へと DNS の向き先を switch しても認証や A/B テストの割り当てなどができなくなってしまいます。先ほどの例で言うと、allow-read-paid-contents
を Fastly が落ちて他の CDN ないしは proxy へと DNS の向き先を switch している間は常に 1
としてしまって、日経電子版 Web にアクセスする人全員が記事にアクセスできるようにする、といった対応は考えられますがいささかもの足りません。
ポータブルな Edge Proxy としての workerd
例えば先ほど紹介したような、Edge Proxy の処理について具体例を交えながら考えてみましょう。
/set_permission
へとアクセスすると Uid が割り当てられ、その Uid の持つ権限に応じて返却されるコンテンツが変わりうるようなサービスを考えてみましょう。Origin Server として単純な Node.js のサーバー、Edge Proxy として Cloudflare Worker を用意します。
まず以下のような Origin Server を想定します。
const fastify = require("fastify");
const PERMISSION_HEADER = "shinyaigeek-profile-access-permission-level";
const app = fastify();
app.get("/", async (request, response) => {
const permission = Number(request.headers[PERMISSION_HEADER] ?? 0);
response.header("content-type", "text/html");
response.header("vary", PERMISSION_HEADER);
response.header("cdn-cache-control", "max-age=604800");
if (permission === 0) {
return response.send(buildBody("<p>you have no permission!!</p>"));
}
let message = "<ul>";
if (permission > 0) {
message += "<li>shinyaigeek is Software Engineer</li>";
}
if (permission > 1) {
message += "<li>shinyaigeek is Shinobu Hayashi</li>";
}
if (permission > 2) {
message += "<li>shinyaigeek loves Nikkei!!";
}
message += "</ul>";
response.send(message);
});
const buildBody = function (message) {
return `<html><head><title>shinyaigeek's profile</title></head><body><h1>This is Shinyaigeek's profile page</h1>${message}</body></html>`;
};
const start = async () => {
try {
await app.listen({ port: 3000 });
} catch (err) {
app.log.error(err);
process.exit(1);
}
};
start();
処理としてはとても単純で、Origin Server へ到達した HTTP Request から shinyaigeek-profile-access-permission-level
Header を取り出し、その値に応じて Response Body の内容を出しわけています。
この Origin Server の Edge Proxy となる、Cloudflare Workers で以下のような script を動かします。
import { parse } from 'cookie';
const handler = async function (event) {
const { url } = event.request;
const { pathname } = new URL(url);
if (pathname === "/set_permission") {
const response = new Response("", {
status: 301,
});
response.headers.append("location", "/");
response.headers.append("cache-control", "no-store");
response.headers.append(
"set-cookie",
`Uid=${Math.floor(Math.random() * 100)}; Path=/; HttpOnly; SameSite=Lax;`,
);
return response;
}
const cookie = parse(event.request.headers.get('Cookie') || '');
const uid = Number(cookie.Uid);
const permission = uid % 4;
const request = new Request("Origin Server への URL", {
headers: {
"shinyaigeek-profile-access-permission-level": permission
}
});
return fetch(request);
};
addEventListener("fetch", (event) => {
event.respondWith(handler(event));
});
Cloudflare Workers で行っている処理もとてもシンプルで、
/set_permission
へアクセスされた際にはランダムな値をUid
として Cookie へと付与し/
へとリダイレクトさせる- そうでない時には HTTP Request 中の cookie の
Uid
をパースし、4 で割った余をshinyaigeek-profile-access-permission-level
として付与する
といったものになっています。
これによって /set_permission
エンドポイントへとアクセスした際に Uid
が割り当てられ、その Uid
をもとに適切な Response Body が返却されるアプリケーションが構築されます。Origin Server から返す HTTP Response に Vary
Header をつけそこに shinyaigeek-profile-access-permission-level
を指定することで、CDN へ到達する HTTP Request の Cookie 中の Uid
の値を、shinyaigeek-profile-access-permission-level
へと CDN で計算した結果その値と URL が等しければ HTTP Request が Origin Server へと到達することはなく、CDN からキャッシュが返されるようになってくれます。

このようなサービスを運用していたとして、通常 Cloudflare が落ちてしまった時にはそもそも DNS の向き先を switch してあげないといけませんし、switch 先でこのような処理を動かせなければ権限付与の処理ができなくなってしまいます。しかしランタイムと言うレイヤーで見るとこの処理は workerd ででも同様に動かせるため、普段から workerd 版の Google Compute Engine なりをスタンバイしておき、有事の際に DNS の向き先を一時的にそちらへと避難させ、そこで workerd で上記のような処理を動かすことで、DNS を切り替えるだけで Edge Proxy でやっていた処理を止めずにサービスの提供を続けていくことができます。
workerd 自体の特性を活かしたユースケースとしては Nanoservice などが強いですが、エッジの効いたユースケースとして、Cloudflare Workers で動かしている処理を Edge を超えてさまざまなところで動かせるようになり、上記のようにポータブルな Edge Proxy としての運用が可能になる、といったものも可能になっていくと言えるでしょう。
一方で Edge Runtime は WinterCG で標準化の動きがあったり、Honojs といったどの Runtime ででも動かせるよう意識され造られているフレームワークも存在するため、ある程度の Compatibility が担保されることが期待できることを考えると、理想論としては初期段階から Portable にできるよう Edge Proxy を設計、開発しておくことでしょう。しかし現実的に考えると、どうしてもプラットフォーム間の微細な差の確認は必要になり一定の開発コストは生じます。その中で特定の CDN 上の Edge Runtime の挙動と同じものを fallback として自前運用できる選択肢がある、というのはやはり心強いですし夢が広がりますね。
余談
本稿のタイトルは "Edge Proxy OVER THE EDGE" としていますが、ニュアンス的には "Edge Proxy beyond the Edge" とかの方が内容にあっているのですが、今年 Netflix で配信された『サイバーパンク エッジランナーズ』が刺さりすぎて、作中の「エッジの向こう側に行ってしまったな」という台詞を使いたくそれをもじったタイトルとしました。
日経電子版 Web 開発チームでは、前述した通りパフォーマンスなどを担保するためにかなり尖った CDN Edge の活用をしてきました。Edge Computing と言う技術トレンドが台頭し、各ベンダーの開発も進み CDN Edge でできることが増えていっていますが、その中でも Edge の効いた Edge の使い方をして Edge の向こう側のユーザー体験を実現していくことを模索しています。こうした取り組みに興味のある方は、カジュアル面談なども Hack The Nikkei から応募できますのでぜひお気軽にご連絡ください。
来月 1/26(木) に、【NIKKEI Tech Talk #3】メディア企業のWebフロントエンド~多様な開発と技術選定~ というイベントを開催します。僕も登壇してお仕事のことについて話します。興味のある方はぜひ遊びにきてください!
明日はチームにジョインした際の僕のオンボーディングを担当してくださった、井手さんによる記事です。お楽しみに!