2024年7月: Cloudflare WorkersのPythonサポートについて解説¶
筒井@ryu22eです。2024年7月の『Python Monthly Topics』は、Cloudflare WorkersのPythonサポートについて解説します。
前半ではCloudflare WorkersでPythonを使う方法について、後半ではCloudflare WorkersでPythonを動かす仕組みと技術的制限について解説します。
なお、Cloudflare WorkersのPythonサポートは本記事執筆時点(2024年7月24日)でオープンベータ版です。正式リリース時には仕様が変更される可能性があります。 また、一部機能はローカル環境でしか利用できません。
Cloudflare Workersとは¶
Cloudflare Workersとは、Cloudflareが提供するサーバーレスアプリケーションを構築・デプロイするためのプラットフォームです。 主に以下のような特徴があります。
プログラマーはサーバーの管理やスケーリングを意識せずに、アプリケーションの開発に集中できる
エンドユーザーは物理的に最も近いサーバーにアクセスするため、高速なレスポンスを期待できる
コールドスタート(しばらく実行されていない関数を実行しなければならない状況)が排除されているため、高速な起動時間が期待できる
無料枠があるため、気軽に試すことができる
類似のサービスとしてAWS Lambda@Edgeがありますが、Lambda@Edgeには無料枠がありません。 また、Cloudflare WorkersにはJavaScript実行を高速化するための最適化が施されているため、Lambda@Edgeよりも高速なレスポンスが期待できます。
Cloudflare WorkersとLambda@Edgeの比較についての詳細は、以下の記事を参照してください。
サーバーレスコンピューティングがパフォーマンスを改善する方法とは?| Lambdaのパフォーマンス | Cloudflare
Cloudflare Workersがサポートする言語¶
Cloudflare Workersがサポートする言語は以下の通りです。
JavaScript
TypeScript
WebAssembly(Wasm)のバイナリにビルドできる言語(Rust、C、C++、Kotlin、Goなど)
Python(オープンベータ版)
Cloudflare Workersの料金¶
Cloudflare Workersは無料のFreeプランと有料のStandardプラン、Enterpriseプランがあります。 本記事ではFreeプランを使います。 Freeプランでの制限は、以下の公式ドキュメントを参照してください。
Pricing · Cloudflare Workers docs
また、リクエスト制限やアプリケーションのサイズ制限などについては以下の公式ドキュメントを参照してください。
Cloudflare WorkersでPythonを使う方法¶
ここでは、Cloudflare WorkersでPythonを使う方法について解説します。
実際にローカル環境で本記事の内容を試すには、以下が必要です。
Node.js 16.17.0以上
npx
また、本番環境にコードをデプロイする際には、Cloudflareアカウントが必要です。
まずはサンプルコードを動かしてみる¶
公式ドキュメントGet startedにあるサンプルコードをローカル環境で動かしてみましょう。
以下の手順でサンプルコードをダウンロードし、Cloudflare Workersの開発者ツールWranglerを使ってローカルサーバーを起動します。
最新のWranglerをインストールするかどうか確認するためOk to proceed? (y)
と聞かれる場合がありますが、Enterキーを押して進めてください。
$ git clone https://github.com/cloudflare/python-workers-examples
$ cd python-workers-examples/01-hello
$ npx wrangler@latest dev
⛅️ wrangler 3.65.0
-------------------
▲ [WARNING] The entrypoint src/entry.py defines a Python worker, support for Python workers is currently experimental.
⎔ Starting local server...
[wrangler:inf] Ready on http://localhost:8787
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ [b] open a browser, [d] open Devtools, [l] turn off local mode, [c] clear console, [x] to exit │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
[b] open a browser, [d] open Devtools, ...
と表示されたら、bキーを押してください。
ブラウザが開いてアプリケーションの画面が表示されます。

