Módulo 8: Migra de ndb y taskqueue de App Engine a Cloud NDB y Cloud Tasks

Esta serie de Codelabs (instructivos prácticos y de autoaprendizaje) tienen como objetivo ayudar a los desarrolladores de Google App Engine (estándar) a modernizar sus apps con una serie de migraciones. El paso más importante es dejar de lado los servicios agrupados del entorno de ejecución porque los entornos de ejecución de última generación son más flexibles y ofrecen a los usuarios una mayor variedad de opciones de servicios. El cambio al entorno de ejecución de generación más reciente te permite la integración en productos de Google Cloud con más facilidad, usar una mayor variedad de servicios compatibles y admitir versiones actuales de lenguaje.

Este codelab ayuda a los usuarios a migrar de las tareas de envío de App Engine y de la API o biblioteca de taskqueue a Cloud Tasks. Si tu aplicación no usa las listas de tareas en cola, puedes usar este codelab como ejercicio para aprender a migrar tareas de envío de App Engine a Cloud Tasks.

Obtendrás información para hacer las siguientes acciones

  • Migrar de App Engine taskqueue a Cloud Tasks
  • Crea tareas de envío con Cloud Tasks
  • Migra de ndb de App Engine a Cloud NDB (igual que el módulo 2)

Qué necesitará

Encuesta

¿Cómo usarás este codelab?

Solo te recomendamos que lo leas Léelo y completa los ejercicios

Ya que agregamos tareas de envío de App Engine a la aplicación de muestra en el codelab anterior (Módulo 7), ahora podemos migrarla a Cloud Tasks. En la migración de este instructivo, se incluyen los siguientes pasos principales:

  1. Configurar/trabajo previo
  2. Actualizar archivos de configuración
  3. Actualizar la aplicación principal

Antes de comenzar con la parte principal del instructivo, configuremos nuestro proyecto, obtengamos el código e implementemos la app de modelo de referencia para que comencemos a trabajar con el código.

1. Configura el proyecto

Te recomendamos que vuelvas a usar el mismo proyecto que usaste para completar el Codelab del Módulo 7. De manera alternativa, puedes crear un proyecto nuevo o reutilizar otro proyecto existente. Asegúrate de que el proyecto tenga una cuenta de facturación activa y que App Engine (app) esté habilitado.

2. Obtén app de ejemplo del modelo de referencia

Uno de los requisitos de este codelab es tener una app de ejemplo del Módulo 7 en funcionamiento. Si no tienes uno, te recomendamos que completes el instructivo del Módulo 7 (vínculo arriba) antes de continuar. De lo contrario, si ya estás familiarizado con su contenido, puedes comenzar leyendo el código del Módulo 7 a continuación.

Sin importar si usas el tuyo o el nuestro, el código del Módulo 7 es el que tendremos en cuenta. Este codelab del módulo 2 te guiará en cada paso y, cuando se complete, debería parecerse al código en el punto FINALIZAR (incluido un puerto de opcional de Python 2 a 3):

El directorio de archivos del módulo 7 (el tuyo o el nuestro) debe verse así:

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

Si completaste el instructivo del módulo 7, también tendrás una carpeta lib con Flask y sus dependencias.

3. (Vuelve a ) implementar la app del Módulo 7

Los pasos del trabajo previo restantes para ejecutar ahora sin estos:

  1. Vuelve a familiarizarte con la herramienta de línea de comandos de gcloud (si es necesario).
  2. (Vuelve a )implementa el código del módulo 7 en App Engine (si es necesario).

Después de ejecutar esos pasos de forma correcta y confirmemos que está funcionando, avanzaremos en este instructivo y comenzaremos con los archivos de configuración.

requirements.txt

El requirements.txt del módulo 7 solo enumera Flask como un paquete obligatorio. Cloud NDB y Cloud Tasks tienen sus propias bibliotecas cliente, por lo que, en este paso, agrega esos paquetes a requirements.txt para que se vea de la siguiente manera:

Flask==1.1.2
google-cloud-ndb==1.7.1
google-cloud-tasks==1.5.0

Recomendamos usar las versiones más recientes de cada biblioteca. Los números de las versiones anteriores son los más recientes de Python 2 en el momento en que se redactó este documento. (Es probable que los paquetes equivalentes de Python 3 estén en versiones posteriores). El código en la carpeta del repositorio FINALIZAR se actualiza con más frecuencia y puede tener versiones más recientes, a pesar de que esto no sea así. Las bibliotecas de Python 2 que generalmente se inmovilizan.

app.yaml

Haz referencia a las bibliotecas integradas grpcio y setuptools en app.yaml en una sección libraries:

libraries:
- name: grpcio
  version: 1.0.0
- name: setuptools
  version: 36.6.0

appengine_config.py

Actualiza appengine_config.py para usar pkg_resources a fin de vincular las bibliotecas integradas a las bibliotecas de terceros copiadas, como Flask y las bibliotecas cliente de Google Cloud:

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)

