App Engine タスクキューの pull タスクから Cloud Pub/Sub に移行する(モジュール 19)

1. 概要

Serverless Migration Station の Codelab シリーズ(セルフペース型のハンズオン チュートリアル)と関連動画は、Google Cloud サーバーレス デベロッパーが主にレガシー サービスからの移行を 1 つ以上の移行を通じてガイドし、アプリをモダナイズできるようにすることを目的としています。これにより、アプリのポータビリティが高まり、選択肢と柔軟性が増し、より広範な Cloud プロダクトと統合してアクセスできるようになり、より新しい言語リリースに簡単にアップグレードできるようになります。当初は初期の Cloud ユーザー、主に App Engine(スタンダード環境)のデベロッパーを対象としていますが、Cloud FunctionsCloud Run など、その他のサーバーレス プラットフォーム(該当する場合)まで幅広くカバーしています。

この Codelab では、Python 2 の App Engine デベロッパーに対し、App Engine タスクキューの pull タスクから Cloud Pub/Sub に移行する方法を紹介します。また、Datastore アクセスのための App Engine NDB から Cloud NDB への暗黙的な移行(主にモジュール 2 で説明)や、Python 3 へのアップグレードもあります。

モジュール 18 では、アプリに pull タスクを追加する方法について説明します。このモジュールでは、モジュール 18 で完成したアプリを Cloud Pub/Sub に移行します。push タスクにタスクキューを使用している場合、Cloud Tasks に移行する代わりに、モジュール 7 ~ 9 を参照してください。

GCP コンソールの

必要なもの

アンケート

このチュートリアルの利用方法をお選びください。

<ph type="x-smartling-placeholder"></ph> 最後まで読んでください 内容を読んで演習を済ませる をご覧ください。

Python のご利用経験はどの程度ありますか?

初心者 中級者 上級者

Google Cloud サービスの使用経験はどの程度ありますか?

<ph type="x-smartling-placeholder"></ph> 初心者 中級 上達 をご覧ください。

2. 背景情報

App Engine タスクキューは、push タスクと pull タスクの両方をサポートしています。Google Cloud では、アプリケーションのポータビリティを向上させるために、タスクキューなどの以前のバンドル サービスから、他の Cloud スタンドアロン サービスまたは同等のサードパーティ サービスに移行することをおすすめします。

移行モジュール 7 ~ 9 では push タスクの移行について説明し、モジュール 18 ~ 19 では pull タスクの移行に焦点を当てています。Cloud Tasks はタスクキューの push タスクに厳密には一致しますが、Pub/Sub はタスクキューの pull タスクに類似していません。

Pub/Sub には、タスクキューが提供する pull 機能よりも多くの機能があります。たとえば、Pub/Sub には push 機能もありますが、Cloud Tasks はタスクキューの push タスクに近いため、Pub/Sub push はどの移行モジュールでも適用されません。モジュール 19 の Codelab では、キューイング メカニズムをタスクキューの pull キューから Pub/Sub に切り替え、Datastore アクセスのために App Engine NDB から Cloud NDB に移行し、モジュール 2 の移行を繰り返します。

モジュール 18 のコードはPython 2 サンプルアプリとして、ソース自体は Python 2 と Python 3 と互換性があります。モジュール 19 で Cloud Pub/Sub(および Cloud NDB)に移行した後も、ソース自体は Python 2 と Python 3 と互換性があります。

このチュートリアルでは、次の手順について説明します。

  1. セットアップ / 事前作業
  2. 構成の更新
  3. アプリケーション コードを変更する

3. 設定/事前作業

このセクションでは、次の方法を説明します。

  1. Cloud プロジェクトを設定する
  2. ベースラインのサンプルアプリを取得する
  3. (再)ベースライン アプリをデプロイして検証する
  4. 新しい Google Cloud サービス/API を有効にする

この手順により、コードを実際に使ってみて、クラウド サービスに移行する準備が整っていることを確認できます。

1. プロジェクトのセットアップ

モジュール 18 の Codelab を完了している場合は、同じプロジェクト(およびコード)を再利用してください。あるいは、新しいプロジェクトを作成するか、別の既存のプロジェクトを再利用します。プロジェクトに有効な請求先アカウントがあり、App Engine アプリが有効になっていることを確認します。この Codelab で必要になるため、プロジェクト ID を検索します。PROJECT_ID 変数が見つかるたびに、プロジェクト ID を使用します。

2. ベースラインのサンプルアプリを取得する

