Python 2 App Engine Cloud NDB およびCloud Tasks アプリから Python 3 および Cloud Datastore へ(モジュール 9)

1. 概要

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

この Codelab の目的は、モジュール 8 サンプルアプリを Python 3 に移植し、Datastore(Datastore モードの Cloud Firestore)へのアクセスを Cloud NDB の使用からネイティブの Cloud Datastore クライアント ライブラリに切り替え、Cloud Tasks クライアント ライブラリの最新バージョンにアップグレードすることです。

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

GCP コンソールの

  • モジュール 8 サンプルアプリを Python 3 に移植する
  • Datastore へのアクセスを Cloud NDB から Cloud Datastore クライアント ライブラリに切り替える
  • Cloud Tasks クライアント ライブラリの最新バージョンにアップグレードする

必要なもの

アンケート

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

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

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

初心者 中級者 上級者

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

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

2. 背景情報

モジュール 7 では、Python 2 Flask App Engine アプリで App Engine タスクキューの push タスクを使用する方法を示します。モジュール 8 では、そのアプリをタスクキューから Cloud Tasks に移行します。モジュール 9 では、その作業を続けて、アプリを Python 3 に移植し、Datastore アクセスを Cloud NDB の使用からネイティブの Cloud Datastore クライアント ライブラリに切り替えます。

Cloud NDB は Python 2 と Python 3 の両方で機能するため、App Engine ユーザーがアプリを Python 2 から Python 3 に移行する場合はこれで十分です。Cloud Datastore へのクライアント ライブラリの追加移行は任意で、考慮すべき理由は 1 つだけです。それは、App Engine 以外のアプリ(または Python 3 App Engine アプリ)がすでに Cloud Datastore クライアント ライブラリを使用しており、1 つのクライアント ライブラリだけで Datastore にアクセスするようにコードベースを統合したい場合です。Cloud NDB は、Python 3 の移行ツールとして Python 2 App Engine デベロッパー向けに作成されました。そのため、Cloud Datastore クライアント ライブラリを使用するコードがまだない場合は、この移行を考慮する必要はありません。

最後に、Cloud Tasks クライアント ライブラリの開発は Python 3 でのみ継続されるため、「移行中」となります。Python 2 の最終バージョンから 最新の Python 3 バージョンまで対応しています幸いなことに、Python 2 から互換性を破る変更はありません。つまり、他に何もする必要はありません。

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

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

3. 設定/事前作業

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

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

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

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

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

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

前提条件の一つは、機能するモジュール 8 App Engine アプリです。モジュール 8 の Codelab を完了するか(推奨)、リポジトリからモジュール 8 アプリをコピーします。自作のものであれ Google のものであれ、まずはモジュール 8 のコードから始まります(「開始」)。この Codelab では移行手順について説明します。最後に、モジュール 9 のリポジトリ フォルダ(「FINISH」)にあるコードと同様のコードを使用して終了します。

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

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

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

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

  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. アプリが問題なく動作することを確認します。モジュール 8 の Codelab を完了すると、上位の訪問者と最近の訪問(下図)がアプリに表示されます。下部には、削除される古いタスクが表示されます。

4aa8a2cb5f527079.png

4. 構成の更新

requirements.txt

新しい requirements.txt はモジュール 8 のものとほぼ同じですが、大きな変更点が 1 つあります。google-cloud-ndbgoogle-cloud-datastore に置き換えます。この変更を行って、requirements.txt ファイルは次のようになります。

flask
google-cloud-datastore
google-cloud-tasks

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

app.yaml

第 2 世代の App Engine ランタイムは、2.x のような組み込みサードパーティ ライブラリ組み込みでないライブラリのコピーをサポートしていません。サードパーティ パッケージの唯一の要件は、パッケージを requirements.txt にリストすることです。その結果、app.yamllibraries セクション全体が削除される可能性があります。

もう 1 つの更新は、Python 3 ランタイムでは、独自のルーティングを行うウェブ フレームワークを使用する必要があることです。そのため、すべてのスクリプト ハンドラを auto に変更する必要があります。ただし、すべてのルートを auto に変更する必要があり、このサンプルアプリからは静的ファイルが提供されないため、ハンドラは必要ありません。そのため、handlers セクション全体も削除します。