Solo hay un archivo de aplicación, main.py, por lo que todos los cambios de esta sección afectan solo a ese archivo.

Actualizar las importaciones y la inicialización

En este momento, nuestra app usa las bibliotecas integradas google.appengine.api.taskqueue y google.appengine.ext.ndb:

  • ANTES:
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

Reemplaza ambos con google.cloud.ndb y google.cloud.tasks. Además, Cloud Tasks requiere que codifiques en JSON la carga útil de la tarea, por lo que también debes importar json. Cuando hayas terminado, la sección import de main.py debería verse de la siguiente manera:

  • DESPUÉS:
from datetime import datetime
import json
import logging
import time
from flask import Flask, render_template, request
from google.cloud import ndb, tasks

Migra a Cloud Tasks (y Cloud NDB)

  • ANTES:
def store_visit(remote_addr, user_agent):
    'create new Visit entity in Datastore'
    Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()

No hay cambios en store_visit() distintos de lo que hiciste en el módulo 2: agrega un administrador de contexto a todo el acceso de Datastore. Esto se aplica en la creación de una nueva entidad Visit en una declaración with.

  • DESPUÉS:
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()

Por el momento, Cloud Tasks requiere que App Engine esté habilitado para tu proyecto de Google Cloud a fin de poder usarlo (incluso si no tienes el código de App Engine). De lo contrario, las listas de tareas en cola no funcionarán. (consulta esta sección en los documentos para obtener más información). Cloud Tasks admite tareas que se ejecutan en App Engine (“objetivos de App Engine”), pero también se puede ejecutar en cualquier extremo HTTP (destinos HTTP) con una dirección IP pública, como Cloud Functions, Cloud Run, GKE, Compute Engine o incluso un servidor web local. Nuestra app simple usa un destino de App Engine para las tareas.

Se requiere cierta configuración para usar Cloud NDB y Cloud Tasks. En la parte superior de main.py, en la inicialización de Flask, inicializa Cloud NDB y Cloud Tasks. Define también algunas constantes que indiquen dónde se ejecutarán tus tareas de aplicaciones en cola.

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

PROJECT_ID = 'PROJECT_ID'  # replace w/your own
REGION = 'REGION'    # replace w/your own
QUEUE_NAME = 'default'     # replace w/your own if desired
QUEUE_PATH = ts_client.queue_path(PROJECT_ID, REGION, QUEUE_NAME)

Una vez que crees la lista de tareas en cola, completa el PROJECT_ID de tu proyecto, la REGION en la que se ejecutarán tus tareas (debe ser la misma que en tu región de App Engine) y el nombre de tu lista de aplicaciones en cola. App Engine muestra una cola “default”, por lo que usaremos ese nombre (pero no es necesario).

La cola default es especial y se crea automáticamente en ciertas circunstancias; una de ellas es cuando se usan las API de App Engine, por lo que reutilizas el mismo proyecto que en el módulo 7, default ya existirán. Sin embargo, si creaste un proyecto nuevo específicamente para el módulo 8, deberás crear default de forma manual. Puedes encontrar más información sobre la cola de default en la documentación de queue.yaml.

El objetivo de ts_client.queue_path() es crear el “nombre de la ruta de acceso completamente calificado” de la lista de tareas en cola (QUEUE_PATH) que se necesita para crear una tarea. También es necesaria una estructura JSON que especifique los parámetros de la tarea:

task = {
    'app_engine_http_request': {
        'relative_uri': '/trim',
        'body': json.dumps({'oldest': oldest}).encode(),
        'headers': {
            'Content-Type': 'application/json',
        },
    }
}

¿Qué estás viendo arriba?

  1. Proporciona información sobre el objetivo de la tarea:
    • Para los objetivos de App Engine, especifica app_engine_http_request como el tipo de solicitud y relative_uri es el controlador de tareas de App Engine.
    • Para los destinos HTTP, en su lugar usa http_request y url.
  2. body: los parámetros codificados en strings JSON y Unicode que se envían a la tarea (enviar)
  3. Especifica un encabezado Content-Type codificado en JSON de manera explícita

Consulte la documentación para obtener más información sobre sus opciones aquí.

Con la configuración al margen, actualicemos fetch_visits(). A continuación, se muestra como se ve en el instructivo anterior:

  • ANTES:
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

Se requieren las siguientes actualizaciones:

  1. Cambia de App Engine ndb a Cloud NDB
  2. Código nuevo para extraer la marca de tiempo de la visita más antigua mostrada
  3. Usa Cloud Tasks para crear una nueva tarea en lugar taskqueue de App Engine

Tu nuevo fetch_visits() debería verse de la siguiente manera:

  • DESPUÉS:
def fetch_visits(limit):
    'get most recent visits and 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

