Перенос приложения Python 2 в облако NDB & Приложение Cloud Tasks для Python 3 и Cloud Datastore (модуль 9)

1. Обзор

Серия курсов по кодированию Serverless Migration Station (практические руководства для самостоятельного обучения) и сопутствующие видеоролики призваны помочь бессерверным разработчикам Google Cloud модернизировать свои приложения, помогая им выполнить одну или несколько миграций, в первую очередь отходя от устаревших сервисов. Это сделает ваши приложения более портативными и предоставит вам больше возможностей и гибкости, позволяя интегрироваться с более широким спектром облачных продуктов и получать к ним доступ, а также упростить обновление до более новых языковых версий. Первоначально эта серия ориентирована на самых первых пользователей облака, в первую очередь на разработчиков App Engine (стандартной среды), но эта серия достаточно широка, чтобы включать в себя другие бессерверные платформы, такие как Cloud Functions и Cloud Run , или другие бессерверные платформы, если это применимо.

Целью этой кодовой лаборатории является перенос примера приложения Модуля 8 на Python 3, а также переключение доступа к Datastore (Cloud Firestore в режиме хранилища данных) с использования Cloud NDB на собственную клиентскую библиотеку Cloud Datastore и обновление до последней версии Cloud Tasks. клиентская библиотека.

Мы добавили использование очереди задач для push -задач в модуле 7, а затем перенесли это использование в облачные задачи в модуле 8. Здесь, в модуле 9, мы переходим к Python 3 и облачному хранилищу данных. Те, кто использует очереди задач для задач извлечения , перейдут на Cloud Pub/Sub и вместо этого должны обратиться к модулям 18–19.

Вы узнаете, как

  • Перенесите пример приложения из Модуля 8 на Python 3.
  • Переключите доступ к хранилищу данных с Cloud NDB на клиентские библиотеки Cloud Datastore.
  • Обновите клиентскую библиотеку Cloud Tasks до последней версии.

Что вам понадобится

Опрос

Как вы будете использовать этот урок?

Только прочитай это Прочитайте его и выполните упражнения.

Как бы вы оценили свой опыт работы с Python?

Новичок Средний Опытный

Как бы вы оценили свой опыт использования сервисов Google Cloud?

Новичок Средний Опытный

2. Предыстория

В модуле 7 показано, как использовать задачи отправки из очереди задач App Engine в приложениях Python 2 Flask App Engine. В модуле 8 вы переносите это приложение из очереди задач в облачные задачи. Здесь, в модуле 9 , вы продолжите это путешествие и перенесете это приложение на Python 3, а также переключите доступ к хранилищу данных с использования Cloud NDB на собственную клиентскую библиотеку Cloud Datastore .

Поскольку Cloud NDB работает как с Python 2, так и с Python 3, его достаточно для пользователей App Engine, портирующих свои приложения с Python 2 на Python 3. Дополнительная миграция клиентских библиотек в Cloud Datastore совершенно необязательна , и есть только одна причина ее рассмотреть: вы у вас есть приложения, не относящиеся к App Engine (и/или приложения Python 3 App Engine), которые уже используют клиентскую библиотеку Cloud Datastore, и вы хотите консолидировать свою кодовую базу для доступа к Datastore только с помощью одной клиентской библиотеки. Cloud NDB был создан специально для разработчиков App Engine Python 2 в качестве инструмента миграции на Python 3, поэтому, если у вас еще нет кода, использующего клиентскую библиотеку Cloud Datastore, вам не нужно рассматривать этот переход.

Наконец, разработка клиентской библиотеки Cloud Tasks продолжается только в Python 3, поэтому мы «мигрируем» с одной из финальных версий Python 2 на его современника Python 3. К счастью, критических изменений по сравнению с Python 2 нет, а это означает, что вам больше ничего делать здесь не нужно.

Это руководство включает в себя следующие шаги:

  1. Настройка/Предварительная работа
  2. Обновить конфигурацию
  3. Изменить код приложения

3. Настройка/Предварительная работа

