Flask アプリで App Engine タスクキュー(push タスク)を使用する方法(モジュール 7)

1. 概要

サーバーレス移行ステーション シリーズの Codelab(ご自分のペースで進められる実践型のチュートリアル)と関連動画は、主にレガシー サービスからの移行を 1 つ以上行うことで、Google Cloud サーバーレスのデベロッパーがアプリケーションをモダナイズできるよう支援することを目的としています。これにより、アプリの移植性が向上し、オプションと柔軟性が高まるため、幅広い Cloud プロダクトとの統合やアクセスが可能になり、新しい言語リリースへのアップグレードが容易になります。このシリーズは、当初は初期の Cloud ユーザー、主に App Engine(標準環境)のデベロッパーを対象としていましたが、Cloud FunctionsCloud Run などの他のサーバーレス プラットフォームや、該当する場合は他のプラットフォームも対象とするほど幅広くなっています。

この Codelab では、モジュール 1 の CodelabサンプルアプリApp Engine タスクキューの push タスクを使用する方法について説明します。モジュール 7 のブログ投稿と動画は、このチュートリアルを補完するもので、このチュートリアルの内容の概要を説明しています。

このモジュールでは、push タスクの使用を追加し、その使用をモジュール 8 で Cloud Tasks に、モジュール 9 で Python 3 と Cloud Datastore に移行します。pull タスクにタスクキューを使用している場合は、Cloud Pub/Sub に移行します。代わりにモジュール 18 ~ 19 を参照してください。

GCP コンソールの

  • App Engine Task Queue API/バンドル サービスを使用する
  • 基本的な Python 2 Flask App Engine NDB アプリにプッシュタスクの使用量を追加する

必要なもの

アンケート

このチュートリアルをどのように使用されますか?

通読のみ 通読して演習を行う

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

初心者 中級者 上級者

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

初心者 中級者 上級者

2. 背景情報

App Engine Task Queue は、push タスクと pull タスクの両方をサポートしています。アプリケーションの移植性を高めるため、Google Cloud チームは、Task Queue などの以前のバンドル サービスから、他の Cloud スタンドアロン サービスまたは同等のサードパーティ サービスへの移行をおすすめします。

プルタスクの移行については移行モジュール 18 ~ 19 で説明し、プッシュタスクの移行についてはモジュール 7 ~ 9 で説明します。App Engine タスクキューの push タスクから移行するには、モジュール 1 の Codelab で作成した既存の Flask アプリと App Engine NDB アプリに、その使用方法を追加します。そのアプリでは、新しいページビューが新しいセッションを登録し、ユーザーに最新のセッションを表示します。古い訪問履歴は二度と表示されず、Datastore の容量を占有するため、最も古い訪問履歴を自動的に削除するプッシュタスクを作成します。モジュール 8 では、このアプリをタスクキューから Cloud Tasks に移行します。

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

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

3. セットアップ/事前作業

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

  1. Cloud プロジェクトを設定する
  2. ベースライン サンプルアプリを入手する
  3. ベースライン アプリを(再)デプロイして検証する

これらの手順により、動作するコードから開始できます。

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

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

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

この Codelab の前提条件の 1 つは、機能するモジュール 1 App Engine アプリを用意することです。モジュール 1 Codelab を完了する(推奨)か、リポジトリからモジュール 1 アプリをコピーします。ご自分のコードと Google で用意したコードのいずれを使用される場合も、モジュール 1 のコードから開始します。この Codelab では、各ステップを順を追って説明します。最後に、モジュール 7 のリポジトリ フォルダ「FINISH」にあるコードに似たコードが完成します。

モジュール 1 のどのアプリを使用しても、フォルダは次のようになります(lib フォルダも含まれる場合があります)。

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

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

Module 1 アプリを(再)デプロイする手順は次のとおりです。

  1. lib フォルダが存在する場合は削除し、pip install -t lib -r requirements.txt を実行して lib を再作成します。Python 2 と 3 の両方がインストールされている場合は、代わりに pip2 コマンドを使用する必要があります。
  2. gcloud コマンドライン ツールをインストールして初期化し、その使用方法を確認してください。
  3. 発行する gcloud コマンドごとに PROJECT_ID を入力したくない場合は、gcloud config set project PROJECT_ID を使用して Cloud プロジェクトを設定します。
  4. gcloud app deploy を使用してサンプルアプリをデプロイする
  5. モジュール 1 アプリが期待どおりに動作し、最新のアクセスが問題なく表示されることを確認します(下図を参照)。

a7a9d2b80d706a2b.png

4. 構成を更新する

標準の App Engine 構成ファイル(app.yamlrequirements.txtappengine_config.py)を変更する必要はありません。

5. アプリケーション ファイルを変更する

メインのアプリケーション ファイルは main.py で、このセクションのすべての更新はこのファイルに関連しています。ウェブ テンプレート templates/index.html のマイナー アップデートもあります。このセクションで実装する変更は次のとおりです。

  1. インポートを更新する
  2. プッシュタスクを追加
  3. タスク ハンドラを追加する
  4. ウェブ テンプレートを更新する

1. インポートを更新する

google.appengine.api.taskqueue をインポートすると、タスクキュー機能が取り込まれます。一部の Python 標準ライブラリ パッケージも必要です。

  • 最も古いアクセスを削除するタスクを追加するため、アプリはタイムスタンプを処理する必要があります。つまり、timedatetime を使用します。
  • タスクの実行に関する有用な情報をログに記録するには、logging が必要です。

これらのインポートをすべて追加すると、変更前後のコードは次のようになります。

変更前:

from flask import Flask, render_template, request
from google.appengine.ext import 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

2. プッシュタスクを追加(タスクのデータを照合し、新しいタスクをキューに追加)

push キューのドキュメントには、「タスクを処理するには、タスクを push キューに追加する必要があります。App Engine には、default という名前のデフォルトの push キューが用意されています。これは、デフォルト設定ですぐに使用できるように構成されています。すべてのタスクをこのデフォルト キューに追加するだけで、これを使用できます。他のキューを作成して構成する必要はありません。」この Codelab では、簡潔にするために default キューを使用します。同じ特性または異なる特性を持つ独自のプッシュキューの定義について詳しくは、プッシュキューの作成に関するドキュメントをご覧ください。

この Codelab の主な目的は、表示されなくなった古いアクセスを Datastore から削除するジョブを持つタスク(default プッシュキュー)を追加することです。ベースライン アプリは、新しい Visit エンティティを作成して各アクセス(/ への GET リクエスト)を登録し、最新のアクセスを取得して表示します。最も古い訪問が再び表示または使用されることはないため、プッシュタスクは表示されている最も古い訪問よりも古いすべての訪問を削除します。これを実現するには、アプリの動作を少し変更する必要があります。

  1. 最近の訪問をクエリする際に、それらの訪問をすぐに返すのではなく、アプリを変更して、最後に表示された Visit のタイムスタンプを保存します。これより古い訪問はすべて削除しても安全です。
  2. このタイムスタンプをペイロードとして含むプッシュタスクを作成し、HTTP POST から /trim を介してアクセス可能なタスク ハンドラに転送します。具体的には、標準の Python ユーティリティを使用して Datastore のタイムスタンプを変換し、タスクに(浮動小数点数として)送信しますが、(文字列として)ログに記録し、その文字列をセンチネル値として返してユーザーに表示します。

これらはすべて fetch_visits() で行われます。更新前と更新後のコードは次のようになります。

変更前:

def fetch_visits(limit):
    return (v.to_dict() for v in Visit.query().order(
            -Visit.timestamp).fetch(limit))

変更後:

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

3. タスク ハンドラを追加する(タスク実行時に呼び出されるコード)

古い訪問の削除は fetch_visits() で簡単に実行できますが、この機能はエンドユーザーとはあまり関係がないことを認識してください。補助的な機能であり、標準アプリ リクエストの外部で非同期で処理するのに適しています。Datastore に保存される情報が少なくなるため、エンドユーザーはクエリの高速化というメリットを享受できます。Task Queue POST リクエストを介して /trim に呼び出される新しい関数 trim() を作成します。この関数は次の処理を行います。

  1. 「最古の訪問」タイムスタンプ ペイロードを抽出します
  2. そのタイムスタンプより古いすべてのエンティティを見つける Datastore クエリを発行します。
  3. 実際のユーザーデータは必要ないため、高速な「キーのみ」のクエリを選択します。
  4. 削除するエンティティの数(ゼロを含む)をログに記録します。
  5. エンティティを削除する ndb.delete_multi() を呼び出します(存在しない場合はスキップされます)。
  6. 空の文字列を返します(暗黙的な HTTP 200 戻りコードも返します)。

以下の trim() で、これらすべてを確認できます。main.pyfetch_visits() の直後に次のコードを追加します。

@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

4. ウェブ テンプレートを更新する

この Jinja2 条件を使用してウェブ テンプレート templates/index.html を更新し、その変数が存在する場合は最も古いタイムスタンプを表示します。

{% if oldest is defined %}
    <b>Deleting visits older than:</b> {{ oldest }}</p>
{% endif %}

このスニペットを表示された訪問リストの後、body を閉じる前に追加します。テンプレートは次のようになります。