前提条件の一つは、モジュール 18 の App Engine アプリを動作させることです。そのため、Codelab を完了するか(推奨、上記のリンク)、リポジトリからモジュール 18 のコードをコピーします。お客様のものか弊社によるものかを問わず、ここから始まります(「開始」)。この Codelab では移行手順について説明します。最後に、モジュール 19 のリポジトリ フォルダ(「FINISH」)にあるコードと同様のコードを使用して終了します。

使用するモジュール 18 のアプリに関係なく、フォルダは次のようになります。lib フォルダも含まれている可能性があります。

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

3. (再)ベースライン アプリをデプロイして検証する

次の手順で Module 18 アプリをデプロイします。

  1. lib フォルダがある場合は削除し、pip install -t lib -r requirements.txt を実行して lib を再設定します。開発マシンに Python 2 と Python 3 の両方がインストールされている場合は、代わりに pip2 を使用する必要があります。
  2. gcloud コマンドライン ツールをインストールして初期化し、その使用方法を確認していることを確認します。
  3. (省略可)発行する gcloud コマンドごとに PROJECT_ID を入力しない場合は、Cloud プロジェクトに gcloud config set project PROJECT_ID を設定します。
  4. gcloud app deploy を使用してサンプルアプリをデプロイする
  5. アプリが問題なく動作することを確認します。モジュール 18 の Codelab を完了すると、上位の訪問者と最近の訪問(下図)がアプリに表示されます。表示されていない場合は、訪問者数が表示されていない可能性があります。

b667551dcbab1a09.png

モジュール 18 のサンプルアプリを移行する前に、まず、変更したアプリで使用するクラウド サービスを有効にする必要があります。

4. 新しい Google Cloud サービス/API の有効化

古いアプリでは App Engine バンドル サービスが使用されていましたが、追加のセットアップは必要ないのに対し、スタンドアロンの Cloud サービスでは必要となっています。アップデート後のアプリでは、Cloud Pub/Sub と Cloud Datastore(Cloud NDB クライアント ライブラリ経由)の両方を使用します。App Engine と Cloud APIs の両方とも「無料枠」 割り当てられており、上限内であれば、このチュートリアルを完了しても料金は発生しません。Cloud APIs は、必要に応じて Cloud コンソールまたはコマンドラインから有効にできます。

Cloud Console から

Cloud コンソールで API Manager の [ライブラリ] ページ(正しいプロジェクト)に移動し、ページ中央の検索バーを使って Cloud Datastore API と Cloud Pub/Sub API を検索します。

c7a740304e9d35b.png

API ごとに [有効にする] ボタンをクリックします。お支払い情報の入力を求められる場合があります。たとえば、これは Cloud Pub/Sub API ライブラリのページです。

1b6c0a2a73124f6b.jpeg

コマンドラインから

コンソールから API を有効にする方法は視覚的にわかりやすくなりますが、コマンドラインを使う方がよい場合もあります。gcloud services enable pubsub.googleapis.com datastore.googleapis.com コマンドを発行して、両方の API を同時に有効にします。

$ gcloud services enable pubsub.googleapis.com datastore.googleapis.com
Operation "operations/acat.p2-aaa-bbb-ccc-ddd-eee-ffffff" finished successfully.

お支払い情報の入力を求められる場合があります。他の Cloud APIs を有効にし、その URI を確認したい場合は、各 API のライブラリ ページの下部で確認できます。たとえば、「サービス名」として pubsub.googleapis.com を確認します。最上部に配置できます

手順を完了すると、プロジェクトで API にアクセスできるようになります。次に、これらの API を使用するようにアプリケーションを更新します。

4. Pub/Sub リソースを作成する

モジュール 18 のタスクキュー ワークフローの順序をまとめます。

  1. モジュール 18 では、queue.yaml ファイルを使用して pullq という名前の pull キューを作成しました。
  2. アプリは、訪問者を追跡するためにタスクを pull キューに追加します。
  3. タスクは最終的にワーカーによって処理され、有限時間(1 時間)リースされます。
  4. 最近の訪問者数を集計するためにタスクが実行されます。
  5. 完了すると、タスクはキューから削除されます。

Pub/Sub を使用して同様のワークフローを複製します。次のセクションでは、Pub/Sub の基本的な用語を紹介し、必要な Pub/Sub リソースを作成する 3 つの方法を紹介します。

App Engine タスクキュー(pull)と Cloud Pub/Sub の用語

