ハンドルネームの敬称は省略できます

🦀パソコンを叩く日々🐈

ローカル LLM を Cloudflare Workers と AI Gateway を使って安全に公開する

こんにちは。id:rokuokun です。

この記事はCloudflare Workers Tech Talks in Kyoto #2 - connpassで発表した内容を記事として書き出した内容になっています。

スライドが見たい方はこちらからどうぞ。

最近手に入れた DGX Spark を使って vLLM や Ollama を動かしているのですが、これらを外部に公開したいという気持ちが芽生えてきました。 普通にやるなら VPN 経由ですが、これだとやや面倒です。やはりパブリックなエンドポイントからアクセスしたいモチベーションはあります。 LiteLLM などのプロキシを設置することを考えたのですが、やや過剰というかメンテナンスにやや手間がかかります。

普段 Cloudflare のサービスをよく使っているので、なんとか Cloudflare のサービスに使うことで手間を減らしつつ手軽に公開できないかを考えていました。 いい感じの方法を模索しているといい感じの方法を見つけたのでそれを共有します。

やりたいこと

ローカルの vLLM を、次の形で呼び出せるようにします。

architecture-beta
    group mesh(cloud)[Cloudflare Mesh]

    service internet(internet)[Internet]
    service gateway(cloud)[Cloudflare AI Gateway]
    service worker(cloud)[Cloudflare Workers] in mesh
    service vllm(server)[vLLM Server] in mesh

    internet:R --> L:gateway
    gateway:R --> L:worker
    worker:R --> L:vllm

アプリケーションは AI Gateway だけを呼びます。 AI Gateway が Workers にリクエストを送り、Workers が Workers VPC 経由でローカルの vLLM をプロキシする構成です。

Workers 側では AI Gateway からのリクエストであることを認証する仕組みを用意します。 アプリケーションには AI Gateway を呼び出すための Cloudflare API Token だけを渡します。

Workers VPC を使う前に

ローカル LLM が動いているマシンを Cloudflare のネットワークに参加させます。

マシンには cloudflaredwarp-cli を入れます。 Cloudflare Tunnel を使う方法もありますが、今回は Cloudflare Mesh を使いました。

Tunnel の方が始めるだけなら手軽そうです。 ただ、Mesh の方がネットワークとして自由に扱えそうだったのでこっちを試しています。

スプリットトンネルの設定

最初は普通に通ると思ったのですが、Workers からローカルの vLLM へ届かないということが発生しました。

どうやら既存のデバイスプロファイルと、Mesh 接続時に自動作成されるデバイスプロファイルが競合することがあるようです。 正しくトラフィックが流れているかをチェックするためにスプリットトンネルの設定で Mesh 宛ての通信を除外していないか確認しましょう。

通信には Mesh ノードに割り当てられたネットワークに加えて、以下のレンジが必要です。

アドレス 用途
100.64.0.0/12 Cloudflare source IP range
2606:4700:cf1:5000::/64 Cloudflare source IP range
100.80.0.0/16 Hostname routing (token IPs)
2606:4700:0cf1:4000::/64 Hostname routing (token IPs)

スプリットトンネルの設定が「含める」の場合は必要なネットワークがリストにあることを確認、「除外する」の場合は、必要なネットワークがリストにない状態にする必要があります。

スプリットトンネルの設定

デバイスプロファイルの例

Workers を作る

今回は薄いプロキシですぐに作れるので Web ダッシュボードから Workers を作成しています。

Workers を作成したら、設定画面から VPC Network の binding を追加します。 Mesh ネットワークを指定しておきます。

Workers に VPC Network を Bindings に追加

Workers は以下のようにしました。

export default {
  async fetch(request, env) {
    if (request.headers.get("authorization") !== "Bearer EXAMPLE_WORKER_TOKEN") {
      return new Response("Unauthorized", { status: 401 });
    }

    const url = new URL(request.url);
    url.protocol = "http:";
    url.hostname = "MESH_NODE_IP"; // e.g. 10.200.0.1
    url.port = "8000";

    const response = await env.MESH.fetch(url, request);

    return new Response(response.body, response);
  },
};

MESH_NODE_IP は vLLM が動いているマシンの Mesh IP に置き換えます。

やっていることは、Authorization ヘッダーを見て、接続先だけローカルの vLLM に変えているだけです。 vLLM は OpenAI 互換 API を出しているので、パスと body はそのまま流せます。

