En Mi Local Funciona

Technical thoughts, stories and ideas

Fn Project, una plataforma serverless multi-cloud y multi-lenguaje

Publicado por Alejandro Font el

En el siguiente post hablaremos de fn. Veremos cómo instalarlo, configurarlo y algunas de sus características como son fn flow y fn ui. Todo ello con un ejemplo práctico para poder entender en detalle la plataforma serverless fn y todo lo que nos aporta.

No entraremos en los conceptos de diseño arquitecturas serverless, FaaS, etc. Sobre ello hay muchísima literatura. Este artículo tiene una bibliografía muy interesante donde se comentan diferentes soluciones y plataformas y en nuestro blog hemos hablado en alguna ocasión.

Como se ha comentado, fn es open source, multi-plataforma cloud y multi-lenguaje por lo que prácticamente no tiene casi dependencias. En realidad, la única dependencia que tendremos es docker, ya que es Container Native.

Cada una de las funcionalidades o módulos que tiene la plataforma están divididas en diferentes contenedores docker, de modo que en función de nuestras necesidades iremos montando la plataforma a nuestra medida.

0. Requisitos

  • Tener docker instalado en una versión más o menos reciente (17.10 o posterior)
  • Estar logado en Docker Hub u otro Docker registry

Una de las ventajas que tenemos es que podremos probar nuestras funciones en local. Y es lo que realizaremos a continuación.

Mencionar que fn funciona tanto en Windows, Linux como macOS. Para la redacción de este post se usará macOS. Aunque en Windows10 la integración con docker es, por fin, buena y no debería haber problema.

1. Instalar Fn cli tool

Lo podéis descargar desde la propia página del proyecto, aunque yo recomiendo hacer uso de un gestor de paquetes.

Desde terminal:

  • brew install fn

2. Arrancar Fn server

Utilizaremos el siguiente comando:

  • fn start

La primera vez, nos descargará una imagen docker, no podía ser de otra manera, y la ejecutará.

Veremos una salida similar a ésta:

alt

3. Crear nuestro primer ejemplo

Los primeros pasos, permiten preparar el entorno de trabajo:

  • Hacemos login, antes de empezar, en nuestro registry con docker login
  • Configuramos nuestro username del registry.
  • export FN_REGISTRY=tuUsuario

A partir de aquí, comenzamos a preparar específicamente el ejemplo:

  • Crearemos una carpeta donde escribir nuestro ejemplo y un fichero con nuestra función. En este caso, un holamundo en Go:
package main

import (  
  "fmt"
)

func main() {  
  fmt.Println("Hola Mundo")
}
  • fn init Esto nos creará el fichero .yaml asociado a la función.
name: samplego  
version: 0.0.2  
runtime: go  
entrypoint: ./func  
format: default  

En realidad, podemos hacer uso de algunos ejemplos ya creados y estructurados:

  • fn init --runtime go hello
  • fn init --runtime java hello

Como hemos comentado al principio, fn soporta múltiples lenguajes por lo que podremos escribir nuestras funciones en el lenguaje que estemos más cómodos. Actualmente, soporta los siguientes runtimes:

  • dotnet, go, java8, java9, java, lambda-nodejs4.3, lambda-node-4, node, php, python, python3.6, ruby, rust, kotlin

4. Desplegar nuestro ejemplo

En este caso estamos desplegando nuestras aplicaciones en local:

  • fn deploy --app saludogo --local

5. Ejecutar nuestro ejemplo

Para probar la aplicación podemos hacerlo de diversas formas:

  • curl -X POST -d '{}' http://localhost:8080/r/saludogo/samplego
  • fn call saludogo /samplego
  • http://localhost:8080/r/saludogo/samplego

Si hacermos un docker images veremos que tenemos, además de las imágenes que ya teníamos en nuestro entorno, una imagen extra relacionada con go:

  • tuUsuario/samplego
  • fnproject/go

Esta segunda imagen contiene el compilador del lenguaje elegido (en este caso go). El objetivo es crear imágenes multilenguaje y multiplataforma intentando que dichas imágenes sean lo más pequeñas posibles.

Lógicamente podemos tener parámetros en nuestras funciones. Éste sería un ejemplo de llamada con paso de parámetro:

  • curl -H "Content-Type: application/json" -d '{"name":"Alejandro"}' http://localhost:8080/r/myapp/hello

6. Modo gráfico

Hasta ahora todo lo estamos haciendo via Terminal. Si queremos un dashboard donde poder ver las funciones que tenemos en cola, corriendo, completadas o falladas podemos levantar el modulo fn ui:

docker run --rm -it --link fnserver:api -p 4000:4000 -e "FN_API_URL=http://api:8080" fnproject/ui  

Ahora si accemos a http://localhost:4000, veremos la siguiente consola:

alt

7. APIs

En esta URL podemos acceder a la API de FN.

Además todas las métricas que nos aporta fn se pueden exportar y hacer un uso más detallado con Prometheus y Grafana.

8. Flow

Como último punto, vamos a ver el módulo de flow ui. Una interfaz gráfica donde podremos ver en detalle la ejecución de aplicaciones que tienen una flujo de llamadas a diferentes funciones. Desde ahí, podremos ver cómo interactúan entre ellas las funciones, el tiempo que consumen, traza, etc.

A modo de ejemplo, haremos uso del caso detallado en la documentación, ya que es el mas sencillo de ver:

  • fn init --runtime=java simple-flow

Substituimos el código de HelloFunction.java por el siguiente y borramos la carpeta de test. Ya que si no al construir la imagen docker, nos fallaran los Unit Tests y no se construirá la imagen:

package com.example.fn;

import com.fnproject.fn.api.flow.Flow;  
import com.fnproject.fn.api.flow.Flows;

public class HelloFunction {

    public String handleRequest(int x) {

    Flow fl = Flows.currentFlow();

    return fl.completedValue(x)
                 .thenApply( i -> i*2 )
             .thenApply( i -> "Your number is " + i )
             .get();
    }
}

Para los ya iniciados, aquí dejo un ejemplo siguiendo el patrón Saga.

Configuramos la IP donde estamos corriendo el flow server que hemos arrancado al principio. Se entiende que nuestro contenedor de flow server se llama fnserver:

  • FNSERVER_IP=$(docker inspect --type container -f '{{.NetworkSettings.IPAddress}}' fnserver)

Arrancamos flowserver:

docker run --rm -d \  
     -p 8081:8081 \
     -e API_URL="http://$FNSERVER_IP:8080/r" \
     -e no_proxy=$FNSERVER_IP \
     --name flowserver \
     fnproject/flow:latest

Configuramos la ip del del flowserver:

  • FLOWSERVER_IP=$(docker inspect --type container -f '{{.NetworkSettings.IPAddress}}' flowserver)

Arrancamos el contenedor que se encarga de la interfaz gráfica del flowserver:

docker run --rm -d \  
      -p 3002:3000 \
      --name flowui \
      -e API_URL=http://$FNSERVER_IP:8080 \
      -e COMPLETER_BASE_URL=http://$FLOWSERVER_IP:8081 \
      fnproject/flow:ui

Llegados a este punto desplegamos la aplicación y la configuramos para que fn flow sea consciente de su ejecución:

  • fn deploy --app flow101 --local
  • fn config app flow101 COMPLETER_BASE_URL "http://$FLOWSERVER_IP:8081"

Ahora la ejecutamos:

  • echo 2 | fn call flow101 /simple-flow

Y accedemos a http://localhost:3002. Podremos ver el flow de nuestra ejecución:
alt Ademas de las algunas metricas como tiempos, líneas de código ejecutada, etc.
alt

Conclusiones

Este post es simplemente una introducción a fn, para, posteriormente, ir avanzando en temas como despliegue en cloud público, creación de aplicaciones fn a partir de imágenes docker, etc. Hay entornos o situaciones donde soluciones de ese tipo pueden aplicar desde el principio, como pueden ser bots o entornos de demo, donde no tenemos porque tener un servicio continuamente levantado.

Alejandro Font
Autor

Alejandro Font

Oracle ACE y Líder Técnico de la Comunidad Oracle Fusion Middleware en atSistemas. Actualmente con foco en Arquitecturas Ágiles y Contenedores