app.yaml で必要なのは、ランタイムを Python 3 のサポートされているバージョン(3.10 など)に設定することだけです。この変更を行うと、短縮された新しい app.yaml が次の 1 行だけになります。

runtime: python310

appengine_config.py と lib の削除

次世代の App Engine ランタイムでは、サードパーティ パッケージの使用が次のように刷新されます。

  • 組み込みライブラリとは、Google によって審査され、App Engine サーバーで利用可能なライブラリのことです。組み込みライブラリには、クラウドへのデプロイが許可されていない C/C++ コードが含まれていることが理由と考えられます。組み込みライブラリは、第 2 世代のランタイムでは使用できません。
  • 第 2 世代のランタイムでは、非組み込みライブラリ(「ベンダリング」や「セルフバンドル」と呼ばれることもあります)のコピーが不要になりました。代わりに、requirements.txt にリストされているはずです。デプロイ時にビルドシステムによって自動的にインストールされます。

サードパーティのパッケージ管理を変更した結果、appengine_config.py ファイルと lib フォルダは必要ないので、削除します。第 2 世代のランタイムでは、App Engine は requirements.txt にリストされているサードパーティ パッケージを自動的にインストールします。要約:

  1. セルフバンドルまたはコピーされたサードパーティ ライブラリは存在しない。requirements.txt にリストする
  2. lib フォルダに pip install が含まれていない(つまり、lib フォルダのピリオドがない)
  3. app.yaml にサードパーティ ライブラリの組み込みリストがない(したがって libraries セクションがない)。requirements.txt にリストアップする
  4. アプリから参照するサードパーティ ライブラリがないため、appengine_config.py ファイルはありません。

デベロッパーの要件は、必要なサードパーティ ライブラリをすべて requirements.txt にリストすることです。

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

アプリケーション ファイルは main.py という 1 つしかないため、このセクションで行った変更はすべてそのファイルにのみ影響します。以下は「差分」新しいアプリに既存のコードをリファクタリングするために必要な全体的な変更を示すイラスト。コードを 1 行ずつ読む必要はありません。このリファクタリングで必要な処理の概要を画像で得ることが目的であるためです(ただし、必要に応じて新しいタブで開くか、ダウンロードしてズームインしてください)。

5d043768ba7be742.png

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

モジュール 8 の main.py のインポート セクションでは、Cloud NDB と Cloud Tasks を使用します。次のようになります。

変更前:

from datetime import datetime
import json
import logging
import time
from flask import Flask, render_template, request
import google.auth
from google.cloud import ndb, tasks

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

Python 3 などの第 2 世代のランタイムでは、ロギングが簡素化および強化されています。

  • 包括的なロギングを実現するには、Cloud Logging を使用します。
  • 単純なロギングを行うには、print()stdout(または stderr)に送信します。
  • Python logging モジュールを使用する必要はない(削除してください)

そのため、logging のインポートを削除し、google.cloud.ndbgoogle.cloud.datastore に置き換えます。同様に、NDB クライアントではなく Datastore クライアントを指すように ds_client を変更します。これらの変更により、新しいアプリの上部は次のようになります。

変更後:

from datetime import datetime
import json
import time
from flask import Flask, render_template, request
import google.auth
from google.cloud import datastore, tasks

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

Cloud Datastore に移行する

次に、NDB クライアント ライブラリの使用を Datastore に置き換えます。App Engine NDB と Cloud NDB はどちらもデータモデル(クラス)を必要とします。このアプリの場合は Visit です。store_visit() 関数は他のすべての移行モジュールでも同じように機能します。新しい Visit レコードを作成して訪問を登録し、訪問クライアントの IP アドレスとユーザー エージェント(ブラウザの種類)を保存します。

変更前:

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'
    with ds_client.context():
        Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()

