Introducción
En un anterior artículo estuvimos analizando las características y ventajas que aporta Quarkus como framework de desarrollo para generar aplicaciones Java, funciones Lambda en AWS para el caso de estudio anterior, como solución a los ya conocidos problemas de los frameworks actuales de generación de aplicaciones en este lenguaje a la hora de construir artefactos que puedan ser integrados en una solución Cloud Serverless.
En esta ocasión seguiremos analizando Quarkus, esta vez veremos cómo podemos construir y desplegar aplicaciones construidas con este framework pero ahora en una solución Serverless un poco menos conocida que AWS, en este caso hablamos de Knative, la solución para dotar a Kubernetes de los componentes esenciales para construir y ejecutar aplicaciones Serverless.
Knative: Solución K8s Serverless
Solo para para tener una conceptualización, Knative otorga a Kubernetes o a cualquiera de las distribuciones basadas en él, como puede ser OpenShift, Rancher, EKS, PKS, Minikube entre otras; la posibilidad de desplegar aplicaciones sobre su cluster y que sean ejecutadas cuando se necesiten, posibilitando al desarrollador a centrarse en el código de su aplicación y sin preocuparse de configurar una infraestructura para ello. Knative se encargará de escalar, incluso a cero, las instancias (pods) necesarias para atender las peticiones sobre el servicio.
Knative está basado en 3 componentes Build, Eventing y Serving; y para nuestras pruebas con Quarkus y Knative utilizaremos este último, Serving, ya que nos proporciona todo lo necesario para desplegar nuestra aplicación Quarkus en contenedores en un contexto Serverless, los cuales escalarán dependiendo de la carga y Knative se encargará además de configurar todos los elementos necesarios para el enrutamiento y configuración de red de nuestros servicios.
Aplicación Quarkus [Caso Práctico]
Para el caso práctico seguiremos utilizando los 2 tipos de ejecutables que ofrece Quarkus; código binario optimizado para ejecución en una JVM y código binario nativo. Con respecto a la aplicación ejemplo, en esta ocasión construiremos una aplicación en la que expondremos al menos un servicio REST para poder realizar las pruebas de arranque y acceso al servicio.
Prerrequisitos
- JDK 11+ (JAVA_HOME Configurado)
- Apache Maven 3.3.9+
- IntelliJ IDEA, como IDE de desarrollo. Puede ser cualquiera.
- Quarkus CLI, para la generación de proyectos a partir de arquetipos.
- Minikube (Distribución K8s para entorno locales)
- Knative CLI instalado y configurado.
- Postman, pruebas del servicio.
- Cuenta Dockerhub (Repositorio de imágenes Docker)
- Docker para la construcción de imágenes nativas. (Utilizado en las pruebas)
Opcionalmente se puede utilizar Mandrel o GraalVM para la construcción de imágenes nativas.
Generación del proyecto base
Para la generación de proyecto Quarkus ofrece varias opciones al desarrollador, a través de Maven (utilizando el arquetipo Maven de Quarkus), otra mediante su página web de Quarkus Code y una tercera opción llamada Quarkus CLI. Sin embargo, para la creación de la aplicación de nuestro proyecto base vamos a utilizar Quarkus CLI, la cual nos permite crear aplicaciones Quarkus a través de líneas de comando.
Generación Quarkus CLI
Se inicia la construcción del proyecto Maven:
$ quarkus create app com.at:quarkus-knative:1.0.0-SNAPSHOT --extension=quarkus-resteasy-jackson,minikube,jib,smallrye-health
Del anterior comando podemos resaltar:
<create app>
: Crea una nueva aplicación (proyecto) Quarkus.<groupId:artifactId:version>
: com.at:quarkus-knative:1.0.0-SNAPSHOT<extension>
: Lista de extensiones a agregar al proyecto.
Extensiones
En la creación del proyecto Maven base se configuran una lista de extensiones básicas que dotan al proyecto de dependencias para lograr construir funcionalidades esenciales en el microservicio a construir:
- quarkus-resteasy-jackson: Agrega soporte de endpoints REST implementando JAX-RS y serialización Jackson.
- Minikube: Generación automática de recursos minikube (k8s) como resultado del proceso de construcción del artefacto.
- Jib: Construcción de imágenes Docker.
- smallrye-health: Permite informar acerca del estado de aplicación a elementos observadores externos como K8s o cualquier entorno Cloud.
Como resultado, se agregan las dependencias Maven en el POM de proyecto relacionadas con la lista de extensiones configuradas en la creación del proyecto:
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-minikube</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-container-image-jib</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-health</artifactId>
</dependency>
.
.
.
<dependencies>
Estructura
Las anteriores acciones generan el proyecto Maven con los parámetros seleccionados y preparado para ser importado en cualquier IDE que soporte este tipo de proyecto.
Configuración e Implementación Microservicio
REST Endpoints
Basándonos en las dependencias de RestEasy del proyecto generamos la implementación para exposición de un endpoint HTTP que nos ayude a ejecutar nuestra lógica y responder a una petición:
@Path("/v1")
public class PersonLambda {
@Inject
PersonService personService;
@ConfigProperty(name = "greeting.prefix")
String prefixGreeting;
@POST
@Path("/serverless")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public PersonResponse handleRequest(Person input) {
return new PersonResponse(prefixGreeting + " " + personService.getName(input));
}
}
El componente PersonService solo es una clase que retorna ya sea el nombre de usuario enviado en el parámetro de entrada Person o un nombre aleatorio de una lista almacenada en las propiedades de la aplicación:
#persons
person.persons[0].name=Vegeta
person.persons[1].name=Kakaroto
person.persons[2].name=Roshi
Health Endpoints
Quarkus CLI genera un proyecto Maven con accesos a los endpoints de información de estado del servicio configurado con valores por defecto, por lo que haremos una sobre escritura del endpoint de health para definir un nuevo prefijo propio del microservicio. Se modifica el archivo application.properties de la carpeta src/resources de la aplicación para sobre escribir el root-path por defecto (/q/health):
quarkus.smallrye-health.root-path=/health
Con este cambio, nuestra aplicación expone 3 nuevos endpoints sobre el path configurado:
/health/live
– Indica si la aplicación está lista y funcionando./health/ready
- Indica si la aplicación se encuentra disponible para recibir peticiones./health/started
- Indica si la aplicación ha iniciado.
Empaquetado y Despliegue
Creación Artefacto Optimizado JVM
Para la creación solo es utilizar Maven o Quarkus CLI para construir el empaquete del artefacto, lanzando la instrucción correspondiente sobre la ruta raíz del proyecto:
Maven
./mvnw clean package
Quarkus CLI
quarkus build
Creación Artefacto Nativo
Maven
./mvnw clean package -Dnative
En caso de tener el entorno de desarrollo diferente a Linux, la recomendación es utilizar Docker como herramienta para que la extensión de Quarkus construya el artefacto nativo:
./mvnw package -Dnative -Dquarkus.native.container-build=true
Quarkus CLI
quarkus build --native
En caso de tener el entorno de desarrollo diferente a Linux, la recomendación es utilizar Docker como herramienta para que la extensión de Quarkus construya el artefacto nativo:
quarkus build --native -Dquarkus.native.container-build=true
Resultado Compilación
Esta acción genera una serie de artefactos o carpetas dentro de la carpeta target del proyecto:
De los cuales destacamos los siguientes:
<<artefactId>>
+<<version>>
+”.jar”
: Corresponde al empaquetado final del artefacto Quarkus construido.kubernetes
: Archivos propios de K8s o Minikube que contiene la configuración necesaria para construir los Kubernetes/Minukube Deployments y Services de la aplicación.
Contendio minikube.yml
---
apiVersion: v1
kind: Service
metadata:
annotations:
app.quarkus.io/commit-id: 718663f7823ed12dc9067eb371cf336e95c45c2b
app.quarkus.io/build-timestamp: 2022-05-03 - 11:21:23 +0000
labels:
app.kubernetes.io/name: knative-quarkus
app.kubernetes.io/version: 1.0.0-SNAPSHOT
name: knative-quarkus
spec:
ports:
- name: http
nodePort: 30984
port: 80
targetPort: 8080
selector:
app.kubernetes.io/name: knative-quarkus
app.kubernetes.io/version: 1.0.0-SNAPSHOT
type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
app.quarkus.io/commit-id: 718663f7823ed12dc9067eb371cf336e95c45c2b
app.quarkus.io/build-timestamp: 2022-05-03 - 11:21:23 +0000
labels:
app.kubernetes.io/name: knative-quarkus
app.kubernetes.io/version: 1.0.0-SNAPSHOT
name: knative-quarkus
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: knative-quarkus
app.kubernetes.io/version: 1.0.0-SNAPSHOT
template:
metadata:
annotations:
app.quarkus.io/commit-id: 718663f7823ed12dc9067eb371cf336e95c45c2b
app.quarkus.io/build-timestamp: 2022-05-03 - 11:21:23 +0000
labels:
app.kubernetes.io/name: knative-quarkus
app.kubernetes.io/version: 1.0.0-SNAPSHOT
spec:
containers:
- env:
- name: KUBERNETES_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: onoriel/knative-quarkus:1.0.0-SNAPSHOT
imagePullPolicy: IfNotPresent
livenessProbe:
failureThreshold: 3
httpGet:
path: /health/live
port: 8080
scheme: HTTP
initialDelaySeconds: 0
periodSeconds: 30
successThreshold: 1
timeoutSeconds: 10
name: knative-quarkus
ports:
- containerPort: 8080
name: http
protocol: TCP
readinessProbe:
failureThreshold: 3
httpGet:
path: /health/ready
port: 8080
scheme: HTTP
initialDelaySeconds: 0
periodSeconds: 30
successThreshold: 1
timeoutSeconds: 10
Despliegue Artefacto
En este punto, el artefacto ya ha sido construido y para el proceso de despliegue el tipo de artefacto ya bien sea nativo u optimizado para JVM, es indiferente ya que el proceso que a continuación de define es el mismo para ambos.
Construcción Imagen
Para el proceso de despliegue en Knative necesitaremos construir una imagen del servicio, la cual estará subida sobre un Docker registry público. La imagen se construirá con los archivos de configuración generados en la construcción del artefacto:
Imagen JVM
docker build -t onoriel/quarkus -f src/main/docker/Dockerfile.jvm .
Imagen Nativa
docker build -t onoriel/quarkus-native -f src/main/docker/Dockerfile.native .
Resultado
Upload image
Una vez creada la imagen es necesario subirlo a un repositorio de imagen público, esto solo es necesario para nuestras pruebas locales y para agilidad en nuestro caso de prueba, por lo que utilizamos en este caso Dockerhub para realizar el proceso de subida y que Knative pueda hacer pull de la imagen en el momento de crear el servicio:
docker login -u <<username>>
Con esto el CMD pedirá las credenciales de acceso para el usuario. Con el usuario ya logado podremos realizar proceso de pull y push de imágenes en repositorios del usuario.
Una vez logado, procedemos a hacer push de la imagen de nuestro microservicio al repositorio del usuario:
Resultado hub.docker.com
Creación Knative Service
Dentro de la raíz del proyecto creamos una carpeta llamada k8s, puede ser nombrada como queramos, y dentro de ella creamos un nuevo archivo YAML con la definición de servicio Knative:
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: knative-quarkus-app
spec:
template:
spec:
containers:
- image: onoriel/quarkus-native
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /health/live
readinessProbe:
httpGet:
path: /health/ready
O podemos crear el servicio utilizando directamente Knative CLI:
kn service create <<service_name>> --image <<image_name>> --port <<app_port>>
$ kn service create knative-quarkus-app --image onoriel/quarkus-native --port 8080
Creating service 'knative-quarkus-app' in namespace 'default':
0.045s The Route is still working to reflect the latest desired specification.
0.048s ...
0.087s Configuration "knative-quarkus-app" is waiting for a Revision to become ready.
14.883s ...
15.000s Ingress has not yet been reconciled.
15.203s Waiting for load balancer to be ready
15.226s Ready to serve.
Service 'knative-quarkus-app' created to latest revision 'knative-quarkus-app-00001' is available at URL:
http://knative-quarkus-app.default.127.0.0.1.sslip.io
Probando la Aplicación Serveless
Una vez desplegada llegamos a lo más interesante, probar y ver el comportamiento del cluster K8s, la gestión de pods para servir el servicio cuando este sea solicitado y la liberación posterior de recursos.
Una vez ha terminado de desplegar el servicio podemos acceder a probar en la URL generada por Knative, en nuestro caso http://knative-quarkus-app.default.127.0.0.1.sslip.io
curl --location --request POST 'http://knative-quarkus-app.default.127.0.0.1.sslip.io/v1/serverless' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "John"
}'
Petición POST al servicio utilizando Postman:
Como podemos observar nuestro servicio se encuentra respondiendo a nuestras peticiones sin problemas. Ahora veamos que sucedes con los pods y la infraestructura de nuestro cluster cuando estas peticiones son realizadas.
Pod activo en el cluster
Actividad de los pods asociados al servicio:
kubectl get pod -l serving.knative.dev/service=<<app_service_name>> -w
Se aprecia la creación y eliminación de pods automáticamente en el cluster, luego de un tiempo nuestros pods empiezan a terminar su ejecución y posterior eliminación; y en cuanto nuestro servicio es solicitado nuevamente Knative se encarga de desplegar y servir otro pod de la aplicación de forma automática.
Terminación Pod Automática
Logs Servicio
Una vez teniendo la aplicación arriba y disponible como un pod de Knative podemos acceder a los logs a través de los comandos propios de K8s, en este caso con su versión de Minikube:
kubectl logs <<pod_name>> -c user-container
Conclusiones
En este artículo hemos visto como es el proceso end to end para construir aplicaciones Java utilizando el framework Quarkus y desplegar nuestro artefacto sobre una plataforma Serverless como Kubernetes + Knative. Con esta aproximación contamos con una nueva alternativa para plantear soluciones Serverless utilizando artefactos construidos con Quarkus y aprovechar sus caracteristicas de aligeramiento de aplicaciones y construcción de artefactos nativos en código máquina para obtener el máximo rendimiento y mejores respuestas en este tipo de arquitecturas cloud con nuestras aplicaciones Java.
¡Síguenos en Twitter para estar al día de próximas entregas!