1. Descripción general
El objetivo de la serie de codelabs de Serverless Migration Station (instructivos prácticos y de autoaprendizaje) y los videos relacionados es ayudar a los desarrolladores de Google Cloud sin servidores a modernizar sus aplicaciones guiándolos a través de una o más migraciones, principalmente alejándose de los servicios heredados. De esta manera, tus apps serán más portátiles y tendrás más opciones y flexibilidad, lo que te permitirá integrar y acceder a una mayor variedad de productos de Cloud, y actualizarte más fácilmente a versiones de lenguaje más recientes. Si bien, en un principio, se enfoca en los primeros usuarios de Cloud, principalmente los desarrolladores de App Engine (entorno estándar), esta serie es lo suficientemente amplia como para incluir otras plataformas sin servidores, como Cloud Functions y Cloud Run, o en cualquier otro lugar si corresponde.
En este codelab del módulo 15, se explica cómo agregar el uso de App Engine blobstore a la app de ejemplo del módulo 0. Luego, en el módulo 16, podrás migrar ese uso a Cloud Storage.
En un próximo lab,
- Agrega el uso de la API/biblioteca de Blobstore de App Engine
- Almacena las cargas de los usuarios en el servicio de
blobstore - Preparación para el siguiente paso de la migración a Cloud Storage
Requisitos
- Un proyecto de Google Cloud Platform con una cuenta de facturación activa de GCP
- Habilidades básicas de Python
- Conocimiento práctico de los comandos comunes de Linux
- Conocimientos básicos sobre el desarrollo y la implementación de aplicaciones de App Engine
- Una app de App Engine en el módulo 0 en funcionamiento (obténla del repo)
Encuesta
¿Cómo usarás este instructivo?
¿Cómo calificarías tu experiencia en Python?
¿Cómo calificarías tu experiencia en el uso de los servicios de Google Cloud?
2. Fondo
Para migrar desde la API de Blobstore de App Engine, agrega su uso a la app de ndb de App Engine existente del módulo 0. La app de ejemplo muestra las diez visitas más recientes del usuario. Modificaremos la app para solicitarle al usuario final que suba un artefacto (un archivo) que corresponda a su "visita". Si el usuario no desea hacerlo, hay una opción para "omitir". Independientemente de la decisión del usuario, la página siguiente renderiza el mismo resultado que la app del módulo 0 (y muchos de los otros módulos de esta serie). Con esta integración de blobstore de App Engine implementada, podemos migrarla a Cloud Storage en el siguiente codelab (módulo 16).
App Engine proporciona acceso a los sistemas de plantillas Django y Jinja2, y algo que hace que este ejemplo sea diferente (además de agregar acceso a Blobstore) es que cambia de usar Django en el módulo 0 a Jinja2 aquí en el módulo 15. Un paso clave para modernizar las apps de App Engine es migrar los frameworks web de webapp2 a Flask. Este último usa Jinja2 como su sistema de plantillas predeterminado, por lo que comenzamos a avanzar en esa dirección implementando Jinja2 y permaneciendo en webapp2 para el acceso a Blobstore. Dado que Flask usa Jinja2 de forma predeterminada, esto significa que no se requerirán cambios en la plantilla más adelante en el Módulo 16.
3. Configurar/trabajo previo
Antes de comenzar con la parte principal del instructivo, configura tu proyecto, obtén el código e implementa la app de referencia para comenzar a trabajar con el código.
1. Configura el proyecto
Si ya implementaste la app del módulo 0, te recomendamos que vuelvas a usar el mismo proyecto (y el código). 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 esté habilitado.
2. Obtén app de ejemplo del modelo de referencia
Uno de los requisitos previos para este codelab es tener una app de ejemplo del módulo 0 que funcione. Si no la tienes, puedes obtenerla de la carpeta "START" del módulo 0 (vínculo a continuación). Este codelab te guiará en cada paso y concluirá con un código que se parezca al que se encuentra en la carpeta "FINISH" del módulo 15.
- INICIO: Carpeta del módulo 0 (Python 2)
- FINALIZAR: Carpeta del módulo 15 (Python 2)
- Repositorio completo (para clonar o descargar el archivo ZIP)
El directorio de archivos de INICIO del módulo 0 debería verse así:
$ ls README.md index.html app.yaml main.py
3. (vuelve a) implementa la aplicación de modelo de referencia
Los pasos del trabajo previo restantes para ejecutar ahora sin estos:
- Familiarízate con la herramienta de línea de comandos de
gcloud - Vuelve a implementar la app de muestra con
gcloud app deploy - Confirma que la app se ejecuta en App Engine sin problemas
Una vez que hayas ejecutado esos pasos de manera correcta y veas que tu app web funciona (con un resultado similar al siguiente), podrás agregar el uso del almacenamiento en caché a tu app.

