Module 8 : Migrer depuis App Engine ndb et taskqueue vers Cloud NDB et Cloud Tasks.

Cette suite d'ateliers de programmation (tutoriels pratiques à suivre à votre rythme) est destinée à aider les développeurs Google App Engine (standard) à moderniser leurs applications en les guidant tout au long d'une série de migrations. L'étape la plus importante est de délaisser les services groupés de l'environnement d'exécution d'origine, car les environnements d'exécution nouvelle génération sont plus souples et offrent aux utilisateurs davantage d'options de service. Le passage à l'environnement d'exécution nouvelle génération vous permet d'intégrer plus facilement les produits Google Cloud, d'utiliser une plus large gamme de services compatibles et de prendre en charge les versions de langage actuelles.

Le présent atelier de programmation permet aux utilisateurs de migrer des tâches d'envoi d'App Engine et de son API/bibliothèque taskqueue vers Cloud Tasks. Si votre application n'utilise pas de files d'attente de tâches, vous pouvez utiliser cet atelier de programmation comme exercice pour apprendre à migrer des tâches d'envoi d'App Engine vers Cloud Tasks.

Vous apprendrez à effectuer les tâches suivantes :

  • Migrer depuis App Engine taskqueue vers Cloud Tasks
  • Créer des tâches d'envoi avec Cloud Tasks
  • Migrer depuis App Engine ndb vers Cloud NDB (identique au module 2)

Prérequis

Enquête

Comment allez-vous utiliser cet atelier de programmation ?

Je vais le lire uniquement Je vais le lire et effectuer les exercices

Comme nous avons ajouté des tâches push App Engine à l'exemple d'application dans l'atelier de programmation précédent (module 7), nous pouvons maintenant le migrer vers Cloud Tasks. Les principales étapes de migration de ce tutoriel sont les suivantes :

  1. Configuration/Préparation
  2. Mettre à jour les fichiers de configuration
  3. Mettre à jour l'application principale

Avant de passer à la partie principale de ce tutoriel, nous allons configurer notre projet, obtenir le code et déployer l'application de base pour nous assurer de bien utiliser du code fonctionnel.

1. Configurer le projet

Nous vous conseillons de réutiliser le même projet que celui utilisé pour l'Atelier de programmation du module 7. Vous pouvez également créer un projet ou réutiliser un autre projet existant. Assurez-vous que le projet dispose d'un compte de facturation actif et que App Engine (app) est activé.

2. Obtenir un exemple d'application de référence

L'une des conditions préalables à cet atelier de programmation est de disposer d'un exemple d'application fonctionnel du Module 7. Si vous n'en avez pas, suivez le tutoriel du module 7 (lien ci-dessus) avant de continuer. Si vous connaissez déjà le contenu de ce tutoriel, vous pouvez récupérer le code du module 7 ci-dessous.

Que vous utilisiez votre code ou le nôtre, le code de départ ("START") est celui du module 7. Le présent atelier de programmation du module 2 vous guide pour chaque étape jusqu'à obtenir un code similaire au code final ("FINISH"), avec le bonus de la migration de Python 2 vers Python 3.

Le répertoire des fichiers du module 7 (qu'il s'agisse de votre code ou du nôtre) doit se présenter comme suit :

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

Si vous avez suivi le tutoriel du module 7, vous verrez également un dossier lib avec Flask et ses dépendances.

3. (Re)Déployer l'application du Module 7

Étapes préliminaires restantes :

  1. Familiarisez-vous avec l'outil de ligne de commande gcloud (si nécessaire).
  2. (Re)déployez le code du module 7 sur App Engine (si nécessaire).

Une fois que vous avez exécuté ces étapes et vérifié le bon fonctionnement du code, nous pouvons passer à la suite de ce tutoriel en commençant par les fichiers de configuration.

requirements.txt

Le fichier requirements.txt du module 7 ne répertorie Flask qu'en tant que package requis. Cloud NDB et Cloud Tasks possèdent leurs propres bibliothèques clientes. À cette étape, il faut donc ajouter les packages correspondants au fichier requirements.txt pour qu'il se présente comme suit :

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

Nous vous recommandons d'utiliser les versions les plus récentes de chaque bibliothèque. les numéros de version ci-dessus correspondent à la dernière version pour Python 2 au moment de la rédaction de cet article. (Les packages équivalents pour Python 3 utiliseront probablement une version supérieure.) Le code du dépôt (code final "FINISH") est mis à jour plus fréquemment et peut contenir des versions plus récentes, mais ce cas de figure est moins probable pour les bibliothèques Python 2 qui sont généralement figées.

app.yaml

Référencez les bibliothèques intégrées grpcio et setuptools dans le fichier app.yaml, dans une section libraries :

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

appengine_config.py

