モジュール 8: App Engine の ndb と taskqueue から Cloud NDB と Cloud Tasks に移行する

この一連の Codelab(ご自分のペースで進められる実践型のチュートリアル)は、一連の移行についてガイダンスを実施することにより、Google App Engine(標準)のデベロッパーがアプリを最新化できるよう支援することを目的としています。最も大きなステップは、元のランタイム バンドル サービスから移行することです。理由は、次世代のランタイムが柔軟性に富み、ユーザーにさまざまなサービス オプションが提供されるためです。新しい世代のランタイムに移行することで、Google Cloud プロダクトとの統合が容易になり、幅広いサポート対象サービスを使用し、現在の言語リリースをサポートできるようになります。

この Codelab は、App Engine の push タスクと taskqueue API / ライブラリから Cloud Tasks に移行する際に役立ちます。アプリでタスクキューを使用していない場合、この Codelab を使用して、App Engine の push タスクを Cloud Tasks に移行する方法を学ぶことができます。

次のことを行う方法について説明します。

  • App Engine taskqueue から Cloud Tasks に移行する
  • Cloud Tasks を使用して push タスクを作成する
  • App Engine ndb から Cloud NDB に移行する(モジュール 2 と同じ)

必要なもの

アンケート

この Codelab の使用方法

全体を通して読むのみ 内容を読んで演習を完了する

前の Codelab(モジュール 7) でサンプルアプリに App Engine push タスクを追加したので、これを Cloud Tasks に移行できるようになりました。このチュートリアルの移行では、次の主なステップに着目します。

  1. セットアップ / 事前作業
  2. 設定ファイルを更新する
  3. メイン アプリケーションを更新する

チュートリアルの主要部分に進む前に、まずプロジェクトをセットアップし、コードを取得してから、ベースライン アプリをデプロイします。これで、作業コードを使って作業を開始できます。

1. プロジェクトを設定する

モジュール 7 Codelab で使用したプロジェクトと同じプロジェクトを再利用することをおすすめします。または、新しいプロジェクトを作成するか、別の既存のプロジェクトを再利用することもできます。プロジェクトにアクティブな請求先アカウントがあり、App Engine(アプリ)が有効になっていることを確認します。

2. ベースライン サンプルアプリを入手する

この Codelab の前提条件の 1 つは、正常に機能するモジュール 7 サンプルアプリを用意することです。まだお持ちでない場合は、先に進む前にモジュール 7 のチュートリアル(上記のリンク)を完了しておくことをおすすめします。すでに内容を理解している場合は、下記のモジュール 7 のコードから始めることもできます。

ご自身のサンプルアプリか当社のものかを問わず、モジュール 7 コードが出発点になります。このモジュール 2 の Codelab では、各ステップについて順を追って説明します。完了すると、終了ポイントのコード(Python 2 から 3 への任意のポートを含む)が次のように表示されます。

モジュール 7 ファイル(ご自身または Google のもの)のディレクトリは、次のようになります。

$ ls
README.md               appengine_config.py     requirements.txt
app.yaml                main.py                 templates

モジュール 7 のチュートリアルを完了すると、Flask とその依存関係を含む lib フォルダも作成されます。

3. モジュール 7 アプリを(再)デプロイする

この段階で実施する必要がある残りの事前作業のステップ:

  1. gcloud コマンドライン ツールについて復習する(必要な場合)
  2. モジュール 7 コードを App Engine に再デプロイする(必要な場合)

各手順を正常に実行して、動作していることを確認したら、このチュートリアルに進み、構成ファイルを使用して作業を開始します。

requirements.txt

モジュール 7 の requirements.txt には、必須パッケージとして Flask だけが表示されます。Cloud NDB と Cloud Tasks には独自のクライアント ライブラリがあるため、このステップでは次のように requirements.txt にパッケージを追加します。

Flask==1.1.2
google-cloud-ndb==1.7.1
google-cloud-tasks==1.5.0

各ライブラリの最新バージョンを使用することをおすすめします。上記のバージョン番号は、このドキュメントの作成時点での Python 2 の最新バージョンです。(Python 3 と同等のパッケージの方が新しいバージョンになる可能性が高くなります)。FINISH リポジトリ フォルダのコードはより頻繁に更新され、新しいリリースを含む可能性もあります。ただし、一般的に凍結されている Python 2 ライブラリにはあまり対応していません。

