Implementa una aplicación de Micronaut en contenedores con Jib en Google Kubernetes Engine

1. Descripción general

Acerca de Micronaut

Micronaut es un framework moderno de pila completa basado en JVM para crear microservicios modulares y aplicaciones sin servidores que se pueden probar fácilmente. Micronaut tiene como objetivo ofrecer un excelente tiempo de inicio, una capacidad de procesamiento rápida y una huella de memoria mínima. Los desarrolladores pueden desarrollar con Micronaut en Java, Groovy o Kotlin.

Micronaut proporciona lo siguiente:

  • Tiempo de inicio rápido y bajo consumo de memoria: Los frameworks de IoC basados en la reflexión cargan y almacenan en caché los datos de reflexión para cada campo, método y constructor de tu código, mientras que, con Micronaut, el tiempo de inicio y el consumo de memoria de tu aplicación no están vinculados al tamaño de tu base de código.
  • Cliente HTTP declarativo, reactivo y de tiempo de compilación: Crea de forma declarativa clientes HTTP reactivos que se implementan en el tiempo de compilación, lo que reduce el consumo de memoria.
  • Servidor HTTP no bloqueante compilado en Netty: Con una curva de aprendizaje fluida, el servidor HTTP de Micronaut facilita al máximo la exposición de APIs que pueden consumir los clientes HTTP.
  • Pruebas rápidas y sencillas: Inicia servidores y clientes fácilmente en tus pruebas de unidades, y ejecútalos de forma instantánea.
  • Inyección de dependencias y AOP eficientes en tiempo de compilación: Micronaut proporciona una API de programación orientada a aspectos simple en tiempo de compilación que no usa la reflexión.
  • Crea apps completamente reactivas y no bloqueantes: Micronaut admite cualquier framework que implemente Reactive Streams, incluidos RxJava y Reactor.

Para obtener más información, visita el sitio web de Micronaut.

Acerca de Kubernetes

Kubernetes es un proyecto de código abierto que se puede ejecutar en muchos entornos distintos, desde laptops hasta clústeres de varios nodos de alta disponibilidad; desde nubes públicas hasta implementaciones locales; desde máquinas virtuales hasta equipos físicos.

En este lab, implementarás un microservicio simple de Micronaut basado en Groovy en Kubernetes, que se ejecuta en Kubernetes Engine.

El objetivo de este codelab es que ejecutes tu microservicio como un servicio replicado que se ejecuta en Kubernetes. Convertirás el código que hayas desarrollado en tu máquina en una imagen de contenedor de Docker y, luego, la ejecutarás en Kubernetes Engine.

Este es un diagrama de las distintas partes que están en juego en este codelab. Lo ayudará a entender cómo encajan las piezas. Úselo como referencia a medida que avanza en el codelab. Todo debería tener sentido cuando llegue al final (pero puede ignorarlo por el momento).

Kubernetes Codelab Diagram 1 (2).png

A los fines de este codelab, utilizar un entorno administrado como Kubernetes Engine (una versión de Kubernetes alojada en Google que se ejecuta en Compute Engine) te permite concentrarte en experimentar con Kubernetes, en vez de configurar la infraestructura subyacente.

Si le interesa ejecutar Kubernetes en su máquina local, como una laptop de desarrollo, le recomendamos que obtenga información sobre Minikube. Esta herramienta ofrece una configuración sencilla de un clúster de Kubernetes de un solo nodo para fines de desarrollo y pruebas. Si lo deseas, puedes usar Minikube durante este codelab.

Acerca de Jib

Jib es una herramienta de código abierto que te permite compilar imágenes de Docker y OCI para tus aplicaciones de Java. Está disponible como complementos para Maven y Gradle, y como biblioteca de Java.

Jib tiene como objetivo ser lo siguiente:

  • Rápido: Implementa tus cambios rápidamente. Jib separa tu aplicación en varias capas y divide las dependencias de las clases. Ahora no tienes que esperar a que Docker vuelva a compilar toda tu aplicación Java, sino que solo debes implementar las capas que cambiaron.
  • Reproducible: Si vuelves a compilar tu imagen de contenedor con el mismo contenido, siempre se generará la misma imagen. Nunca más se activará una actualización innecesaria.
  • Sin daemon: Reduce las dependencias de la CLI. Compila tu imagen de Docker desde Maven o Gradle y envíala a cualquier registro que elijas. Ya no es necesario escribir Dockerfiles ni llamar a docker build/push.