Mettez à jour appengine_config.py pour utiliser pkg_resources de manière à lier ces bibliothèques intégrées aux bibliothèques tierces copiées telles que Flask ou les bibliothèques clientes 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)

Il n'y a qu'un fichier d'application : main.py. Par conséquent, toutes les modifications de la présente section s'appliquent uniquement à ce fichier.

Mettre à jour les importations et l'initialisation

Notre application utilise actuellement les bibliothèques intégrées google.appengine.api.taskqueue et google.appengine.ext.ndb :

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

Remplacez les deux éléments par google.cloud.ndb et google.cloud.tasks. De plus, Cloud Tasks nécessitant un encodage JSON de la charge utile de la tâche, vous devez également importer json. Lorsque vous avez terminé, voici à quoi la section import de main.py devrait ressembler :

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

Migrer vers Cloud Tasks (et Cloud NDB)

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

Le fonctionnement de store_visit() reste le même que dans le module 2 : il faut ajouter un gestionnaire de contexte pour tous les accès Datastore. Cela se traduit par le déplacement de la création d'une nouvelle entité Visit encapsulée dans une instruction with.

  • APRÈ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()

Cloud Tasks nécessite actuellement qu'App Engine soit activé pour votre projet Google Cloud afin que vous puissiez l'utiliser (même si vous n'avez pas de code App Engine). Sinon, les files d'attente de tâches ne fonctionneront pas. (Consultez cette section de la documentation pour en savoir plus.) Cloud Tasks accepte les tâches exécutées sur App Engine ("cibles" App Engine), mais peut également être exécuté sur n'importe quel point de terminaison HTTP (cibles HTTP) avec une adresse IP publique, par exemple Cloud Functions, Cloud Run, GKE, Compute Engine ou même un serveur sur site. Notre application simple utilise une cible App Engine pour les tâches.

Un processus de configuration est nécessaire pour utiliser Cloud NDB et Cloud Tasks. En haut de la page main.py sous Initialisation de Flask, initialisez Cloud NDB et Cloud Tasks. Définissez également des constantes indiquant où s'exécuteront vos tâches d'envoi.

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)

Une fois que vous avez créé votre file d'attente de tâches, renseignez le PROJECT_ID de votre projet, la REGION dans laquelle vos tâches seront exécutées (qui doit être identique à votre région App Engine) et le nom de votre file d'attente d'envoi. App Engine dispose d'une file d'attente "default" et nous allons donc utiliser ce nom (vous pouvez utiliser un autre nom si vous le souhaitez).

La file d'attente default est une file spéciale créée automatiquement dans certaines circonstances, notamment lorsque vous utilisez les API App Engine. Par conséquent, si vous (ré)utilisez le même projet que pour le module 7, default existe déjà. Toutefois, si vous avez créé un nouveau projet pour le module 8, vous devez créer default manuellement. Vous trouverez plus d'informations sur la file d'attente default dans la documentation du fichier queue.yaml.

L'objectif de ts_client.queue_path() est de créer le nom de chemin complet (QUEUE_PATH) d'une file d'attente de tâches nécessaire à la création d'une tâche. Une structure JSON est également nécessaire pour spécifier les paramètres de tâche :

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

Que voyez-vous ci-dessus ?

  1. Informations sur la cible de tâche :
    • Pour les cibles App Engine, spécifiez app_engine_http_request comme type de requête et relative_uri comme gestionnaire de tâches App Engine.
    • Pour les cibles HTTP, utilisez plutôt http_request et url.
  2. body : paramètre(s) encodé(s) en JSON et Unicode à envoyer à la tâche (push).
  3. Spécifier explicitement un en-tête Content-Type encodé en JSON.

Pour en savoir plus sur vos options, consultez la documentation.

Maintenant que la configuration test erminée, mettons à jour fetch_visits(). Voici le résultat du tutoriel précédent :

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

Les mises à jour requises :

  1. Passer d'App Engine ndb à Cloud NDB.
  2. Nouveau code permettant d'extraire l'horodatage de la visite la plus ancienne affichée.
  3. Utiliser Cloud Tasks pour créer une tâche au lieu d'App Engine taskqueue.

Voici à quoi devrait ressembler votre nouvelle fonction fetch_visits() :

  • APRÈ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

Récapitulatif de la mise à jour du code :

  • Passer à Cloud NDB implique de déplacer le code Datastore dans une instruction with.
  • Passer à Cloud Tasks implique d'utiliser ts_client.create_task() au lieu de taskqueue.add().
  • Transmettre le chemin d'accès complet de la file d'attente et la charge utile task (décrite précédemment).

Gestionnaire de tâches de mise à jour (push)

Quelques modifications doivent être apportées à la fonction du gestionnaire de tâches (push).

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

Il suffit de placer tous les accès à Datastore dans l'instruction with du gestionnaire de contexte (la requête et la requête de suppression). En gardant cela en tête, mettez à jour votre gestionnaire trim() comme suit :

  • APRÈ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

