2022/01/14

Fastly Compute@Edgeについて分かったこと

トラベルブックでは CDN に Fastly を使っています。 その Fastly が提供する Compute@Edge が一般でも使えるようになりました。今回は Compute@Edge とはなにか、といった概要と、実際に「Slack スラッシュコマンド echo 」を作ってみた件、それで分かったことを紹介してみたいと思います。

Compute@Edge とは?

Compute@Edge とは、Fastly の CDN エッジでスクリプトを実行できる環境のことを言います。 去年の 11 月に一般ユーザーに開放されました。

Compute@Edge は一般的に「エッジコンピューティング」と呼ばれるもので、同様には以下があります。

  • Cloudflare Workers
  • Vercel Edge Functions
  • AWS CloudFront Functions
  • Deno Deploy

これらには特徴があります。

  • CDN のエッジで動く
  • サーバーレス
  • 反応が速い
  • 実行時間が短い(短くなければいけない)
  • プログラムが小さい(小さくなければいけない)

ダイナミックだけど速いというのがポイントです。

特に Faslty では「35.4 マイクロ秒」で起動するとのことで、いわゆるコールドスタートが限りなくゼロに近いです。また、Fastly はモダンな CDN として非常に強力です。 POP 数は全世界で 60 以上。平均 150 ms でパージする「インスタンスパージ」を備えています。そこでスクリプトを動かせるというのはなかなか熱いです。

さてどんなことができるのか。 Fastly のページに書かれているユースケースをピックアップしましょう。

  • 認証
  • API パフォーマンスの強化
  • ターゲティング広告
  • コンテンツスイッチ
  • A/B テスト
  • ウェイティングルームトークン
  • オリジンのヘルスチェック

これだけだとよく分からないので、まずは手を動かせ!と実際にアプリを作ってみました。

Fastly vs Cloudflare

ちなみにちょっと脱線…

Fastly はこの Compute@Edge を巡って、Cloudflare Workers を提供する Cloudflare と「バチバチに」やりあってました。

発端となったのが Cloudflare が書いたこのブログ記事です。

世界中の Catchpoint のデータを使用して 50 のノードで実施したテストから最初の 1 バイトを受信するまでの時間(TTFB)を基に算出し、Cloudflare Workers が Fastly の Compute@Edge よりも 196%高速でした。

要は Cloudflare Workers は Fastly より速いと言っています。それに対して、Fastly は以下の記事を公開しました。

タイトルがそれを物語っていますね。Cloudflare のテスト手法には問題があると指摘します。

実のところ、TTFB だけではどちらのプラットフォームの方が速いと断言することはできません (その点については後ほど)。しかし、今回のテストをより公平な方法で再現した結果では、TTFB においても Fastly のネットワークと Compute@Edge のパフォーマンスのスコアが、Cloudflare のネットワークと Cloudflare Workers のスコアを上回りました。

TTFB 以外には「ネットワークの RTT」も考慮した方がいいとのことです。

さて…

どちらに分があるかは置いておいて、Fastly と Cloudflare はエッジコンピューティングについてお互い相当意識していることが分かります。ですので Compute@Edge を評価する際は Cloudflare Workers との比較が欠かせなくなるのだと思います。

脱線終わり!

Slack スラッシュコマンドを作る

いよいよ Compute@Edge のアプリを作っていきます。

作ったもの

echoするだけの Slack のスラッシュコマンドのアプリです。 /fastly-echo というコマンドのあとに入れた文字列をそのまま返してくれます。

完成品のレポジトリはこちらになります。

環境構築

まず環境を整えます。

Fastly には CLI があるので、それをまずインストールしました。macOS ですとhomebrewで入ります。

$ brew install fastly/tap/fastly

このfastlyコマンドを使ってプロジェクトの雛形を作ります。

$ fastly compute init

と打ったのち、どの言語で書くかを選択します。

Language:
[1] Rust
[2] AssemblyScript (beta)
[3] JavaScript (beta)
[4] Other ('bring your own' Wasm binary)

Compute@Edge は Rust か AssemblyScript か JavaScript で書けます。今回は馴染みのある JavaSript を選択しました。

生成されたファイルの中にあるsrc/index.jsを編集してアプリを書いていきます。Cloudflare Workers と同じように Service Worker の API に近い書き方が出来ます。Hello Compute@Edge!と表示するだけのアプリはこう書きます。

const handleRequest = async () => {
  return new Response("Hello Compute@Edge!", {
    status: 200,
  });
};