Puedes encontrar más información sobre Jib en la página del proyecto de GitHub.

Acerca de este instructivo

En este instructivo, se usa el código de muestra de la herramienta Jib para compilar contenedores para aplicaciones de Java.

La muestra es un servicio simple de hello world que usa el framework de Micronaut y el lenguaje de programación de Apache Groovy.

Qué aprenderás

  • Cómo empaquetar una aplicación simple de Java como un contenedor de Docker con Jib
  • Cómo crear tu clúster de Kubernetes en Kubernetes Engine
  • Cómo implementar tu servicio de Micronaut en Kubernetes en Kubernetes Engine
  • Cómo escalar su servicio verticalmente e implementar una actualización
  • Cómo acceder al panel gráfico de Kubernetes

Requisitos

  • Un proyecto de Google Cloud Platform
  • Un navegador como Chrome o Firefox
  • Se recomienda estar familiarizado con editores de texto estándares de Linux, como Vim, Emacs o Nano.

¿Cómo usarás este instructivo?

Ler Leer y completar los ejercicios

¿Cómo calificarías tu experiencia con la compilación de aplicaciones web con HTML/CSS?

Principiante Intermedio Avanzado

¿Cómo calificarías tu experiencia con el uso de los servicios de Google Cloud Platform?

Principiante Intermedio Avanzado

2. Configuración y requisitos

Configuración del entorno de autoaprendizaje

  1. Accede a la consola de Cloud y crea un proyecto nuevo o reutiliza uno existente. (Si todavía no tienes una cuenta de Gmail o de G Suite, debes crear una).

dMbN6g9RawQj_VXCSYpdYncY-DbaRzr2GbnwoV7jFf1u3avxJtmGPmKpMYgiaMH-qu80a_NJ9p2IIXFppYk8x3wyymZXavjglNLJJhuXieCem56H30hwXtd8PvXGpXJO9gEUDu3cZw

ci9Oe6PgnbNuSYlMyvbXF1JdQyiHoEgnhl4PlV_MFagm2ppzhueRkqX4eLjJllZco_2zCp0V0bpTupUSKji9KkQyWqj11pqit1K1faS1V6aFxLGQdkuzGp4rsQTan7F01iePL5DtqQ

8-tA_Lheyo8SscAVKrGii2coplQp2_D1Iosb2ViABY0UUO1A8cimXUu6Wf1R9zJIRExL5OB2j946aIiFtyKTzxDcNnuznmR45vZ2HMoK3o67jxuoUJCAnqvEX6NgPGFjCVNgASc-lg

Recuerde el ID de proyecto, un nombre único en todos los proyectos de Google Cloud (el nombre anterior ya se encuentra en uso y no lo podrá usar). Se mencionará más adelante en este codelab como PROJECT_ID.

  1. A continuación, deberás habilitar la facturación en la consola de Cloud para usar los recursos de Google Cloud recursos.

Ejecutar este codelab no debería costar mucho, tal vez nada. Asegúrate de seguir las instrucciones de la sección “Realiza una limpieza”, en la que se aconseja cómo cerrar recursos para que no se te facture más allá de este instructivo. Los usuarios nuevos de Google Cloud son aptos para participar en el programa Prueba gratuita de $300.

3. Obtén el código fuente de muestra de Micronaut

Después de que se inicie Cloud Shell, podrás utilizar la línea de comandos para clonar el código fuente de ejemplo en el directorio principal y cambiar al directorio que contiene nuestro servicio de ejemplo:

$ git clone https://github.com/GoogleContainerTools/jib.git
$ cd jib/examples/micronaut/

4. Un vistazo rápido al código

Nuestro servicio simple de Micronaut se compone de un controlador que genera el infame mensaje Hello World:

@Controller("/hello")
class HelloController {
    @Get("/")
    String index() {
        "Hello World"
    }
}

El controlador HelloController responde a las solicitudes en la ruta /hello, y el método index() acepta las solicitudes HTTP GET.

También hay disponible una clase de prueba Spock para verificar que se muestre el mensaje correcto en el resultado.

class HelloControllerSpec extends Specification {
    @Shared
    @AutoCleanup
    EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer)

    @Shared
    @AutoCleanup
    RxHttpClient client = embeddedServer.applicationContext.createBean(RxHttpClient, embeddedServer.getURL()) 

    void "test hello world response"() {
        when:
        HttpRequest request = HttpRequest.GET('/hello')
        String rsp  = client.toBlocking().retrieve(request)

        then:
        rsp == "Hello World"
    }
}

Más que una simple prueba de unidades, esta prueba ejecuta la misma pila de servidores de Micronaut (basada en el framework Netty) que se ejecuta en producción. Por lo tanto, el comportamiento de tu código será el mismo en el producto que en tus pruebas.

Para ejecutar las pruebas, puedes ejecutar el siguiente comando y verificar que todo esté bien:

./gradlew test

5. Ejecuta la aplicación de manera local

Puedes iniciar el servicio de Micronaut de forma normal con el siguiente comando de Gradle:

$ ./gradlew run

Una vez que se inicie la aplicación, puedes abrir una instancia adicional de Cloud Shell gracias al pequeño ícono de signo más y, luego, verificar con curl que obtengas el resultado esperado:

$ curl localhost:8080/hello

Deberías ver un mensaje simple de "Hello World".

6. Empaqueta la aplicación como un contenedor de Docker con Jib

A continuación, prepare su aplicación para que se ejecute en Kubernetes. Para ello, aprovecharemos Jib para que haga el trabajo pesado por nosotros, ya que no tendremos que tocar un Dockerfile nosotros mismos.

Ejecutemos el comando para compilar nuestro contenedor:

$ ./gradlew jibDockerBuild

Este es el resultado que deberías ver:

Tagging image with generated image reference micronaut-jib:0.1. If you'd like to specify a different tag, you can set the jib.to.image parameter in your build.gradle, or use the --im
age=<MY IMAGE> commandline flag.

Containerizing application to Docker daemon as micronaut-jib:0.1...
warning: Base image 'gcr.io/distroless/java' does not use a specific image digest - build may not be reproducible
Getting base image gcr.io/distroless/java...
Building dependencies layer...
Building resources layer...
Building classes layer...
Finalizing...

Container entrypoint set to [java, -cp, /app/resources:/app/classes:/app/libs/*, example.micronaut.Application]
Loading to Docker daemon...

Built image to Docker daemon as micronaut-jib:0.1

Ahora que nuestra imagen está compilada, comprobemos si podemos ver nuestro mensaje de saludo ejecutando nuestra imagen de Docker en la primera pestaña de Cloud Shell:

$ docker run -it -p 8080:8080 micronaut-jib:0.1
16:57:20.255 [main] INFO  i.m.context.env.DefaultEnvironment - Established active environments: [cloud, gcp]
16:57:23.203 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 2926ms. Server Running: http://97b7d76ccf3f:8080

Nuestro servicio se está ejecutando, por lo que ahora podemos iniciar nuestro comando curl en la segunda pestaña de Cloud Shell para ver si funciona según lo esperado:

$ curl localhost:8080/hello
Hello World

Puedes detener el contenedor presionando Ctrl+C en Cloud Shell.

7. Envía nuestro servicio en contenedores al registro

Ahora que la imagen funciona como debe, puedes enviarla a Google Container Registry, un repositorio privado para tus imágenes de Docker al que puedes acceder desde cada proyecto de Google Cloud (y también desde fuera de Google Cloud Platform).

Antes de poder enviar elementos al registro, asegúrate de que Container Registry esté habilitado para tu proyecto. Para ello, ve a Tools > Container Registry. Si no está habilitada, deberías ver el siguiente diálogo. Luego, haz clic en "Habilita la API de Container Registry" para habilitarla:

ac812e6260ac7dfb.png

Una vez que el registro esté listo, ejecuta los siguientes comandos para enviar la imagen al registro:

$ gcloud auth configure-docker
$ docker tag micronaut-jib:0.1 \
         gcr.io/$GOOGLE_CLOUD_PROJECT/micronaut-jib:0.1
$ docker push gcr.io/$GOOGLE_CLOUD_PROJECT/micronaut-jib:0.1

Los comandos anteriores permiten que el SDK de gcloud configure y autorice Docker para enviar imágenes a tu instancia de Container Registry, etiquetar la imagen para que apunte a su ubicación en el registro y, luego, enviarla al registro.

Si todo sale bien, después de un tiempo, deberías poder ver la imagen de contenedor en la consola: Herramientas > Container Registry. Ahora tiene una imagen de Docker disponible para todo el proyecto, y Kubernetes puede acceder a ella y organizarla, como verá en unos minutos.

12224c4e42183b4e.png

8. Cree su clúster

De acuerdo. Ahora puedes crear tu clúster de Kubernetes Engine, pero antes de eso, navega a la sección de Google Kubernetes Engine de la consola web y espera a que se inicialice el sistema (solo debería tardar unos segundos).

20c0587c0108b8ba.png

Un clúster consiste en un servidor de API de instancia principal de Kubernetes que administra Google y un conjunto de nodos trabajadores. Los nodos trabajadores son máquinas virtuales de Compute Engine. Usemos la CLI de gcloud desde tu sesión de Cloud Shell para crear un clúster con dos nodos n1-standard-1 (esto tardará unos minutos en completarse):

$ gcloud container clusters create hello-cluster \
  --num-nodes 2 \
  --machine-type n1-standard-1 \
  --zone us-central1-c

Cuando el proceso se complete, debería ver que se creó el clúster.

Creating cluster hello-cluster in us-central1-c...done.
Created [https://container.googleapis.com/v1/projects/mn-gke-test/zones/us-central1-c/clusters/hello-cluster].
To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/us-central1-c/hello-cluster?project=mn-gke-test
kubeconfig entry generated for hello-cluster.
NAME           LOCATION       MASTER_VERSION  MASTER_IP       MACHINE_TYPE   NODE_VERSION  NUM_NODES  STATUS
hello-cluster  us-central1-c  1.9.7-gke.7     35.239.224.115  n1-standard-1  1.9.7-gke.7   2          RUNNING

Ahora deberías tener un clúster de Kubernetes completamente funcional con la tecnología de Google Kubernetes Engine:

d9e1e314769753e7.png

Llegó la hora de implementar su propia aplicación en contenedores en el clúster de Kubernetes. A partir de ahora, usarás la línea de comandos kubectl (que ya está configurada en tu entorno de Cloud Shell). Para el resto de este codelab, se requiere que la versión del cliente de Kubernetes y del servidor sea la 1.2 o una posterior. kubectl version te mostrará la versión actual del comando.

9. Implementa tu aplicación en Kubernetes

Una implementación de Kubernetes puede crear, administrar y escalar varias instancias de tu aplicación con la imagen de contenedor que acabas de crear. Creemos una implementación de tu aplicación en Kubernetes con el comando kubectl create deployment:

$ kubectl create deployment hello-micronaut \
  --image=gcr.io/$GOOGLE_CLOUD_PROJECT/micronaut-jib:0.1

Para ver la implementación que acabas de crear, simplemente ejecuta el siguiente comando:

$ kubectl get deployments
NAME              DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
hello-micronaut   1         1         1            1           5m

Para ver las instancias de la aplicación creadas por la implementación, ejecuta este comando:

$ kubectl get pods
NAME                               READY     STATUS    RESTARTS   AGE
hello-micronaut-5647fb98c5-lh5h7   1/1       Running   0          5m

En este punto, deberías tener tu contenedor ejecutándose bajo el control de Kubernetes, pero aún tienes que hacerlo accesible para otras personas.

10. Permite el tráfico externo

Según la configuración predeterminada, el Pod únicamente es accesible a través de su IP interna dentro del clúster. Para que se pueda acceder al contenedor hello-micronaut desde fuera de la red virtual de Kubernetes, tienes que exponer el pod como un servicio de Kubernetes.

Desde Cloud Shell, puedes exponer el pod a la Internet pública con el comando kubectl expose combinado con la marca --type=LoadBalancer. Esta marca es necesaria para crear una IP accesible de forma externa:

$ kubectl expose deployment hello-micronaut --type=LoadBalancer --port=8080

La marca utilizada en este comando especifica que usarás el balanceador de cargas proporcionado por la infraestructura subyacente (en este caso, el balanceador de cargas de Compute Engine). Tenga en cuenta que se expone la implementación, y no el pod directamente. Esto hará que el servicio resultante balancee la carga de tráfico en todos los Pods administrados por la implementación (en este caso, solo 1 Pod, pero luego podrás agregar más réplicas).

La instancia principal de Kubernetes crea el balanceador de cargas y las reglas de reenvío, las reglas de firewall y los grupos de destino de Compute Engine relacionados para que se pueda acceder al servicio completamente desde fuera de Google Cloud Platform.

Para encontrar la dirección IP accesible de forma pública del servicio, solo debes solicitar que kubectl muestre todos los servicios del clúster:

$ kubectl get services
NAME              TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)          AGE
hello-micronaut   LoadBalancer   10.39.243.251   aaa.bbb.ccc.ddd 8080:30354/TCP   1m
kubernetes        ClusterIP      10.39.240.1     <none>          443/TCP          31m

Observa que se muestran 2 direcciones IP para tu servicio, y ambas entregan contenido al puerto 8080. Una es la IP interna que solo es visible dentro de su red virtual de Cloud; la otra es la IP externa de balanceo de cargas. En este ejemplo, la dirección IP externa es aaa.bbb.ccc.ddd.

Ahora deberías poder acceder al servicio dirigiendo tu navegador a esta dirección: http://<EXTERNAL_IP>:8080/hello

11. Escala tu servicio verticalmente

Una de las características avanzadas que ofrece Kubernetes es la gran facilidad para escalar tu aplicación. Supongamos que, de repente, necesitas más capacidad para tu aplicación. Simplemente puedes indicarle al controlador de replicación que administre una nueva cantidad de réplicas para las instancias de tu aplicación:

$ kubectl scale deployment hello-micronaut --replicas=3
deployment.extensions "hello-micronaut" scaled

$ kubectl get deployment
NAME              DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
hello-micronaut   3         3         3            3           16m

Observa el enfoque declarativo que usamos aquí: en lugar de iniciar o detener nuevas instancias, debes declarar cuántas instancias deberían estar en ejecución constantemente. Los bucles de conciliación de Kubernetes simplemente se aseguran de que la realidad coincida con lo que solicitaste y, de ser necesario, ejecutan acciones.

12. Implementa una actualización para tu servicio

En algún momento, la aplicación que implementó en producción requerirá la corrección de errores o funciones adicionales. Kubernetes lo ayudará a implementar una versión nueva en producción sin afectar a sus usuarios.

Primero, modifiquemos la aplicación. Abre el editor de código desde Cloud Shell.

5aee8f3d1e003571.png

Navega a /jib/examples/micronaut/src/main/groovy/example/micronaut/HelloController.groovy y actualiza el valor de la respuesta:

@Controller("/hello")
class HelloController {
    @Get("/")
    String index() {
        "Hello Kubernetes World"
    }
}

En /jib/examples/micronaut/build.gradle, actualizaremos la versión de nuestra imagen de 0.1 a 0.2 actualizando esta línea:

version '0.2'

Luego, vuelve a compilar y empaquetar la aplicación con los cambios más recientes:

$ ./gradlew jibDockerBuild

Etiqueta y envía la imagen al registro de imágenes de contenedor:

$ docker tag micronaut-jib:0.2 \
         gcr.io/$GOOGLE_CLOUD_PROJECT/micronaut-jib:0.2
$ docker push gcr.io/$GOOGLE_CLOUD_PROJECT/micronaut-jib:0.2

Ahora está listo para que Kubernetes actualice sin problemas su controlador de replicación a una versión nueva de la aplicación. Para cambiar la etiqueta de la imagen de tu contenedor en ejecución, debes editar el hello-micronaut deployment existente y cambiar la imagen de gcr.io/PROJECT_ID/micronaut-jib:0.1 a gcr.io/PROJECT_ID/micronaut-jib:0.2.

Puedes usar el comando kubectl set image para solicitarle a Kubernetes que implemente la versión nueva de tu aplicación en todo el clúster, una instancia a la vez, con una actualización progresiva:

$ kubectl set image deployment/hello-micronaut \
          micronaut-jib=gcr.io/$GOOGLE_CLOUD_PROJECT/micronaut-jib:0.2

deployment.apps "hello-micronaut" image updated

Vuelve a verificar http://EXTERNAL_IP:8080 para ver que muestre la nueva respuesta.

13. Revertir

¿Cometiste un error con una versión nueva de la aplicación? Quizás la nueva versión contenía un error y necesitas revertirla rápidamente. Con Kubernetes, puedes revertir al estado anterior con facilidad. Ejecuta el siguiente comando para revertir la aplicación:

$ kubectl rollout undo deployment/hello-micronaut

Si observas el resultado del servicio, volveremos a nuestro mensaje inicial "Hello World".

14. Resumen

En este paso, configuraste un servicio simple de Hello World de Micronaut basado en Apache Groovy y lo ejecutaste directamente desde Cloud Shell, lo empaquetaste como un contenedor con Jib y lo implementaste en Google Kubernetes Engine.

15. ¡Felicitaciones!

Aprendiste a compilar e implementar un nuevo microservicio basado en la Web de Apache Groovy / Micronaut en Kubernetes en Google Kubernetes Engine.

Más información

Licencia

Este trabajo cuenta con una licencia Atribución 2.0 Genérica de Creative Commons.