В этом разделе объясняется, как:

  1. Настройте свой облачный проект
  2. Получить базовый образец приложения
  3. (Повторное) развертывание и проверка базового приложения.

Эти шаги гарантируют, что вы начнете с рабочего кода и будете готовы к миграции в облачные службы.

1. Проект установки

Если вы завершили лабораторную работу по Модулю 8 , повторно используйте тот же проект (и код). Альтернативно создайте новый проект или повторно используйте другой существующий проект. Убедитесь, что у проекта есть активный платежный аккаунт и включенное приложение App Engine. Найдите идентификатор своего проекта, так как он понадобится вам во время этой лабораторной работы, используя его всякий раз, когда вы встретите переменную PROJECT_ID .

2. Получите базовый образец приложения.

Одним из обязательных условий является работающее приложение App Engine Модуля 8: выполните лабораторную работу по Модулю 8 (рекомендуется) или скопируйте приложение Модуля 8 из репозитория. Независимо от того, используете ли вы свой или наш, мы начнем с кода Модуля 8 («СТАРТ»). Эта лаборатория кода проведет вас через миграцию, завершающуюся кодом, похожим на тот, что находится в папке репозитория Модуля 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 . Вместо этого вам может потребоваться использовать pip2 если на вашей машине разработки установлены Python 2 и 3.
  2. Убедитесь, что вы установили и инициализировали инструмент командной строки gcloud и проверили его использование .
  3. (необязательно) Настройте свой облачный проект с помощью gcloud config set project PROJECT_ID если вы не хотите вводить PROJECT_ID с каждой командой gcloud , которую вы вводите.
  4. Разверните пример приложения с помощью gcloud app deploy
  5. Убедитесь, что приложение работает как положено и без проблем. Если вы завершили лабораторную работу по Модулю 8, приложение отображает самых популярных посетителей, а также самые последние посещения (показано ниже). Внизу указаны старые задачи, которые будут удалены.

4aa8a2cb5f527079.png

4. Обновить конфигурацию

требования.txt

Новый requirements.txt почти такой же, как и для Модуля 8, с одним большим изменением: замените google-cloud-ndb на google-cloud-datastore . Внесите это изменение, чтобы ваш файл requirements.txt выглядел следующим образом:

flask
google-cloud-datastore
google-cloud-tasks

В этом файле requirements.txt не указаны номера версий, что означает, что выбраны самые последние версии. Если возникают какие-либо несовместимости, использование номеров версий для блокировки рабочих версий приложения является стандартной практикой.

app.yaml

Среда выполнения App Engine второго поколения не поддерживает встроенные сторонние библиотеки, как в версии 2.x, а также не поддерживает копирование невстроенных библиотек . Единственное требование для сторонних пакетов — указать их в requirements.txt . В результате весь раздел libraries в app.yaml может быть удален.

Еще одно обновление заключается в том, что среда выполнения Python 3 требует использования веб-фреймворков, которые выполняют собственную маршрутизацию. В результате все обработчики скриптов необходимо изменить на auto . Однако, поскольку все маршруты должны быть изменены на auto , а в этом примере приложения нет статических файлов, наличие каких-либо обработчиков не имеет значения, поэтому удалите также весь раздел handlers .

Единственное, что необходимо в app.yaml — это установить в качестве среды выполнения поддерживаемую версию Python 3, например 3.10. Внесите это изменение, чтобы новый сокращенный app.yaml представлял собой всего лишь одну строку:

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. Нет pip install в папку lib , что означает отсутствие периода в папке lib
  3. В app.yaml нет списка встроенных сторонних библиотек (поэтому нет раздела libraries ); перечислите их в requirements.txt
  4. Отсутствие сторонних библиотек, на которые можно ссылаться в вашем приложении, означает отсутствие файла appengine_config.py

Перечисление всех желаемых сторонних библиотек в requirements.txt — единственное требование разработчика.

5. Обновите файлы приложения.

