Entorno de ejecución de Cloud Functions

Cloud Functions trabaja en un entorno sin servidores completamente administrado en el que Google se encarga por ti 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:

Entorno de ejecución Versión del lenguaje Imagen base
Node.js 6 (obsoleto) Node.js 6.16.0 Debian 8
Node.js 8 Node.js 8.15.0 Ubuntu 18.04
Node.js 10 (Beta) Node.js 10.15.3 Ubuntu 18.04
Python Python 3.7.1 Ubuntu 18.04
Go Go 1.11.6 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 tu código sin preocuparte por la infraestructura subyacente, como los servidores o las máquinas virtuales. Para permitir que Google administre y escale automáticamente 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 reutilizarse como una optimización del rendimiento; consulta la recomendación en Sugerencias y trucos para obtener 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

// Global variable, but only shared within function instance.
let count = 0;

/**
 * HTTP Cloud Function that counts how many times
 * it is executed within a specific instance.
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
exports.executionCount = (req, res) => {
  count++;

  // Note: the total function invocation count across
  // all instances may not be equal to this value!
  res.send(`Instance execution count: ${count}`);
};

Python

# Global variable, modified within the function by using the global keyword.
count = 0

def statelessness(request):
    """
    HTTP Cloud Function that counts how many times it is executed
    within a specific instance.
    Args:
        request (flask.Request): The request object.
        <http://flask.pocoo.org/docs/1.0/api/#flask.Request>
    Returns:
        The response text, or any set of values that can be turned into a
        Response object using `make_response`
        <http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>.
    """
    global count
    count += 1

    # Note: the total function invocation count across
    # all instances may not be equal to this value!
    return 'Instance execution count: {}'.format(count)

Go


// Package http provides a set of HTTP Cloud Functions samples.
package http

import (
	"fmt"
	"net/http"
)

// count is a global variable, but only shared within a function instance.
var count = 0

// ExecutionCount is an HTTP Cloud Function that counts how many times it
// is executed within a specific instance.
func ExecutionCount(w http.ResponseWriter, r *http.Request) {
	count++

	// Note: the total function invocation count across
	// all instances may not be equal to this value!
	fmt.Fprintf(w, "Instance execution count: %d", count)
}

Si necesitas compartir estados en las invocaciones de función, tu función deberá usar un servicio como Cloud Datastore, Cloud 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 maneje muchas solicitudes en paralelo, cada una a través de una instancia diferente de la función.

Puesto que diferentes instancias de función procesan las 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 dado. 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 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 tu 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 normalmente es insignificante, a menos que la función falle con frecuencia y requiera el reinicio del entorno de la función. Consulta Errores para aprender cómo solucionar los errores correctamente 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 manejar 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 preparada 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 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

// Global (instance-wide) scope
// This computation runs at instance cold-start
const instanceVar = heavyComputation();

/**
 * HTTP function that declares a variable.
 *
 * @param {Object} req request context.
 * @param {Object} res response context.
 */
exports.scopeDemo = (req, res) => {
  // Per-function scope
  // This computation runs every time this function is called
  const functionVar = lightComputation();

  res.send(`Per instance: ${instanceVar}, per function: ${functionVar}`);
};

Python

# Global (instance-wide) scope
# This computation runs at instance cold-start
instance_var = heavy_computation()

def scope_demo(request):
    """
    HTTP Cloud Function that declares a variable.
    Args:
        request (flask.Request): The request object.
        <http://flask.pocoo.org/docs/1.0/api/#flask.Request>
    Returns:
        The response text, or any set of values that can be turned into a
        Response object using `make_response`
        <http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>.
    """

    # Per-function scope
    # This computation runs every time this function is called
    function_var = light_computation()
    return 'Instance: {}; function: {}'.format(instance_var, function_var)

Go


// h is in the global (instance-wide) scope.
var h string

// init runs during package initialization. So, this will only run during an
// an instance's cold start.
func init() {
	h = heavyComputation()
}

// ScopeDemo is an example of using globally and locally
// scoped variables in a function.
func ScopeDemo(w http.ResponseWriter, r *http.Request) {
	l := lightComputation()
	fmt.Fprintf(w, "Global: %q, Local: %q", h, l)
}

Puedes suponer que el alcance global se ejecutó exactamente 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 señalar el final de la ejecución de tu función correctamente y debes 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

