Cloudflare Workers in Pythonで​サーバーレスアプリケーションを​作ろう

Ryuji Tsutsui/PyCon JP 2024資料

Creative Commons License This work is licensed under a Creative Commons Attribution 4.0 International License.

は​じめに

自己紹介

  • Ryuji Tsutsui@ryu22e

  • さくらインターネット株式会社所属

  • Python歴は​13年くらい​(主に​Django)

  • Python Boot Camp、​Shonan.py、​GCPUG Shonanなど​コミュニティ活動も​しています

  • 著書​(共著)​:『Python実践レシピ

今​日話したい​こと

  • Cloudflare Workersで​Pythonが​使えるようになった​話

  • デモを​交えて​実際に​動かしてみる

  • 中の​仕組みに​ついても​解説

この​トークの​対象者

  • Pythonの​基礎的な​文法が​わかる​人

この​トークで​得られる​こと

  • Cloudflare Workersの​概要が​わかる

  • Cloudflare Workersで​Pythonを​使う上で​必要な​設定、​仕様の​クセが​わかる

  • Cloudflare Workersで​Pythonが​動く​仕組みを​知る​ことで、​仕様の​クセが​ある​理由を​理解できる

トークの​構成

  • Cloudflare Workersとは

  • Cloudflare Workersで​Pythonを​使う方​法

  • Cloudflare Workersで​Pythonが​動く​仕組み

トークの​元ネタ

2024年7月に​私が​書いた​以下の​記事を​ベースに​しています。

Cloudflare Workersで​サーバーレスPythonアプリを​構築してみよう | gihyo.jp

Cloudflare Workersとは

Cloudflare Workersの​概要

Cloudflare Workersとは、​サーバーレスアプリケーションを​デプロイできる​プラットフォーム。

Cloudflare Workersの​特徴(1)

プログラマーは​世界中に​ある​サーバー​(エッジ環境)に​コードを​デプロイし、​ユーザーは​物理的に​近い​サーバーから​レスポンスを​受け取る。

Cloudflare Workersの​特徴(1)

イメージ図

イメージ図

Cloudflare Workersの​特徴(2)

コールドスタートが​排除されている。

  • しばらく​実行されていない​関数を​実行しなければならない​状況を​コールドスタートと​いう

  • Cloudflare Workersは​コールドスタートが​排除されている​ため、​高速な​レスポンスが​期待できる

Cloudflare Workersの​類似サービス

類似サービスに​AWS Lamnda@Edgeが​ある。

Cloudflare Workersが​AWS Lambda@Edgeと​異なる​点

  • 無料枠が​ある

  • JavaScriptを​高速に​実行する​ための​チューニングが​されている

参考: サーバーレスコンピューティングが​パフォーマンスを​改善する​方​法とは​?| Lambdaの​パフォーマンス | Cloudflare

Cloudflare Workersが​サポートする​言語

  • JavaScript​(TypeScript)

  • WebAssembly​(WASM)の​バイナリに​ビルドできる​言語​(Rust、​C、​C++、​Kotlin、​Goなど)

  • Python←NEW!

Cloudflare Workersで​Pythonを​使う方​法

必要な​もの

  • Node.js 16.17.0以上

  • npx

Cloudflare Workersを​簡単に​試す方​法​(デモ)

公式の​サンプルコードを​使うと​簡単に​試すことができる。

% git clone https://github.com/cloudflare/python-workers-examples.git
% cd python-workers-examples/01-hello
% npx wrangler@latest dev

デプロイも​やってみる​(デモ)

デプロイは​以下の​コマンドで​行う。

% npx wrangler@latest deploy

Wranglerとは​何か

  • Cloudflare Workersの​開発者ツール

  • ローカルサーバーの​立ち上げ、​デプロイ、​環境変数の​設定など、​Workersプロジェクトの​管理を​行う

ちなみに、​wrangleの​意味は

  1. 争う、​口論する

  2. 世話を​する

おそらく​2の​意味で​使われている。

Hello worldアプリを​構成している​各ファイルに​ついて​解説

以下に​ついて​解説する。

  • src/entry.py: アプリケーションの​ソースコード

  • wrangler.toml: プロジェクトの​設定ファイル

src/entry.pyの​中身

from js import Response  # ←これは何?

async def on_fetch(request, env):
    return Response.new("Hello world!")

jsモジュールとは​何か

  • Pythonから​JavaScript APIを​呼び出すための​モジュール

  • 標準モジュールではなく、​Cloudflare Workersの​独自モジュール

  • Headersや​fetchなども​呼べる

jsモジュールの​使用例

from js import Headers, Response, fetch, console, URL

API_URL = "https://httpbin.org"