app.yaml

libraries セクションの app.yamlgrpciosetuptools の組み込みライブラリを参照します。

libraries:
- name: grpcio
  version: 1.0.0
- name: setuptools
  version: 36.6.0

appengine_config.py

pkg_resources を使用して、これらの組み込みライブラリを Flask や Google Cloud クライアント ライブラリなどのコピーされたサードパーティ ライブラリに関連付けるように appengine_config.py を更新します。

import pkg_resources
from google.appengine.ext import vendor

# Set PATH to your libraries folder.
PATH = 'lib'
# Add libraries installed in the PATH folder.
vendor.add(PATH)
# Add libraries to pkg_resources working set to find the distribution.
pkg_resources.working_set.add_entry(PATH)

main.py というアプリケーション ファイルは 1 つのみであるため、このセクションで説明する変更はすべてそのファイルにのみ反映されます。

インポートと初期化を更新する

このアプリは現在、組み込みの google.appengine.api.taskqueue ライブラリと google.appengine.ext.ndb ライブラリを使用しています。

  • 変更前:
from datetime import datetime
import logging
import time
from flask import Flask, render_template, request
from google.appengine.api import taskqueue
from google.appengine.ext import ndb

google.cloud.ndbgoogle.cloud.tasks を両方とも置き換えます。さらに、Cloud Tasks ではタスクのペイロードを JSON エンコードする必要があるため、json もインポートする必要があります。設定が完了すると、main.pyimport セクションは次のようになります。

  • 変更後:
from datetime import datetime
import json
import logging
import time
from flask import Flask, render_template, request
from google.cloud import ndb, tasks

Cloud Tasks(および Cloud NDB)に移行する

  • 変更前:
def store_visit(remote_addr, user_agent):
    'create new Visit entity in Datastore'
    Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()

モジュール 2 で行った変更、つまりすべての Datastore アクセスに対するコンテキスト マネージャーの追加、を除き store_visit() に変更はありません。この追加は、with ステートメントでラップされた新しい Visit エンティティの移動作成の形で行われます。

  • 変更後:
def store_visit(remote_addr, user_agent):
    'create new Visit entity in Datastore'
    with ds_client.context():
        Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()

現在のところ、Cloud Tasks を使用するには、Google Cloud プロジェクトで App Engine を有効にする必要があります(App Engine コードがない場合も含む)。そうしないとタスクキューは機能しません。(詳細については、こちらのセクションをご覧ください)。Cloud Tasks は、App Engine(App Engine の「ターゲット」)で実行されるタスクをサポートするだけでなく、Cloud Functions、Cloud Run、GKE、Compute Engine などのパブリック IP アドレスの HTTP エンドポイント(HTTP ターゲット)で、さらにオンプレミス ウェブサーバーでも実行できます。このシンプルなアプリはタスクに App Engine ターゲットを使用します。

Cloud NDB と Cloud Tasks を使用するには、いくつかの設定を行う必要があります。Flask 初期化での main.py の最初の部分で、Cloud NDB と Cloud Tasks を初期化します。また、push タスクの実行場所を示す定数も定義します。

app = Flask(__name__)
ds_client = ndb.Client()
ts_client = tasks.CloudTasksClient()

PROJECT_ID = 'PROJECT_ID'  # replace w/your own
REGION = 'REGION'    # replace w/your own
QUEUE_NAME = 'default'     # replace w/your own if desired
QUEUE_PATH = ts_client.queue_path(PROJECT_ID, REGION, QUEUE_NAME)

タスクキューを作成したら、プロジェクトの PROJECT_ID、タスクが実行される REGION、push キューの名前を入力します(App Engine のリージョンと同じにする必要があります)。App Engine には「default」キューが含まれるので、その名前を使用します(必須ではありません)。

default キューは特別なもので、App Engine API を使用する特定の状況下で自動的に作成されます。したがって、モジュール 7 と同じプロジェクトを使用する(再使用する)場合、default はすでに存在します。ただし、モジュール 8 専用の新規プロジェクトを作成した場合は、default を手動で作成する必要があります。default キューの詳細については、queue.yaml ドキュメントをご覧ください。

ts_client.queue_path() の目的は、タスクの作成に必要なタスクキューの「完全修飾されたパス名」(QUEUE_PATH)を作成することです。タスク パラメータを指定する JSON 構造も必要です。

