1. Descripción general
La serie de codelabs de Serverless Migration Station (instructivos prácticos de autoaprendizaje) y los videos relacionados tienen como objetivo 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 para dejar de usar 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á integrarte a una gama más amplia de productos de Cloud y acceder a ellos, además de actualizar con mayor facilidad a versiones de lenguaje más recientes. Si bien inicialmente se enfoca en los primeros usuarios de Cloud, principalmente 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 otros lugares, si corresponde.
En este codelab del módulo 15, se explica cómo agregar el uso de blobstore de App Engine a la app de ejemplo del módulo 0. Luego, podrás migrar ese uso a Cloud Storage en el módulo 16.
En un próximo lab,
- Agrega el uso de la API o biblioteca de Blobstore de App Engine
- Almacena las cargas de los usuarios en el servicio de
blobstore - Prepárate para el siguiente paso de la migración a Cloud Storage
Requisitos
- Un proyecto de Google Cloud Platform con una cuenta de facturación de GCP activa
- 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 apps de App Engine
- Una app de App Engine del módulo 0 que funcione (obténla del repositorio)
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 referencia existente de App Engine ndb del módulo 0. La app de ejemplo muestra las diez visitas más recientes al usuario. Modificaremos la app para que le solicite 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 de Django y Jinja2, y un aspecto que diferencia este ejemplo (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 mientras permanecemos en webapp2 para el acceso a Blobstore. Como Flask usa Jinja2 de forma predeterminada, no se requerirán cambios en la plantilla más adelante en el módulo 16.
3. Configurar/trabajo previo
Antes de pasar a la parte principal del instructivo, configura tu proyecto, obtén el código y, luego, implementa la app de referencia para comenzar con el código de trabajo.
1. Configura el proyecto
Si ya implementaste la app del módulo 0, te recomendamos que vuelvas a usar el mismo proyecto (y código). Como alternativa, puedes crear un proyecto nuevo o reutilizar otro existente. Asegúrate de que el proyecto tenga una cuenta de facturación activa y de que App Engine esté habilitado.
2. Obtén app de ejemplo del modelo de referencia
Uno de los requisitos previos de 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). En este codelab, se te guiará por cada paso y se concluirá con un código similar al que se encuentra en la carpeta "FINISH" del módulo 15.
- INICIO: Carpeta del módulo 0 (Python 2)
- FINALIZACIÓN: Carpeta del módulo 15 (Python 2)
- Repositorio completo (para clonar o descargar el archivo ZIP)
El directorio de los archivos START del módulo 0 debería verse de la siguiente manera:
$ 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 correctamente y veas que tu app web funciona (con un resultado similar al que se muestra a continuación), podrás agregar el uso del almacenamiento en caché a tu app.

4. Actualiza archivos de configuración
app.yaml
No hay cambios significativos en la configuración de la aplicación. Sin embargo, como se mencionó antes, pasaremos 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, y lo hacen agregándola a la sección de bibliotecas de terceros 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 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 agregar el uso de la API de Blobstore y reemplazar 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 cambiaremos a Jinja2, donde se controla esto, ya no se necesitan el uso deosni 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, ya que 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 de la lista anterior reemplazando la sección de importación actual en main.py por 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 un código estándar para admitir el uso de Jinja2, como se define en los documentos de webapp2_extras. En el siguiente fragmento de código, se une la clase de controlador de solicitudes webapp2 estándar 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 pedirle 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 se complete 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 e implementa 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.
Aquí puedes ver el antes y el después de 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)
Aquí tienes una representación gráfica de los cambios que se realizaron hasta el momento:

Admite cargas de archivos
El cambio más significativo en la funcionalidad es la compatibilidad con las cargas de archivos, ya sea que se le solicite un archivo al usuario, que se admita la función "omitir" o que se renderice un archivo correspondiente a una visita. Todo eso es parte del panorama. Estos son los cambios necesarios para admitir las cargas de archivos:
- La solicitud
GETdel controlador principal ya no recupera las visitas más recientes para mostrarlas. En su lugar, 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 sube ningún archivo, pero la visita se registra de todos modos, seguida del mismo redireccionamiento. - La pantalla de visitas más recientes incluye un campo nuevo que se muestra al usuario, ya sea un hipervínculo "ver" si hay un archivo de carga disponible o "ninguno" de lo contrario. Estos cambios se realizan en la plantilla HTML junto con la adición de un formulario de carga (más información sobre esto próximamente).
- 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 controlador y del nuevo par de rutas para enviarles tráfico, el controlador principal necesita un nuevo método
POSTpara recibir el redireccionamiento 307 descrito 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) 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 los 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 envía el formulario conPOSTy 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 elBlobKeyde un archivo hace que se recupere el archivo y se reenvíe al navegador del usuario final.
Estas llamadas representan la mayor parte del acceso a las funciones que se agregaron a la app. Esta es 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 subir el archivo y omitir, respectivamente.
- Actualiza el resultado de las visitas más recientes agregando un vínculo de "vista" 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 que se incluyan en 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, así que crea esa carpeta y mueve index.html dentro de ella. Con este último paso, ya realizaste todos los cambios necesarios para agregar el uso de Blobstore a la app de ejemplo del módulo 0.
"Mejora" de Cloud Storage (opcional)
Con el tiempo, el almacenamiento de Blobstore evolucionó hasta convertirse en Cloud Storage. Esto significa que las cargas de Blobstore son visibles en la consola de Cloud, específicamente en el navegador de Cloud Storage. La pregunta es dónde. La respuesta es el bucket de Cloud Storage predeterminado 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 proyecto 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 de forma programática al bucket predeterminado a través de google.appengine.api.app_identity.get_default_gcs_bucket_name(), lo que requiere una nueva importación si quieres acceder a este valor, por ejemplo, para usarlo como prefijo para organizar los archivos subidos. Por ejemplo, para ordenar por tipo de archivo, haz lo siguiente:

Para implementar algo similar en las imágenes, por ejemplo, tendrás un código como este junto con otro que verifique los tipos de archivo 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, es probable que quieras limitar el tamaño de las cargas en caso de que haya agentes maliciosos.
Supongamos que ya se hizo todo eso. ¿Cómo podemos actualizar nuestra app para que admita la especificación de dónde almacenar los archivos subidos? La clave es ajustar 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 quieres especificar dónde deben ir las cargas, no forma parte del archivo main.py en el repositorio. En su lugar, hay una alternativa llamada main-gcs.py disponible para que la revises en el repositorio. En lugar de usar una "carpeta" de bucket independiente, 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 quieres derivar la muestra en algo más, como se indica 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 concluye este 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 y considera los pasos siguientes.
Implementa y verifica la aplicación
Vuelve a implementar tu app con gcloud app deploy y confirma que funcione como se anuncia, 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 visitas:
Desde allí, los usuarios finales pueden subir un archivo y hacer clic en "Enviar" o en "Omitir" para no subir nada. En cualquier caso, el resultado es la pantalla de visitas más reciente, ahora aumentada con vínculos de "vista" o "ninguno" entre las marcas de tiempo de las visitas y la información de los visitantes:

Felicitaciones por completar este codelab y agregar 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 que se encuentra en la carpeta FINISH (módulo 15). La alternativa main-gcs.py también está presente en esa carpeta.
Limpia
General
Si terminaste por ahora, te recomendamos que inhabilites tu app de App Engine para evitar que se generen cargos de facturación. Sin embargo, si quieres realizar más pruebas o experimentos, la plataforma de App Engine tiene una cuota gratuita, por lo que, siempre que no excedas ese nivel de uso, no se te cobrará. Eso es para el procesamiento, pero también puede haber cargos por los servicios de App Engine pertinentes, así que consulta su página de precios para obtener más información. Si esta migración involucra otros servicios de Cloud, estos se facturan por separado. En cualquier caso, si corresponde, consulta la sección "Específico de este codelab" que aparece más abajo.
Para que tengas 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 consume 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 el uso del almacenamiento para minimizar los costos potenciales. Estas son algunas de las "carpetas" específicas de Cloud Storage que debes revisar:
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 de la ubicación de *LOC*, por ejemplo, "us" si tu app se aloja 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 el proyecto.
Específico de 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 Blobstore de App Engine se rige por las cuotas y los límites de datos almacenados, por lo que debes revisarlos, así como la página de precios de los servicios agrupados heredados.
- El servicio de 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 incluye poder 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, en 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 permanecer en Blobstore de App Engine.
Más allá 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 cualquier "orden" relevante de las migraciones.
7. Recursos adicionales
Problemas o comentarios sobre el codelab
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, se incluyen vínculos a las carpetas del repositorio para el módulo 0 (START) y el módulo 15 (FINISH). También se puede acceder a ellos desde el repositorio para todas las migraciones de codelabs de App Engine, que puedes clonar o descargar como 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 datos almacenados de App Engine
- Documentación de App Engine
- Entorno de ejecución de Python 2 de App Engine (entorno estándar)
- Usa bibliotecas integradas de App Engine en App Engine para Python 2
- Información sobre los precios y las cuotas de App Engine
- Lanzamiento de la plataforma de 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 gratuito" 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 de Django y Jinja2
- Framework web de
webapp2 - Documentación de
webapp2 webapp2_extrasvínculos- Documentación de Jinja2 de
webapp2_extras
Videos
- Serverless Migration Station
- Serverless Expeditions
- 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.