bキーを押した後の画面¶
xキーを押すとローカルサーバーが停止します。
サンプルコードの中身を見てみましょう。 src/entry.pyの内容は以下の通りです。
from js import Response
async def on_fetch(request, env):
return Response.new("Hello world!")
リクエストはon_fetch
という名前の関数で処理され、jsというモジュールを使ってレスポンスを返しています。
このjsモジュールは、JavaScript APIを呼び出すためのものです。
PythonなのになぜJavaScript?と思うかもしれませんが、jsモジュールが存在する理由は後述する「Cloudflare WorkersでPythonはどうやって動いているのか」で説明しますので、一旦置いておいてください。
次に、本番環境にデプロイしてみましょう。
以下のコマンドを実行してください。
Ok to proceed? (y)
と聞かれる場合がありますが、Enterキーを押して進めてください。
デプロイしたアプリケーションに割り当てられるサブドメインが未設定の場合は、サブドメインの入力も求められます。全Cloudflareユーザーで一意な名前を入力してください(後で変更可能です)。
$ npx wrangler@latest deploy
⛅️ wrangler 3.65.0
-------------------
▲ [WARNING] The entrypoint src/entry.py defines a Python worker, support for Python workers is currently experimental.
Attempting to login via OAuth...
Opening a link in your default browser:(省略)
コマンド実行後、以下のようなアプリケーションのデプロイを許可する画面が表示されます(Cloudflareにログインしていない場合はログイン画面が表示されます)。 「Allow」をクリックしてください。

アプリケーションのデプロイを許可する画面¶
以下の画面が表示されたらデプロイ成功です。以下の画面はこのまま閉じても問題ありません。

デプロイ成功画面¶
CloudflareダッシュボードのWorkers & Pagesには、今デプロイしたアプリケーション「hello-python」が表示されているはずです。

CloudflareダッシュボードのWorkers & Pages¶
「hello-python」の詳細画面から、デプロイしたアプリケーションをブラウザで表示することができます。

デプロイしたアプリケーションをブラウザで表示¶
または、npx wrangler@latest deploy
を実行した際に表示されるPublished hello-python
の下にあるURLをブラウザで開いても同じ画面が表示されます。

デプロイしたアプリケーションのURL¶
環境変数を使ってみる¶
APIの認証情報やデータベースの接続情報などは、本番環境、ステージング環境によって異なる場合があります。 そのような情報は環境変数としてソースコードの外に定義しておくと、環境ごとに異なる情報を簡単に切り替えることができます。
Cloudflare Workersでも環境変数を扱うことができます。 前述のサンプルコードを使って、環境変数の値を取得してみましょう。
src/entry.pyを以下のように変更してください。
from js import Response
async def on_fetch(request, env):
return Response.new(f"My name is {env.MY_NAME}.\nSECRET_KEY: {env.SECRET_KEY}")
上記のコードで参照している環境変数MY_NAME
、SECRET_KEY
を定義します。
SECRET_KEY
は秘密の情報という前提です。
wrangler.tomlに環境変数MY_NAME
を設定します。
name = "hello-python"
main = "src/entry.py"
compatibility_flags = ["python_workers"]
compatibility_date = "2024-03-29"
# ↓これを追加
[vars]
MY_NAME = "Ryuji Tsutsui"
秘密の情報をローカル環境で参照する場合は、プロジェクト直下の.dev.varsというファイルに環境変数と値を定義します(本番環境での定義方法は後述します)。 このファイルは、.gitignoreに追加してGitリポジトリには含めないようにしてください。
SECRET_KEY="local_value"
ローカル環境で動作確認を行います。npx wrangler@latest dev
を実行すると、以下のようにwrangler.tomlと.dev.varsに書かれた環境変数の値が表示されます。

ローカル環境で環境変数の値を表示¶
次に、本番環境にデプロイするため、SECRET_KEYの値を環境変数として設定します。
本番環境への環境変数の設定はnpx wrangler secret put
コマンドを使います。
****************
の部分には「production_value」を入力してください。
$ npx wrangler secret put SECRET_KEY
⛅️ wrangler 3.65.0
-------------------
✔ Enter a secret value: … ****************
🌀 Creating the secret for the Worker "hello-python"
✨ Success! Uploaded secret SECRET_KEY
npx wrangler@latest deploy
を実行してから、ブラウザでアプリケーションを表示すると、以下のように環境変数の値が表示されます。

本番環境で環境変数の値を表示¶
環境変数の内容は、CloudflareダッシュボードのWorkers & Pagesからも確認できます(ただし、秘密の情報は変数名の表示のみで、値は表示されません)。