Pub/Sub に切り替える場合は、語彙を少し調整する必要があります。以下に、両サービスの主要カテゴリと関連する用語を示します。また、同様の比較が記載されている移行ガイドもご覧ください。

  • キューイングのデータ構造: タスクキューでは、データは pull キューに入れられます。Pub/Sub では、データはトピックに入ります。
  • キューに格納されたデータの単位: タスクキューでの pull タスクは、Pub/Sub ではメッセージと呼ばれます。
  • データ処理者: タスクキューでは、ワーカーが pull タスクにアクセスします。Pub/Sub でメッセージを受信するにはサブスクリプション/サブスクライバーが必要
  • データの抽出: pull タスクをリースすることは、トピックから(サブスクリプションを介して)メッセージを pull することと同じです。
  • クリーンアップ/完了: 完了時にタスクキューのタスクを pull キューから削除することは、Pub/Sub メッセージの確認応答に似ています。

キューに入れるプロダクトは異なりますが、ワークフローは比較的似ています。

  1. pull キューではなく、アプリは pullq という名前のトピックを使用します。
  2. タスクを pull キューに追加するのではなく、アプリはトピック(pullq)にメッセージを送信します。
  3. ワーカーが pull キューからタスクをリースするのではなく、worker という名前のサブスクライバーpullq トピックからメッセージを pull します。
  4. このアプリはメッセージ ペイロードを処理し、Datastore で訪問者数をインクリメントします。
  5. アプリは pull キューからタスクを削除せずに、処理されたメッセージに確認応答します。

タスクキューを使用する場合、pull キューを作成します。Pub/Sub を使用して設定するには、トピックとサブスクリプションの両方を作成する必要があります。モジュール 18 では、アプリ実行以外で queue.yaml を処理しました。Pub/Sub でも同じことをする必要があります。

トピックとサブスクリプションを作成するには、次の 3 つの方法があります。

  1. Cloud コンソール
  2. コマンドライン
  3. コードから(短い Python スクリプト)

以下のいずれかのオプションを選択し、対応する手順に沿って Pub/Sub リソースを作成します。

Cloud コンソール

Cloud コンソールからトピックを作成する手順は次のとおりです。

  1. Cloud コンソールの Pub/Sub の [トピック] ページに移動します。
  2. 上部にある [トピックを作成] をクリックします。新しいダイアログ ウィンドウが開きます(下の画像を参照)。
  3. [トピック ID] フィールドに「pullq」と入力します。
  4. オンになっているすべてのオプションの選択を解除し、[Google-managed encryption key] を選択します。
  5. [トピックを作成] ボタンをクリックします。

トピック作成ダイアログは次のようになります。

a05cfdbf64571ceb.png

トピックを作成したので、そのトピックのサブスクリプションを作成する必要があります。

  1. Cloud コンソールの Pub/Sub の [サブスクリプション] ページに移動します。
  2. 上部にある [Create subscription] をクリックします(下の画像を参照)。
  3. [Subscription ID] フィールドに「worker」と入力します。
  4. [Cloud Pub/Sub トピックを選択してください] プルダウンから pullq を選択し、「完全修飾パス名」に注目してください。例: projects/PROJECT_ID/topics/pullq
  5. [配信タイプ] で [pull] を選択します。
  6. その他のオプションはすべてそのままにして、[作成] ボタンをクリックします。

サブスクリプションの作成画面は次のようになります。

c5444375c20b0618.jpeg

[トピック] ページからサブスクリプションを作成することもできます。この「ショートカット」トピックをサブスクリプションに関連付けるのに便利です。サブスクリプションの作成について詳しくは、こちらのドキュメントをご覧ください。

コマンドラインから

Pub/Sub のユーザーは、gcloud pubsub topics create TOPIC_ID コマンドと gcloud pubsub subscriptions create SUBSCRIPTION_ID --topic=TOPIC_ID コマンドを使用して、トピックとサブスクリプションを作成できます。pullqTOPIC_IDworkerSUBSCRIPTION_ID でこれらを実行すると、プロジェクト PROJECT_ID の出力は次のようになります。

$ gcloud pubsub topics create pullq
Created topic [projects/PROJECT_ID/topics/pullq].

$ gcloud pubsub subscriptions create worker --topic=pullq
Created subscription [projects/PROJECT_ID/subscriptions/worker].