addEventListener("fetch", (event) => event.respondWith(handleRequest()));

fastlyコマンドを使えば、プレビューが出来ます。

$ fastly compute serve

とすると、ビルドが走り、開発用のローカルサーバーが立ち上がってアプリケーションが動きます。手元のブラウザを使ってアクセスしてみましょう。

おお、動いてますね!

ではいよいよ Fastly の環境にデプロイしてみます。環境変数 FASTLY_API_TOKENさえ指定しておけば、自動的にサービスを作ってくれて、さらにedgecompute.appのサブドメインにあたるホストも作ってくれます。

$ fastly compute publish
✓ Initializing...
✓ Verifying package manifest...
✓ Verifying local javascript toolchain...
✓ Building package using javascript toolchain...
✓ Creating package archive...

SUCCESS: Built package 'xxxxxxxx' (pkg/xxxxxxxx.tar.gz)


There is no Fastly service associated with this package. To connect to an existing service
add the Service ID to the fastly.toml file, otherwise follow the prompts to create a
service now.

Press ^C at any time to quit.

Create new service: [y/N] y

✓ Initializing...
✓ Creating service...

Domain: [immensely-sound-dane.edgecompute.app] xxxxxxxx.edgecompute.app

Backend (hostname or IP address, or leave blank to stop adding backends):

✓ Initializing...
✓ Creating domain 'xxxxxxx.edgecompute.app'...
✓ Uploading package...
✓ Activating version...

Manage this service at:
        https://manage.fastly.com/configure/services/xxxxxxxxxxxx

View this service at:
        https://xxxxxxxx.edgecompute.app


SUCCESS: Deployed package (service xxxxxxxxxxxx, version 1)

これでhttps://xxxxxxxx.edgecompute.appにデプロイできます! ここまでやってくれるのはすごいですね。サービスの ID や名前を明示的に指定したい場合はfastly.tomlに書けば OK です。

アプリを作る

さてアプリを作っていきましょう。

基本形

Slack のスラッシュコマンドアプリの動きは、以下のようになっています。

  • Slack で/fastly-echo こんにちわと打つ
  • アプリのエンドポイントに「こんにちわ」という文字列を含んだリクエストが飛ぶ
  • アプリはコマンドに対する返事をレスポンスに入れて返す
  • 返事が Slack に表示される

今回はechoするだけなので、送られてきたテキスト「こんにちわ」をそっくりそのまま返してあればいいです。 具体的には以下のようなコードになります。

// リクエストボディをパースする
const params = new URLSearchParams(await request.text());
// テキストを取り出す
const message = params.get("text");
// レスポンスボディを作る
const data = {
  text: message,
};
// レスポンスオブジェクトを作成して返却
const response = new Response(JSON.stringify(data), {
  headers: { "content-type": "application/json" },
  status: 200,
});
return response;

外部変数を扱う

これだけで最低限動くのですが、来たリクエストが本当に期待する Slack アプリからのものかどうかを検証しなくてはいけません。さもなければ異なった Slack アプリにも同様にechoしてしまいます。 そのためには予め取得しておいた Slack アプリの Token とリクエストボディの中にある文字列を照らし合わせます。

そこで問題が発生しました。Token をハードコードせずに外部に持たせ、アプリから参照する方法が分からなかったのです。 一般的な Node アプリなどでよくあるのは環境変数にいれた値を参照させ、本番環境でも何かしらの方法で、変数をセットします。 ところが、Compute@Edge の場合は process.envが動かず、そもそも本番環境でユーザーが独自の環境変数を設置することはできないようです。

そこで悩んで結果、 Edge Dictionary を使うことにしました。

Edge Dictionary の設定 | Fastly ヘルプガイド のページにはこうあります。

Fastly では、更新可能でグローバルな Edge Dictionary を提供しています。Edge Dictionary は、データをキーと値のペアとして保存し…

とのことで今回の Compute@Edge だけではなく VCL の中でも使う概念らしいです。今回は、envという Dictionary にSLACK_TOKENという名前で Token を保存しました。それを参照するコードはこんな感じです。

const env = new Dictionary("env")
const slack_token = env.get("SLACK_TOKEN")

...

const token = params.get('token')
if (!(token == slack_token)) {
  return handleNotFound();
}

果たしてこれが正解かどうか定かではありませんが、これで変数をコード外に書いて、呼び出すことができました。

制限

