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

1. 概要

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

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

GCP コンソールの

  • App Engine Task Queue API/バンドル サービスを使用する
  • 基本的な Python 2 Flask App Engine NDB アプリに pull キュー使用状況を追加する

必要なもの

アンケート

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

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

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

初心者 中級者 上級者

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

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

2. 背景情報

App Engine タスクキューの pull タスクから移行するには、モジュール 1 の Codelab で生成された既存の Flask と App Engine NDB アプリに、その使用法を追加します。このサンプルアプリは、エンドユーザーへの最新のアクセスを表示します。それなら問題ありませんが、訪問者を追跡して、最も多くアクセスしたのは誰かを確認することもより一層興味深いものです。

訪問者数をpush タスクにすることもできますが、ここでは、訪問の登録とユーザーへの即時応答を担うサンプルアプリと、指定された「ワーカー」の間で責任を分担します。通常のリクエスト / レスポンス ワークフロー外で訪問者数を集計します。

この設計を実装するために、メイン アプリケーションに pull キューを使用するとともに、ワーカー機能をサポートします。ワーカーは、独立したプロセス(バックエンド インスタンスや常時稼働している VM で実行されるコードなど)、cron ジョブ、または curlwget を使用した基本的なコマンドライン HTTP リクエストとして実行できます。この統合の後、次の Codelab(モジュール 19)でアプリを Cloud Pub/Sub に移行できます。

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

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

3. 設定/事前作業

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

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

この手順により、コードを確実に稼働させることができます。

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

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

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

この Codelab の前提条件の 1 つは、モジュール 1 の App Engine アプリを準備しておくことです。モジュール 1 の Codelab を完了する(推奨)か、リポジトリからモジュール 1 アプリをコピーします。皆さんのコードであれ私たちのものであれ、モジュール 1 のコードが「開始」となります。この Codelab では各ステップを説明します。最後に、モジュール 18 のリポジトリ フォルダ「FINISH」にあるコードに似たコードで締めくくります。

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

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

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

次の手順でモジュール 1 アプリをデプロイします。

  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. モジュール 1 のアプリが想定どおりに動作し、最近のアクセスが表示されていることを確認します(以下の図を参照)。

a7a9d2b80d706a2b.png

4. 構成の更新

標準の App Engine 構成ファイル(app.yamlrequirements.txtappengine_config.py)を変更する必要はありません。代わりに、次の内容の新しい構成ファイル queue.yaml を追加し、同じ最上位ディレクトリに配置します。

queue:
- name: pullq
  mode: pull

queue.yaml ファイルには、アプリに存在するすべてのタスクキューを指定します(App Engine によって自動的に作成される default [push] キューを除く)。この場合、pullq という名前の pull キューは 1 つだけです。App Engine では、mode ディレクティブを pull として指定する必要があります。指定しない場合、デフォルトで push キューが作成されます。pull キューの作成について詳しくは、ドキュメントをご覧ください。その他のオプションについては、queue.yaml リファレンス ページもご覧ください。

このファイルをアプリとは別にデプロイします。引き続き gcloud app deploy を使用しますが、コマンドラインで queue.yaml も指定します。

$ gcloud app deploy queue.yaml
Configurations to update:

descriptor:      [/tmp/mod18-gaepull/queue.yaml]
type:            [task queues]
target project:  [my-project]

WARNING: Caution: You are updating queue configuration. This will override any changes performed using 'gcloud tasks'. More details at
https://cloud.google.com/tasks/docs/queue-yaml

Do you want to continue (Y/n)?

Updating config [queue]...⠹WARNING: We are using the App Engine app location (us-central1) as the default location. Please use the "--location" flag if you want to use a different location.
Updating config [queue]...done.

Task queues have been updated.

Visit the Cloud Platform Console Task Queues page to view your queues and cron jobs.
$

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

このセクションでは、次のファイルの更新を行っています。

  • main.py - pull キューの使用をメイン アプリケーションに追加する
  • templates/index.html - ウェブ テンプレートを更新して新しいデータを表示する

インポートと定数

まず、pull キューをサポートするために、新しいインポートを 1 つといくつかの定数を追加します。

  • タスクキュー ライブラリ google.appengine.api.taskqueue のインポートを追加します。
  • 3 つの定数を追加して、pull キュー(QUEUE)から 1 時間(HOUR)に pull タスク(TASKS)の最大数をリースできるようにします。
  • 最近のアクセス数とアクセス数の多いユーザー(LIMIT)を表示するための定数を追加します。

