Миграция с push-задач из очереди задач App Engine на облачные задачи (модуль 8)

1. Обзор

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

Цель этого практического занятия — показать разработчикам приложений на Python 2, как перейти от очереди задач App Engine (push tasks) к облачным задачам. Также предусмотрена неявная миграция с App Engine NDB на Cloud NDB для доступа к хранилищу данных (в основном это рассматривается в модуле 2).

В модуле 7 мы добавили использование задач с отправкой (push tasks), а в модуле 8 перенесли это использование в Cloud Tasks, после чего в модуле 9 продолжили работу с Python 3 и Cloud Datastore. Тем, кто использует очереди задач (Task Queues) для задач с получением данных (pull tasks), следует перейти к Cloud Pub/Sub и обратиться к модулям 18-19.

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

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

Опрос

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

Прочитайте только до конца. Прочитайте текст и выполните упражнения.

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

Новичок Средний Профессионал

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

Новичок Средний Профессионал

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

Очередь задач App Engine поддерживает как задачи с отправкой (push), так и задачи с получением (pull). Для повышения переносимости приложений команда Google Cloud рекомендует перейти с устаревших встроенных сервисов, таких как очередь задач, на другие автономные облачные сервисы или аналогичные сервисы сторонних разработчиков.

Миграция задач с запросом данных рассматривается в модулях миграции 18-19, а модули 7-9 посвящены миграции задач с отправкой данных. Для миграции с задач с отправкой данных из очереди задач App Engine мы добавили ее использование в существующее демонстрационное приложение App Engine на Python 2, которое регистрирует новые посещения страниц и отображает самые последние посещения. В практическом занятии по модулю 7 добавлена ​​задача с отправкой данных для удаления самых старых посещений — они больше никогда не будут отображаться, так зачем им занимать дополнительное место в хранилище данных? В практическом занятии по модулю 8 сохраняется та же функциональность, но базовый механизм организации очередей переносится с задач с отправкой данных из очереди задач на облачные задачи, а также повторяется миграция из модуля 2 с App Engine NDB на Cloud NDB для доступа к хранилищу данных.

В этом руководстве описаны следующие шаги:

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

3. Подготовка/Предварительные работы

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

  1. Настройте свой облачный проект
  2. Получите базовый образец приложения
  3. (Повторное) развертывание и проверка базового приложения.
  4. Включите новые сервисы/API Google Cloud.

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

1. Настройка проекта

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

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

Одно из предварительных условий — наличие работающего приложения App Engine модуля 7: выполните практическое задание по модулю 7 (рекомендуется) или скопируйте приложение модуля 7 из репозитория. Независимо от того, используете ли вы свой или наш код, мы начнем с кода модуля 7 ("НАЧАЛО"). Это практическое задание проведет вас через процесс миграции и завершится кодом, похожим на тот, что находится в папке репозитория модуля 8 ("ЗАВЕРШЕНИЕ").

Независимо от того, какое приложение из Модуля 7 вы используете, папка должна выглядеть примерно так, как показано ниже, возможно, с дополнительной папкой lib :

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

3. (Повторное) развертывание и проверка базового приложения.

Для развертывания приложения «Модуль 7» выполните следующие действия:

  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. Убедитесь, что приложение работает должным образом и без проблем. Если вы выполнили практическое задание модуля 7, приложение отображает самых активных посетителей, а также самые последние посещения (иллюстрация ниже). Внизу отображается информация о более старых задачах, которые будут удалены.

4aa8a2cb5f527079.png

4. Включите новые сервисы/API Google Cloud.

В старой версии приложения использовались встроенные сервисы App Engine, которые не требуют дополнительной настройки, но автономные облачные сервисы требуют её, а в обновлённой версии будут использоваться как Cloud Tasks, так и Cloud Datastore (через клиентскую библиотеку Cloud NDB). Ряд облачных продуктов имеют лимиты на использование , включая App Engine , Cloud Datastore и Cloud Tasks . Пока вы не превышаете эти лимиты, вам не придётся платить за выполнение этого руководства. API облачных сервисов можно включить либо через консоль Cloud, либо через командную строку, в зависимости от ваших предпочтений.

Из облачной консоли

Перейдите на страницу библиотеки API-менеджера (для соответствующего проекта) в консоли Cloud и найдите API Cloud Datastore и Cloud Tasks, используя строку поиска в центре страницы:

c7a740304e9d35b.png

Нажмите кнопку «Включить» для каждого API отдельно — возможно, вам потребуется ввести платежную информацию. Это пример страницы библиотеки API Cloud Pub/Sub (для этого практического занятия не включайте API Pub/Sub, только Cloud Tasks и Datastore):

1b6c0a2a73124f6b.jpeg

Из командной строки

Хотя включение API из консоли визуально информативно, некоторые предпочитают командную строку. Для одновременного включения обоих API выполните команду ` gcloud services enable cloudtasks.googleapis.com datastore.googleapis.com :

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

Возможно, вам потребуется ввести платежную информацию. Если вы хотите включить другие облачные API и узнать их URI, их можно найти внизу страницы библиотеки каждого API. Например, обратите внимание на pubsub.googleapis.com в качестве имени сервиса внизу страницы Pub/Sub, расположенной чуть выше.

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

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

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

requirements.txt

В модуле 8 использование App Engine NDB и Task Queue из модуля 1 заменено на Cloud NDB и Cloud Tasks. Добавьте google-cloud-ndb и google-cloud-tasks в requirements.txt , чтобы интегрировать flask из модуля 7:

flask
google-cloud-ndb
google-cloud-tasks

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

app.yaml

При использовании клиентских библиотек Cloud среда выполнения Python 2 App Engine требует наличия определенных сторонних пакетов, а именно grpcio и setuptools . Пользователям Python 2 необходимо указать в файле app.yaml список встроенных библиотек, включая доступную версию или "latest" . Если у вас еще нет раздела libraries , создайте его и добавьте обе библиотеки следующим образом:

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

При миграции вашего приложения раздел libraries " может уже присутствовать . Если это так, и отсутствуют grpcio и setuptools , просто добавьте их в существующий раздел libraries . Обновленный app.yaml теперь должен выглядеть следующим образом:

runtime: python27
threadsafe: yes
api_version: 1

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

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

appengine_config.py

Вызов google.appengine.ext.vendor.add() в файле appengine_config.py связывает скопированные (иногда называемые "встроенными" или "самостоятельно упакованными") сторонние библиотеки из lib с вашим приложением. Выше в app.yaml мы добавили встроенные сторонние библиотеки, и для их подключения к этим встроенным пакетам в lib требуется setuptools.pkg_resources.working_set.add_entry() . Ниже представлены исходный файл appengine_config.py модуля 1 и файл после внесения изменений в модуль 8:

ДО:

from google.appengine.ext import vendor

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

ПОСЛЕ:

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)

Аналогичное описание можно найти и в документации по миграции в App Engine .

5. Измените код приложения.

В этом разделе представлены обновления основного файла приложения, main.py , заменяющие использование очередей задач App Engine Task Queue на Cloud Tasks. Веб-шаблон, templates/index.html , остаётся без изменений — оба приложения должны работать идентично, отображая одни и те же данные. Изменения в основном приложении разбиты на четыре задачи:

  1. Обновление импорта и инициализации
  2. Обновление функциональности модели данных (Cloud NDB)
  3. Переход на облачные задачи (и облачную базу данных NDB)
  4. Обработчик задач обновления (отправки)

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

  1. Замените App Engine NDB ( google.appengine.ext.ndb ) и Task Queue ( google.appengine.api.taskqueue ) на Cloud NDB ( google.cloud.ndb ) и Cloud Tasks ( google.cloud.tasks ) соответственно.
  2. Для работы с облачными клиентскими библиотеками требуется инициализация и создание «API-клиентов»; назначьте их соответственно переменным ds_client и ts_client .
  3. В документации по очереди задач указано: «App Engine предоставляет очередь отправки по умолчанию с именем default , которая настроена и готова к использованию с настройками по умолчанию». Cloud Tasks не предоставляет очередь default (поскольку это автономный облачный продукт, независимый от App Engine), поэтому для создания очереди Cloud Tasks с именем default требуется новый код.
  4. Очередь задач App Engine не требует указания региона, поскольку использует регион, в котором работает ваше приложение. Однако, поскольку Cloud Tasks теперь является независимым продуктом, для него требуется указать регион, и этот регион должен совпадать с регионом, в котором работает ваше приложение. Название региона и идентификатор проекта Cloud необходимы для создания «полного пути» в качестве уникального идентификатора очереди.

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

ДО:

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

app = Flask(__name__)

ПОСЛЕ:

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

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

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

2. Обновление функциональности модели данных (Cloud NDB)

App Engine NDB и Cloud NDB работают практически идентично. Существенных изменений ни в модели данных, ни в функции store_visit() нет. Единственное заметное отличие заключается в том, что создание сущности Visit в store_visit() теперь инкапсулировано в блок Python with . Cloud NDB требует, чтобы весь доступ к Datastore контролировался в рамках его контекстного менеджера, отсюда и оператор with . Приведенные ниже фрагменты кода иллюстрируют это незначительное различие при миграции на Cloud NDB. Внедрите это изменение.

ДО:

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

ПОСЛЕ:

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

3. Переход на Cloud Tasks (и Cloud NDB)

Наиболее существенное изменение в этой миграции касается базовой инфраструктуры очередей. Оно происходит в функции fetch_visits() , где создается и ставится в очередь задача (push) по удалению старых посещений. Однако исходная функциональность из модуля 7 остается неизменной:

  1. Запрос информации о последних посещениях.
  2. Вместо того чтобы сразу же возвращать эти посещения, сохраните метку времени последнего Visit , самую старую из отображаемых — все посещения старше этой даты можно смело удалять.
  3. С помощью стандартных утилит Python извлеките метку времени в виде числа с плавающей запятой и строки и используйте оба варианта в различных целях, например, для отображения пользователю, добавления в логи, передачи обработчику и т. д.
  4. Создайте задачу отправки, указав в качестве полезной нагрузки эту метку времени и URL-адрес /trim .
  5. В конечном итоге обработчик задач вызывается посредством HTTP POST к указанному URL-адресу.

Этот рабочий процесс иллюстрируется фрагментом кода, приведенным "до":

ДО:

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

Хотя функциональность остаётся прежней, платформа выполнения задач Cloud Tasks становится основной. Для внесения этих изменений были внесены следующие изменения:

  1. Оберните запрос Visit в with Python (повторяя миграцию модуля 2 в Cloud NDB).
  2. Создайте метаданные для облачных задач, включая ожидаемые атрибуты, такие как метка времени и URL-адрес, а также укажите MIME-тип и закодируйте полезную нагрузку в формате JSON.
  3. Используйте клиент Cloud Tasks API для создания задачи, указав метаданные и полный путь к очереди.

Изменения в функции fetch_visits() показаны ниже:

ПОСЛЕ:

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 data, oldest_str

4. Обработчик задач обновления (push)

Функция обработчика задач (push) не требует существенных обновлений; ей требуется только выполнение. Это применимо к очередям задач или облачным задачам. «Код есть код», как говорится. Однако есть некоторые незначительные изменения:

  1. Временная метка передавалась в очередь задач без изменений, но для облачных задач она была закодирована в формате JSON, поэтому при поступлении её необходимо было разобрать в формате JSON.
  2. В случае HTTP POST запроса к /trim с использованием Task Queue неявно указывался MIME-тип application/x-www-form-urlencoded , но в случае Cloud Tasks он явно обозначается как application/json , поэтому существует несколько иной способ извлечения полезной нагрузки.
  3. Используйте менеджер контекста клиента Cloud NDB API (миграция из модуля 2 в Cloud NDB).

Ниже приведены фрагменты кода до и после внесения этих изменений в обработчик задачи, trim() :

ДО:

@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

ПОСЛЕ:

@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

Никаких изменений в основном обработчике приложения root() и в веб-шаблоне templates/index.html не внесено.

6. Подведение итогов/Завершение

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

Разверните и проверьте приложение.

Разверните свое приложение с помощью gcloud app deploy . Результат должен быть идентичен приложению из модуля 7, но учтите, что вы перешли на совершенно другой продукт для создания очередей отправки, что делает ваше приложение более портативным, чем раньше!

4aa8a2cb5f527079.png

Уборка

Общий

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

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

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

Это относится именно к данному практическому занятию.

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

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

На этом завершается наш переход от задач, отправляемых через App Engine Task Queue, к задачам Cloud Tasks. Если вас интересует дальнейшая разработка этого приложения на Python 3 и дальнейшая миграция с Cloud NDB на Cloud Datastore, рассмотрите Модуль 9 .

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

И Cloud NDB, и Cloud Datastore обращаются к Datastore (хотя и разными способами), поэтому единственная причина для перехода на Cloud Datastore — это если у вас уже есть другие приложения, в частности, не относящиеся к App Engine, использующие Cloud Datastore, и вы хотите стандартизировать использование единой клиентской библиотеки Datastore. Этот необязательный переход с Cloud NDB на Cloud Datastore также рассматривается отдельно (без очереди задач или облачных задач) в Модуле 3 .

Помимо модулей 3, 8 и 9, следует рассмотреть и другие модули миграции, направленные на отказ от устаревших встроенных сервисов App Engine:

  • Модуль 2 : миграция с App Engine NDB на Cloud NDB
  • Модули 12-13 : миграция с App Engine Memcache на Cloud Memorystore
  • Модули 15-16 : миграция с App Engine Blobstore на Cloud Storage.
  • Модули 18-19 : Очередь задач App Engine (запросы на добавление/отправку) в Cloud Pub/Sub

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 (практические занятия, видео, исходный код [при наличии]) доступен в его репозитории с открытым исходным кодом . README репозитория также содержится информация о том, какие миграции следует рассмотреть и в каком порядке следует выбирать модули миграции.

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

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

Вопросы/отзывы о Codelabs

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

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

Ссылки на папки репозитория для Модуля 7 (НАЧАЛО) и Модуля 8 (ЗАВЕРШЕНИЕ) можно найти в таблице ниже.

Кодлаб

Python 2

Python 3

Модуль 7

код

код (не представлен в этом уроке)

Модуль 8 (данная практическая работа)

код

(н/д)

Онлайн-ресурсы

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

Очередь задач App Engine и облачные задачи

App Engine NDB и Cloud NDB (хранилище данных)

платформа App Engine

Прочая информация об облачных сервисах

Видео

Лицензия

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