スラッシュコマンドは実装が簡単なのですが、本格的な Slack ボットとなると、ライブラリを使いたくなります。 ところが気をつけたいのは、Compute@Edge では外部のライブラリが使えないケースが多いということです。

試しに Slack が提供している Bolt というライブラリをsrc/index.jsで読み込み、fastly compute serveでビルドしてみると、エラーがでてしまいました。Compute@Edge の JavaScript の環境では利用できるライブラリが限られているのです。

基本的には Service Worker API を踏襲しているので、そこにあるFetchEventRequestResponse オブジェクトが中心になります。 また、今回のアプリの実装ではURLSearchParamsが使えました。

JavaScript に関する制限の他に、ビルドされたパッケージも「50MB 以内」である必要があったりします。

本番環境のデバグ

本番環境のデバグには log-tail という機能が便利でした。 その名の通り「log を tail」出来ます。以下のコードのようにconsole.logを使って標準出力にメッセージを出すようにします。

const handleRequest = async (event) => {
  const request = event.request;
  const url = new URL(request.url);
  console.log("Request to: " + url.pathname);
  ...
};

本番にデプロイした状態で、以下のコマンドを打ちます。

$ fastly log-tail

すると、console.logの内容がログとして流れてきます。

これは便利ですね!アクセスの多い、サービスでやるとどんどん流れてくるのでしょうか。気になりますね。

Rust 版

ちなみに gfx さんが Rust 版を作っていました。

分かったこと

Compute@Edge のアプリを作りました。分かったことを紹介します。

開発環境について

見てきた通り、環境の構築から実装、デプロイまで非常にスムーズに進みました。 特に CLI が優秀で fastly compute init でプロジェクトの作成、 fastly compute serve でローカルサーバーの立ち上げ、 fastly compute publish で公開。とそれぞれの過程がワンコマンドでできるのが分かりやすいです。

Cloudflare Workers では CLI として Wrangler を提供していますが、使い勝手がほぼ同じです。 ただ、Cloudflare Workers はそもそもユーザーが多く、 例えば miniflare というオルタナティブな実行環境が開発されていて、より成熟している印象です。 それもそのはず、Cloudflare Workers は 2018 年から提供されいてるので当然です。 Compute@Edge のこれからの盛り上がりに期待しましょう。

できること・できないこと

実行環境の違いとファイルのサイズ制限があるので、通常の node アプリを作るようにはいきません。 現に、「Bolt」は使えませんでした。それは肝に銘じなくてはいけないでしょう。

基本的には Web Standard の Service Worker API に準拠した HTTP の RequestResponse を扱うことになります。 それらの中身はヘッダ、ステータスコード、ボディ、URL となりますね。 おさらいすると、今回の Slack アプリの場合はこんな感じで扱いました。

  • Response を受け取る
  • リクエストボディを request.text() で取得
  • URLSearchParams でパースしてパラメータを取得
  • レスポンス用のオブジェクトを作る
  • JSON.stringify でテキスト化する
  • bodyheadersstatus を指定した Response を返却する

それに加えて、今回は扱わなかったのですが、Compute@Edge では「バックエンド」という概念があります。 これはつまり、CDN のオリジンサーバーのことです。

User <=> Compute@Edge <=> Origin

デプロイ環境ではこのような構成になります。 Compute@Edge は Origin の前段にいるわけですね。

ユースケース

さて、できること・できないことが分かったところで、ユースケースについて考えてみましょう。

今回「Slack のスラッシュコマンド」を作って感じたのは「Compute@Edge はバックエンドがあることで真価を発揮する」ということでした。

もともと、Compute@Edge は「バックエンド」があることを前提として作られています。 今回のようにバックエンドを指定しないで、実行すると以下のように警告がでます。

冒頭で挙げた Fastly が謳っているユースケースがバックエンドありきですね。 例えば、同じ URL でも、デバイスや位置情報などのユーザー情報によって、違うオリジンや違うバックエンドからのコンテンツを返したりといった前段処理を Compute@Edge が担うのです。

このあたりが Cloudflare Workers との差だと思います。 Cloudflare も CDN の前に置くことが出来ますが、CDN を使わないケースをよく見ます。 また、Cloudflare KV や Durable Objects など、アプリケーション作成に便利な機能が提供されていて、 それだけで完結することが想定されています。

バックエンドを補足するという意味では、Vercel の Edge Functions に近いかもしれません。 バックエンドに相当する Next.js のアプリケーションに対してダイナミックなミドルウェアを提供するからです。

VCL の代替として