async def on_fetch(request, env):
    # /old にアクセスされた場合は/にリダイレクト
    # JavaScriptだとnew URL(request.url)と書くが、Pythonにnew演算子はないのでこう書く
    url = URL.new(request.url)
    if url.pathname == "/old":
        return Response.redirect(url.origin, 307)

    # JSON形式でレスポンスを返すためのヘッダーを設定
    headers = Headers.new({"content-type": "application/json; charset=utf-8"}.items())

    # fetch()関数を使ってAPIサーバーにリクエストを送信
    res = await fetch(f"{API_URL}/ip")

    # レスポンスの内容をコンソールに出力
    console.log(res)

    return Response.new(res.body, headers=headers)

jsモジュールの​サンプルコード

以下の​サンプルコードを​参照。

% git clone https://github.com/ryu22e/python-workers-examples.git
% cd python-workers-examples/js-sample
% # 設定方法はREADME.mdを参照

その​他の​jsモジュールの​使用例

以下の​公式サイトを​参照。

Examples | Cloudflare Workers docs

Q. PythonなのになぜJavaScriptの​APIを​使うの?

この​疑問に​答えるには、​Cloudflare Workersで​Pythonが​動く​仕組みを​知る​必要が​あるので、​一旦置いておいてください。

wrangler.tomlの​中身

# Workersプロジェクト名
name = "hello-python"
# エントリーポイント
main = "src/entry.py"
# 互換性フラグ(ランタイムの特定の機能を有効化)
compatibility_flags = ["python_workers"]
# 互換性日付(ランタイムのバージョン番号のようなもの)
compatibility_date = "2024-03-29"

環境変数を​参照するには

env引数を​使う​(osモジュールでは​参照できない)。

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}")

環境変数を​定義するには​(1)

公開しても​よい値の​場合、​wrangler.tomlに​書く。

name = "environment-variables"
main = "src/entry.py"
compatibility_flags = ["python_workers"]
compatibility_date = "2024-03-29"

# ↓ここに環境変数を書く
[vars]
MY_NAME = "Ryuji Tsutsui"

環境変数を​定義するには​(2)

秘密の​値​(例: APIキー)の​場合、​ローカルでは​プロジェクト直下の​.dev.varsファイルに​書く。

SECRET_KEY="local_value"

環境変数を​定義するには​(2)

本番環境は​npx wrangler secret put {環境変数名}で​設定。

% npx wrangler secret put SECRET_KEY

 ⛅️ wrangler 3.78.8
-------------------

✔ Enter a secret value: … ****************
🌀 Creating the secret for the Worker "environment-variables"
✨ Success! Uploaded secret SECRET_KEY

実際に​環境変数を​定義・​参照してみる​(時間が​あれば​デモ)

以下の​サンプルコードを​参照。

% git clone https://github.com/ryu22e/python-workers-examples.git
% cd python-workers-examples/environment-variables
% # 設定方法はREADME.mdを参照

Cloudflare D1を​使った​シンプルな​API​(時間が​あれば​デモ)

Cloudflare D1とは

  • SQLiteベースの​サーバーレスデータベース

  • Cloudflareの​エッジ環境に​SQLiteの​リードレプリカが​配置される​ことで、​高速な​読み込みを​実現

Cloudflare D1を​使った​シンプルな​API​(時間が​あれば​デモ)

データベースの​作り方は​以下の​通り。

% # データベース「bookshelf」の作成
% npx wrangler d1 create bookshelf
% # ↑出力された内容をwrangler.tomlに追記
% # テーブルの作成(ローカル)
% npx wrangler d1 execute bookshelf --local --file=./schema.sql
% # テーブルの作成(本番)
% npx wrangler d1 execute bookshelf --remote --file=./schema.sql

Cloudflare D1を​使った​シンプルな​API​(時間が​あれば​デモ)

DBアクセスの​サンプルコードは​以下の​通り。

async def on_fetch(request, env):
    ...
    # INSERT文
    await (
        env.DB.prepare("INSERT INTO books (title, description) VALUES (?, ?)")
        .bind(title, description)
        .run()
    )
    # SELECT文
    r = await env.DB.prepare("SELECT * from books").all()
    print(r.results)
    ...

Cloudflare D1を​使った​シンプルな​API​(時間が​あれば​デモ)

以下の​サンプルコードを​参照。

% git clone https://github.com/ryu22e/python-workers-examples.git
% cd python-workers-examples/simple-api
% # 設定方法はREADME.mdを参照

Built-in packagesとは

  • Cloudflare Workersで​提供されている​Pythonパッケージ

  • requirements.txtに​パッケージ名を​記述する​ことで​利用できる

  • 予め用意された​パッケージのみ​利用可能

requirements.txtの​記述例

fastapi

FastAPIの​コード例

from fastapi import FastAPI

# この関数の定義は必ず必要
async def on_fetch(request, env):
    import asgi

    return await asgi.fetch(app, request, env)

# これ以降は普通のFastAPIのコード
app = FastAPI()

@app.get("/")
async def root(req: Request):
    ...

Q. requirements.txtって​普通こう​書かない?

# パッケージ名の右側にバージョンを指定する
fastapi==0.112.0

A. Cloudflare Workersでは​別の​方法で​バージ​ョンを​指定する

