Serverless y Edge Runtime

Compartir esta publicación

Este es el post introductorio de una serie de dos partes, explorando el mundo de Serverless y Edge Runtime. El enfoque principal de este post será Serverless, mientras que el segundo se centrará en un enfoque alternativo y más reciente en forma de Edge Computing. La segunda parte de este artículo estará pronto disponible en el blog de Apiumhub.

Entendiendo el paradigma serveless

Probablemente sea mejor empezar intentando definir de qué se está hablando exactamente cuando se utiliza el término «serverless». Con serverless, nosotros como desarrolladores ya no somos responsables de aprovisionar y mantener los servidores subyacentes para nuestras aplicaciones, de ahí el nombre server-less. Viniendo de un entorno más tradicional, esto puede parecer una idea descabellada. Si no somos responsables de gestionar nuestros servidores, ¿cómo se servirían nuestras API a nuestros clientes? Por supuesto, es sólo una elección discutible de una palabra: todavía hay servidores bajo el capó, pero estos ya no son creados, escalados, ni mantenidos por nosotros, los desarrolladores. En una arquitectura sin servidor, nuestro proveedor en la nube crea nuevas instancias de un servidor cada vez que recibe una solicitud.

De hecho, cada solicitud tiene su propio servidor.

Estas instancias (normalmente) se eliminan poco después de que se sirva la solicitud.

Se trata de un cambio de paradigma bastante radical: hace hincapié en la importancia de que los desarrolladores se ocupen de los problemas de negocio que tienen entre manos, en lugar de preocuparse por la infraestructura de servidores. Ya no tenemos que pensar en cuántos servidores tenemos, dónde y cuál atiende cada petición. No tenemos que tratar de hacer suficientes servidores para facilitar las peticiones, sino que las peticiones «hacen» servidores por sí mismas. Luego, estos servidores se reducen a la nada absoluta, con lo que sólo pagamos por la pequeña cantidad de computación que hemos utilizado. Incluso a escala masiva, este enfoque puede ser a menudo menos costoso que poseer y gestionar nuestros propios servidores persistentes.

serverless 1 light

Al igual que con la mayoría de los aspectos de la ingeniería de software, la decisión de optar por la tecnología sin servidor depende de tus circunstancias. Si eres un desarrollador independiente o una startup, con unos pocos cientos de usuarios que pueden llamar ocasionalmente a una de tus APIs, podría ser una decisión obvia. Podrías acabar con una factura mensual de céntimos mientras proporcionas una experiencia similar a tus usuarios que con un VPS más tradicional. El caso en cuestión me recuerda a la reciente charla de Brandon Minnick en NDC Oslo, en la que explica cómo aloja los backends de sus aplicaciones móviles personales en AWS Lambda (una opción extremadamente popular para alojar funciones sin servidor), por menos del precio de un solo café, cada mes.

Sin embargo, si usted ha estado construyendo monolitos, con innumerables puntos finales de API, esto todavía podría parecer una locura – sin duda, si miles de usuarios por minuto están golpeando cientos de APIs, ¡el coste de esto sería inimaginable! Esta es una preocupación justa – el post que estás leyendo en este momento fue escrito pocas semanas después de que Amazon Prime Video saliera con un post impresionantemente transparente propio, en el que detallan cómo migraron una de sus soluciones de una arquitectura distribuida, sin servidor a un monolito clásico, reduciendo su coste en un 90%.

Abordemos ahora este punto.

Escalabilidad

Por supuesto, la tecnología sin servidor no es solo para proyectos pequeños. En retrospectiva, el enorme proyecto de Amazon Prime no se benefició de su solución sin servidor, debido a sus cuellos de botella únicos relacionados con el procesamiento de cada fotograma de cada vídeo esencialmente dos veces, pero esto no significa que las soluciones sin servidor no escalen. Puesto que no estamos gestionando las instancias de los servidores, cuando el tráfico que llega a nuestras funciones sin servidor aumenta, nuestro proveedor de nube autoescalará nuestra función para facilitar la mayor carga. Y viceversa, cuando el tráfico disminuye, lo reducen, incluso hasta que no funciona en absoluto, por lo que no cobran a nuestra cuenta según el modelo de pago por uso. Con los servidores gestionados tradicionales, esto podría ser un poco más difícil de replicar, especialmente si el autoescalado no está disponible.

  Crecimiento de la deuda técnica: ¿Cómo puede ocurrir sin darte cuenta?