更新後の元のコードは次のようになります。

変更前:

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

app = Flask(__name__)

変更後:

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__)

pull タスクを追加する(タスクのデータを集め、pull キューにタスクを作成する)

データモデル Visit は、fetch_visits() に表示する訪問の照会のデータと同じままです。コードのこの部分で必要な変更は、store_visit() 内のみです。訪問を登録し、訪問者の IP アドレスを使って pull キューにタスクを追加し、ワーカーが訪問者カウンタをインクリメントできるようにします。

変更前:

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 entity in Datastore'
    Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()

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'
    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)

ユーザーをトラッキングするためのデータモデルとクエリ関数を作成する

データモデル VisitorCount を追加して訪問者をトラッキングします。visitor 自体のフィールドと、訪問数をトラッキングするための整数の counter が必要です。次に、fetch_counts() という名前の新しい関数(Python classmethod でも可)を追加し、上位の訪問者を上位から最小の順序でクエリして返します。fetch_visits() の本文のすぐ下にクラスと関数を追加します。

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

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

ワーカーコードを追加

新しい関数 log_visitors() を追加して、/log への GET リクエストで訪問者をログに記録します。辞書/ハッシュを使用して最新の訪問者数を追跡し、1 時間で可能な限り多くのタスクをリースします。タスクごとに、同じユーザーによるすべての訪問が集計されます。集計が取得できたら、アプリは Datastore にすでに存在する対応する VisitorCount エンティティをすべて更新するか、必要に応じて新しいエンティティを作成します。最後のステップでは、処理されたタスクの数のうち、何人の訪問者が登録されたのかを示す書式なしテキスト メッセージが返されます。この関数を fetch_counts() のすぐ下の main.py に追加します。

@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))

新しい表示データでメインハンドラを更新する

上位の訪問者を表示するには、メインハンドラ root() を更新して fetch_counts() を呼び出すようにします。さらに、上位の訪問者と最近の訪問者数を示すようにテンプレートが更新されます。fetch_visits() の呼び出しからの最新のアクセス数と訪問者数をパッケージ化し、それを 1 つの context にドロップしてウェブ テンプレートに渡します。この変更の前と後のコードを以下に示します。

変更前:

@app.route('/')
def root():
    'main application (GET) handler'
    store_visit(request.remote_addr, request.user_agent)
    visits = fetch_visits(10)
    return render_template('index.html', visits=visits)

変更後:

@app.route('/')
def root():
    'main application (GET) handler'
    store_visit(request.remote_addr, request.user_agent)
    context = {
        'limit':  LIMIT,
        'visits': fetch_visits(LIMIT),
        'counts': fetch_counts(LIMIT),
    }
    return render_template('index.html', **context)

以上が main.py に必要な変更です。ここでは、main.py に対する変更の概要を示すため、これらの更新を画像で表しています。

ad5fd3345efc13d0.png

新しい表示データでウェブ テンプレートを更新する

ウェブ テンプレート templates/index.html では、最近の訪問者の通常のペイロードに加えて、上位の訪問者が表示されるように更新する必要があります。上位の訪問者とその数をページ上部の表にドロップし、前回と同様に最新の訪問を表示します。唯一の変更点は、数値をハードコードするのではなく、limit 変数を介して表示される数値を指定することです。ウェブ テンプレートに必要な更新は次のとおりです。

変更前:

<!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>

変更後:

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

<h1>VisitMe example</h1>

<h3>Top {{ limit }} visitors</h3>
<table border=1 cellspacing=0 cellpadding=2>
    <tr><th>Visitor</th><th>Visits</th></tr>
{% for count in counts %}
    <tr><td>{{ count.visitor|e }}</td><td align="center">{{ count.counter }}</td></tr>
{% endfor %}
</table>

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

これで、モジュール 1 サンプルアプリに App Engine タスクキューの pull タスクを使用するために必要な変更が完了しました。ディレクトリにモジュール 18 のサンプルアプリが表示され、次のファイルが含まれている必要があります。

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

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

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

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

