趣味で作った GAE/Python Python 2.7 のサービスを Python 3.7 に移行したときにやったことのまとめです。
サービスの概要
- cronで定期的にクローリングしてDatastoreに格納
- cronから呼び出されるエンドポイントは
/_ah/のlogin: adminの場所に設置
- cronから呼び出されるエンドポイントは
- DatastoreからRSSを動的生成
cron.yaml → Cloud Scheduler
- App Engine HTTPで
/_ah/のエンドポイントを指定することですぐに移行できた。 - gcloud コマンドでは、べき等にできないため、デプロイコマンドが作りづらい。
Cloud ndb 移行
- そもそも db ライブラリだったので、ndb に移行するところからスタート
- requirements.txt に googleapis_common_protos と google-cloud-ndb を追加
from google.appengine.ext import dbをfrom google.cloud import ndbに置き換え- ndb.Client() を作って、
with client.context()のブロックに入れる必要あり。webapp2 では BaseHandler の dispatch で対応する。
client = ndb.Client()
class BaseHandler(webapp2.RequestHandler):
def dispatch(self):
with client.context():
super(BaseHandler, self).dispatch()
urlfetch を urllib2 へ移行
- urllib2 が使えるようになっているのでそちらに移行する
- 以下のようなラッパー関数を作った
def fetch(url, headers=None):
if headers is not None:
req = urllib2.Request(url, headers=headers)
result = urllib2.urlopen(req)
return result.read()
else:
result = urllib2.urlopen(url)
return result.read()
今回のアプリはこの時点でApp Engine Serviceの参照が切れた
Python 3.7 ランタイムへ移行
Python レベルでの変更
- urllib2 を標準 urllib ライブラリへ移行
import urllib2をimport urllib.errorimport urllib.requestに分ける必要があるurllib2.Request→urllib.request.Requesturllib2.urlopen→urllib.request.urlopenurllib2.URLError→urllib.error.URLError- urlopen の引数に
context = ssl._create_unverified_context()を与えないと SSL 証明書のルートがうまくいってないと通らない
- 正規表現の
ur'...'はr'...'に変更
App Engine の変更
- requirements.txt に以下の変更
webapp2==3.0.0b1に変更gunicorn追加googleapis_common_protos削除
- app.yaml に以下の変更
rumtime: python37に変更entrypoint: gunicorn -b :$PORT main:app追加- handlers の script: は auto に変更
login: adminは一旦消すしかなさそう
appengine_config.py削除
構造変更
- 以下の形にパッケージ構成を変更 (外部APIコール、RSS生成も別パッケージに分割している)
- model: ndb 含めてモデルクラス
- datastore: Cloud Datastore 関連処理
- web: App Engine 関連処理 (
__init__.pyに app を記載)- handlers: Request Handler ごとにファイル分けて分割。admin 用のものは削除した。
- app.yaml の entrypoint は
gunicorn -b :$PORT web:appに変更
Cloud Functions に移行
- admin にあったのは cron のエントリポイントだけだったので、Functionsへの移行が楽ではないかと考えて移設
- functions パッケージに関数ごとにファイルを作って、実装はそちらに記載
- main.py に Cloud Functions から呼び出される関数を記載。
with client.context():でラッパーして functions パッケージの実装を呼ぶdef store_update(event, context)の2引数の関数を記載
- Cloud PubSub にトピックを作成
gcloud functions deploy [Cloud Functions名] --project [プロジェクト名] --trigger-topic [main.py内の関数名] --runtime python37でデプロイ- Cloud Scheduler をApp Engine HTTPからPubSubに向ける
この時点で準備完了したので、App Engineをメインドメインへデプロイした。
その他
- GAEのコードとCloud Functionsのコードはこの方法を使うことで共有可能
- 画像等、Cloud Functionsにデプロイしなくて良いファイルが多い場合は gcloud コマンドの引数に
--ignore-fileで.gcloudignore以外のファイル名の ignore ファイルを指定可能- 逆に App Engine 側は skip_files の指定ができなくなっているので、直下の
.gcloudignoreは App Engine 用にして、Cloud Functions 用は --ignore-file にしたほうが良いかと。
- 逆に App Engine 側は skip_files の指定ができなくなっているので、直下の
- 移行完了までGAEのデプロイは
--no-promoteにして、アクセスには影響がでないようにした。 - cronのPubSubは1本にして、 Schedulerからどの関数を呼ぶかペイロードに指定して、main.py でペイロード見て振り分けでも良さそう。
- wepapp2はメンテ止まってそうなので、本当はそれも移行した方が良さそう。