これはつまり「VCL」に置き換わる可能性を意味しています。

Fastly は Varnish をフォークしてカスタマイズしたものをベースにキャッシュ機構を作っています。 それゆえ、「VCL=Varnish Configuration Language」を使って設定を書きます。 「Language」とありますがどちらかと言うと「設定ファイル」に近いものです。 それを Compute@Edge でやってしまうというのが狙いです。

Fastly のページには Example があり、様々な機能の実装方法が載っていますが、 それぞれ、VCL での書き方と Compute@Edge での実装方法が書かれています(VCL のみ、もしくは Compute@Edge のみのケースもあります)。

例えば、特定の URL のクエリパラメータを取り除く例ではそれぞれこのような書き方が紹介されています。

# VCL
set req.url = querystring.globfilter(req.url, "utm_*");
// JavaScript
let searchEntries = url.searchParams.entries();
let filteredEntries = searchEntries.filter(
  (entry) => !minimatch(entry[0], "utm_*")
);

url.search = new URLSearchParams(filteredEntries);
const newReq = new Request(url, originalReq);

JavaScript の方が冗長ですが、間違いなく「JavaScript」です。 これまで設定だったものをコードで書く= Code over Configuration と言えるでしょう。 これは Vercel の Edge Functions でも謳っていてこれから主流になりそうです。

ちなみに…

トラベルブックではメソッドとパスによってキャッシュの TTL を変える処理を VCL で書いています。 それの分岐がわりと激しいので、Compute@Edge の JavaScript や Rust で分かりやすく書けないかと期待しています。 後ほど紹介するフレームワークを使えばできるかもしれません。

追記

そういえば、Fastly の公式ドキュメントにそのものズバリのページがありました!

サーバーレスプラットフォームとしても

とはいえ、 Fastly の Developer のページ では、 ゲーム「DOOM」を移植した 意欲的なデモ などがあります。

Fastly は VCL の代替としてだけではなく、 サーバーレスプラットフォームとしても頑張っていきたいのだと思います。 実際、開発〜デプロイが非常に楽です。 比較するのは野暮かもしれませんが 他の GCP の Cloud Functions や AWS の Lambda より手軽という印象です。

フレームワーク

Compute@Edge で使える Web フレームワークが出てくるでしょう。 それを使うと VCL で黒魔術的に書いていたルーティングをキレイに書けたりします。

拙作ですが、 Hono というフレームワークを使えばできます。 試しに書いてみると、VCL では HTTP メソッドとパスを見て if 文を多用するところがスッキリします。

# VCL
if (req.method == "GET") {
  if (req.url.path ~ "^/abc") {
    set beresp.ttl = 3700s;
  }
} elsif(req.method == "POST")
  if (req.url.path ~ "^/def") {
    # キャッシュしない設定
  }
...
// JavaScript
import { Hono } from "hono";
const app = new Hono();

// ルーティング
app.get("/abc", (c) => c.text("キャッシュのTTLを3700sに設定"));
app.post("/def", (c) => c.text("キャッシュしない"));

// ディスパッチ
app.fire();

さらに Hono の場合はルーターの実装が賢いので、 冒頭から正規表現でマッチするかを判断するよりも高速に動作します。

ちなみに、Hono は外部モジュールに依存せず、Servie Workers の API を使っています。 よって、Cloudflare Workers でも動くのが面白いです。

VCL の代わりに JavaScript や Rust のフレームワークを使ってルーティングを書く。 そんな将来も近いことでしょう。

まとめ

以上、Fastly の Compute@Edge について

  • 概要
  • Slack のスラッシュコマンド echo を作った件について
    • 開発環境の構築
    • 実装
  • 分かったこと
    • 開発環境ついて
    • できること・できないこと
    • ユースケース
    • VCL の代替になるか
    • フレームワーク

と書いてきました。

VCL の代替、それのみならずサーバーレスのプラットフォーム

というのが Compute@Edge の目指すところかと思いました。

Slack アプリはただechoするだけの簡単なものでしたが、開発の一通りを体験できました。 Compute@Edge はまだまだ進化の途中だと思うので、これからが楽しみです。

エンジニア募集中!

さて、トラベルブックではエンジニアを募集中です。トラベルブックは Fastly をバリバリ使っております。 現在は設定を VCL で記述しているのですが、近い将来 Compute@Edge に置き換えるかもしれません。 楽しみですね!Fastly を使い倒したい方。もしくは、フロント、バックエンドに関わらずご興味があればご応募ください!