wrangler.tomlの​以下​項目に​よって​パッケージの​バージ​ョンが​決まる。

  • compatibility_flags: 互換性フラグ

  • compatibility_date: 互換性日付

サポートする​パッケージと​バージ​ョンの​一覧

以下の​公式ドキュメントで​確認できる。

https://developers.cloudflare.com/workers/languages/python/packages/#supported-packages

Q. Built-in packagesに​自分が​使いたいパッケージが​ない……

A. Built-in packagesの​micropipを​使えば、​他の​パッケージも​使える​(ただし、​これにも​制限が​ある)。

micropipの​コード例(1)

import micropip

# FastAPIの設定は省略

@app.get("/example")
async def example(req: Request):
    await micropip.install("beautifulsoup4==4.12.3")

    # beautifulsoup4はbuilt-in packagesにはないが
    # micropipでインストールできる
    from bs4 import BeautifulSoup
    ...

micropipの​コード例(2)

import micropip

# FastAPIの設定は省略
@app.get("/example")
async def example(req: Request):
    """micropip.install()が失敗する例"""
    # pandas==2.2.2はpure Pyhon wheelがないため、
    # micropipでインストールできずエラーになる
    # 参考: https://pyodide.org/en/stable/usage/faq.html#why-can-t-micropip-find-a-pure-python-wheel-for-a-package
    # (wheelとはPythonコードを1個のファイルにまとめたアーカイブ)
    await micropip.install("pandas==2.2.2")
    ...

Built-in packagesを​使った​API​(時間が​あれば​デモ)

以下の​サンプルコードを​参照。

% git clone https://github.com/ryu22e/python-workers-examples.git
% cd python-workers-examples/built-in-sample
% # 設定方法はREADME.mdを参照

残念な​お知らせ

この​発表時点では、​Built-in packagesは​本番環境に​デプロイできない。

% npx wrangler@latest deploy
(省略)
✘ [ERROR] A request to the Cloudflare API (/accounts/****/workers/scripts/built-in-sample) failed.

  You cannot yet deploy Python Workers that depend on packages defined in requirements.txt. Support
  for Python packages is coming soon. [code: 10021]

  If you think this is a bug, please open an issue at:
  https://github.com/cloudflare/workers-sdk/issues/new/choose

Cloudflare Workersで​Pythonが​動く​仕組み

Q. WASMを​サポートしない​Pythonが​なぜ​動くの?

  • Cloudflare Workersは​JavaScript(TypeScript)または​WebAssembly​(WASM)を​サポートしている

  • しかし、​Pythonには​コードを​WASMに​コンパイルする​機能が​ない

  • では、​なぜPythonが​動くのか?

A.Pyodideが​Pythonコードを​解釈して​実行している

  • Pyodideとは、​CPythonの​WASM実装

  • Cloudflare Workersの​ランタイムである​workerdには、​Pyodideが​組み込まれている

Pyodideで​Pythonを​動かせると​いう​ことは……

  • Rubyの​ruby.wasm、​PHPの​php-wasmなどを​使って、​他の​言語も​動かせるのでは?

  • (あくまで​私の​空想です)

Pyodideの​技術的制限

  • OpenSSLなどの​Cライブラリに​依存する​標準パッケージに​利用制限が​ある

  • 軽量化の​ため削除されている​標準パッケージが​ある

参考: Standard Library provided to Python Workers

サードパーティパッケージも​何でも​使えるわけではない

  • 一部の​Built-in packagesは​Cloudflare Workersで​動か​すために​パッチが​当てられている

  • 本番環境への​デプロイが​なかなかできるようにならないのは、​この​制限が​関係しているのかも?

jsモジュールが​存在する​理由

  • workerdは、​現状では​JavaScript(TypeScript)または​WASMの​ランタイムと​して​作られている

  • これに​Pythonを​加えると、​1から​実装する​ことになって​大変

  • そこで、​FFI​(Foreign Function Interface)を​提供して、​Pythonから​JavaScriptの​APIを​呼べるように​した

  • jsモジュールは、​Pythonでも​JavaScriptと​同等の​実装が​できるように​提供されている​次善の​策

以下の​公式ブログも​参照

Bringing Python to Workers using Pyodide and WebAssembly

jsモジュールは​正直​使いにくいが​……

  • JavaScriptと​Pythonの​流儀の​違いが​ある​ため、​違和感を​感じる​ことがある

  • request.json()で​辞書型で​扱えるのを​期待していたら​属性アクセスが​求められたりして、​混乱する

  • Built-in packagesが​この​使いにくさを​緩和してくれる​ことを​期待

最後に

まとめ

  • Cloudflare Workersは​サーバーレスアプリケーションを​デプロイできる​プラットフォーム

  • JavaScript(TypeScript)または​WASMを​サポートしているが、​Pythonも​使えるようになった

  • Pythonが​動くのは、​WASM実装の​PythonインタプリタPyodideを​使っている​ため

ご清聴​ありがとう​ございました

AIが考えた「Cloudflare Workers in Python」

AIが​考えた​「Cloudflare Workers in Python」