ただし、Cloud Datastore はデータモデル クラスを使用しないため、クラスを削除します。また、Cloud Datastore ではレコードの作成時にタイムスタンプが自動的に作成されないため、手動で作成する必要があります。これは datetime.now() 呼び出しを使用して行います。

データクラスがない場合、変更した store_visit() は次のようになります。

変更後:

def store_visit(remote_addr, user_agent):
    'create new Visit entity in Datastore'
    entity = datastore.Entity(key=ds_client.key('Visit'))
    entity.update({
        'timestamp': datetime.now(),
        'visitor': '{}: {}'.format(remote_addr, user_agent),
    })
    ds_client.put(entity)

主な機能は fetch_visits() です。最新の Visit に対する元のクエリを実行するだけでなく、最後に表示された Visit のタイムスタンプを取得し、/trim(つまり trim())を呼び出す push タスクを作成して古い Visit を一括削除します。ここでは、Cloud NDB を使用しています。

変更前:

def fetch_visits(limit):
    'get most recent visits & 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

主な変更点は次のとおりです。

  1. Cloud NDB クエリを同等の Cloud Datastore クエリと入れ替える。クエリのスタイルが若干異なります。
  2. Datastore ではコンテキスト マネージャーを使用する必要はなく、Cloud NDB のように(to_dict() を使用して)データを抽出することも必要ありません。
  3. ロギング呼び出しを print() に置き換える

これらの変更の後、fetch_visits() は次のようになります。

変更後:

def fetch_visits(limit):
    'get most recent visits & add task to delete older visits'
    query = ds_client.query(kind='Visit')
    query.order = ['-timestamp']
    visits = list(query.fetch(limit=limit))
    oldest = time.mktime(visits[-1]['timestamp'].timetuple())
    oldest_str = time.ctime(oldest)
    print('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 visits, oldest_str

通常、これだけで十分です。残念ながら、大きな問題が 1 つあります。

(必要に応じて)新しい(push)キューを作成する

モジュール 7 では、既存のモジュール 1 アプリに App Engine taskqueue の使用を追加しました。push タスクを以前の App Engine 機能として使用する主なメリットの 1 つは、「デフォルト」の自動的に作成されます。そのアプリがモジュール 8 で Cloud Tasks に移行されたとき、そのデフォルト キューはすでに存在していたため、当時はまだ気にする必要はありませんでした。これはモジュール 9 で変更されます。

考慮すべき重要な点の 1 つは、新しい App Engine アプリケーションでは App Engine サービスが使用されなくなったため、App Engine が別のプロダクト(Cloud Tasks)でタスクキューを自動的に作成することはないということです。記述されているように、fetch_visits() で(存在しないキューに対して)タスクを作成すると失敗します。新しい関数では、キュー(「デフォルト」)が存在するかどうかを確認し、存在しない場合は作成する必要があります。

この関数 _create_queue_if() を呼び出して、アプリケーションの fetch_visits() のすぐ上に追加します(ここで呼び出されているからです)。追加する関数の本文:

def _create_queue_if():
    'app-internal function creating default queue if it does not exist'
    try:
        ts_client.get_queue(name=QUEUE_PATH)
    except Exception as e:
        if 'does not exist' in str(e):
            ts_client.create_queue(parent=PATH_PREFIX,
                    queue={'name': QUEUE_PATH})
    return True

Cloud Tasks の create_queue() 関数には、キュー名を除くキューのフルパス名が必要です。わかりやすくするため、QUEUE_PATH からキュー名(QUEUE_PATH.rsplit('/', 2)[0])を引いた値を表す別の変数 PATH_PREFIX を作成します。その定義を上部付近に追加して、すべての定数割り当てを含むコードブロックは次のようになります。

_, PROJECT_ID = google.auth.default()
REGION_ID = 'REGION_ID'    # replace w/your own
QUEUE_NAME = 'default'     # replace w/your own
QUEUE_PATH = ts_client.queue_path(PROJECT_ID, REGION_ID, QUEUE_NAME)
PATH_PREFIX = QUEUE_PATH.rsplit('/', 2)[0]

次に、_create_queue_if() を使用するように fetch_visits() の最後の行を変更します。必要に応じてキューを作成し、その後でタスクを作成します。

    if _create_queue_if():
        ts_client.create_task(parent=QUEUE_PATH, task=task)
    return visits, oldest_str

_create_queue_if()fetch_visits() の両方を集計すると次のようになります。

def _create_queue_if():
    'app-internal function creating default queue if it does not exist'
    try:
        ts_client.get_queue(name=QUEUE_PATH)
    except Exception as e:
        if 'does not exist' in str(e):
            ts_client.create_queue(parent=PATH_PREFIX,
                    queue={'name': QUEUE_PATH})
    return True

def fetch_visits(limit):
    'get most recent visits & add task to delete older visits'
    query = ds_client.query(kind='Visit')
    query.order = ['-timestamp']
    visits = list(query.fetch(limit=limit))
    oldest = time.mktime(visits[-1]['timestamp'].timetuple())
    oldest_str = time.ctime(oldest)
    print('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',
            },
        }
    }
    if _create_queue_if():
        ts_client.create_task(parent=QUEUE_PATH, task=task)
    return visits, oldest_str

