Cloud Functions se ejecuta en un entorno sin servidores y completamente administrado en el que Google se encarga de la infraestructura, los sistemas operativos y los entornos de ejecución. Cada función de Cloud Functions se ejecuta en un contexto propio, seguro y aislado, se escala automáticamente y cuenta con un ciclo de vida independiente de otras funciones.
Entornos de ejecución
Cloud Functions admite varios entornos de ejecución de lenguajes. Necesitarás el valor del ID del entorno de ejecución si implementas funcionesdesde la línea de comandos o medianteTerraform.
Entorno de ejecución | Imagen base | ID de entorno de ejecución |
---|---|---|
Node.js 6 (obsoleto) | Debian 8 | nodejs6 |
Node.js 8 (obsoleto) | Ubuntu 18.04 | nodejs8 |
Node.js 10 | Ubuntu 18.04 | nodejs10 |
Node.js 12 | Ubuntu 18.04 | nodejs12 |
Python 3.7 | Ubuntu 18.04 | python37 |
Python 3.8 | Ubuntu 18.04 | python38 |
Go 1.11 | Ubuntu 18.04 | go111 |
Go 1.13 | Ubuntu 18.04 | go113 |
Java 11 | Ubuntu 18.04 | java11 |
.NET Core 3.1 | Ubuntu 18.04 | dotnet3 |
Ruby 2.6 | Ubuntu 18.04 | |
Ruby 2.7 | Ubuntu 18.04 |
Las actualizaciones de los entornos de ejecución generalmente se realizan automáticamente, a menos que se notifique lo contrario. Todos los entornos de ejecución reciben actualizaciones automáticas en la versión del lenguaje a medida que están disponibles para la comunidad lingüística. De manera similar, Cloud Functions podría aplicar actualizaciones a otros aspectos del entorno de ejecución, como el sistema operativo o los paquetes incluidos. Estas actualizaciones ayudan a mantener tus funciones protegidas.
Funciones sin estado
Cloud Functions implementa el paradigma sin servidores, en el que solo ejecutas el código sin preocuparte por la infraestructura subyacente, como los servidores o las máquinas virtuales. Para permitir que Google administre y escale de forma automática las funciones, estas no deben tener estado; la invocación de una función no debe basarse en un estado de la memoria configurado por una invocación previa. Sin embargo, el estado existente a menudo puede volver a utilizarse como una optimización del rendimiento. Consulta la recomendación en la sección Sugerencias y trucos para obtener más detalles.
Por ejemplo, el valor del contador que muestra la siguiente función no corresponde al recuento total de invocación de funciones, ya que a estas podrían haberlas manejado instancias de función diferentes, que no comparten variables globales, memoria, sistemas de archivos ni otro estado:
Node.js
Python
Go
Java
C#
Ruby
Si necesitas compartir estados en las invocaciones de función, la función debe usar un servicio como Datastore, Firestore o Cloud Storage para conservar los datos. Para ver una lista completa de las opciones de almacenamiento disponibles, consulta Elige una opción de almacenamiento.
Ajuste de escala automático y simultaneidad
Cloud Functions maneja las solicitudes entrantes asignándolas a las instancias de tu función. Dependiendo del volumen de solicitudes, así como de la cantidad de instancias de funciones existentes, Cloud Functions puede asignar una solicitud a una instancia existente o crear una nueva.
Cada instancia de una función maneja solo una solicitud concurrente a la vez. Esto significa que mientras tu código procesa una solicitud, no hay posibilidad de que una segunda solicitud se enrute a la misma instancia. Por lo tanto, la solicitud original puede usar la cantidad total de recursos (CPU y memoria) que solicitaste.
En los casos en que el volumen de solicitudes entrantes exceda la cantidad de instancias existentes, Cloud Functions puede iniciar varias instancias nuevas para manejar las solicitudes. Este comportamiento de ajuste de escala automático permite que Cloud Functions controle muchas solicitudes en paralelo, cada una a través de una instancia diferente de la función.
Dado que diferentes instancias de función procesan solicitudes simultáneas, estas no comparten variables ni memoria local. Esto se analiza en detalle más adelante en este documento.
Controla el comportamiento del ajuste de escala automático
Cloud Functions te permite establecer un límite en la cantidad total de instancias de función que pueden coexistir en un momento determinado. En algunos casos, no se recomienda el escalamiento ilimitado. Por ejemplo, tu función puede depender de un recurso (como una base de datos) que no se puede escalar verticalmente al mismo nivel que Cloud Functions. Un gran aumento en el volumen de solicitudes podría provocar que Cloud Functions cree más instancias de función de las que puede tolerar la base de datos.
Inicios en frío
Una instancia de función nueva se inicia en dos casos:
Cuando implementas la función.
Cuando una instancia de función nueva se crea automáticamente para aumentar la escala hasta la carga o, de manera ocasional, a fin de reemplazar una instancia existente.
Iniciar una instancia de función nueva implica cargar el entorno de ejecución y tu código. Las solicitudes que incluyen el inicio de instancias de función (inicios en frío) pueden resultar más lentas que las que alcanzan instancias de función existentes. Sin embargo, si tu función recibe una carga constante, la cantidad de inicios en frío suele ser insignificante, a menos que la función falle con frecuencia y requiera el reinicio del entorno de la función. Consulta la sección Errores para obtener información sobre cómo solucionar los errores de forma correcta y evitar los inicios en frío.
Vida útil de la instancia de función
El entorno que ejecuta una instancia de función generalmente es resiliente y las invocaciones de función posteriores lo reutilizan, a menos que la cantidad de instancias se escale hacia abajo (debido a la falta de tráfico en curso) o la función falle. Esto significa que, cuando la ejecución de una función termina, la misma instancia de función puede controlar otra invocación de función. Por lo tanto, se recomienda almacenar en caché el estado en todas las invocaciones de alcance global cuando sea posible. La función aún debería estar lista para trabajar sin caché disponible, ya que no hay garantía de que la siguiente invocación alcance la misma instancia de función (consulta la sección Funciones sin estado).
Alcance de la función en comparación con alcance global
La invocación de una sola función da como resultado la ejecución del cuerpo de la función declarada solamente como el punto de entrada. El alcance global en el archivo de función, que se espera que contenga su definición, se ejecuta en cada inicio en frío, pero no si la instancia ya se inicializó.
Node.js
Python
Go
Java
Puedes suponer que el alcance global se ejecutó solo una vez antes de que se invocara el código de la función en una instancia de función nueva (y en cada creación posterior de una instancia de función nueva). Sin embargo, no deberías depender de la cantidad total de ejecuciones de alcance global o de su tiempo, ya que estas dependen del ajuste de escala automático que administra Google.
Cronograma de ejecución de funciones
Una función tiene acceso a los recursos solicitados (CPU y memoria) solo por el tiempo que dura la ejecución de la función. El código que se ejecuta fuera del período de ejecución no tiene garantía de ejecutarse y puede detenerse en cualquier momento. Por lo tanto, siempre debes indicar el final de la ejecución de tu función de forma correcta y evitar ejecutar cualquier otro código. Consulta Funciones de HTTP y Funciones en segundo plano para obtener asesoramiento.
Por ejemplo, el código ejecutado después de enviar la respuesta de HTTP podría sufrir una interrupción en cualquier momento:
Node.js
Es importante tener en cuenta el cronograma de ejecución cuando inicialices la aplicación. Las tareas en segundo plano no deben crearse en el alcance global durante la inicialización, ya que se ejecutan fuera de la duración de una solicitud.
Garantías de ejecución
Por lo general, las funciones se invocan una vez por cada evento entrante. Sin embargo, Cloud Functions no garantiza una sola invocación en todos los casos debido a las diferencias en las situaciones de error.
La cantidad máxima o mínima de veces que tu función se invocará en un solo evento depende del tipo de función.
Las funciones de HTTP se invocan máximo una vez. Esto se debe a la naturaleza síncrona de las llamadas a HTTP y quiere decir que se mostrará cualquier error en el manejo de la invocación de función sin reintentar el proceso. Se espera que el emisor de una función de HTTP corrija los errores y vuelva a intentar el proceso si es necesario.
Las funciones en segundo plano se invocan mínimo una vez. Esto se debe a su naturaleza asíncrona del manejo de eventos, en la que no hay ningún emisor que espere por la respuesta. En extrañas circunstancias, el sistema podría invocar una función más de una vez con el fin de asegurar el envío del evento. Si la invocación de una función en segundo plano falla con un error, no se invocará de nuevo, a menos que se habiliten los reintentos en caso de error para la función.
Para asegurarte de que tu función se comporta correctamente en los demás intentos de ejecución, debes hacerla idempotente mediante su implementación, de manera que un evento resulte como se espera (así como los efectos secundarios) incluso si se envía varias veces. En el caso de las funciones de HTTP, esto también implica mostrar el valor deseado, incluso si el emisor vuelve a intentar las llamadas al extremo de la función de HTTP. Consulta Reintenta las funciones en segundo plano para obtener más información sobre cómo hacer que tu función sea idempotente.
Errores
La forma recomendada para que una función indique un error depende del tipo de función, como se explica a continuación:
Las funciones de HTTP deben mostrar códigos de estado HTTP correctos que denoten un error. Consulta Funciones de HTTP para ver ejemplos.
Las funciones en segundo plano deben registrar y mostrar un mensaje de error. Consulta Funciones en segundo plano para ver ejemplos.
Si se muestra un error de la manera recomendada, la instancia de función que mostró el error se etiqueta como que tiene un comportamiento normal y puede entregar solicitudes futuras si se necesita.
Si tu código o cualquier otro código que llames arroja una excepción no detectada o hace que falle el proceso actual, la instancia de función puede reiniciarse antes de manejar la siguiente invocación. Esto puede generar más inicios en frío, lo que da como resultado una latencia más alta, y no es recomendable.
Consulta Genera informes de errores para ver más análisis de cómo informar errores en Cloud Functions.
Tiempo de espera
El tiempo de ejecución de la función está limitado por la duración del tiempo de espera, que puedes especificar en el momento de implementar la función. Según la configuración predeterminada, una función agota el tiempo de espera después de 1 minuto, pero puedes extender este período hasta 9 minutos.
Cuando la ejecución de la función excede el tiempo de espera, se muestra inmediatamente un estado de error a la persona que llama. Los recursos de la CPU utilizados por la instancia de la función que agotó el tiempo de espera están limitados y el procesamiento de la solicitud puede pausarse inmediatamente. El trabajo en pausa puede o no proceder con solicitudes posteriores, lo que puede causar efectos secundarios inesperados.
El fragmento que se encuentra a continuación incluye un código que está programado para ejecutarse 2 minutos después de que comience la ejecución de la función. Si el tiempo de espera se configuró en 1 minuto, es posible que este código nunca se ejecute:
Node.js
Python
Go
Java
En algunas circunstancias, el código anterior puede ejecutarse con éxito, pero de una manera inesperada. Considera la situación cuando la función agote el tiempo de espera. La instancia que entrega la solicitud se pausa (a partir de la limitación de la CPU). El trabajo pendiente está en pausa. Si una solicitud posterior se enruta a la misma instancia, el trabajo se reanuda y se emite Function running...
a los registros.
Un síntoma común de este comportamiento es la apariencia de que el trabajo y los registros de una solicitud se “filtran” a una solicitud posterior. Debido a que no puedes depender de que se reanude el trabajo en pausa, no debes confiar en este comportamiento. Por el contrario, tu función debe evitar los tiempos de espera mediante una combinación de las siguientes técnicas:
- Establece un tiempo de espera superior al tiempo de ejecución esperado de la función.
- Realiza un seguimiento de la cantidad de tiempo restante durante la ejecución y realiza la limpieza o la salida antes.
Para establecer el tiempo máximo de ejecución de una función con la herramienta de línea de comandos de gcloud
, usa la marca --timeout
en el momento de la implementación:
gcloud functions deploy FUNCTION_NAME --timeout=TIMEOUT FLAGS...
En el comando anterior, FLAGS...
hace referencia a otras opciones que pasas durante la implementación de la función. Para obtener una referencia completa del comando deploy
, consulta gcloud functions deploy
.
También puedes establecer el tiempo de espera durante la creación de la función en Cloud Console, de la siguiente manera:
Ve a la página Descripción general de Cloud Functions en Cloud Console.
Haz clic en Crear función.
Completa los campos obligatorios para la función.
Haz clic en Más para ver la configuración avanzada.
Ingresa un valor en el campo Tiempo de espera.
Memoria
Para configurar la memoria de una función mediante la herramienta de línea de comandos gcloud
, usa la marca --memory
con la cantidad de megabytes (128
, 256
, 512
, 1024
, 2048
, 4096
). Por ejemplo:
gcloud functions deploy FUNCTION_NAME --memory=MEMORY
De forma predeterminada, la memoria asignada a cada función es de 256 MB.
Sistema de archivos
El entorno de ejecución de funciones contiene un archivo ejecutable de función, además de archivos y directorios incluidos en el paquete de la función implementada, como las dependencias locales. Estos archivos están disponibles en un directorio de solo lectura que se puede determinar con base en la ubicación del archivo de función. Ten en cuenta que el directorio de la función puede ser diferente del directorio de trabajo actual.
El siguiente ejemplo presenta una lista de funciones ubicadas en el directorio de funciones:
Node.js
Python
Go
Java
C#
También puedes cargar código desde otros archivos implementados con la función.
La única parte editable del sistema de archivos es el directorio /tmp
, el cual puedes utilizar para almacenar archivos temporales en una instancia de función. Se trata de un punto de activación de disco local, conocido como un volumen “tmpfs”, en el que los datos escritos se almacenan en la memoria. Ten en cuenta que este proceso consumirá recursos de memoria aprovisionados para la función.
El resto del sistema de archivos es de solo lectura y accesible para la función.
Red
Tu función puede acceder a la red pública de Internet con las bibliotecas estándar que ofrece el entorno de ejecución o los proveedores de terceros. Por ejemplo, puedes llamar a un extremo HTTP como se muestra abajo:
Node.js
Python
Go
Java
C#
Ruby
Vuelve a usar las conexiones de red en las invocaciones de funciones, como se describe en Optimiza las Herramientas de redes. Sin embargo, ten en cuenta que el sistema podría cerrar una conexión que permanece 2 minutos sin usarse y que los intentos adicionales para usar una conexión cerrada podrían dar como resultado un error de “restablecimiento de la conexión”. El código debe usar una biblioteca que administre bien las conexiones cerradas o que las administre de manera explícita si usa construcciones de herramientas de redes de nivel inferior.
Varias funciones
Cada función implementada se aísla de todas las demás funciones, incluso de aquellas implementadas desde el mismo archivo fuente. En particular, no comparten memoria, variables globales, sistemas de archivos ni otros estados.
Para compartir datos entre las funciones implementadas, puedes usar servicios de almacenamiento como Datastore, Firestore o Cloud Storage. De manera alternativa, puedes invocar una función desde otra, con los activadores apropiados. Por ejemplo, realiza una solicitud HTTP al extremo de una función de HTTP o publica un mensaje en un tema de Pub/Sub para activar una función de Pub/Sub.