Evitar facturas inesperadas por la autoescala

Dado que nuestra solución sin servidor se autoescala en función de la cantidad de tráfico que recibe, es justo preguntarse qué sucede si una gran cantidad inesperada de solicitudes comienza a golpear nuestras funciones, en el peor de los casos con intenciones maliciosas, tratando de DDoS nuestro sistema. Con el modelo de pago por uso, esto podría suponer una factura bastante abultada a final de mes. A principios de 2023, hubo un gran debate sobre este tema en el Twitter de tecnología, ya que varios productos de código abierto, probablemente el más notable ping.gg, una startup de videollamadas en línea, estaba recibiendo 1 TB de tráfico en una ventana de 20 minutos. Al fin y al cabo, con una limitación de costes razonable, alertas de presupuesto y límites de cuota, nuestras facturas no se dispararán, ya que la mayoría de los proveedores de nube ofrecen estas funciones. Los limitadores de tarifa y el estrangulamiento también son enfoques que podrían ser útiles para evitar situaciones como ésta. Por supuesto, hay (y probablemente siempre habrá) historias de terror de facturas inesperadamente altas, pero si se tienen en cuenta los pasos antes mencionados, el despliegue de un sistema sin servidor no debería ser más aterrador que el aprovisionamiento de un servidor estándar.

Resiliencia y seguridad

Vale la pena mencionar la tolerancia automática a fallos que ofrecen la mayoría de los proveedores de servicios sin servidor: por lo general, existe un mecanismo integrado para fallos o problemas. Dado que nuestra aplicación está distribuida en varios servidores, en caso de que un servidor específico tenga problemas operativos, nuestro proveedor de nube migra nuestra aplicación a un servidor en buen estado. Esto no suele requerir ninguna intervención manual por nuestra parte, lo que aumenta la fiabilidad de nuestros servicios.

Otra característica incorporada suelen ser las mejores prácticas de seguridad listas para usar establecidas para los servidores. El proveedor sin servidor se encarga de la seguridad de la infraestructura (por ejemplo, parches, supervisión, detección de intrusiones). Los proveedores también suelen ofrecer mecanismos de control de acceso y autorización de granularidad fina, lo que nos permite definir una matriz de permisos más granular para nuestras funciones. Esto suele simplificar la gestión de la seguridad por parte del desarrollador, reduciendo así el riesgo de errores de configuración o de introducción de vulnerabilidades por accidente o falta de familiaridad con la seguridad (que es todo un campo en sí mismo).

Si bien estos dos puntos son bastante atractivos, pueden implicar la dependencia de un proveedor: dependemos en gran medida de la infraestructura, las formas de trabajar, las API, los modelos de despliegue, las convenciones, etc. de nuestro proveedor de nube. Si decidimos cambiar de proveedor, puede acabar siendo una migración bastante dolorosa, por lo que es importante tener en cuenta el impacto potencial en la portabilidad de nuestra aplicación y la viabilidad a largo plazo del proveedor elegido. En el asombroso libro Building Evolutionary Architectures, esto se señala como un antipatrón, conocido como Vendor King: «una arquitectura construida enteramente alrededor de un producto de un proveedor que acopla patológicamente la organización a una herramienta».

Arranque en frío

A partir de 2023, sin duda, la mayor consideración a tener en cuenta sobre esta arquitectura es el infame arranque en frío: cada vez que se recibe una petición, se pone en marcha una nueva instancia de nuestra función. Obviamente, esto lleva tiempo. Si nos conectamos a una base de datos dentro de nuestra función, establecer la conexión SSL/TLS podría añadir un tiempo considerable. Haciendo referencia de nuevo a la presentación de Brandon Minnick, señaló las siguientes cifras, para arquitecturas ARM64 en .NET:

  • Percentil 50 para:
  • .NET 6: 873ms
  • .NET 7: 372ms
  • Percentil 90 para:
  • .NET 6: 909ms
  • .NET 7: 435ms
  El principio No te repitas ( DRY )

La mayoría de los proveedores de nube ofrecen una solución de «arranque en caliente», para mitigar este problema: después de que una instancia de nuestra función se pone en marcha, se queda unos minutos (normalmente 10-15), para servir cualquier otra solicitud entrante después. Esto elimina el tiempo de arranque para las solicitudes de seguimiento, ya que no hay necesidad de aprovisionar una nueva instancia del servidor, ni de establecer la conexión SSL/TLS a una base de datos, ya que podemos «reutilizar» el servidor anterior. Usando las estadísticas de Brandon, para arranques en caliente, los números cambian como:

  • Percentil 50 para:
  • .NET 6: 5.5ms
  • .NET 7: 6.7ms
  • Percentil 90 para:
  • .NET 6: 9.2ms
  • .NET 7: 12.5ms

Sin estado

También tenemos que tener en cuenta el atributo implícito de ser sin estado, cuando nuestros servidores son de corta duración. Simplemente no podemos persistir en el estado, ya que nuestros servidores se caerán en cuestión de minutos. Sin embargo, esta es la clave que permite que nuestra solución sea efímera y también escalable y resistente, sin un punto claro de fallo. Piensa en las fugas de memoria: en mi experiencia, siempre acaban siendo un dolor de cabeza, ya que es difícil localizar una causa raíz clara. En uno de mis proyectos anteriores, no serverless, incluso decidimos reiniciar nuestro servidor backend persistente cada noche, para evitar una fuga de memoria imposible de rastrear. Con las funciones sin servidor, simplemente no necesitamos preocuparnos tanto por las posibles fugas de memoria, si nuestros servidores mueren después de servir una sola solicitud.

Por supuesto, un estado todavía puede (y debe) ser persistido, donde la mayoría de las veces una base de datos es una opción sensata – teniendo en cuenta que no podemos leer o escribir desde el sistema de archivos en una función sin servidor.

¿Qué no se puede hacer?

Hay algunos casos de uso obvios en los que serverless claramente no es la respuesta:

  • Cualquier proceso de larga duración: La mayoría de los proveedores de la nube limitan el tiempo total de ejecución de nuestras funciones (a unos 10 minutos).
  • Websockets: Dado que también son de larga ejecución y con estado, su uso en un entorno sin servidor puede ser complicado. Existen soluciones, como AWS API Gateway, pero hay limitaciones, por ejemplo, una duración de conexión de 2 horas.
  • Cualquier cosa que requiera ser localmente stateful.
  • Sistema/sistema de archivos IO.
  • Transacciones: a través de muchas funciones diferentes, asegurándose de que el ámbito de una sola transacción se delega por completo a la persona que llama a la función. Es posible hacerlo, pero resulta complicado.

Construir y desplegar una función sencilla sin servidor

La mayoría de los principales proveedores de nube como AWS, Azure, Google Cloud y Cloudflare ofrecen su propia versión de un producto sin servidor. En mi experiencia, el uso de Vercel tiende a ser el más simple, ya que la empresa pone mucho esfuerzo en la creación de una experiencia de desarrollador increíble. En esta rápida demostración, construiremos una simple función Go sin servidor y la desplegaremos en Vercel. La función esperará una carga JSON con un esquema predefinido del cuerpo de una solicitud HTTP POST y la convertirá en un archivo CSV, luego lo devolverá a la persona que llama de forma sincrónica.

Primero tendremos que instalar la CLI de Vercel (npm i -g vercel), luego crear una nueva carpeta, con una carpeta «api» en ella, donde podemos colocar nuestros archivos .go. Si creamos un archivo index.go, la API servirá peticiones para http://localhost:3000/api. Con Vercel, nuestras funciones se basan en rutas, así que dada la siguiente estructura de carpetas:

/api/index.go
/api/weather/index.go
/api/weather/forecast.go

Tendríamos tres APIs:

  1. http://localhost:3000/api
  2. http://localhost:3000/api/weather
  3. http://localhost:3000/api/weather/forecast
  Scala Generics I: Clases genéricas y Type bounds

Siguiendo la plantilla de Vercel para Go, vamos primero a añadir lo siguiente a nuestro index.go bajo la carpeta raíz /api:

package handler

import (
    "fmt"
    "net/http"
)

func Handler(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintf(w, "<h1>Hello from Go!</h1>")
}

Para ejecutarlo localmente, utilizando la CLI de Vercel, podemos ejecutarlo como: vercel dev

Después de asegurarnos de que la cadena ‘Hello from Go!’ es devuelta por http://localhost:3000/api, podemos pensar en desplegarla. Una vez más, utilizando la CLI de Vercel, podemos escribir vercel deploy para empujar nuestros cambios a un entorno de ensayo, y luego vercel --prod a un entorno de producción. La CLI responderá con una URL única a nuestro proyecto, donde podemos probar nuestra función sin servidor, desplegada en la Internet pública.

Ahora que estamos un poco más seguros, volvamos a nuestro ejemplo, dada la siguiente carga útil JSON que esperamos que nuestros clientes proporcionen en el cuerpo de una solicitud POST:

{
    "name": string,
    "age": number,
    "jobTitle": string,
    "badgeNumber": number
}[]

…nuestra función Go sin servidor devolvería un archivo CSV, donde cada fila es un elemento del array. Tenemos que asegurarnos de no cambiar la firma del método de la función de ejemplo proporcionada por Vercel, pero podemos importar libremente varios paquetes, y hacer nuestra lógica personalizada en la función Handler, de la siguiente manera:

package handler

import (
    "encoding/csv"
    "encoding/json"
    "fmt"
    "net/http"
)

// this is the incoming JSON payload's schema
type EmployeeData struct {
    Name        string `json:"name"`
    Age         int    `json:"age"`
    JobTitle    string `json:"jobTitle"`
    BadgeNumber int    `json:"badgeNumber"`
}

func Handler(w http.ResponseWriter, r *http.Request) {

    // we make sure only POST requests are served
    if r.Method != http.MethodPost {
        http.Error(w, "Only POST requests are allowed.", http.StatusMethodNotAllowed)
        return
    }

    w.Header().Set("Content-Type", "text/csv")
    w.Header().Set("Content-Disposition", "attachment; filename=data.csv")

    // parse input JSON
    var employees []EmployeeData
    err := json.NewDecoder(r.Body).Decode(&employees)
    if err != nil {
        http.Error(w, "Could not parse the JSON payload.", http.StatusBadRequest)
        return
    }

    // create CSV result
    writer := csv.NewWriter(w)
    writer.Write([]string{"Name", "Age", "Job Title", "Badge Number"})

    for _, employee := range employees {
        row := []string{employee.Name, fmt.Sprintf("%d", employee.Age), employee.JobTitle, fmt.Sprintf("%d", employee.BadgeNumber)}
        err := writer.Write(row)
        if err != nil {
            http.Error(w, "Could not write CSV data.", http.StatusInternalServerError)
            return
        }
    }

    writer.Flush()

    if err := writer.Error(); err != nil {
        http.Error(w, "Could not complete writing CSV data", http.StatusInternalServerError)
        return
    }
}

Podemos probarlo localmente a través de vercel dev de nuevo, a continuación, vercel deploy – mi URL particular terminó siendo https://vercel-serverless-go-fawn.vercel.app, por lo tanto, si usted decide ejecutar el siguiente curl, usted será capaz de convertir los datos de los empleados a un CSV – e incluso podría tener la suerte de experimentar el arranque en frío de la pequeña aplicación Go de arriba (que tiende a ser ~ 100ms).

curl --location 'https://vercel-serverless-go-fawn.vercel.app/api' \
--header 'Content-Type: application/json' \
--data '[
    {
        "name": "John Doe",
        "age": 45,
        "jobTitle": "Software Developer",
        "badgeNumber": 58195
    },
    {
        "name": "Jane Doe",
        "age": 32,
        "jobTitle": "Software Developer",
        "badgeNumber": 58191
    }
]'

Referencias


Author

  • Daniel Agg

    Full stack developer, with 6+ years of experience, building back-, and frontends with various technologies, designing and developing scalable infrastructures on Microsoft Azure.

    Ver todas las entradas

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Suscríbete a nuestro boletín de noticias

Recibe actualizaciones de los últimos descubrimientos tecnológicos

¿Tienes un proyecto desafiante?

Podemos trabajar juntos

apiumhub software development projects barcelona
Secured By miniOrange