この Codelab の前半で gcloud app deploy queue.yaml を使用して行ったように、pull キューがすでにセットアップされていることを確認します。それが完了し、サンプルアプリを使用する準備ができたら、gcloud app deploy でアプリをデプロイします。出力はモジュール 1 のアプリの場合とほぼ同じですが、「上位の訪問者」が表示されるようになっています。次のテーブルがあります。

b667551dcbab1a09.png

更新されたウェブ フロントエンドには上位の訪問者と最近の訪問が表示されますが、訪問者数にはこの訪問は含まれていないことに注意してください。このアプリでは、処理を待機しているタスクである pull キューで、この訪問者のカウントをインクリメントする新しいタスクをドロップしながら、以前の訪問者数を表示します。

このタスクを実行するには、次の方法で /log を呼び出します。

たとえば、curl を使用して GET リクエストを /log に送信する場合、PROJECT_ID を指定した場合、出力は次のようになります。

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

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

お疲れさまでした。以上で、この Codelab は以上で、App Engine タスクキューの pull キューサービスをサンプルアプリに追加する方法を学習しました。これで、モジュール 19 で Cloud Pub/Sub、Cloud NDB、Python 3 に移行する準備が整いました。

クリーンアップ

全般

現時点で完了したら、課金が発生しないように 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 に固有のものです。詳細については、各プロダクトのドキュメントをご覧ください。

次のステップ

この「移行」ではタスクキューの push キューの使用をモジュール 1 のサンプルアプリに追加して、訪問者の追跡のサポートを追加し、モジュール 18 のサンプルアプリを実装しました。次の移行では、App Engine の pull タスクを Cloud Pub/Sub にアップグレードします。2021 年後半から、Python 3 にアップグレードする際に Cloud Pub/Sub への移行は不要になりました。詳しくは次のセクションで説明します。

Cloud Pub/Sub への移行については、モジュール 19 の Codelab をご覧ください。これ以外にも、Cloud Datastore、Cloud Memorystore、Cloud Storage、Cloud Tasks(push キュー)などの移行を検討する必要があります。また、Cloud Run と Cloud Functions へのプロダクト間の移行もあります。Serverless Migration Station のすべてのコンテンツ(Codelab、動画、ソースコード(利用可能な場合))には、オープンソース リポジトリからアクセスできます。

7. Python 3 への移行

2021 年秋、App Engine チームは、バンドル サービスの多くのサポートを第 2 世代ランタイム(第 1 世代ランタイムを含む)に拡張しました。その結果、アプリを Python 3 に移行する際に、App Engine タスクキューなどのバンドル サービスからスタンドアロン クラウドや Cloud Pub/Sub などのサードパーティ サービスに移行する必要がなくなりました。つまり、次世代ランタイムからバンドル サービスにアクセスするようにコードを改良すれば、Python 3 の App Engine アプリでタスクキューを引き続き使用できます。

バンドル サービスの使用を Python 3 に移行する方法については、モジュール 17 の Codelab と対応する動画をご覧ください。このトピックはモジュール 18 の範囲外ですが、以下のリンクは、Python 3 に移植され、引き続き App Engine NDB を使用しているモジュール 1 の Python 3 バージョンです。(将来的には、Module 18 アプリの Python 3 バージョンも利用できるようになります)。

8. 参考情報

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

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

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

移行に関するリソース

以下の表に、モジュール 1(START)とモジュール 18(FINISH)のリポジトリ フォルダへのリンクを示します。また、すべての App Engine Codelab 移行用のリポジトリからもアクセスできます。クローンを作成するか、ZIP ファイルをダウンロードします。

Codelab

Python 2

Python 3

モジュール 1

コード

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

モジュール 18(この Codelab)

コード

なし

オンライン リファレンス

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

App Engine のタスクキュー

App Engine プラットフォーム

App Engine ドキュメント

Python 2 App Engine(スタンダード環境)ランタイム

Python 3 App Engine(スタンダード環境)ランタイム

Python 2 と3 つの App Engine(スタンダード環境)ランタイム

Python 2 から 3 への App Engine(スタンダード環境)の移行ガイド

App Engine の料金割り当てに関する情報

第 2 世代の App Engine プラットフォームのリリース(2018 年)

レガシー ランタイムの長期サポート

ドキュメントの移行サンプル

Cloud のその他の情報

動画

ライセンス

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