task = {
    'app_engine_http_request': {
        'relative_uri': '/trim',
        'body': json.dumps({'oldest': oldest}).encode(),
        'headers': {
            'Content-Type': 'application/json',
        },
    }
}

上記からわかること:

  1. タスク ターゲット情報を指定しています。
    • App Engine ターゲットの場合、リクエストの種類に app_engine_http_request を指定します。relative_uri は App Engine タスクハンドラです。
    • HTTP ターゲットの場合、代わりに http_requesturl を使用します。
  2. body: (push)タスクに送信する JSON と Unicode 文字列によるエンコード パラメータです。
  3. JSON エンコードされた Content-Type ヘッダーを明示的に指定します。

使用できるオプションの詳細については、関連するドキュメントをご覧ください。

設定が終わったら、fetch_visits() を更新しましょう。前のチュートリアルから内容は次のようになります。

  • 変更前:
def fetch_visits(limit):
    'get most recent visits and add task to delete older visits'
    data = Visit.query().order(-Visit.timestamp).fetch(limit)
    oldest = time.mktime(data[-1].timestamp.timetuple())
    oldest_str = time.ctime(oldest)
    logging.info('Delete entities older than %s' % oldest_str)
    taskqueue.add(url='/trim', params={'oldest': oldest})
    return (v.to_dict() for v in data), oldest_str

必要な更新は次のとおりです。

  1. App Engine ndb から Cloud NDB に切り替える
  2. 最古の訪問タイムスタンプを抽出して表示する新しいコード
  3. App Engine taskqueue ではなく Cloud Tasks を使用して新しいタスクを作成する

新しい fetch_visits() は次のようになります。

  • 変更後:
def fetch_visits(limit):
    'get most recent visits and add task to delete older visits'
    with ds_client.context():
        data = Visit.query().order(-Visit.timestamp).fetch(limit)
    oldest = time.mktime(data[-1].timestamp.timetuple())
    oldest_str = time.ctime(oldest)
    logging.info('Delete entities older than %s' % oldest_str)
    task = {
        'app_engine_http_request': {
            'relative_uri': '/trim',
            'body': json.dumps({'oldest': oldest}).encode(),
            'headers': {
                'Content-Type': 'application/json',
            },
        }
    }
    ts_client.create_task(parent=QUEUE_PATH, task=task)
    return (v.to_dict() for v in data), oldest_str

コード更新の要約

  • Cloud NDB への切り替えは、with ステートメント内で Datastore コードを移動することを意味します。
  • クラウドタスクへの切り替えにより、taskqueue.add() ではなく ts_client.create_task() が使用されます。
  • キューのフルパスと task ペイロードを渡します(上記を参照)。

タスク ハンドラを更新(push)する

(push)タスク ハンドラ関数に対して行う変更はほとんどありません。

  • 変更前:
@app.route('/trim', methods=['POST'])
def trim():
    '(push) task queue handler to delete oldest visits'
    oldest = request.form.get('oldest', type=float)
    keys = Visit.query(
            Visit.timestamp < datetime.fromtimestamp(oldest)
    ).fetch(keys_only=True)
    nkeys = len(keys)
    if nkeys:
        logging.info('Deleting %d entities: %s' % (
                nkeys, ', '.join(str(k.id()) for k in keys)))
        ndb.delete_multi(keys)
    else:
        logging.info('No entities older than: %s' % time.ctime(oldest))
    return ''   # need to return SOME string w/200

必要な唯一の変更は、コンテキスト マネージャー with ステートメント内(クエリと削除リクエストの両方)にすべての Datastore アクセスを配置することです。この点を考慮して、trim() ハンドラを次のように更新します。

  • 変更後:
@app.route('/trim', methods=['POST'])
def trim():
    '(push) task queue handler to delete oldest visits'
    oldest = float(request.get_json().get('oldest'))
    with ds_client.context():
        keys = Visit.query(
                Visit.timestamp < datetime.fromtimestamp(oldest)
        ).fetch(keys_only=True)
        nkeys = len(keys)
        if nkeys:
            logging.info('Deleting %d entities: %s' % (
                    nkeys, ', '.join(str(k.id()) for k in keys)))
            ndb.delete_multi(keys)
        else:
            logging.info('No entities older than: %s' % time.ctime(oldest))
    return ''   # need to return SOME string w/200