Cloudflareダッシュボードで環境変数の値を表示¶
簡単なAPIを作成する¶
書籍の情報を登録・取得するAPIを作成してみましょう。 APIの要件は以下の通りです。
POSTメソッドの場合: 書籍のtitleとdescriptionを受け取るとDBに登録する
titleとdescriptionのいずれかがない場合は400エラーを返す
GETメソッドの場合: DBに保存されている書籍情報(titleとdescription)を取得する
POST, GET以外のメソッドの場合: 405エラーを返す
データベースはCloudflare D1を使います。 Cloudflare D1とは、SQLiteで構築されたサーバーレスSQLデータベースです。
src/entry.pyを以下のように変更します。
from js import Headers, Response
from pyodide.ffi import JsException
async def on_fetch(request, env):
# JSON形式でレスポンスを返すためのヘッダーを設定
headers = Headers.new({"content-type": "application/json; charset=utf-8"}.items())
if "POST" in request.method:
# POSTリクエストの場合
try:
data = await request.json()
except JsException:
# json()メソッドはリクエストボディがJSONでない場合に例外を発生させる仕様なので、ここで例外処理を行う
return Response.new({"error": "Invalid JSON"}, status=400, headers=headers)
# JSONリクエストボディからtitleとdescriptionを取得
title = getattr(data, "title")
description = getattr(data, "description")
if not title or not description:
# titleまたはdescriptionがない場合はエラーレスポンスを返す
return Response.new(
{"error": "Title and description are required"},
status=400,
headers=headers,
)
# データベースに書籍情報を登録
await env.DB.prepare(
"INSERT INTO books (title, description) VALUES (?, ?)"
).bind(title, description).run()
return Response.new({"message": "ok"}, headers=headers)
elif "GET" in request.method:
# GETリクエストの場合
# データベースから書籍情報を取得
r = await env.DB.prepare("SELECT * from books").all()
return Response.json(r.results, headers=headers)
else:
# POST, GET以外のリクエストの場合
# POST, GET以外には非対応なので、405エラーを返す
return Response.new({"error": "Method not allowed"}, status=405, headers=headers)
次に、以下コマンドで本番環境にbookshelfデータベースを作成します。
$ npx wrangler d1 create bookshelf
⛅️ wrangler 3.65.0
-------------------
✅ Successfully created DB 'bookshelf' in region APAC
Created your new D1 database.
[[d1_databases]]
binding = "DB" # i.e. available in your Worker on env.DB
database_name = "bookshelf"
database_id = "***"
上記の出力内容から[[d1_databases]]
以下をコピーして、wrangler.tomlに追加します。
name = "hello-python"
main = "src/entry.py"
compatibility_flags = ["python_workers"]
compatibility_date = "2024-03-29"
# ↓これを追加
[[d1_databases]]
binding = "DB" # i.e. available in your Worker on env.DB
database_name = "bookshelf"
database_id = "***" # ここにはnpx wrangler d1 create bookshelf実行時に表示されたdatabase_idが入る
テーブルを作成するため、schema.sqlを作成して以下の内容を記述します。
DROP TABLE IF EXISTS books;
CREATE TABLE IF NOT EXISTS books (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, description TEXT);
以下コマンドで、schema.sqlの内容を元にローカルの.wrangler以下にテーブルを作成します。
$ npx wrangler d1 execute bookshelf --local --file=./schema.sql
⛅️ wrangler 3.65.0
-------------------
🌀 Executing on local database bookshelf (***) from .wrangler/state/v3/d1:
🌀 To execute on your remote database, add a --remote flag to your wrangler command.
これで完成です。
npx wrangler@latest dev
を実行してローカルサーバーを起動し、以下のコマンドでPOSTリクエストを送信してください。
$ curl -X POST "http://localhost:8787/" -d '{"title": "Python実践レシピ", "description": "Pythonでプログラムを作成するときに役立つ機能とライブラリを網羅した、実践的なレシピ集です。"}'
{'message': 'ok'}
$ curl -X POST "http://localhost:8787/" -d '{"title": "最短距離でゼロからしっかり学ぶ Python入門 必修編", "description": "プログラミング環境の用意,基本的なプログラムの書き方に始まり,リスト,辞書,クラス,関数といった基礎的な知識からエラー処理,テストコードの書き方までを演習問題を交えながら,わかりやすく解説します。"}'
{'message': 'ok'}
$ curl -X POST "http://localhost:8787/" -d '{"title": "最短距離でゼロからしっかり学ぶ Python入門 実践編", "description": "「エイリアン侵略ゲーム」「データの可視化」「Webアプリケーション」という3つのプロジェクトにチャレンジします。"}'
{'message': 'ok'}
以下コマンドでGETリクエストを送信すると、上記で登録した情報を確認できます。
$ curl "http://localhost:8787/"
[{"id":1,"title":"Python実践レシピ","description":"Pythonでプログラムを作成するときに役立つ機能とライブラリを網羅した、実践的なレシピ集です。"},{"id":2,"title":"最短距離でゼロからしっかり学ぶ Python入門 必修編","description":"プログラミング環境の用意,基本的なプログラムの書き方に始まり,リスト,辞書,クラス,関数といった基礎的な知識からエラー処理,テストコードの書き方までを演習問題を交えながら,わかりやすく解説します。"},{"id":3,"title":"最短距離でゼロからしっかり学ぶ Python入門 実践編","description":"「エイリアン侵略ゲーム」「データの可視化」「Webアプリケーション」という3つのプロジェクトにチャレンジします。"}]
本番環境にデプロイする場合は、以下コマンドを実行してテーブルを作成してください。
This process may take some time, during which your D1 database will be unavailable to serve queries. Ok to proceed?
と聞かれたらEnterキーを押して進めてください。
$ npx wrangler d1 execute bookshelf --remote --file=./schema.sql
⛅️ wrangler 3.65.0
-------------------
(省略)
┌────────────────────────┬───────────┬──────────────┬────────────────────┐
│ Total queries executed │ Rows read │ Rows written │ Database size (MB) │
├────────────────────────┼───────────┼──────────────┼────────────────────┤
│ 2 │ 4 │ 2 │ 0.02 │
└────────────────────────┴───────────┴──────────────┴────────────────────┘
上記コマンドの実行後、npx wrangler@latest deploy
を実行してデプロイしてください。
デプロイ後、ローカル環境と同じくデータの登録、取得ができます。
デプロイしたアプリケーションのURLを確認する方法は、「まずはサンプルコードを動かしてみる」を参照してください。
$ export API_URL="https://****" # デプロイしたアプリケーションのURL
$ curl -X POST $API_URL -d '{"title": "Python実践レシピ", "description": "Pythonでプログラムを作成するときに役立つ機能とライブラリを網羅した、実践的なレシピ集です。"}'
{'message': 'ok'}
$ curl -X POST $API_URL -d '{"title": "最短距離でゼロからしっかり学ぶ Python入門 必修編", "description": "プログラミング環境の用意,基本的なプログラムの書き方に始まり,リスト,辞書,クラス,関数といった基礎的な知識からエラー処理,テストコードの書き方までを演習問題を交えながら,わかりやすく解説します。"}'
{'message': 'ok'}
$ curl -X POST $API_URL -d '{"title": "最短距離でゼロからしっかり学ぶ Python入門 実践編", "description": "「エイリアン侵略ゲーム」「データの可視化」「Webアプリケーション」という3つのプロジェクトにチャレンジします。"}'
{'message': 'ok'}
$ curl $API_URL
[{"id":1,"title":"Python実践レシピ","description":"Pythonでプログラムを作成するときに役立つ機能とライブラリを網羅した、実践的なレシピ集です。"},{"id":2,"title":"最短距離でゼロからしっかり学ぶ Python入門 必修編","description":"プログラミング環境の用意,基本的なプログラムの書き方に始まり,リスト,辞書,クラス,関数といった基礎的な知識からエラー処理,テストコードの書き方までを演習問題を交えながら,わかりやすく解説します。"},{"id":3,"title":"最短距離でゼロからしっかり学ぶ Python入門 実践編","description":"「エイリアン侵略ゲーム」「データの可視化」「Webアプリケーション」という3つのプロジェクトにチャレンジします。"}]
built-in packagesを使ってみる¶
built-in packagesとは、Cloudflare Workersで提供されているPythonパッケージです。 利用できるパッケージは以下の公式ドキュメントを参照してください。
Python packages supported in Cloudflare Workers · Cloudflare Workers docs
利用するには、requirements.txtにパッケージ名を記述します。 たとえば、FastAPIを使いたい場合は、以下のように記述します。
fastapi
通常のPython開発では、requirements.txtに書くパッケージ名にはfastapi==0.68.0
のようにバージョンを指定することができますが、Cloudflare Workersではこの書き方ができません。
Cloudflare Workersでは、wrangler.tomlに書かれたCompatibility datesとCompatibility flagsに基づいてパッケージのバージョンが決まります。
Compatibility datesとCompatibility flagsの記述例は以下の通りです。
compatibility_date = "2023-12-18" # Compatibility dates
compatibility_flags = ["python_workers"] # Compatibility flags
実際に動かしてみる場合は、「まずはサンプルコードを動かしてみる」で使ったサンプルコードの03-fastapiを使うとすぐ試すことができます。 なお、本記事執筆時点ではbuilt-in packagesは本番環境にデプロイすることができないため、ローカル環境でのみ利用可能です。
$ git clone https://github.com/cloudflare/python-workers-examples # すでに実行済みなら省略
$ cd python-workers-examples/03-fastapi
$ npx wrangler@latest dev
その他のサンプルコード¶
以下公式ドキュメントにケース別のサンプルコードがあります。興味がある方は試してみてください。
Cloudflare WorkersでPythonはどうやって動いているのか¶
Cloudflare WorkersではWebAssemblyをサポートしているため、WebAssemblyのバイナリを生成できる言語であれば、Cloudflare Workersで動作させることができます。 ところが、Pythonに関してはコードをWebAssemblyバイナリに変換しているわけではありません。 Cloudflare Workersのランタイムであるworkerdには、CPythonのWebAssembly実装であるPyodideが組み込まれています。 PythonコードはPyodideが解釈することで実行されます。
なお、Pyodideについては以下の過去記事でも紹介しています。興味がある方は読んでみてください。
WebブラウザでPythonが動作する!PyScriptの詳解 | gihyo.jp
また、現在のCloudflare WorkersワーカーのほとんどはJavaScriptで書かれていますが、Pythonで同様の機能を持つワーカーを1から実装するのはかなりの労力がかかります。 そこで、JavaScriptの外部インターフェイス、つまりFFI(Foreign Function Interface)を提供し、PythonからJavaScriptのAPIを呼び出すことができるようにしています。 前述の「まずはサンプルコードを動かしてみる」でも紹介したとおり、Pythonコードからjsモジュールを通してJavaScriptのAPIを呼び出すことができます。
Cloudflare WorkersでPythonを動かす仕組みについては、以下のCloudflareのブログ記事でも詳しく解説しています。
Cloudflare WorkersでPythonを使う上での技術的制限¶
Cloudflare WorkersではPythonアプリケーションはPyodideで動作するため、いくつかの技術的制限があります[1]。
利用制限がある標準ライブラリ¶
Cloudflare Workersでは、以下の標準ライブラリは利用制限があります。
ライブラリ名 |
制限の内容 |
---|---|
hashlib |
OpenSSLに依存するハッシュアルゴリズムはデフォルトで利用不可 |
decimal |
C実装(_decimal)、Python実装(_pydecimal)のうち、C実装のみ利用可能 |
pydoc |
組み込みのヘルプメッセージは利用不可 |
webbrowser |
オリジナルのWebブラウザモジュールは利用不可 |
なお、hashlibの制限回避方法については、本記事執筆時点では公式ドキュメントに記載がありませんでした。
importできない標準ライブラリ¶
以下の標準ライブラリはランタイムから削除されているため、importできません。
curses、dbm、ensurepip、fcntl、grp、idlelib、lib2to3、msvcrt、pwd、resource、syslog、termios、tkinter、turtle.py、turtledemo、venv、winreg、winsound
また、以下標準ライブラリはランタイムに含まれていますが、削除されたtermiosに依存しているため、importできません。
pty
tty
importできるが利用できない標準ライブラリ¶
以下の標準ライブラリはimportできますが、利用できません。
multiprocessing
threading
sockets
最後に¶
Cloudflare WorkersでPythonを使う方法、内部の仕組み、技術的制限について解説しました。 オープンベータ版ということもあり、まだ発展途上ではありますが、PythonプログラマーにとってCloudflare Workersが魅力的なプラットフォームになりつつあるのではないでしょうか。
筆者が使ってみた実感としては、JavaScriptのFFIがあることでWebアプリケーションに必要な機能は揃ってはいるものの、Pythonの流儀と少し異なるために戸惑う場面がいくつかありました。
たとえば、リクエストボディのJSONを取得する際に書くコードrequest.json()
は、期待する挙動と実際の挙動が以下のように異なっていました。
期待する挙動 |
実際の挙動 |
---|---|
辞書型が返ってきて |
|
リクエストボディがないと空の辞書を返す |
リクエストボディがないと例外 |
この違和感は、built-in packagesが本番環境で使えるようになれば、ある程度解消されるのではないかと期待しています。
また、Pyodideを組み込んだ仕組みも面白いですね。これはあくまで筆者の想像ですが、Rubyのruby.wasm、PHPのphp-wasmなど、他の言語のWebAssembly実装インタプリタを組み込んで、Cloudflare Workersで動かせるようになる日が来るかもしれません。