EXAMPLE_WORKER_TOKEN は記事用の値です。 実際にはopenssl rand -hex 16などでランダムな値を生成して、Worker の Secrets などから読むようにしておきましょう。

/v1/models を叩いてみる

VPC Network を通じて実際に疎通しているかを チェックするために、とりあえず /v1/models に GET リクエストを飛ばしてみます。

GET /v1/models して結果が返っている様子

結果から vLLM で提供しているモデルの ID が返っているのが確認できます。Workers から Mesh を経由してローカルマシンまで到達できていそうです。 ここで使っている API Token の値は使っていないので気にしないでください。

AI Gateway を通す

このままでは機能が足りませんが、Cloudflare には AI Gateway という神のようなサービスが存在します。 AI Gateway にはログ収集、キャッシュ、レート制限、支出上限、リトライといった機能があります。

とりあえず Cloudflare AI Gateway で認証済みゲートウェイを作成します。作成時に認証済みという項目にチェックを入れておいてください。 こうすることで、AI Gateway のエンドポイントで認証が有効になります。

カスタムプロバイダーを追加する

AI Gateway では任意のエンドポイントをプロバイダーとして登録できる機能があります。 これを使って、先ほど作成した Workers をプロバイダーとして登録します。

カスタムプロバイダーに Workers を指定している

続いてプロバイダーキーを作成します。 これを登録しておくと AI Gateway が Workers へリクエストを送る時に、Authorization ヘッダーへ自動でセットしてくれるようになるので忘れずに設定しておきましょう。

ここには Workers 側で定義した Token を、Bearer を付けずに登録します。

カスタムプロバイダーにキーを登録する

API Token を発行する

アプリケーションから認証済み AI Gateway を呼ぶために、Cloudflare API Token を発行します。

この Token は AI Gateway の実行にしか使わないので、必要以上の権限を付けないようにします。 Workers の管理などができる権限は要りません。とりあえず AI Gateway の閲覧権限だけ与えていますが、もっと弱い権限でも良いはずです。

Cloudflare API Token を作成している様子

アプリケーションにはこの Token と AI Gateway の URL を渡します。 Workers で定義した Token はアプリケーションで使用しません。

AI Gateway から呼んでみる

AI Gateway の URL は以下のようになります。

https://gateway.ai.cloudflare.com/v1/ACCOUNT_ID/GATEWAY_NAME/custom-PROVIDER_NAME

例えば、ゲートウェイ名を example-gateway、プロバイダー名を example にした場合は、次のようにモデル一覧を取れます。

curl -s \
  "https://gateway.ai.cloudflare.com/v1/ACCOUNT_ID/example-gateway/custom-example/v1/models" \
  -H "Authorization: Bearer CLOUDFLARE_API_TOKEN" | jq

ここで使う CLOUDFLARE_API_TOKEN は、AI Gateway 用に発行した Token です。

実際に呼び出してみると以下のような結果が得られました。

AI Gateway を経由して vLLM に POST している様子

vLLM のモデル一覧が返ってきました。AI Gateway、Workers、Mesh、vLLM まで通っていそうですね。 この写真に写っているトークンはすでに Revoke しているので安心してください。

Codex から使ってみる

OpenAI 互換 API を使えるクライアントなら、base URL を AI Gateway の URL にすればそのまま利用可能です。 例として Codex のモデルとしてローカルの vLLM を指定し、簡単なメッセージを送ってみました。

Codex から vLLM で動作する Qwen3.6 を呼び出している

モデルメタデータが見つからないという警告は出ましたが、応答が返っていることが確認できます。

おまけ: OpenTelemetry 統合

AI Gateway には OpenTelemetry 統合という機能が存在します。

これをうまく活用することで AI Gateway 経由で発生した AI モデル呼び出しについても1つのトレースとして追えるようになります。

無料で使えて便利。

感想

Cloudflare のサービスに乗ってローカル LLM を上手い具合に公開できてよかったです。

この構成のメリットとしてWorkers はかなり薄い転送処理だけにして、認証やレート制限などは AI Gateway 側に寄せられます。 このぐらいの用途ならこれで十分ですし、AI Gateway が今後発展していけばさらに便利になります。

Mesh のネットワーク設定だけは少し迷いどころではありますが、ドキュメントとダッシュボードを眺めることでなんとか乗り切ることができました。

AI Gateway や Workers VPC、Mesh Network などは Beta のサービスなので、料金や仕様は今後変わるかもしれません。

参考

developers.cloudflare.com

developers.cloudflare.com

developers.cloudflare.com