Существует только один файл приложения — main.py , поэтому все изменения в этом разделе затрагивают только этот файл. Ниже приведена иллюстрация «отличий» общих изменений, которые необходимо внести для рефакторинга существующего кода в новое приложение. От читателей не ожидается, что они будут читать код построчно, поскольку его цель — просто получить наглядное представление о том, что требуется в этом рефакторинге (но вы можете открыть его в новой вкладке или загрузить и увеличить масштаб, если хотите).

5d043768ba7be742.png

Обновление импорта и инициализации

Раздел импорта в main.py для модуля 8 использует 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:

  • Для комплексного ведения журналов используйте Cloud Logging.
  • Для простого ведения журнала просто отправьте на stdout (или stderr ) через print()
  • Нет необходимости использовать модуль logging Python (поэтому удалите его)

Таким образом, удалите импорт logging и замените google.cloud.ndb на google.cloud.datastore . Аналогичным образом измените ds_client , чтобы он указывал на клиент хранилища данных, а не на клиент NDB. После внесения этих изменений верхняя часть вашего нового приложения теперь выглядит так:

ПОСЛЕ:

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

Миграция в облачное хранилище данных

Теперь пришло время заменить использование клиентской библиотеки NDB хранилищем данных. И 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 и создает задачу push, которая вызывает /trim (таким образом, trim() ) для массового удаления старых 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 не требует использования контекстного менеджера и не требует извлечения его данных (с помощью to_dict() ), как это делает Cloud NDB.
  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

Обычно это все, что необходимо. К сожалению, есть одна серьезная проблема.

(Возможно) Создайте новую (push) очередь.

В Модуле 7 мы добавили использование taskqueue App Engine в существующее приложение Модуля 1. Одним из ключевых преимуществ использования push-задач в качестве устаревшей функции App Engine является то, что автоматически создается очередь «по умолчанию». Когда это приложение было перенесено в Cloud Tasks в Модуле 8, эта очередь по умолчанию уже существовала, поэтому нам еще не нужно было об этом беспокоиться. Здесь, в Модуле 9, ситуация меняется.

Одним из важнейших аспектов, который следует учитывать, является то, что новое приложение 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

Для функции create_queue() Cloud Tasks требуется полный путь к очереди, кроме имени очереди. Для простоты создайте еще одну переменную PATH_PREFIX , представляющую QUEUE_PATH минус имя очереди ( QUEUE_PATH.rsplit('/', 2)[0] ). Добавьте его определение вверху, чтобы блок кода со всеми присвоениями констант выглядел следующим образом:

_, 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]

Теперь измените последнюю строку в fetch_visits() , чтобы использовать _create_queue_if() , сначала создав очередь, если необходимо, а затем создав задачу:

    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 запрашивает посещения старше, чем самое раннее отображаемое. Для ускорения работы он использует запрос только с ключами — зачем извлекать все данные, если вам нужны только идентификаторы посетителей? Получив все идентификаторы посещений, удалите их все одновременно с помощью функции delete_multi() Cloud NDB.

ДО:

@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 NDB на Cloud Datastore, настройку стилей запросов, отказ от использования его контекстного менеджера и изменение вызовов журналирования на 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, — 1.5.0. На момент написания этой статьи последняя версия клиентской библиотеки для Python 3 полностью совместима с этой версией, поэтому никаких дополнительных обновлений не требуется.

Обновление HTML-шаблона

Никаких изменений не требуется и в файле HTML-шаблона, templates/index.html , поэтому на этом завершаются все необходимые изменения для создания приложения Модуля 9.

6. Подведение итогов/очистка

Развертывание и проверка приложения

После завершения обновления кода, в основном порта на Python 3, разверните свое приложение с помощью gcloud app deploy . Вывод должен быть идентичен приложениям из модулей 7 и 8, за исключением того, что вы переместили доступ к базе данных в клиентскую библиотеку Cloud Datastore и обновились до Python 3:

Модуль 7 Приложение Visitme

Этот шаг завершает работу над кодом. Мы предлагаем вам сравнить ваш код с тем, что находится в папке Module 9 . Поздравляем!

Очистить

Общий

