Azure Functions: el recorrido

Publicado el 27 abril, 2016

El autor de esta entrada de blog es Mathew Charles, ingeniero de software principal, Microsoft.

Nuestro equipo estaba entusiasmado con el reciente lanzamiento de una versión preliminar del nuevo servicio Azure Functions en //build. Ya hemos publicado algunos blogs sobre el servicio, como Introducing Azure Functions (Presentación de Azure Functions), pero en esta entrada nos gustaría profundizar un poco en los entresijos para hablar sobre cómo empezó el proyecto y el recorrido que hemos realizado hasta hoy. Abordaremos el Sistema en ejecución de Functions, la capa de proceso dinámico (“sin servidor”) y el portal de Functions y, a un nivel profesional, analizaremos la forma en que estos componentes evolucionaron hasta fusionarse en un producto cohesivo. Ha sido una experiencia divertida para el equipo, y no ha hecho más que empezar.

La evolución de este proyecto es un magnífico ejemplo de la identificación de sinergias entre un montón de componentes existentes en la plataforma y su fusión en una nueva oferta de producto. En Azure App Service, ya disponíamos de muchos de los bloques de compilación para poder poner en práctica con bastante rapidez la visión sobre Azure Functions. Con el uso de todos estos recursos disponibles y la aportación de nuevas innovaciones y funcionalidades, pudimos materializar el proyecto bastante rápido.

SDK de WebJobs

En la ponencia de Chris en //build Introducing Azure Functions (Presentación de Azure Functions), explicó por qué Azure Functions se basa en el SDK de Azure WebJobs. El SDK de WebJobs existe desde hace un par de años, y tenemos muchos clientes satisfechos con su uso para compilar trabajos de procesamiento de back-end que se desencadenan en una amplia variedad de orígenes de eventos. El SDK de WebJobs tiene un modelo de programación declarativa sencillo que facilita bastante la escritura de funciones de trabajos sofisticados con una cantidad mínima de código. Veamos un ejemplo:

public static void ProcessOrder(
    [QueueTrigger(“orders”)] Order order,
    [Blob(“processed/{Id}”)] out string receipt,
TraceWriter log)
{
log.Verbose(string.Format(“Processing Order {0}”, order.Id));

    // business logic

    receipt = “<some value>”;
}

Si se hospeda en el JobHost del SDK de WebJobs en una aplicación de consola de .NET convencional, esta función se desencadenará automáticamente cada vez que se agregue un nuevo mensaje de cola a los “pedidos” de la cola de Azure, y la carga útil de la cola se deserializará en la instancia de Order POCO. La función también se enlaza automáticamente a un blob de salida con la propiedad “Id.” del mensaje de entrada como parte de la ruta de acceso al blob. Con este modelo de programación, la función del trabajo solo se centra en su lógica de negocios y no tiene que preocuparse por ninguna de las operaciones de almacenamiento. Increíble.

El modelo de hospedaje de dichas funciones con el SDK de WebJobs consiste en implementarlas como instancias de Azure WebJobs. Esto funciona perfectamente y aporta mucha flexibilidad; además, sigue siendo una característica muy popular de Azure App Service.

Sistema en de ejecución de Functions

A mediados del año pasado, empezamos a hablar sobre lo que conllevaría incorporar este sencillo modelo de programación en otros lenguajes, algo que también nos pidieron los clientes. No todo el mundo es programador de C# en .NET, pero a muchos les gustaría usar estos patrones del SDK de WebJobs. Empezamos a trabajar en la creación de prototipos para conseguirlo y logramos crear un modelo que nos permitiera utilizar el SDK de WebJobs probado existente en .NET en tiempo de ejecución, con aplicación en un nuevo modelo de descripción JSON para los metadatos. Como resultado, puede escribir la misma función anterior en Node.js o en otros lenguajes:

module.exports = function (context, order) {
context.log(‘Processing order’, order.id);

    // business logic

context.bindings.receipt = “<some value>”;
context.done();
}

Observará que la estructura de esta función es exactamente la misma que la de la función de C# anterior. Eso se debe a que se asigna a la misma implementación en tiempo de ejecución. Los atributos de código declarativos son solo una forma de especificar metadatos. Descubrimos que podíamos capturar la misma información en un archivo de descripción JSON sencillo. A continuación, se presenta el archivo de matadatos correspondiente en el que se describen los enlaces para esta función (es decir, todos los bits que se encuentran en los atributos declarativos del ejemplo de C#):

{
  “bindings”: [
    {
      “type”: “queueTrigger”,
      “name”: “order”,
      “direction”: “in”,
      “queueName”: “orders”
    },
    {
      “type”: “blob”,
      “name”: “receipt”,
      “direction”: “out”,
      “path”: “processed/{id}”
    }
  ]
}

La idea principal es que podemos usar estos metadatos para generar un adaptador en memoria entre varios lenguajes y el SDK de WebJobs de .NET en tiempo de ejecución. Generamos de forma eficaz la función de C# presentada anteriormente, y el cuerpo del mensaje de dicha función solo se delega en la función de usuario real (es decir, la función de Node.js que escribió). Una función de Azure puede ser simplemente un archivo de metadatos function.json sencillo en el que se describen los enlaces de la función, junto con una colección de uno o varios archivos de script que implementan la función. A continuación, se muestra el mismo ejemplo presentado anteriormente, usando el mismo archivo de metadatos, con la función escrita como un archivo BAT de Windows:

SET /p order=<%order%
echo Processing order ‘%order%’
echo ‘<some value>’ > %receipt%

Se puede usar el mismo archivo de metadatos para describir una función en cualquiera de nuestros siete lenguajes compatibles. Evidentemente, cada lenguaje tiene sus propias peculiaridades y funcionalidades, y algunos son más convenientes que otros para varias tareas. La cuestión fundamental aquí es que podemos tener el mismo tiempo de ejecución de desencadenador/enlace para todos estos lenguajes, de tal forma que se permite a cada lenguaje realizar una asignación a dicho modelo a su manera. Los archivos BAT tienen algunas limitaciones, pero, con las variables de entorno y las secuencias de archivos, pueden recibir entradas y escribir salidas, que el Sistema en ejecución de Functions asigna automáticamente a los artefactos de Azure Storage subyacentes.

Basar Azure Functions en el SDK de WebJobs principal conlleva que no tengamos que escribir y mantener diferentes versiones del SDK de WebJobs por cada lenguaje, algo que es un gran logro en ingeniería. Tenemos un único tiempo de ejecución principal que controla toda la lógica de enlace/desencadenador, así como las inversiones que realizamos en las principales funciones beneficiosas y todos nuestros clientes del SDK de WebJobs. Esto también supone que todas las extensiones de enlace/desencadenador que los usuarios escriben para el SDK principal también puedan usarse en Functions. Continuaremos realizando grandes inversiones en las extensiones y el SDK de WebJobs principales para nuestros clientes tradicionales y para Azure Functions.

Compatibilidad con webhooks

Otro ámbito importante en el que empezamos a centrarnos fue nuestra historia con los webhooks. La posibilidad de que las funciones se desencadenen en los eventos de Azure Storage resulta increíble, pero algunos clientes de WebJobs nos pidieron que incorporáramos la capacidad de desencadenar las funciones de sus trabajos también mediante solicitudes de webhooks. Ya habíamos experimentado con esta opción durante el último año mediante la escritura de una extensión de webhooks que funcionaba bien, pero tenía un gran inconveniente derivado del hecho de que WebJobs se ejecuta en el sitio de Kudu SCM, lo que supone que las credenciales de autenticación resulten necesarias para realizar solicitudes. Se trata de un factor decisivo para la mayoría de los escenarios de integración de webhooks, ya que desea tener la posibilidad de distribuir una dirección URL con un sencillo código de autenticación que tenga la restricción de que solo se pueda acceder a dicho punto de conexión.

Para abordar esta cuestión, decidimos empaquetar el Sistema en ejecución de Functions como una extensión de sitio que se ejecuta en la raíz de una aplicación web. Esto significa que NO se encuentra detrás del punto de conexión de SCM, lo que nos permite conseguir los patrones de autenticación necesarios. Gracias a ello, pudimos exponer un conjunto sencillo de puntos de conexión autenticados para las funciones de webhooks. También integramos aquí la biblioteca de webhooks de ASP.NET, a fin de poder recurrir al amplio abanico de proveedores de webhooks que dicha biblioteca admite, lo que nos permite contar con compatibilidad de primera clase para proveedores como GitHub, Slack, DropBox, Instagram, etc.

Por tanto, en este punto, contábamos con un Sistema en ejecución de Functions flexible que era compatible con el modelo completo de enlace/desencadenador del SDK de WebJobs para siete lenguajes (Node.js, C#, F#, Bash, BAT, Python y PHP), que también disponía de un atributo HTTP HEAD compatible con un amplio conjunto de escenarios de integración de webhooks.

Proceso dinámico

En paralelo con el trabajo del sistema en tiempo de ejecución anterior, también mantuvimos conversaciones sobre la informática sin servidor y lo que queríamos hacer en ese ámbito. Nos dimos cuenta de que este trabajo que estábamos haciendo para WebJobs era bastante sinérgico. Estábamos desarrollando un entorno de tiempo de ejecución para las funciones flexible y para varios lenguajes en el que se pudiera ejecutar código en un entorno aislado a gran escala. Sin embargo, el modelo de WebJobs tradicional requiere que los usuarios creen y administren el host de la aplicación web en el que dichos WebJobs se ejecutan. ¿Qué pasaría si pudiéramos abstraer esa parte del todo para que los usuarios solo tengan que escribir las funciones por sí mismos mientras que nosotros nos ocupamos de todo lo relativo a la implementación y el escalado? Básicamente, utilizaríamos el SDK de WebJobs como un servicio. Eso es.

Movilizamos a un equipo para que empezara a investigar esa parte del plan, es decir, el “proceso dinámico”. Esta fue la fase del proyecto en la que crecimos de un pequeño grupo de personas hasta convertirnos en un equipo mucho más grande; a nuestras reuniones de Scrum se incorporaban cada día entre dos y tres personas; parecía que nuestra capa de proceso dinámico es responsable de las funciones de escalado automático a medida que la carga aumenta y de reducir el escalado cuando esta se reduce. Como resultado, los usuarios finales no tienen que preocuparse por esto en absoluto, ya que solo se les cobrará el tiempo de proceso que realmente consuman. El ámbito del proceso dinámico del proyecto es amplio y también incluye otros aspectos del servicio, como supervisión, diagnóstico, telemetría, etc. Este tema merece que se le dedique su propia entrada de blog en el futuro.

Portal de Functions

El siguiente aspecto en el que empezamos a centrarnos fue una experiencia con el portal, a fin de facilitar significativamente la creación y administración de estas funciones. En el modelo del SDK de WebJobs tradicional, debe compilar e implementar una aplicación de consola de .NET (JobHost) que contiene todas las funciones de trabajo compiladas previamente. Para Azure Functions, el modelo de implementación es mucho más sencillo. El Sistema en ejecución de Functions se creó para tener un diseño muy sencillo del sistema de archivos. Eso facilita la disponibilidad de una interfaz de usuario sencilla en el portal que funcione con dichos archivos mediante las API de Kudu. Podríamos contar con un editor sencillo en el portal que le permitiera crear y editar estos archivos e insertarlos en el contenedor de las funciones (la aplicación web que ejecuta las funciones). El sencillo modelo del sistema de archivos también permite implementar Azure Functions con plantillas de ARM. Eso ya es posible hoy en día, pero no se ha documentado bien aún.

El equipo pudo crear un portal y ponerlo en funcionamiento muy rápido, y estábamos muy entusiasmados con la posibilidad de empezar a usarlo para experimentar con el nuevo producto. Con la existencia del portal, realmente se empezó a sentir que todo se estaba fusionando. Nuestro amplio equipo pudo empezar a experimentar con el producto, lo que generó muchos debates y mejoras de uso y también nos ayudó a empezar a resolver los errores.  Cuando empezó el trabajo con el portal, al igual que con el Sistema en ejecución de Functions, había una o dos personas trabajando en ello, pero, a medida que el trabajo inicial empezó a coger impulso y que nuestros planes y nuestro ámbito aumentaron, incorporamos a más personas. Las reuniones de Scrum se alargaron más aún.

Plantillas

El sencillo modelo del sistema de archivos para Functions también nos permitió desarrollar el increíble modelo de plantilla que se encuentra disponible actualmente en el portal de Functions. Empezamos a elaborar en masa sencillas plantillas de metadatos y scripts para escenarios comunes en los diferentes lenguajes: “QueueTrigger – Node”, “GitHub WebHook C#”, etc. La idea es disponer de puntos de partida o “recetas” sencillos para las funciones que se ejecutan inmediatamente listas para su uso, que posteriormente puede personalizar y adaptar a sus necesidades. En el futuro, esperamos dar la oportunidad a la comunidad de elaborar también dichas plantillas para crear un ecosistema.

Extensibilidad

Otra cuestión en la que nos centramos durante el período previo a nuestro anuncio de Azure Functions en //build fue un nuevo conjunto de extensiones del SDK de WebJobs que habilitamos en Functions. Lanzamos el modelo de extensibilidad del SDK de WebJobs el otoño pasado, que abrió el nuevo modelo de programación a nuevos orígenes de desencadenador/enlace. Nuestro equipo ya había proporcionado a la comunidad algunas nuevas extensiones útiles (por ejemplo, TimerTrigger, FileTrigger, enlace de SendGrid, etc.) en el repositorio de extensiones del SDK de WebJobs. Algunos de nuestros miembros de la comunidad también han empezado a crear sus propias extensiones. Puesto que Functions se basa en el SDK, todas estas extensiones también se pueden habilitar para Azure Functions. Queríamos escribir muchas extensiones, pero no tuvimos tiempo, aunque, con el nuevo equipo más numeroso con el que contamos, disponíamos de los recursos para empezar a elaborar algunas de ellas. En los dos últimos meses, hemos agregado las siguientes extensiones adicionales de primera categoría en Functions: EventHub, DocumentDb, NotificationHub, MobileApps y ApiHub. Esto es solo el principio; hay muchas extensiones más previstas y, además, esperamos que la comunidad también cree más. Estamos trabajando en un modelo sencillo que permita a terceros incorporar sus extensiones en Functions. Esté al tanto de esto.

Otra cuestión novedosa es que decidimos previamente que queríamos hacer todo el trabajo con código abierto, exactamente como lo hicimos con los repositorios principales del SDK de WebJobs y las Extensiones del SDK de WebJobs. Por tanto, creamos el script para el SDK de WebJobs que contiene el Sistema en ejecución de Functions. Asimismo, el portal de Functions también es de código abierto: AzureFunctionsPortal.

Para concluir, hay que decir que todo lo anterior constituye un resumen de un nivel bastante alto de los distintos componentes de un proyecto y cómo se fusionan: el Sistema en ejecución de Functions, el portal de Functions y el proceso dinámico. En futuras entradas, profundizaremos más en los detalles de estos distintos componentes.