Il n'y a aucune modification de templates/index.html dans cet atelier de programmation, ni d'ailleurs d'en le prochain.

Déployer l'application

Vérifiez toutes les modifications apportées à votre code, vérifiez qu'il se compile correctement et redéployez l'application. Vérifiez que l'application fonctionne toujours. Vous devriez obtenir un résultat identique à celui du module 7. Vous venez de remettre toutes les pièces en place. L'application devrait fonctionner normalement.

Si vous avez suivi ce tutoriel sans suivre l'atelier de programmation du module 7, l'application n'a pas changé : elle enregistre toutes les visites de la page Web principale (/) et se présente comme suit une fois que vous avez consulté le site un nombre suffisant de fois (elle indique également qu'elle a supprimé toutes les visites antérieures à la dixième) :

Application Visitme du module 7

Voici qui conclut cet atelier de programmation. Votre code doit maintenant correspondre à celui du dépôt du module 8. Bravo ! Vous avez réussi la plus critique des migrations de tâches d'envoi ! Le module 9 (lien vers l'atelier de programmation ci-dessous) est facultatif et permet aux utilisateurs de migrer vers Python 3 et Cloud Datastore.

Facultatif : Effectuer un nettoyage

Que diriez-vous d'un bon nettoyage pour éviter que des frais vous soient facturés tant que vous n'êtes pas prêt à passer à l'atelier de programmation suivant sur la migration ? En tant que développeur existant, vous connaissez probablement déjà les informations de tarification d'App Engine.

Facultatif : Désactiver l'application

Si vous n'êtes pas encore prêt à passer au tutoriel suivant, désactivez votre application pour éviter les frais. Lorsque vous serez prêt à passer au prochain atelier de programmation, vous pourrez le réactiver. Tant que votre application est désactivée, elle ne génère aucun trafic, donc aucuns frais. Toutefois, si vous dépassez le quota gratuit, votre utilisation de Datastore vous sera facturée alors supprimez suffisamment d'éléments pour rester dans la limite.

En revanche, si vous ne souhaitez pas poursuivre vos migrations et souhaitez supprimer tous les éléments, vous pouvez arrêter le projet.

Étapes suivantes

Après le présent tutoriel, l'étape suivante consiste à suivre le module 9 et son atelier de programmation pour le portage vers Python 3. Cette étape est facultative car tout le monde n'est pas prêt pour cette étape. Il existe également un portage facultatif de Cloud NDB vers Cloud Datastore (qui est définitivement facultatif) pour les personnes qui souhaitent arrêter d'utiliser NDB et consolider du code utilisant Cloud Datastore (cette migration est identique à celle de l'atelier de programmation du module 3.

  • Module 9 : Migrer de Python 2 vers Python 3 et de Cloud NDB vers Cloud Datastore
    • Module de migration facultatif pour le portage vers Python 3.
    • Inclut également une migration facultative de Cloud NDB vers Cloud Datastore (identique au module 3), et
    • Une migration mineure de Cloud Tasks v1 vers la v2 (puisque la bibliothèque cliente est figée pour Python 2)
  • Module 4 : Migrer vers Cloud Run avec Docker
    • Conteneurisez votre application pour l'exécuter sur Cloud Run avec Docker.
    • Cette migration vous permet de rester sur Python 2.
  • Module 5 : Migrer vers Cloud Run avec les packs de création Cloud
    • Conteneurisez votre application pour qu'elle s'exécute sur Cloud Run avec les packs de création Cloud.
    • Il n'est pas nécessaire de déjà connaître Docker, les conteneurs ou les Dockerfile.
    • Nécessite que votre application ait déjà été migrée vers Python 3 (les packs de création ne sont pas compatibles avec Python 2)
  • Module 6 : Migrer vers Cloud Firestore
    • Migrer vers Cloud Firestore pour utiliser les fonctionnalités de Firebase.
    • Bien que Cloud Firestore soit compatible avec Python 2, cet atelier de programmation n'est disponible que pour Python 3.

Problèmes/commentaires concernant le module de migration App Engine en atelier de programmation

Si vous rencontrez des problèmes avec cet atelier de programmation, commencez par faire une recherche avant de les signaler. Liens vers la recherche et la création d'un signalement :

Ressources de migration

Le tableau ci-dessous contient des liens vers les dossiers du dépôt pour les modules 7 (START) et 8 (FINISH). Vous pouvez également y accéder depuis le dépôt pour toutes les migrations d'ateliers de programmation App Engine que vous pouvez cloner ou télécharger sous forme de fichier ZIP.

Atelier de programmation

Python 2

Python 3

Module 7

code

(n/a)

Module 8

code

(n/a)

Ressources App Engine

Vous trouverez ci-dessous d'autres ressources concernant cette migration spécifique :