<!doctype html>
<html>
<head>
<title>VisitMe Example</title>
<body>

<h1>VisitMe example</h1>
<h3>Last 10 visits</h3>
<ul>
{% for visit in visits %}
    <li>{{ visit.timestamp.ctime() }} from {{ visit.visitor }}</li>
{% endfor %}
</ul>

{% if oldest is defined %}
    <b>Deleting visits older than:</b> {{ oldest }}</p>
{% endif %}
</body>
</html>

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

このセクションでは、アプリをデプロイし、意図したとおりに動作することと、出力に反映されることを確認して、この Codelab を終了します。アプリの検証が完了したら、クリーンアップを行い、次のステップを検討します。

アプリケーションをデプロイして検証する

gcloud app deploy を使用してアプリをデプロイします。出力は、削除されるアクセスを表示する新しい行が下部に追加されている点を除き、モジュール 1 のアプリと同じになります。

4aa8a2cb5f527079.png

以上で、この Codelab は完了です。これで、コードはモジュール 7 リポジトリ フォルダの内容と一致するはずです。これで、モジュール 8 で Cloud Tasks に移行する準備が整いました。

クリーンアップ

全般

現時点で完了した場合は、課金が発生しないように App Engine アプリを無効にすることをおすすめします。ただし、さらにテストや実験を行う場合は、App Engine プラットフォームに無料割り当てがあります。この使用量枠を超えない限り、課金されることはありません。これはコンピューティングの料金ですが、関連する App Engine サービスの料金も発生する可能性があります。詳細については、料金ページをご覧ください。この移行に他の Cloud サービスが含まれる場合は、それらのサービスは別途請求されます。どちらの場合も、該当する場合は、以下の「この 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 固有のものです。詳細については、各プロダクトのドキュメントをご覧ください。

次のステップ

この「移行」では、Module 1 のサンプルアプリに Task Queue push キューの使用を追加し、訪問者の追跡のサポートを追加して、Module 7 のサンプルアプリを作成しました。次の移行では、必要に応じて App Engine push タスクから Cloud Tasks にアップグレードする方法を学習します。2021 年秋の時点で、Python 3 にアップグレードする際に Cloud Tasks に移行する必要はなくなりました。詳しくは、次のセクションをご覧ください。

Cloud Tasks に移行する場合は、モジュール 8 の Codelab に進みます。それ以外にも、Cloud Datastore、Cloud Memorystore、Cloud Storage、Cloud Pub/Sub(pull キュー)などの追加の移行を検討する必要があります。Cloud Run と Cloud Functions へのプロダクト間の移行もあります。すべての Serverless Migration Station コンテンツ(Codelab、動画、ソースコード [利用可能な場合])は、オープンソース リポジトリでアクセスできます。

7. Python 3 への移行

2021 年秋に、App Engine チームは、第 2 世代ランタイム(元々は第 1 世代ランタイムでのみ使用可能)への多くのバンドル サービスのサポートを拡大しました。つまり、アプリを Python 3 に移植する際に、App Engine Task Queue などのバンドル サービスから Cloud Tasks などのスタンドアロンの Cloud またはサードパーティの同等サービスに移行する必要がなくなりました。つまり、次世代ランタイムからバンドル サービスにアクセスするようにコードを改造すれば、Python 3 App Engine アプリで引き続き Task Queue を使用できます。

バンドル サービスの使用状況を Python 3 に移行する方法については、モジュール 17 の Codelab とその対応する動画をご覧ください。このトピックはモジュール 7 の範囲外ですが、モジュール 1 と 7 の両方のアプリの Python 3 バージョンを以下にリンクします。これらのアプリは Python 3 に移植され、App Engine NDB とタスクキューをまだ使用しています。

8. 参考情報

以下に、デベロッパーがこの移行モジュールまたは関連する移行モジュールや関連プロダクトをさらに詳しく調べるための追加リソースを示します。このコンテンツに関するフィードバックを送信できる場所、コードへのリンク、役立つ可能性のあるさまざまなドキュメントなどが含まれます。

Codelab に関する問題/フィードバック

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

移行に関するリソース

モジュール 2(開始)とモジュール 7(終了)のリポジトリ フォルダへのリンクを以下の表に示します。

Codelab

Python 2

Python 3

モジュール 1

コード

コード(このチュートリアルでは取り上げません)

モジュール 7(この Codelab)

コード

コード(このチュートリアルでは取り上げません)

オンライン リソース

このチュートリアルに関連する可能性のあるオンライン リソースは次のとおりです。

App Engine のタスクキュー

App Engine プラットフォーム

その他のクラウド情報

動画

ライセンス

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