/**
 * HTTP Cloud Function that may not completely
 * execute due to early HTTP response
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
exports.afterResponse = (req, res) => {
  res.end();

  // This statement may not execute
  console.log('Function complete!');
};

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 opciones para esa función a fin de reintentar en caso de error.

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 significa 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 Informa 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

/**
 * HTTP Cloud Function that may not completely
 * execute due to function execution timeout
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
exports.afterTimeout = (req, res) => {
  setTimeout(() => {
    // May not execute if function's timeout is <2 minutes
    console.log('Function running...');
    res.end();
  }, 120000); // 2 minute delay
};

Python

def timeout(request):
    print('Function running...')
    time.sleep(120)

    # May not execute if function's timeout is <2 minutes
    print('Function completed!')
    return 'Function completed!'

Go


// Package tips contains tips for writing Cloud Functions in Go.
package tips

import (
	"fmt"
	"log"
	"net/http"
	"time"
)

// Timeout sleeps for 2 minutes and may time out before finishing.
func Timeout(w http.ResponseWriter, r *http.Request) {
	log.Println("Function execution started...")
	time.Sleep(2 * time.Minute)
	log.Println("Function completed!")
	fmt.Fprintln(w, "Function completed!")
}

En algunas circunstancias, el código anterior puede ejecutarse exitosamente, 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:

  1. Establece un tiempo de espera superior al tiempo de ejecución esperado de la función.
  2. 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 el marcador --timeout en el momento de la implementación:

gcloud functions deploy FUNCTION_NAME --timeout=TIMEOUT FLAGS...

En el comando anterior, FLAGS... refiere a las otras opciones que pasas durante la implementación de la función. A fin de obtener una referencia completa para el comando de deploy, consulta gcloud functions deploy.

También puedes configurar el tiempo de espera durante la creación de la función en GCP Console con estos pasos:

  1. Ve a la página Descripción general de Cloud Functions en GCP Console.

  2. Haz clic en Crear función.

  3. Completa los campos obligatorios para la función.

  4. Haz clic en Más para ver la configuración avanzada.

  5. Ingresa un valor en el campo Tiempo de espera.

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

const fs = require('fs');

/**
 * HTTP Cloud Function that lists files in the function directory
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
exports.listFiles = (req, res) => {
  fs.readdir(__dirname, (err, files) => {
    if (err) {
      console.error(err);
      res.sendStatus(500);
    } else {
      console.log('Files', files);
      res.sendStatus(200);
    }
  });
};

Python

def list_files(request):
    import os
    from os import path

    root = path.dirname(path.abspath(__file__))
    children = os.listdir(root)
    files = [c for c in children if path.isfile(path.join(root, c))]
    return 'Files: {}'.format(files)

Go


// Package tips contains tips for writing Cloud Functions in Go.
package tips

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
)

// ListFiles lists the files in the current directory.
func ListFiles(w http.ResponseWriter, r *http.Request) {
	files, err := ioutil.ReadDir("./")
	if err != nil {
		http.Error(w, "Unable to read files", http.StatusInternalServerError)
		log.Printf("ioutil.ListFiles: %v", err)
		return
	}
	fmt.Fprintln(w, "Files:")
	for _, f := range files {
		fmt.Fprintf(w, "\t%v\n", f.Name())
	}
}

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, que 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

const request = require('request');

/**
 * HTTP Cloud Function that makes an HTTP request
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
exports.makeRequest = (req, res) => {
  // The URL to send the request to
  const url = 'https://example.com';

  request(url, (err, response) => {
    if (!err && response.statusCode === 200) {
      res.sendStatus(200);
    } else {
      res.sendStatus(500);
    }
  });
};

Python

def make_request(request):
    """
    HTTP Cloud Function that makes another HTTP request.
    Args:
        request (flask.Request): The request object.
        <http://flask.pocoo.org/docs/1.0/api/#flask.Request>
    Returns:
        The response text, or any set of values that can be turned into a
        Response object using `make_response`
        <http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>.
    """
    import requests

    # The URL to send the request to
    url = 'http://example.com'

    # Process the request
    response = requests.get(url)
    response.raise_for_status()
    return 'Success!'

Go


// Package http provides a set of HTTP Cloud Functions samples.
package http

import (
	"fmt"
	"net/http"
	"time"
)

var urlString = "https://example.com"

// client is used to make HTTP requests with a 10 second timeout.
// http.Clients should be reused instead of created as needed.
var client = &http.Client{
	Timeout: 10 * time.Second,
}

// MakeRequest is an example of making an HTTP request. MakeRequest uses a
// single http.Client for all requests to take advantage of connection
// pooling and caching. See https://godoc.org/net/http#Client.
func MakeRequest(w http.ResponseWriter, r *http.Request) {
	resp, err := client.Get(urlString)
	if err != nil {
		http.Error(w, "Error making request", http.StatusInternalServerError)
		return
	}
	if resp.StatusCode != http.StatusOK {
		msg := fmt.Sprintf("Bad StatusCode: %d", resp.StatusCode)
		http.Error(w, msg, http.StatusInternalServerError)
		return
	}
	fmt.Fprintf(w, "ok")
}

Intenta volver 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”. Tu 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 en todas las funciones implementadas, puedes usar los servicios de almacenamiento, como Cloud Datastore, Cloud Firestore o Cloud Storage. De manera alternativa, puedes invocar una función desde otra, con los activadores apropiados. Por ejemplo, haz una solicitud HTTP al extremo de una función de HTTP o publica un mensaje en un tema de Cloud Pub/Sub para activar una función de Cloud Pub/Sub.

¿Te ha resultado útil esta página? Enviar comentarios:

Enviar comentarios sobre...

Documentación de Cloud Functions