趣味で作った 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.error
import urllib.request
に分ける必要があるurllib2.Request
→urllib.request.Request
urllib2.urlopen
→urllib.request.urlopen
urllib2.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はメンテ止まってそうなので、本当はそれも移行した方が良さそう。