クイックスタート ドキュメントのこちらのページもご覧ください。コマンドラインを使用すると、トピックやサブスクリプションが定期的に作成されるワークフローが簡素化され、このようなコマンドをシェル スクリプトでこの目的で使用できます。

コードから(短い Python スクリプト)

トピックとサブスクリプションの作成を自動化するもう一つの方法は、ソースコードで Pub/Sub API を使用することです。以下は、モジュール 19 リポジトリ フォルダにある maker.py スクリプトのコードです。

from __future__ import print_function
import google.auth
from google.api_core import exceptions
from google.cloud import pubsub

_, PROJECT_ID = google.auth.default()
TOPIC = 'pullq'
SBSCR = 'worker'
ppc_client = pubsub.PublisherClient()
psc_client = pubsub.SubscriberClient()
TOP_PATH = ppc_client.topic_path(PROJECT_ID, TOPIC)
SUB_PATH = psc_client.subscription_path(PROJECT_ID, SBSCR)

def make_top():
    try:
        top = ppc_client.create_topic(name=TOP_PATH)
        print('Created topic %r (%s)' % (TOPIC, top.name))
    except exceptions.AlreadyExists:
        print('Topic %r already exists at %r' % (TOPIC, TOP_PATH))

def make_sub():
    try:
        sub = psc_client.create_subscription(name=SUB_PATH, topic=TOP_PATH)
        print('Subscription created %r (%s)' % (SBSCR, sub.name))
    except exceptions.AlreadyExists:
        print('Subscription %r already exists at %r' % (SBSCR, SUB_PATH))
    try:
        psc_client.close()
    except AttributeError:  # special Py2 handler for grpcio<1.12.0
        pass

make_top()
make_sub()

このスクリプトを実行すると、次のような出力が表示されます(エラーがない場合)。

$ python3 maker.py
Created topic 'pullq' (projects/PROJECT_ID/topics/pullq)
Subscription created 'worker' (projects/PROJECT_ID/subscriptions/worker)

API を呼び出して既存のリソースを作成すると、クライアント ライブラリから google.api_core.exceptions.AlreadyExists 例外がスローされ、スクリプトによって適切に処理されます。

$ python3 maker.py
Topic 'pullq' already exists at 'projects/PROJECT_ID/topics/pullq'
Subscription 'worker' already exists at 'projects/PROJECT_ID/subscriptions/worker'

Pub/Sub を初めて使用する場合は、Pub/Sub アーキテクチャに関するホワイト ペーパーで詳細をご覧ください。

5. 構成の更新

構成の更新には、さまざまな構成ファイルを変更することや、Cloud Pub/Sub エコシステム内で App Engine の pull キューに相当するものを作成することが含まれます。

queue.yaml を削除する

タスクキューから完全に移行します。Pub/Sub はこのファイルを使用しないため、queue.yaml を削除します。pull キューを作成する代わりに、Pub/Sub トピック(およびサブスクリプション)を作成します。

requirements.txt

google-cloud-ndbgoogle-cloud-pubsub の両方を requirements.txt に追加して、モジュール 18 の flask を結合します。更新したモジュール 19 requirements.txt は次のようになります。

flask
google-cloud-ndb
google-cloud-pubsub

この requirements.txt ファイルにはバージョン番号がありません。つまり、最新バージョンが選択されます。互換性の問題がある場合は、バージョン番号を使用してアプリの動作バージョンをロックインする標準的な方法に従います。

app.yaml

app.yaml への変更は、Python 2 を引き続き使用するか、Python 3 にアップグレードするかによって異なります。

Python 2

上記の requirements.txt の更新により、Google Cloud クライアント ライブラリの使用が追加されます。これらのライブラリを使用するには、App Engine からの追加サポート、つまり組み込みライブラリsetuptoolsgrpcio が必要です。組み込みライブラリを使用するには、app.yamllibraries セクションとライブラリのバージョン番号または「latest」が必要をご覧ください。モジュール 18 app.yaml には、まだこれらのセクションのいずれもありません。

変更前:

runtime: python27
threadsafe: yes
api_version: 1

handlers:
- url: /.*
  script: main.app

libraries セクションを app.yaml に追加して、setuptoolsgrpcio の両方のエントリとともに最新バージョンを選択します。また、Python 3 のプレースホルダの runtime エントリを追加します。これは、この記事の執筆時点で現在の 3.x リリース(3.10 など)とともにコメントアウトされています。この変更により、app.yaml は次のようになります。

変更後:

#runtime: python310
runtime: python27
threadsafe: yes
api_version: 1