Cloud Tasks の残りのコードは、この余分なコードを追加すること以外は、ほとんどがモジュール 8 のものです。最後に確認するコードはタスクハンドラです。

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

タスクハンドラ trim() で、Cloud NDB コードは、表示されている最も古いものより古い訪問を照会します。キーのみのクエリを使用して処理時間を短縮します。訪問 ID のみが必要なのに、すべてのデータを取得する必要はないのでしょうか。すべての訪問 ID を取得したら、Cloud NDB の delete_multi() 関数を使用して、すべて一括で削除します。

変更前:

@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

fetch_visits() と同様に、変更の大半には、Cloud Datastore への Cloud NDB コードのスワップアウト、クエリスタイルの調整、コンテキスト マネージャーの使用の削除、print() へのロギング呼び出しの変更が含まれます。

変更後:

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

メインのアプリケーション ハンドラ root() に変更はありません。

Python 3 に移植する

このサンプルアプリは、Python 2 と Python 3 の両方で動作するように設計されています。Python 3 固有の変更については、このチュートリアルの関連セクションですでに説明されています。追加の手順や互換性ライブラリは必要ありません。

Cloud Tasks の更新

Python 2 をサポートする Cloud Tasks クライアント ライブラリの最終バージョンは 1.5.0 です。このドキュメントの作成時点では、Python 3 用のクライアント ライブラリの最新バージョンにはそのバージョンと完全な互換性があるため、それ以上の更新は必要ありません。

HTML テンプレートの更新

HTML テンプレート ファイル templates/index.html も変更する必要がないため、モジュール 9 アプリに必要なすべての変更は以上です。

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

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

コードの更新(主に Python 3 へのポート)が完了したら、gcloud app deploy を使用してアプリをデプロイします。データベース アクセスを Cloud Datastore クライアント ライブラリに移動し、Python 3 にアップグレードした点を除き、出力はモジュール 7 および 8 のアプリの場合とほぼ同じになるはずです。

モジュール 7 Visitme アプリ

これで Codelab は終了です。作成したコードを、モジュール 9 フォルダにあるコードと比較してみましょう。お疲れさまでした

クリーンアップ

全般

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

次のステップ

これで、App Engine タスクキューの push タスクから Cloud Tasks への移行は完了です。モジュール 3 では、Cloud NDB から Cloud Datastore へのオプションの移行についても(タスクキューや Cloud Tasks を使用しないで)取り上げています。モジュール 3 以外にも、App Engine の以前のバンドル サービスからの移行に焦点を当てた、次のような移行モジュールがあります。

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 には、検討すべき移行や関連する「順序」に関するガイダンスも用意されています。概要をまとめたものです

7. 参考情報

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

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

移行に関するリソース

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

Codelab

Python 2

Python 3

モジュール 8

コード

該当なし

モジュール 9

該当なし

コード

オンライン リソース

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

App Engine

Cloud NDB

Cloud Datastore

Cloud Tasks

Cloud のその他の情報

ライセンス

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