この Codelab と次の Codelab では、templates/index.html に変更はありません。

アプリケーションのデプロイ

コードがコンパイルするすべての変更を再確認し、再デプロイします。アプリが正常に動作することを確認します。モジュール 7 と同じ出力になるはずです。内部の再配線が完了したので、これですべてが想定どおりに機能するはずです。

モジュール 7 の Codelab を行わずにこのチュートリアルに飛び級参加した場合、アプリ自体は変更されません。このアプリは、メインのウェブページ(/)へのすべての訪問を登録します。サイトへの訪問は以下の画像のように表示され、10 回を超えた古い訪問履歴を削除したことも通知されます。

モジュール 7 visitme アプリ

これでこの Codelab は終了です。これで、コードはモジュール 8 リポジトリのコードと一致するようになりました。push タスクの移行は非常に重要です。モジュール 9(下の Codelab リンク)はオプションとして、ユーザーが Python 3 と Cloud Datastore に移行するのを支援します。

オプション: クリーンアップ

次の移行 Codelab に進む準備が整うまで、課金されるのを回避するためのクリーンアップを行うにはどうすればよいでしょうか。既存のデベロッパーの方は、App Engine の料金に関する情報に関する最新の内容をすでにご存じと思われます。

オプション: アプリを無効にする

次のチュートリアルに進む準備がまだ完了していない場合は、アプリを無効にすることで課金されないようにできます。次の Codelab に進む準備ができた時点で、再度有効にできます。アプリケーションが無効になっている間、料金のかかるトラフィックは発生しません。ただし、もう 1 つの課金対象は、Datastore の使用量です。無料割り当て量を超過した場合は課金が発生するため、上限を超えないよう削除してください。

ただし、移行を続けず、完全に削除したい場合は、プロジェクトをシャットダウンしてください。

次のステップ

このチュートリアルの次のステップでは、モジュール 9、その Codelab、Python 3 への移植を行います。このステップへの準備が整っていない方もいるので、履修は任意のレベルです。また、Cloud NDB から Cloud Datastore へのオプション ポートもあります。このポートは完全に任意で、NDB から移行して Cloud Datastore を使用するコードと統合するユーザーを想定しています。この移行はモジュール 3 移行 Codelab と同じです。

  • モジュール 9: Python 2 から 3 へ、Cloud NDB から Cloud Datastore への移行。
    • Python 3 への移植用オプション モジュールです。
    • Cloud NDB から Cloud Datastore へのオプションの移行も含まれます(モジュール 3 と同じ)。
    • Cloud Tasks v1 から v2 の小規模な移行(Python 2 ではクライアント ライブラリが凍結されてため)
  • モジュール 4: Docker を使用して Cloud Run に移行します。
    • Docker を使用して、Cloud Run で実行できるようにアプリをコンテナ化します。
    • この移行により、Python 2 をそのまま利用できます。
  • モジュール 5: Cloud Build を使用した Cloud Run への移行。
    • Cloud Build を使用して Cloud Run で実行するようにアプリをコンテナ化します。
    • Docker、コンテナ、Dockerfile についての知識は何も必要ありません。
    • すでに Python 3 に移行しているアプリが必要です(Buildpacks では Python 2 はサポートされていません)。
  • モジュール 6: Cloud Firestore への移行。
    • Cloud Firestore に移行して Firebase の機能にアクセスします。
    • Cloud Firestore は Python 2 をサポートしていますが、この Codelab は Python 3 でのみ使用できます。

App Engine 移行モジュールの Codelab に関する問題 / フィードバック

この Codelab に問題が見つかった場合は、提出する前にまず問題を検索してください。新しい問題の検索と作成を行うためのリンク:

移行に関するリソース

モジュール 7(START)とモジュール 8(FINISH)のリポジトリ フォルダへのリンクを以下の表に示します。これらのファイルには、すべての App Engine Codelab 移行のリポジトリからアクセスすることもできます。このクローンの作成、および ZIP ファイルのダウンロードも可能です。

Codelab

Python 2

Python 3

モジュール 7

コード

該当なし

モジュール 8

コード

該当なし

App Engine リソース

この特定の移行に関する追加リソースは以下のとおりです。