サマリ
Cloud RunがGAされましたね。今までローカルで実行していたちょっと処理時間がかかるpythonスクリプトをお試しでCloud Runで実行したくなりました。
Cloud Runで実行するコンテナーはHTTPでリクエストを受けて実行できる必要があります。また、Cloud Runではリクエスト タイムアウトのことも考慮しないといけません。
リクエスト タイムアウトのことを考慮せずにとりあえずさっとスクリプトを動かすのにresponderが簡単だったので紹介します。
2020/09/05追記
公式のホームページでこのやり方について注意がありました。
Cloud Run で実行中のアプリケーションがリクエストの処理を終了すると、コンテナ インスタンスの CPU へのアクセスが無効になるか、厳しく制限されます。したがって、リクエスト ハンドラの範囲外で実行されるバックグラウンド スレッドやルーティンを開始しないでください。
バックグラウンド アクティビティは、HTTP レスポンスの送信後に発生します。コードを確認し、レスポンスの送信前にすべての非同期処理が完了するようにしてください。
この記事で紹介しているやり方を実施するときは上記のことを踏まえて実施してください。
@ChainSmokers さんコメントありがとうございました。
前提の話
試した環境
- OS: Mac OS 10.14.6
- docker desktop community: 2.1.0.5
- docker image: python:3.7.5-buster
responder
pythonのHTTPサービスフレームワークです。ASGI(Asynchronous Server Gateway Interface)を実装したものなので、Asynchronousと名前がついているとおり非同期処理もできます。
非同期処理自体は他のフレームワークでもできます。しかし、pythonのフレームワークで有名なFlaskやDjangoだとざっと調べたところパッケージを追加でインストールしたり、設定ファイルを変えたりする必要がありました。responderのサンプルコードを見るとインストールも実装も楽そうだったので使ってみました。
やったこと
ディレクトリ構成と各ファイルについて
vaivailx@MacBook-Pro-2 responder_sample_for_cloudrun % tree -L 3 .
.
├── Dockerfile
├── requirements.txt
└── src
├── sample.py
└── webapp.py
pythonの各ファイルは以下の処理をします。
- sample.py: 実行したいメインの処理
- webapp.py: HTTPリクエストを受け付ける処理
requirements.txtはこれだけ。
responder==2.0.4
Dockerfileではaptでパッケージの更新とrequirements.txtに書かれているパッケージをインストール。
FROM python:3.7.5-buster
ENV APP_HOME /app
WORKDIR $APP_HOME
ADD ./requirements.txt /app
ADD ./src /app
# Avoid warnings by switching to noninteractive
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get -y install --no-install-recommends apt-utils dialog 2>&1 \
# Install pylint
&& pip --disable-pip-version-check --no-cache-dir install pylint \
# Update Python environment based on requirements.txt
&& pip --disable-pip-version-check --no-cache-dir install -r requirements.txt \
&& rm -rf requirements.txt \
# Clean up
&& apt-get autoremove -y \
&& apt-get clean -y \
&& rm -rf /var/lib/apt/lists/*
# Switch back to dialog for any ad-hoc use of apt-get
ENV DEBIAN_FRONTEND=
# run app
ENV PORT '8080'
CMD python3 webapp.py
EXPOSE 8080
responderを使ってHTTPリクエストを受け付ける処理。
def
の前にasync
とつけるだけでその関数は非同期処理になります。
webapp.py
import logging
import responder
from sample import process_sample
logging.basicConfig(filename=f'/var/log/webapp.log', level=logging.DEBUG)
api = responder.API()
@api.route("/async")
async def asyncsample(req, resp):
@api.background.task
def process_param(params):
t = int(params.get('time', 10))
process_sample(t)
process_param(req.params)
resp.media = {'async success': True}
@api.route("/sync")
def syncsample(req, resp):
t = int(req.params.get('time', 10))
process_sample(t)
resp.media = {'sync success': True}
if __name__ == '__main__':
api.run()
ログ出力をしているのは、Croud Runで実行時に確認できるためです。
ルート名通り、ルートパス/asyncにアクセスすると非同期処理が実行され、ルートパス/syncにアクセスすると動機処理が実行されます。
今回はクエリパラメーターの値分スリープをするようにしました。
sample.py
import time
import logging
def process_sample(secs):
logging.debug('process starts.')
time.sleep(secs)
logging.debug('process ends.')
Cloud Runでの実行結果確認
では、Cloud Runのリクエスト タイムアウトを30秒に設定して実行してみます。(5分待つのが面倒なためデフォルト値から変更しています。)
同期実行:実行時間10秒
vaivailx@cloudshell:~ (cloudrun-test-259804)$ date;curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://responderasync-jpwo3ltkhq-an.a.run.app/sync?time=10
Fri Dec 13 09:52:59 +09 2019
{"sync success": true}vaivailx@cloudshell:~ (cloudrun-test-259804)$

→正常に終了
同期実行:実行時間40秒
vaivailx@cloudshell:~ (cloudrun-test-259804)$ date;curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://responderasync-jpwo3ltkhq-an.a.run.
app/sync?time=40
Fri Dec 13 09:55:45 +09 2019
upstream request timeoutvaivailx@cloudshell:~ (cloudrun-test-259804)$

→タイムアウト
非同期実行:実行時間10秒
vaivailx@cloudshell:~ (cloudrun-test-259804)$ date;curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://responderasync-jpwo3ltkhq-an.a.run.app/async?time=10
Fri Dec 13 10:02:31 +09 2019
{"async success": true}vaivailx@cloudshell:~ (cloudrun-test-259804)$

→
正常に終了
非同期実行:実行時間40秒
vaivailx@cloudshell:~ (cloudrun-test-259804)$ date;curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://responderasync-jpwo3ltkhq-an.a.run.app/async?time=40
Fri Dec 13 10:03:57 +09 2019
{"async success": true}vaivailx@cloudshell:~ (cloudrun-test-259804)$

→正常に終了
ついでに確認したこと
ちなみにHTTPリクエスト タイムアウトは最大900秒と設定画面にありましたが、これは実行時間ではなく
あくまでリクエストを受け付けてレスポンスを返すまでの時間で、非同期にしておけば900秒以上できることは確認できました。
以下非同期で1200秒実行した結果です。

最後に
- responderを使うと非同期処理を簡単に実装できるのを紹介しました。
- 処理時間がかかるバッチのpythonスクリプトをCloud Runで動かしてみたいときに使うのがありかなと思います。
- Cloud Runは、料金ページの通り、HTTPのレスポンスタイムではなく処理時間に応じて課金されます。非同期にしても処理を実行した時間はその分課金対象時間されるので注意してください。