4. Actualiza archivos de configuración
app.yaml
No hay cambios importantes en la configuración de la aplicación, pero, como se mencionó anteriormente, migraremos de las plantillas de Django (predeterminadas) a Jinja2, por lo que, para cambiar, los usuarios deben especificar la versión más reciente de Jinja2 disponible en los servidores de App Engine. Para ello, deben agregarla a la sección de bibliotecas externas integradas de app.yaml.
ANTES:
runtime: python27
threadsafe: yes
api_version: 1
handlers:
- url: /.*
script: main.app
Edita tu archivo app.yaml agregando una nueva sección libraries como la que se muestra aquí:
DESPUÉS:
runtime: python27
threadsafe: yes
api_version: 1
handlers:
- url: /.*
script: main.app
libraries:
- name: jinja2
version: latest
No es necesario actualizar ningún otro archivo de configuración, así que pasemos a los archivos de la aplicación.
5. Modifica los archivos de la aplicación
Importaciones y compatibilidad con Jinja2
El primer conjunto de cambios para main.py incluye la adición del uso de la API de Blobstore y el reemplazo de las plantillas de Django por Jinja2. Estos son los cambios:
- El propósito del módulo
oses crear una ruta de acceso de archivo a una plantilla de Django. Como cambiamos a Jinja2, donde se controla esto, ya no es necesario usarosni el renderizador de plantillas de Django,google.appengine.ext.webapp.template, por lo que se quitarán. - Importa la API de Blobstore:
google.appengine.ext.blobstore - Importa los controladores de Blobstore que se encuentran en el framework
webapporiginal. No están disponibles enwebapp2:google.appengine.ext.webapp.blobstore_handlers - Importa la compatibilidad con Jinja2 desde el paquete
webapp2_extras
ANTES:
import os
import webapp2
from google.appengine.ext import ndb
from google.appengine.ext.webapp import template
Implementa los cambios en la lista anterior reemplazando la sección de importación actual en main.py con el siguiente fragmento de código.
DESPUÉS:
import webapp2
from webapp2_extras import jinja2
from google.appengine.ext import blobstore, ndb
from google.appengine.ext.webapp import blobstore_handlers
Después de las importaciones, agrega código estándar para admitir el uso de Jinja2 como se define en la documentación de webapp2_extras. El siguiente fragmento de código encapsula la clase estándar del controlador de solicitudes de webapp2 con la funcionalidad de Jinja2, por lo que debes agregar este bloque de código a main.py justo después de las importaciones:
class BaseHandler(webapp2.RequestHandler):
'Derived request handler mixing-in Jinja2 support'
@webapp2.cached_property
def jinja2(self):
return jinja2.get_jinja2(app=self.app)
def render_response(self, _template, **context):
self.response.write(self.jinja2.render_template(_template, **context))
Agrega compatibilidad con Blobstore
A diferencia de otras migraciones de esta serie en las que mantenemos la funcionalidad o el resultado de la app de ejemplo idénticos (o casi iguales) sin (muchos) cambios en la UX, este ejemplo se aleja de la norma de forma más radical. En lugar de registrar de inmediato una visita nueva y, luego, mostrar las diez más recientes, actualizaremos la app para que le solicite al usuario un artefacto de archivo con el que registrar su visita. Luego, los usuarios finales pueden subir un archivo correspondiente o seleccionar "Omitir" para no subir nada. Una vez que completes este paso, se mostrará la página “Visitas más recientes”.
Este cambio permite que nuestra app use el servicio de Blobstore para almacenar (y, posiblemente, renderizar más adelante) esa imagen o cualquier otro tipo de archivo en la página de visitas más recientes.
Actualiza el modelo de datos y aplica su uso
Almacenaremos más datos, específicamente actualizaremos el modelo de datos para almacenar el ID (llamado "BlobKey") del archivo subido a Blobstore y agregaremos una referencia para guardarlo en store_visit(). Dado que estos datos adicionales se devuelven junto con todo lo demás en la consulta, fetch_visits() permanece igual.
A continuación, se muestran los resultados antes y después de aplicar estas actualizaciones con file_blob, un ndb.BlobKeyProperty:
ANTES:
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()
def fetch_visits(limit):
'get most recent visits'
return Visit.query().order(-Visit.timestamp).fetch(limit)
DESPUÉS:
class Visit(ndb.Model):
'Visit entity registers visitor IP address & timestamp'
visitor = ndb.StringProperty()
timestamp = ndb.DateTimeProperty(auto_now_add=True)
file_blob = ndb.BlobKeyProperty()
def store_visit(remote_addr, user_agent, upload_key):
'create new Visit entity in Datastore'
Visit(visitor='{}: {}'.format(remote_addr, user_agent),
file_blob=upload_key).put()
def fetch_visits(limit):
'get most recent visits'
return Visit.query().order(-Visit.timestamp).fetch(limit)
A continuación, se muestra una representación gráfica de los cambios que se realizaron hasta el momento:

Admite la carga de archivos
El cambio más significativo en la funcionalidad es la compatibilidad con la carga de archivos, ya sea que se le solicite al usuario un archivo, se admita la función "omitir" o se renderice un archivo correspondiente a una visita. Todo es parte de la imagen. Estos son los cambios necesarios para admitir las cargas de archivos:
- La solicitud del controlador principal
GETya no recupera las visitas más recientes para mostrarlas. En cambio, le solicita al usuario que suba un archivo. - Cuando un usuario final envía un archivo para subirlo o se salta ese proceso, un
POSTdel formulario pasa el control al nuevoUploadHandler, derivado degoogle.appengine.ext.webapp.blobstore_handlers.BlobstoreUploadHandler. - El método
POSTdeUploadHandlerrealiza la carga, llama astore_visit()para registrar la visita y activa un redireccionamiento HTTP 307 para enviar al usuario de vuelta a "/", donde… - El método
POSTdel controlador principal consulta (a través defetch_visits()) y muestra las visitas más recientes. Si el usuario selecciona "Omitir", no se subirá ningún archivo, pero la visita se registrará de todos modos y se realizará el mismo redireccionamiento. - La pantalla de visitas más recientes incluye un campo nuevo que se muestra al usuario, ya sea un "ver" con hipervínculo si hay un archivo de carga disponible o "ninguno" en caso contrario. Estos cambios se implementan en la plantilla HTML junto con la incorporación de un formulario de carga (pronto habrá más información sobre esto).
- Si un usuario final hace clic en el vínculo "Ver" de cualquier visita con un video subido, se realiza una solicitud
GETa un nuevoViewBlobHandler, derivado degoogle.appengine.ext.webapp.blobstore_handlers.BlobstoreDownloadHandler, que renderiza el archivo si es una imagen (en el navegador si es compatible), solicita la descarga si no lo es o devuelve un error HTTP 404 si no se encuentra. - Además del nuevo par de clases de controladores y un nuevo par de rutas para enviar tráfico a ellos, el controlador principal necesita un nuevo método
POSTpara recibir el redireccionamiento 307 que se describió anteriormente.
Antes de estas actualizaciones, la app del módulo 0 solo incluía un controlador principal con un método GET y una sola ruta:
ANTES:
class MainHandler(webapp2.RequestHandler):
'main application (GET) handler'
def get(self):
store_visit(self.request.remote_addr, self.request.user_agent)
visits = fetch_visits(10)
tmpl = os.path.join(os.path.dirname(__file__), 'index.html')
self.response.out.write(template.render(tmpl, {'visits': visits}))
app = webapp2.WSGIApplication([
('/', MainHandler),
], debug=True)
Con esas actualizaciones implementadas, ahora hay tres controladores: 1) un controlador de carga con un método POST, 2) un controlador de descarga de "ver blob" con un método GET y 3) el controlador principal con métodos GET y POST. Realiza estos cambios para que el resto de tu app se vea como se muestra a continuación.
DESPUÉS:
class UploadHandler(blobstore_handlers.BlobstoreUploadHandler):
'Upload blob (POST) handler'
def post(self):
uploads = self.get_uploads()
blob_id = uploads[0].key() if uploads else None
store_visit(self.request.remote_addr, self.request.user_agent, blob_id)
self.redirect('/', code=307)
class ViewBlobHandler(blobstore_handlers.BlobstoreDownloadHandler):
'view uploaded blob (GET) handler'
def get(self, blob_key):
self.send_blob(blob_key) if blobstore.get(blob_key) else self.error(404)
class MainHandler(BaseHandler):
'main application (GET/POST) handler'
def get(self):
self.render_response('index.html',
upload_url=blobstore.create_upload_url('/upload'))
def post(self):
visits = fetch_visits(10)
self.render_response('index.html', visits=visits)
app = webapp2.WSGIApplication([
('/', MainHandler),
('/upload', UploadHandler),
('/view/([^/]+)?', ViewBlobHandler),
], debug=True)
Hay varias llamadas clave en este código que acabamos de agregar:
- En
MainHandler.get, hay una llamada ablobstore.create_upload_url. Esta llamada genera la URL a la que se dirige el formularioPOST, y llama al controlador de carga para enviar el archivo a Blobstore. - En
UploadHandler.post, hay una llamada ablobstore_handlers.BlobstoreUploadHandler.get_uploads. Esta es la verdadera magia que coloca el archivo en Blobstore y devuelve un ID único y persistente para ese archivo, suBlobKey. - En
ViewBlobHandler.get, llamar ablobstore_handlers.BlobstoreDownloadHandler.sendcon unBlobKeyde archivo genera la recuperación del archivo y su reenvío al navegador del usuario final.
Estas llamadas representan la mayor parte del acceso a las funciones agregadas a la app. A continuación, se muestra una representación gráfica de este segundo y último conjunto de cambios en main.py:

Actualiza la plantilla HTML
Algunas de las actualizaciones de la aplicación principal afectan la interfaz de usuario (IU) de la app, por lo que se requieren cambios correspondientes en la plantilla web, dos de hecho:
- Se requiere un formulario de carga de archivos con 3 elementos de entrada: un archivo y un par de botones de envío para cargar el archivo y omitirlo, respectivamente.
- Actualiza el resultado de las visitas más recientes agregando un vínculo de "ver" para las visitas con una carga de archivo correspondiente o "ninguno" en caso contrario.
ANTES:
<!doctype html>
<html>
<head>
<title>VisitMe Example</title>
<body>
<h1>VisitMe example</h1>
<h3>Last 10 visits</h3>
<ul>
{% for visit in visits %}
<li>{{ visit.timestamp.ctime }} from {{ visit.visitor }}</li>
{% endfor %}
</ul>
</body>
</html>
Implementa los cambios de la lista anterior para incluir la plantilla actualizada:
DESPUÉS:
<!doctype html>
<html>
<head>
<title>VisitMe Example</title>
<body>
<h1>VisitMe example</h1>
{% if upload_url %}
<h3>Welcome... upload a file? (optional)</h3>
<form action="{{ upload_url }}" method="POST" enctype="multipart/form-data">
<input type="file" name="file"><p></p>
<input type="submit"> <input type="submit" value="Skip">
</form>
{% else %}
<h3>Last 10 visits</h3>
<ul>
{% for visit in visits %}
<li>{{ visit.timestamp.ctime() }}
<i><code>
{% if visit.file_blob %}
(<a href="/view/{{ visit.file_blob }}" target="_blank">view</a>)
{% else %}
(none)
{% endif %}
</code></i>
from {{ visit.visitor }}
</li>
{% endfor %}
</ul>
{% endif %}
</body>
</html>
En esta imagen, se ilustran las actualizaciones necesarias para index.html:

Un último cambio es que Jinja2 prefiere sus plantillas en una carpeta templates, por lo que debes crear esa carpeta y mover index.html dentro de ella. Con este último paso, ya terminaste con todos los cambios necesarios para agregar el uso de Blobstore a la app de muestra del módulo 0.
(Opcional) "Mejora" de Cloud Storage
Con el tiempo, el almacenamiento de Blobstore evolucionó hasta convertirse en Cloud Storage. Esto significa que las cargas de Blobstore son visibles en Cloud Console, específicamente en el navegador de Cloud Storage. La pregunta es dónde. La respuesta es el bucket predeterminado de Cloud Storage de tu app de App Engine. Su nombre es el nombre de dominio completo de tu app de App Engine, PROJECT_ID.appspot.com. Es muy conveniente porque todos los IDs de proyectos son únicos, ¿verdad?
Las actualizaciones realizadas en la aplicación de ejemplo colocan los archivos subidos en ese bucket, pero los desarrolladores tienen la opción de elegir una ubicación más específica. Se puede acceder al bucket predeterminado de forma programática a través de google.appengine.api.app_identity.get_default_gcs_bucket_name(), lo que requiere una nueva importación si deseas acceder a este valor, por ejemplo, para usarlo como prefijo para organizar los archivos subidos. Por ejemplo, ordenar por tipo de archivo:

Para implementar algo similar para las imágenes, por ejemplo, tendrás un código como este junto con otro código que verifique los tipos de archivos para elegir el nombre del bucket deseado:
ROOT_BUCKET = app_identity.get_default_gcs_bucket_name()
IMAGE_BUCKET = '%s/%s' % (ROOT_BUCKET, 'images')
También validarás las imágenes subidas con una herramienta como el módulo imghdr de la biblioteca estándar de Python para confirmar el tipo de imagen. Por último, probablemente querrás limitar el tamaño de las cargas en caso de que haya usuarios maliciosos.
Digamos que todo eso ya se hizo. ¿Cómo podemos actualizar nuestra app para especificar dónde almacenar los archivos subidos? La clave es modificar la llamada a blobstore.create_upload_url en MainHandler.get para especificar la ubicación deseada en Cloud Storage para la carga agregando el parámetro gs_bucket_name de la siguiente manera:
blobstore.create_upload_url('/upload', gs_bucket_name=IMAGE_BUCKET))
Como esta es una actualización opcional si deseas especificar dónde deben ir las cargas, no forma parte del archivo main.py en el repo. En cambio, hay una alternativa llamada main-gcs.py disponible para tu revisión en el repo. En lugar de usar una "carpeta" de bucket separada, el código de main-gcs.py almacena las cargas en el bucket "raíz" (PROJECT_ID.appspot.com) al igual que main.py, pero proporciona la estructura que necesitas si derivas la muestra en algo más, como se sugiere en esta sección. A continuación, se muestra una ilustración de las "diferencias" entre main.py y main-gcs.py.

6. Resumen/Limpieza
En esta sección, se completa el codelab con la implementación de la app y la verificación de que funcione según lo previsto y en cualquier resultado reflejado. Después de la validación de la app, realiza los pasos de limpieza necesarios y considera los próximos pasos.
Implementa y verifica la aplicación
Vuelve a implementar tu app con gcloud app deploy y confirma que funciona según lo anunciado, con una experiencia del usuario (UX) diferente de la app del módulo 0. Ahora hay dos pantallas diferentes en tu app. La primera es el mensaje del formulario de carga de archivos de visita:
Desde allí, los usuarios finales suben un archivo y hacen clic en "Enviar", o bien hacen clic en "Omitir" para no subir nada. En ambos casos, el resultado es la pantalla de visita más reciente, ahora aumentada con vínculos de "ver" o "ninguno" entre las marcas de tiempo de la visita y la información del visitante:

Felicitaciones por completar este codelab en el que agregaste el uso de Blobstore de App Engine a la app de ejemplo del módulo 0. Tu código ahora debería coincidir con el contenido de la carpeta FINISH (módulo 15). El main-gcs.py alternativo también está presente en esa carpeta.
Limpia
General
Si terminaste por ahora, te recomendamos que inhabilites tu app de App Engine para evitar incurrir en cargos de facturación. Sin embargo, si deseas realizar más pruebas o experimentos, la plataforma de App Engine tiene una cuota gratuita, por lo que no se te cobrará siempre que no excedas ese nivel de uso. Esto se aplica a la capacidad de procesamiento, pero también puede haber cargos por los servicios relevantes de App Engine, por lo que debes consultar su página de precios para obtener más información. Si esta migración involucra otros servicios de Cloud, estos se facturarán por separado. En cualquier caso, si corresponde, consulta la sección "Específico para este codelab" que se encuentra más abajo.
Para divulgar toda la información, la implementación en una plataforma de computación sin servidores de Google Cloud, como App Engine, genera costos menores de compilación y almacenamiento. Cloud Build tiene su propia cuota gratuita, al igual que Cloud Storage. El almacenamiento de esa imagen usa parte de esa cuota. Sin embargo, es posible que vivas en una región que no tenga ese nivel gratuito, por lo que debes tener en cuenta tu uso de almacenamiento para minimizar los costos potenciales. Las "carpetas" específicas de Cloud Storage que debes revisar incluyen las siguientes:
console.cloud.google.com/storage/browser/LOC.artifacts.PROJECT_ID.appspot.com/containers/imagesconsole.cloud.google.com/storage/browser/staging.PROJECT_ID.appspot.com- Los vínculos de almacenamiento anteriores dependen de tu
PROJECT_IDy *LOC*ación, por ejemplo, "us" si tu app está alojada en EE.UU.
Por otro lado, si no vas a continuar con esta aplicación ni con otros codelabs de migración relacionados y quieres borrar todo por completo, cierra tu proyecto.
Específico para este codelab
Los servicios que se indican a continuación son exclusivos de este codelab. Consulta la documentación de cada producto para obtener más información:
- El servicio de Blobstore de App Engine se incluye en las cuotas y los límites de datos almacenados, por lo que también debes revisar la página de precios de los servicios en paquetes heredados.
- El servicio App Engine Datastore lo proporciona Cloud Datastore (Cloud Firestore en modo Datastore), que también tiene un nivel gratuito. Consulta su página de precios para obtener más información.
Próximos pasos
La siguiente migración lógica que se debe considerar se aborda en el módulo 16, en el que se muestra a los desarrolladores cómo migrar del servicio Blobstore de App Engine al uso de la biblioteca cliente de Cloud Storage. Entre los beneficios de la actualización, se incluyen la posibilidad de acceder a más funciones de Cloud Storage y familiarizarse con una biblioteca cliente que funciona para apps fuera de App Engine, ya sea en Google Cloud, otras nubes o incluso de forma local. Si crees que no necesitas todas las funciones disponibles en Cloud Storage o te preocupan sus efectos en el costo, puedes seguir usando Blobstore de App Engine.
Después del módulo 16, hay una gran cantidad de otras migraciones posibles, como Cloud NDB y Cloud Datastore, Cloud Tasks o Cloud Memorystore. También hay migraciones entre productos a Cloud Run y Cloud Functions. El repositorio de migración incluye todas las muestras de código, te vincula a todos los codelabs y videos disponibles, y también proporciona orientación sobre qué migraciones considerar y el "orden" pertinente de las migraciones.
7. Recursos adicionales
Problemas o comentarios de los codelabs
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
En la siguiente tabla, puedes encontrar vínculos a las carpetas del repositorio para el módulo 0 (INICIAR) y el módulo 15 (FINALIZAR). También se puede acceder a ellos desde el repositorio de todas las migraciones de codelab de App Engine, que puedes clonar o descargar un archivo ZIP.
Codelab | Python 2 | Python 3 |
Módulo 0 | N/A | |
Módulo 15 (este codelab) | N/A |
Recursos en línea
A continuación, se incluyen recursos en línea que pueden ser pertinentes para este instructivo:
App Engine
- Servicio de Blobstore de App Engine
- Cuotas y límites de los datos almacenados de App Engine
- Documentación de App Engine
- Tiempo de ejecución de Python 2 App Engine (entorno estándar)
- Usa las bibliotecas integradas de App Engine en App Engine de Python 2
- Información sobre los precios y las cuotas de App Engine
- Lanzamiento de la plataforma App Engine de segunda generación (2018)
- Comparación de las plataformas de primera y segunda generación
- Asistencia a largo plazo para entornos de ejecución heredados
- Repositorio de muestras de migración de documentación
- Repositorio de muestras de migración aportadas por la comunidad
Google Cloud
- Python en Google Cloud Platform
- Bibliotecas cliente de Python de Google Cloud
- Nivel "Siempre gratis" de Google Cloud
- SDK de Google Cloud (herramienta de línea de comandos de gcloud)
- Toda la documentación de Google Cloud
Python
- Sistemas de plantillas Django y Jinja2
webapp2framework web- Documentación de
webapp2 webapp2_extrasvínculoswebapp2_extrasDocumentación de Jinja2
Videos
- Serverless Migration Station
- Expediciones sin servidores
- Suscríbete a Google Cloud Tech
- Suscríbete a Google Developers
Licencia
Este trabajo cuenta con una licencia Atribución 2.0 Genérica de Creative Commons.