handlers:
- url: /.*
  script: main.app

libraries:
- name: setuptools
  version: latest
- name: grpcio
  version: latest

Python 3

Python 3 ユーザーと app.yaml にとって、これはすべてを削除することを意味します。このセクションでは、handlers セクション、threadsafe および api_version ディレクティブを削除します。libraries セクションは作成しません。

第 2 世代のランタイムは組み込みサードパーティ ライブラリを提供していないため、app.yamllibraries セクションは必要ありません。さらに、組み込みされていないサードパーティ パッケージをコピー(ベンダリングや自己バンドルと呼ばれることもあります)する必要はなくなりました。アプリで使用するサードパーティ ライブラリを requirements.txt にリストするだけで済みます。

app.yamlhandlers セクションは、アプリケーション(script)と静的ファイル ハンドラを指定するためのものです。Python 3 ランタイムでは、ウェブ フレームワークが独自のルーティングを行う必要があるため、すべてのスクリプト ハンドラを auto に変更する必要があります。アプリ(モジュール 18 のような)が静的ファイルを提供しない場合、すべてのルートが auto になり、ルートは無関係になります。そのため、handlers セクションも不要なので、削除します。

最後に、Python 3 では threadsafe ディレクティブも api_version ディレクティブも使用されていないため、それらも削除します。結論として、runtime ディレクティブのみが残るように app.yaml のすべてのセクションを削除し、Python 3 の最新バージョン(3.10 など)を指定します。更新前と更新後の app.yaml の外観は次のとおりです。

変更前:

runtime: python27
threadsafe: yes
api_version: 1

handlers:
- url: /.*
  script: main.app

変更後:

runtime: python310

Python 3 用の app.yaml からすべてを削除する準備ができていない方のために、モジュール 19 のリポジトリ フォルダに app3.yaml の代替ファイルを用意しています。これをデプロイに使用する場合は、コマンドの末尾に gcloud app deploy app3.yaml というファイル名を追加します(そうしないと、デフォルトで Python 2 の app.yaml ファイルを変更せずにデプロイします)。

appengine_config.py

Python 3 にアップグレードする場合、appengine_config.py は不要なため、削除します。サードパーティ ライブラリのサポートでは、requirements.txt で指定するだけでよいからです。Python 2 ユーザー向けです。

モジュール 18 appengine_config.py には、サードパーティ ライブラリをサポートする適切なコードが含まれています。たとえば、Flask や、先ほど requirements.txt に追加された Cloud クライアント ライブラリなどです。

変更前:

from google.appengine.ext import vendor

# Set PATH to your libraries folder.
PATH = 'lib'
# Add libraries installed in the PATH folder.
vendor.add(PATH)

ただし、このコードだけでは、追加した組み込みライブラリ(setuptoolsgrpcio)をサポートするには不十分です。さらに数行が必要になるため、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)

Cloud クライアント ライブラリのサポートに必要な変更について詳しくは、バンドル サービスの移行に関するドキュメントをご覧ください。

その他の構成の更新

lib フォルダがある場合は削除します。Python 2 を使用している場合は、次のコマンドを実行して lib フォルダを補充します。

pip install -t lib -r requirements.txt  # or pip2

開発システムに Python 2 と Python 3 の両方がインストールされている場合は、pip ではなく pip2 を使用する必要があります。

6. アプリケーション コードを変更する

このセクションでは、App Engine タスクキューの pull キューの使用が Cloud Pub/Sub に置き換えられた、メイン アプリケーション ファイル main.py の更新について説明します。ウェブ テンプレート templates/index.html に変更はありません。どちらのアプリも同じように動作し、同じデータを表示する必要があります。

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

インポートと初期化が更新されています。

  1. インポートについては、App Engine NDB とタスクキューを Cloud NDB と Pub/Sub に置き換えます。
  2. pullq の名前を QUEUE から TOPIC の名前に変更しました。
  3. pull タスクではワーカーが 1 時間リースしたのに対し、Pub/Sub ではタイムアウトがメッセージ単位で測定されるため、HOUR 定数は削除します。
  4. Cloud APIs では API クライアントを使用する必要があるため、Cloud NDB と Cloud Pub/Sub で API クライアントを開始します。後者はトピックとサブスクリプションの両方のクライアントを提供します。
  5. Pub/Sub には Cloud プロジェクト ID が必要なため、google.auth.default() からインポートして取得します。
  6. Pub/Sub には「完全修飾パス名」が必要そのため、*_path() コンビニエンス関数を使用して作成してください。

以下にモジュール 18 のインポートと初期化を示します。その後に、上記の変更を実装した後のセクションの内容を示します。新しいコードのほとんどは、さまざまな Pub/Sub リソースです。

変更前:

from flask import Flask, render_template, request
from google.appengine.api import taskqueue
from google.appengine.ext import ndb

HOUR = 3600
LIMIT = 10
TASKS = 1000
QNAME = 'pullq'
QUEUE = taskqueue.Queue(QNAME)
app = Flask(__name__)

変更後:

from flask import Flask, render_template, request
import google.auth
from google.cloud import ndb, pubsub

LIMIT = 10
TASKS = 1000
TOPIC = 'pullq'
SBSCR = 'worker'

app = Flask(__name__)
ds_client  = ndb.Client()
ppc_client = pubsub.PublisherClient()
psc_client = pubsub.SubscriberClient()
_, PROJECT_ID = google.auth.default()
TOP_PATH = ppc_client.topic_path(PROJECT_ID, TOPIC)
SUB_PATH = psc_client.subscription_path(PROJECT_ID, SBSCR)

データモデルの更新内容にアクセスする

Visit データモデルは変更されません。Datastore にアクセスするには、Cloud NDB API クライアント コンテキスト マネージャー ds_client.context() を明示的に使用する必要があります。つまり、コードでは、Datastore の呼び出しを Python with ブロック内の store_visit()fetch_visits() の両方でラップすることになります。この更新はモジュール 2 で取り上げる内容と同じです。

Pub/Sub にとって最も重要な変更は、タスクキューの pull タスクのキュー登録を、pullq トピックへの Pub/Sub メッセージのパブリッシュに置き換えることです。上記の更新の前後のコードを次に示します。

変更前:

class Visit(ndb.Model):
    'Visit entity registers visitor IP address & timestamp'
    visitor   = ndb.StringProperty()
    timestamp = ndb.DateTimeProperty(auto_now_add=True)

def store_visit(remote_addr, user_agent):
    'create new Visit in Datastore and queue request to bump visitor count'
    Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()
    QUEUE.add(taskqueue.Task(payload=remote_addr, method='PULL'))

def fetch_visits(limit):
    'get most recent visits'
    return Visit.query().order(-Visit.timestamp).fetch(limit)

変更後:

class Visit(ndb.Model):
    'Visit entity registers visitor IP address & timestamp'
    visitor   = ndb.StringProperty()
    timestamp = ndb.DateTimeProperty(auto_now_add=True)

def store_visit(remote_addr, user_agent):
    'create new Visit in Datastore and queue request to bump visitor count'
    with ds_client.context():
        Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()
    ppc_client.publish(TOP_PATH, remote_addr.encode('utf-8'))

def fetch_visits(limit):
    'get most recent visits'
    with ds_client.context():
        return Visit.query().order(-Visit.timestamp).fetch(limit)

VisitorCount データモデルの更新

VisitorCount データモデルは変更されず、fetch_counts() を実行します。ただし、Datastore クエリを with ブロックでラップする点が異なります。

変更前:

class VisitorCount(ndb.Model):
    visitor = ndb.StringProperty(repeated=False, required=True)
    counter = ndb.IntegerProperty()

def fetch_counts(limit):
    'get top visitors'
    return VisitorCount.query().order(-VisitorCount.counter).fetch(limit)

変更後:

class VisitorCount(ndb.Model):
    visitor = ndb.StringProperty(repeated=False, required=True)
    counter = ndb.IntegerProperty()

def fetch_counts(limit):
    'get top visitors'
    with ds_client.context():
        return VisitorCount.query().order(-VisitorCount.counter).fetch(limit)

ワーカーコードを更新する

NDB を Cloud NDB に、タスクキューを Pub/Sub に置き換える限り、ワーカーコードが更新されますが、ワークフローは変わりません。

  1. Cloud NDB コンテキスト マネージャーの with ブロックで Datastore 呼び出しをラップする。
  2. タスクキューのクリーンアップでは、pull キューからすべてのタスクを削除します。Pub/Sub では「確認応答 ID」がacks で収集され、最後に削除/確認応答されます。
  3. タスクキューの pull タスクは、Pub/Sub メッセージが pull されるのと同様の方法でリースされます。pull タスクの削除はタスク オブジェクト自体で行われますが、Pub/Sub メッセージは確認応答 ID を介して削除されます。
  4. Pub/Sub メッセージ ペイロードには(Python 文字列ではなく)バイトが必要なため、トピックにメッセージをパブリッシュし、トピックからメッセージを pull するときには、それぞれ UTF-8 のエンコードとデコードがあります。

log_visitors() を、上述の変更を実装する以下の更新したコードに置き換えます。

変更前:

@app.route('/log')
def log_visitors():
    'worker processes recent visitor counts and updates them in Datastore'
    # tally recent visitor counts from queue then delete those tasks
    tallies = {}
    tasks = QUEUE.lease_tasks(HOUR, TASKS)
    for task in tasks:
        visitor = task.payload
        tallies[visitor] = tallies.get(visitor, 0) + 1
    if tasks:
        QUEUE.delete_tasks(tasks)

    # increment those counts in Datastore and return
    for visitor in tallies:
        counter = VisitorCount.query(VisitorCount.visitor == visitor).get()
        if not counter:
            counter = VisitorCount(visitor=visitor, counter=0)
            counter.put()
        counter.counter += tallies[visitor]
        counter.put()
    return 'DONE (with %d task[s] logging %d visitor[s])\r\n' % (
            len(tasks), len(tallies))

変更後:

@app.route('/log')
def log_visitors():
    'worker processes recent visitor counts and updates them in Datastore'
    # tally recent visitor counts from queue then delete those tasks
    tallies = {}
    acks = set()
    rsp = psc_client.pull(subscription=SUB_PATH, max_messages=TASKS)
    msgs = rsp.received_messages
    for rcvd_msg in msgs:
        acks.add(rcvd_msg.ack_id)
        visitor = rcvd_msg.message.data.decode('utf-8')
        tallies[visitor] = tallies.get(visitor, 0) + 1
    if acks:
        psc_client.acknowledge(subscription=SUB_PATH, ack_ids=acks)
    try:
        psc_client.close()
    except AttributeError:  # special handler for grpcio<1.12.0
        pass

    # increment those counts in Datastore and return
    if tallies:
        with ds_client.context():
            for visitor in tallies:
                counter = VisitorCount.query(VisitorCount.visitor == visitor).get()
                if not counter:
                    counter = VisitorCount(visitor=visitor, counter=0)
                    counter.put()
                counter.counter += tallies[visitor]
                counter.put()
    return 'DONE (with %d task[s] logging %d visitor[s])\r\n' % (
            len(msgs), len(tallies))

メインのアプリケーション ハンドラ root() に変更はありません。HTML テンプレート ファイル templates/index.html も変更する必要がないため、必要な更新はすべてラップされます。Cloud Pub/Sub を使用した新しいモジュール 19 アプリケーションへようこそ。

7. 概要/クリーンアップ

アプリをデプロイして、意図したとおりに動作し、出力に反映されていることを確認します。また、ワーカーを実行して訪問者数を処理します。アプリの検証後、クリーンアップ手順を実施し、次のステップを検討します。

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

pullq トピックと worker サブスクリプションがすでに作成されていることを確認します。それが完了し、サンプルアプリを使用する準備ができたら、gcloud app deploy でアプリをデプロイします。基になるキューイング メカニズム全体を正常に置き換えた点を除き、出力はモジュール 18 のアプリと同じになるはずです。

b667551dcbab1a09.png

アプリのウェブ フロントエンドが、アプリケーションのこの部分が機能することを確認します。アプリのこの部分は、上位の訪問者と最近の訪問を正常にクエリして表示しますが、この訪問者を合計数に追加する pull タスクを作成しながら、アプリ側でこの訪問を登録し、そのタスクは、処理を待機中のキューに追加されます。

これは、App Engine バックエンド サービス、cron ジョブ、/log の閲覧、コマンドラインの HTTP リクエストの発行で実行できます。curl でワーカーコードを呼び出している実行例を次に示します(実際の PROJECT_ID に置き換えます)。

$ curl https://PROJECT_ID.appspot.com/log
DONE (with 1 task[s] logging 1 visitor[s])

更新されたカウントは、次回のウェブサイト訪問時に反映されます。これで作業は完了です。

クリーンアップ

全般

現時点で完了したら、課金が発生しないように App Engine アプリを無効にすることをおすすめします。さらにテストや実験を行う場合は、App Engine プラットフォームに無料の割り当てが用意されています。この使用量ティアを超えない限り、料金は発生しません。これはコンピューティングに関するものですが、関連する App Engine サービスに対して料金が発生する場合もあります。詳細については、料金ページをご覧ください。この移行に他のクラウド サービスが含まれる場合、それらは別途請求されます。いずれの場合も、該当する場合は「この Codelab に固有の情報」をご覧ください。セクションをご覧ください。

App Engine のような Google Cloud サーバーレス コンピューティング プラットフォームにデプロイすると、わずかなビルドとストレージの費用が発生します。Cloud Build には、Cloud Storage と同様に無料の割り当てがあります。イメージのストレージが割り当ての一部を使い果たします。ただし、このような無料枠がない地域に住んでいる可能性もあるため、潜在的な費用を最小限に抑えるためにストレージの使用量に注意してください。特定の Cloud Storage「フォルダ」確認すべき項目には

  • console.cloud.google.com/storage/browser/LOC.artifacts.PROJECT_ID.appspot.com/containers/images
  • console.cloud.google.com/storage/browser/staging.PROJECT_ID.appspot.com
  • 上記のストレージ リンクは、ご利用の PROJECT_ID と *LOC*に依存します(例: 「us」)アプリが米国でホストされている場合は、

一方、このアプリケーションや他の関連する移行 Codelab を続行せず、すべてを完全に削除したい場合は、プロジェクトをシャットダウンします。

この Codelab のみ

以下に示すサービスは、この Codelab に固有のものです。詳細については、各プロダクトのドキュメントをご覧ください。

  • Cloud Pub/Sub のさまざまなコンポーネントには無料枠があります。全体的な使用量を判断することで、費用への影響を把握できます。詳しくは、料金ページをご覧ください。
  • App Engine Datastore サービスは Cloud Datastore(Datastore モードの Cloud Firestore)で提供しており、これにも無料枠があります。詳しくは料金ページをご覧ください。

次のステップ

このチュートリアル以外にも、以前のバンドル サービスからの移行に重点を置いた他の移行モジュールを以下に示します。

App Engine はもはや Google Cloud の唯一のサーバーレス プラットフォームではありません。小規模な App Engine アプリまたは機能が限られているアプリをスタンドアロンのマイクロサービスに変換したい場合、またはモノリシック アプリを複数の再利用可能なコンポーネントに分割する必要がある場合は、こうした理由から Cloud Functions への移行を検討することをおすすめします。コンテナ化がアプリケーション開発ワークフローの一部になっている場合、特に CI/CD(継続的インテグレーション/継続的デリバリーまたはデプロイ)パイプラインで構成されている場合は、Cloud Run への移行を検討してください。これらのシナリオについては、次のモジュールで説明します。

  • App Engine から Cloud Functions への移行: モジュール 11 を参照
  • App Engine から Cloud Run への移行: モジュール 4 を参照して、Docker を使用してアプリをコンテナ化するか、モジュール 5 を参照して、コンテナ、Docker の知識、または Dockerfile を使用せずに

別のサーバーレス プラットフォームへの切り替えは任意です。変更を行う前に、アプリやユースケースに最適なオプションを検討することをおすすめします。

次に検討する移行モジュールに関係なく、Serverless Migration Station のすべてのコンテンツ(Codelab、動画、ソースコード(利用可能な場合))には、オープンソース リポジトリからアクセスできます。リポジトリの README には、検討すべき移行や関連する「順序」に関するガイダンスも用意されています。概要をまとめたものです

8. 参考情報

以下に、本モジュールや関連する移行モジュールと関連プロダクトについて詳しく学ぶデベロッパー向けの追加リソースを示します。これには、このコンテンツに関するフィードバックを提供する場所、コードへのリンク、役立つさまざまなドキュメントが含まれます。

Codelab の問題/フィードバック

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

移行に関するリソース

以下の表に、モジュール 18(START)とモジュール 19(FINISH)のリポジトリ フォルダへのリンクを示します。

Codelab

Python 2

Python 3

モジュール 18

コード

該当なし

モジュール 19(この Codelab)

コード

(上記のように app.yaml を更新した場合を除き、app3.yaml を使用する以外は Python 2 と同じです)

オンライン リファレンス

このチュートリアルに関連するリソースは次のとおりです。

App Engine のタスクキュー

Cloud Pub/Sub

App Engine NDB と Cloud NDB(Datastore)

App Engine プラットフォーム

Cloud のその他の情報

動画

ライセンス

この作業はクリエイティブ・コモンズの表示 2.0 汎用ライセンスにより使用許諾されています。