Vercel(旧now.sh)でPythonアプリを公開する

2020/05/27

使ってみた

t f B! P L
おはこんばんにちは。
今日はVercelというウェブアプリを公開できるPaaSの備忘録になります。

機能としては静的ファイルのホスティングと、Serverless Functionの実行が無料でできます。無料プランの仕様は公式サイトを確認してください。

以前はnow.shというサービス名だったのですが、最近Vercelに変わったようです。設定ファイルの仕様なども少々変わりつつあるようなので、ここにまとめておきます。

Vercel自体は非常に手軽で、爆速にアプリを公開できるのでおすすめです。

大まかな手順

  1. Git(Github、Gitlab、Bitbucketなど)のレポジトリを作成しておき、Vercelと連携させる
  2. vercel.jsonに設定事項を記入
  3. 静的ファイル、PythonのAPIコードやrequirements.txtなどを用意し、ローカルで動作確認
  4. git pushすると自動でVercelにデプロイされる

1.Git連携

そもそもVercelに登録するのに何らかのGitアカウントが必要。登録後に、Vercelのダッシュボードに「Gitレポジトリからプロジェクトをインポート」的なボタンがあるので、それを押すだけ。

これで連携したレポジトリにvercel.jsonを含むcommitをgitにpushするだけで、Vercelが自動デプロイしてくれる。神。

Gitレポジトリを経由せず、Vercelに直接デプロイすることもできますが、今回は割愛。

2.vercel.json

私は以下のようにしています。

functionsには、Serverless Functionとして実行したいPythonファイルのパスを書きます。
routesには、URLパスと実際のファイルパスのマッピングを書きます。これはApacheでいうRewriteRuleみたいなものでしょう。
{
  "version": 2,
  "regions": ["hnd1"],
  "functions": {
    "api/index.py": {
      "memory": 1024,
      "maxDuration": 10
    }
  },
  "routes": [
	{"src": "/hello", "dest": "api/index.py" },
	{"src": "/", "dest": "index.html" }
  ]
}
ちなみに日本サーバーのregionはhnd1らしい。羽田?

その他詳しいことは公式ドキュメントをご覧ください。

3.ローカルでの動作確認

今回はFlaskを使うということで。

prj-root/
 ├ vercel.json
 ├ index.html
 ├ api/
 │ └index.py 
 └ requirements.txt

ディレクトリ構造が重要です。他の言語でもそうですが、VercelのローカルルールとしてServerless Functionのコードはapiディレクトリに入れておく必要があるようです。


api/index.pyは例えば以下のようにします。

from flask import Flask

app = Flask(__name__)

@app.route('/hello')
def index():
  return 'Hello World'

if __name__ == '__main__':
  app.run(host='localhost', port=8080)

重要な点として、app.route()に書くパスを、先ほどのvercel.jsonで書いたapi/index.pyへのパスと一致するようにします(今回ならhello)。

ちなみに/に対して、index.htmlをFlaskから返す必要はありません。静的ファイル(今回であればPythonスクリプトによって動的に加工する必要のないファイル)は、vercelのサーバーがそのまま配信してくれます。

Flaskをウェブサーバーとして動かす場合は、もちろんFlaskからindex.htmlを返す必要があります。しかしサイトごとにサーバープログラムを常時起動しておくのはリソースを食うので、そんなことができるのは専用サーバー、一部のPaaS、もしくは自分のローカルマシンくらいです。多くの安い共用ウェブサーバーでは、Apacheなどのウェブサーバー専用プログラムを共用で起動しておき、そこからCGIを経由してプログラム実行するスタイルがまだまだ主流です。

言うまでもないですが、CGIではリクエストがあるごとに、プロセスを起動して処理を行い、レスポンス後にプロセスを終了させる方式をとります。

その点でServerless FunctionとCGIは似てる、というと雑すぎると怒られるかもしれませんが、ウェブサーバーとの関係性においては両者は結構似ています。

例えばよくあるApacheサーバーとCGIの組み合わせだと、そもそもApache自体がウェブサーバーですので

html(静的ファイル)へのアクセスの場合は
Apache -> index.html
とシンプルですが

cgiへのアクセスの場合は
Apache -> CGI -> (Flask) -> Pythonコード実行
という流れになります。

1つ目のCGIは仕組みとしてのCGIで、PythonのCGIライブラリのことでありません。また、(Flask)と括弧書きになっているところはhttpリクエストを扱えるライブラリなら何でもよく、もしくは使わずに自分でHeaderとかを書いてもよい。

ところがFlaskも静的ファイルを返すことはできるので、
Apache -> CGI -> (Flask) -> (Pythonコード実行) -> index.html
という大仰なレスポンスもできるわけです。これも無駄が多いだけで間違っているところは無いのでふつうに動きます。

自分はApacheとかよく理解していなかった頃は、上記の大仰なレスポンスをしていたわけですが、静的ファイルならそのまま置いとくだけでApacheがよしなにレスポンスしてくれます。

Vercelでもこれと同じで

静的ファイルはそのまま(ふつうの静的ファイルのホスティング)
Vercel -> index.html

それ以外のプログラムは
Vercel -> Serverless Function -> (Flask) -> Pythonコード実行

という流れにしましょう。静的ファイルもFlaskを経由してしまうとリソースの無駄です。

共用サーバーのCGIとServerless Functionの違いはホストを分けられるとか、スケールできるとか色々ありますが、正直アクセス数のほとんどない零細ウェブアプリにはあまり関係のない話です。

唯一アプリの規模のかかわらず差があるとすれば、それはServerless FunctionではWriteができないという点です。
例えばテキストファイルをreadできますがwriteはできません。SQliteならSELECTはできますがINSERTはできません。これらのファイルは静的ファイル扱いであり、そもそもgitのcommitベースで管理しているため、勝手に書き換わったらgitのcommitとコンフリクトしてしまいます。
そのため、静的ファイルホスティングやServerless Functionでは、ファイルは基本的にImmutableになります。

その一方CGIは、安いレンサバであってもディレクトリやファイルのパーミッションはいじれることが多いので、パーミッションが適切にすればディスク上のファイルへwriteも可能です。

Serverless Functionで変化するデータを扱いたい場合は、マイクロサービスの時代なのでDB as a serviceを別途用意して連携させる必要があります。

4.Gitへpush

最後にpushして無事にデプロイされるか確認します。

Writeの無い単機能ウェブアプリなら、フロントはペラのhtmlで作っておいて、ちょっと古いですがajax等でバックのapiサーバー(今回はVercelのServerless Function)にアクセスしてフロントのhtmlを一部書き換える、という構成にすればすぐにデプロイできると思います。

QooQ