Resumen de la actualización del código:

  • Cambiar a Cloud NDB significa mover el código de Datastore dentro de una declaración with
  • Cambiar a Cloud Tasks significa usar ts_client.create_task() en lugar de taskqueue.add()
  • Pasa la ruta de acceso completa de la cola y la carga útil task (que se describió antes)

Actualiza el controlador de tareas (envío)

Hay muy pocos cambios que se deben realizar en la función del controlador de tareas (envío).

  • ANTES:
@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

Lo único que se debe hacer es colocar todo el acceso de Datastore dentro de la instrucción with del administrador de contexto, tanto la solicitud de consulta como la de eliminación. En este sentido, actualiza el controlador trim() de la siguiente manera:

  • DESPUÉS:
@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

No hay cambios en templates/index.html en este o en el siguiente codelab.

Implemente la aplicación

Vuelve a verificar todos los cambios que compila tu código y vuelve a implementarlo. Confirma que la app (todavía) funciona. Se espera un resultado idéntico al del módulo 7. Como acabamos de restablecer todo tipo de energía, todo debería funcionar como se espera.

Si llegaste a este instructivo sin realizar el codelab del módulo 7, la app en sí no cambiará. registra todas las visitas a la página web principal (/) y tendrá este aspecto una vez que visitas el sitio con el tiempo suficiente y te indica que borró todas las visitas anteriores a la décima.

App de visitme del módulo 7

Con esto concluye este codelab. El código ahora debería coincidir con el contenido del repositorio del módulo 8. Felicitaciones por completar la migración más importante de las tareas de envío. El módulo 9 (vínculo al codelab siguiente) es opcional, y ayuda a los usuarios a pasar a Python 3 y a Cloud Datastore.

Opcional: Limpieza

¿Qué te parece limpiar a fin de evitar que se te facture hasta que estés listo para pasar a la siguiente codelab de migración? Como desarrolladores existentes, es probable que ya estés al día en la información sobre precios de App Engine.

Opcional: Inhabilita la app

Si aún no estás listo para continuar con el siguiente instructivo, inhabilita tu app a fin de evitar que se apliquen cargos. Cuando estés listo para pasar al siguiente codelab, puedes volver a habilitarla. Aunque tu app esté inhabilitada, no recibirá tráfico que genere cargos. Sin embargo, si se excede la cuota gratuita, se te cobrará por el uso de Datastore. así que borra lo suficiente para que quede dentro de ese límite.

Por otro lado, si no vas a continuar con las migraciones y quieres borrar todo por completo, puedes cerrar tu proyecto.

Próximos pasos

Más allá de este instructivo, el siguiente paso es el módulo 9 y su codelab, y cómo trasladarse a Python 3. Es opcional, ya que no todos están listos para ese paso. También hay un puerto opcional de Cloud NDB a Cloud Datastore, que es definitivamente opcional, y solo para aquellos que desean salir de NDB y el código consolidado que usa Cloud Datastore. Esa migración es idéntica al Codelab de migración del módulo 3.

  • Módulo 9 Migra de Python 2 a 3 y Cloud NDB a Cloud Datastore
    • Portar módulos de migración opcionales a Python 3
    • También incluye una migración opcional de Cloud NDB a Cloud Datastore (igual que el módulo 3)
    • Una migración menor de Cloud Tasks v1 a v2 (ya que su biblioteca cliente está inactiva para Python 2)
  • Módulo 4: Migra a Cloud Run con Docker
    • Organiza tu aplicación en contenedores para que se ejecute en Cloud Run con Docker
    • Esta migración te permite permanecer en Python 2.
  • Módulo 5: Migra a Cloud Run con Cloud Buildpacks
    • Crea contenedores para tu app a fin de que se ejecute en Cloud Run con paquetes de Cloud Build
    • No necesitas conocer los detalles de Docker, contenedores, ni Dockerfile.
    • Requiere que tu app ya se haya migrado a Python 3 (Buildpacks no es compatible con Python 2).
  • Módulo 6: Migra a Cloud Firestore
    • Migra a Cloud Firestore para acceder a las funciones de Firebase
    • Si bien Cloud Firestore es compatible con Python 2, este codelab solo está disponible en Python 3.

Problemas o comentarios de los Codelabs del módulo de migración de App Engine

Si encuentras algún problema con este Codelab, primero busca el problema antes de enviarlo. Vínculos para buscar y crear problemas nuevos:

Recursos de migración

Puede encontrar vínculos a las carpetas del repositorio para el módulo 7 (INICIAR) y el módulo 8 (FINALIZAR) en la tabla a continuación. También se puede acceder a ellos desde el repositorio de todas las migraciones de codelab de App Engine, que te permite clonar o descargar un archivo ZIP.

Codelab

Python 2

Python 3

Módulo 7

código

(n/a)

Módulo 8

código

(n/a)

Recursos de App Engine

A continuación, se muestran recursos adicionales con respecto a esta migración específica: