Cuando se trabaja en ambientes Cloud, unas de las cosas más importantes a considerar son los recursos que se usan en las aplicaciones que se quieren implementar. Considerando esto, Java no suele ser muy amado para estos propósitos. Aun así, muchas empresas evitan desechar todos sus sistemas inmediatamente y es por esto que sigue siendo muy utilizado, incluso en ambientes Cloud; para solucionar en parte este problema es que se desarrolló Spring Native.
Spring Native provee soporte para generar ejecutables usando GraalVM y Native Image. Como muchos sabrán, la principal desventaja de Java frente a otros lenguajes es la cantidad de recursos requeridos para sus aplicaciones. Allí es donde Spring Native viene a intentar dar una solución.
En esta ocasión utilizaremos Spring Nativeen la parte práctica, Cloud Run (GCP) para el ambiente y Buildpacks para generar el container de la aplicación.
Para crear el proyecto usaremos el clásico spring initializr:
Estructura del proyecto
La idea no es realizar algo complejo, sino más bien mostrar cómo Spring Native, Build Pack y Java/SprintBoot pueden ser usados en ambientes Cloud con la ayuda de Cloud Run.
Tecnologías
Spring Native
Este es un proyecto experimental impulsado para mejorar la cantidad de recursos que Java/Springboot utiliza en su entorno de ejecución. Al necesitar de la JVM, Java consume por defecto más recursos que otros lenguajes. Por su parte, Spring Native se basa en GraalVM y Native Image; en palabras simples, GraalVM es un JDK diseñado para brindar high-performance y acelerar la ejecución de aplicaciones que utilizan la JVM, mientras que Native Image es una tecnología que compila el código de Java “ahead-of-time” a código binario, es decir, un “ejecutable nativo” el cual solo incluye el código requerido en runtime.
GraalVM y Native Image son mucho más complejos y podrían abarcar un artículo individual para cada uno, por lo que profundizaré en las características de cada uno en otra ocasión.
Buildpacks
Actualmente, cuando se trabaja en ambientes Cloud es raro que no se ocupen containers para las aplicaciones; lo más común es usar un Dockerfile para ello. Generar un buen container es bastante complicado, ya que debe cumplir con varias características para seguir las buenas prácticas. Como developer esto no es mi fuerte, por lo que busqué alternativas y llegué a Builpacks, una herramienta open source que ayuda a generar containers de manera automática a partir del código fuente.
Buildpacks incluye una serie de componentes que ayudan a generar un container cumpliendo con las mejores prácticas recomendadas para ambientes Cloud, por lo que usando esta herramienta se pueden generar containers “production-ready” sin mayores inconvenientes.
Cloud Run
Es un servicio serverless ofrecido por GCP, el cual tiene la particularidad de que si tienes ya tu aplicación containerized, entonces puedes deployarla muy fácilmente y generar una URL de manera automática. Todo esto sin necesidad de levantar un cluster ni ninguna otra configuración más que tu app.
Parte práctica
Para comenzar, primero mostraré algunas diferencias que se pueden notar en una aplicación que no usa Spring Native y una que sí lo ocupa. Esto lo haré con Buildpacks para generar la imagen de esta app. En el ejemplo utilizaré diferentes builders para distinguir las imágenes y finalmente subiré la imagen nativa a artifact registry en GCP para “deployarla” en Cloud Run. Esta última parte se puede hacer en un solo paso usando Cloud Build, pero será para otro artículo. 😉
Para generar la imagen de la app con Buildpack se puede definir directamente el builder que se quiere ocupar o utilizar uno por defecto. Los builders que ya vienen integrados son los siguientes:
Desde ahí se puede escoger cualquiera y va a depender de cada caso en particular. En mi caso ocupare dos: el builder de Google y paketo buildpack en su versión “tiny”.
Para generar una imagen a partir del código fuente con Buildpack es tan simple como ocupar el siguiente comando:
Pack build “nombre-imagen”
Este comando ocupará el builder por defecto que se tenga configurado en mi caso el de packeto-tiny. Si se quiere ocupar uno en particular, ya sea de los listados arriba u otro, se debe usar - -builder ”ejem/ejemplo-builder”. Para este artículo generé tres imágenes que permitan diferenciar los tamaños de cada una:
Lab-cloud-run-google es la imagen generada con el builder de Google.
Lab-cloud-run es la imagen generada con el builder de paketo tiny.
Lab-cloud-run-native es la imagen generada con Spring Native.
Como se puede observar, estas imágenes varían bastante en su tamaño total dependiendo del builder que se ocupó, aunque todas ellas corren perfectamente y sin problemas. Pero como dije, no todo es para todos los casos y siempre va a depender la situación particular que se esté evaluando.
Para la imagen nativa hay que usar una variable de entorno en el comando de buildpack; para que se genere con Spring Native la variable es “BP_NATIVE_IMAGE=true”:
Esta última será la imagen que subiré al artifact-registry de GCP y la que posteriormente deployaré en Cloud-Run.
Deploy con Cloud Run
Para hacer el deploy en Cloud Run primero subiré la imagen lab-cloud-run-native a artifact-registry. Para ello se tiene que crear un repositorio de imágenes Docker y luego autentificarse con la región en la que se haya creado el repo; en mi caso ocuparé southamerica-west1 y finalmente hacer push de la imagen a este nuevo repo.
Para autenticarte debes ocupar el siguiente comando y luego hacer el push con Docker push “el_tag_imagen”:
Una vez la imagen esta alojada en artifact-registry podemos comenzar con el deploy en Cloud-run.
Para realizar el deploy simplemente utilizamos el siguiente comando, ya sea en Cloud Shell en la consola de GCP o si se tiene SDK instalado se puede hacer directo de nuestra terminal.
Cuando se termina de hacer el deploy, GCP nos da la URL asignada a nuestro servicio. En mi caso le especifiqué que solo aceptara solicitudes con autenticación, por eso si es que se quiere hacer la solicitud es necesario autenticarse, si no arrojará un error 403 como muestro a continuación.
Acá el servicio en Cloud Run:
Si hacemos CURL a la URL de arriba más la autorización, nos muestra “lab spring-native” que es la respuesta del endpoint de ejemplo que tiene la aplicación.
Acá los logs del request. Al principio se ve cómo hay tres peticiones con 403, debido a que estaba probando si le llegaba al servicio sin autenticación.
Conclusiones
En este mini lab se puede comprobar que con Buildpack no es necesario ser un maestro generando Dockerfiles para cumplir con todas las buenas prácticas que se recomiendan, sino que basta con el código fuente y un builder que se adapte a tus requerimientos.
Spring Native nos ayuda a reducir considerablemente el tamaño de las imágenes de una aplicación de Java/Springboot, haciéndola más apta para ambientes Cloud y, además, mejorando la performance respecto a la partida de la aplicación. Así evitas “cold start” muy largos.
Con Cloud Run pudimos deployar un servicio en cosa de segundos, solo dando la imagen de la aplicación y dejando a GCP todo lo demás.
Nota: la aplicación no tiene nada de especial y se puede genera con las características que detallé al comienzo. Por eso no la subí a un repo y todos los pasos son exactamente como se muestran acá, obviamente adaptándolos a tus ambientes locales y la cuenta de GCP correspondiente.