Если вы закончили, мы рекомендуем вам отключить приложение App Engine , чтобы избежать выставления счетов. Однако, если вы хотите протестировать или поэкспериментировать еще, на платформе App Engine предусмотрена бесплатная квота , поэтому, пока вы не превысите этот уровень использования, с вас не будет взиматься плата. Это касается вычислений, но за соответствующие службы App Engine также может взиматься плата, поэтому для получения дополнительной информации посетите страницу с ценами . Если эта миграция включает в себя другие облачные службы, они оплачиваются отдельно. В любом случае, если применимо, см. раздел «Специально для этой кодовой лаборатории» ниже.

Для полной информации: развертывание на бессерверной вычислительной платформе Google Cloud, такой как App Engine, требует незначительных затрат на сборку и хранение . Cloud Build имеет собственную бесплатную квоту, как и 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 », если ваше приложение размещено в США.

С другой стороны, если вы не собираетесь продолжать работу с этим приложением или другими связанными с ним программами по миграции и хотите полностью удалить все, закройте свой проект .

Специально для этой кодовой лаборатории

Перечисленные ниже услуги являются уникальными для этой лаборатории кода. Дополнительную информацию см. в документации каждого продукта:

Следующие шаги

На этом завершается переход от push-задач App Engine Task Queue к Cloud Tasks. Дополнительная миграция из Cloud NDB в Cloud Datastore также рассматривается отдельно (без очереди задач или облачных задач) в Модуле 3 . Помимо модуля 3, существуют и другие модули миграции, направленные на отказ от устаревших комплексных сервисов App Engine, которые следует рассмотреть:

App Engine больше не является единственной бессерверной платформой в Google Cloud. Если у вас есть небольшое приложение App Engine или приложение с ограниченной функциональностью, и вы хотите превратить его в автономный микросервис, или вы хотите разбить монолитное приложение на несколько повторно используемых компонентов, это веские причины рассмотреть возможность перехода на облачные функции . Если контейнеризация стала частью вашего рабочего процесса разработки приложений, особенно если он состоит из конвейера CI/CD (непрерывная интеграция/непрерывная доставка или развертывание), рассмотрите возможность перехода на Cloud Run . Эти сценарии рассматриваются в следующих модулях:

  • Миграция с App Engine на облачные функции: см. Модуль 11.
  • Миграция с App Engine на Cloud Run: см. Модуль 4 , чтобы контейнеризировать приложение с помощью Docker, или Модуль 5 , чтобы сделать это без контейнеров, знаний Docker или Dockerfile s.

Переход на другую бессерверную платформу не является обязательным, и мы рекомендуем рассмотреть лучшие варианты для ваших приложений и вариантов использования, прежде чем вносить какие-либо изменения.

Независимо от того, какой модуль миграции вы рассматриваете следующим, весь контент Serverless Migration Station (лаборатории кода, видео, исходный код [при наличии]) можно получить в его репозитории с открытым исходным кодом . README репозитория также содержит рекомендации о том, какие миграции следует учитывать, а также любой соответствующий «порядок» модулей миграции.

7. Дополнительные ресурсы

Проблемы/отзывы Codelabs

Если вы обнаружите какие-либо проблемы с этой кодовой лабораторией, сначала найдите свою проблему, прежде чем подавать заявку. Ссылки для поиска и создания новых задач:

Миграционные ресурсы

Ссылки на папки репозитория для Модуля 8 (НАЧАЛО) и Модуля 9 (ФИНИШ) можно найти в таблице ниже. Доступ к ним также можно получить из репозитория для всех миграций лабораторий кода App Engine , которые можно клонировать или загрузить в виде ZIP-файла.

Кодлаб

Питон 2

Питон 3

Модуль 8

код

(н/д)

Модуль 9

(н/д)

код

Интернет-ресурсы

Ниже приведены онлайн-ресурсы, которые могут иметь отношение к этому руководству:

Механизм приложений

Облачный НБД

Облачное хранилище данных

Облачные задачи

Другая информация об облаке

Лицензия

Эта работа распространяется под лицензией Creative Commons Attribution 2.0 Generic License.