Escalado de Scala: cómo dockerizar usando Kubernetes

Publicado: 2022-03-11

Kubernetes es el nuevo chico del bloque y promete ayudar a implementar aplicaciones en la nube y escalarlas más rápidamente. Hoy en día, al desarrollar una arquitectura de microservicios, es bastante estándar elegir Scala para crear servidores API.

Los microservicios están reemplazando los servidores back-end monolíticos clásicos con múltiples servicios independientes que se comunican entre sí y tienen sus propios procesos y recursos.

Si hay una aplicación de Scala en sus planes y desea escalarla a una nube, entonces está en el lugar correcto. En este artículo, mostraré paso a paso cómo tomar una aplicación genérica de Scala e implementar Kubernetes con Docker para iniciar varias instancias de la aplicación. El resultado final será una sola aplicación implementada como varias instancias y carga equilibrada por Kubernetes.

Todo esto se implementará simplemente importando el kit fuente de Kubernetes en su aplicación Scala. Tenga en cuenta que el kit oculta muchos detalles complicados relacionados con la instalación y la configuración, pero es lo suficientemente pequeño como para ser legible y fácil de entender si desea analizar lo que hace. Para simplificar, implementaremos todo en su máquina local. Sin embargo, la misma configuración es adecuada para una implementación en la nube del mundo real de Kubernetes.

Escale su aplicación Scala con Kubernetes

Sea inteligente y duerma tranquilo, escale su Docker con Kubernetes.
Pío

¿Qué es Kubernetes?

Antes de entrar en los detalles sangrientos de la implementación, analicemos qué es Kubernetes y por qué es importante.

Es posible que ya haya oído hablar de Docker. En cierto sentido, es una máquina virtual ligera.

Docker ofrece la ventaja de implementar cada servidor en un entorno aislado, muy similar a una máquina virtual independiente, sin la complejidad de administrar una máquina virtual completa.

Por estos motivos, ya es una de las herramientas más utilizadas para el despliegue de aplicaciones en la nube. Una imagen de Docker es bastante fácil y rápida de construir y duplicar, mucho más fácil que una máquina virtual tradicional como VMWare, VirtualBox o XEN.

Kubernetes complementa a Docker y ofrece un entorno completo para administrar aplicaciones dockerizadas. Al usar Kubernetes, puede implementar, configurar, orquestar, administrar y monitorear fácilmente cientos o incluso miles de aplicaciones de Docker.

Kubernetes es una herramienta de código abierto desarrollada por Google y ha sido adoptada por muchos otros proveedores. Kubernetes está disponible de forma nativa en la plataforma en la nube de Google, pero otros proveedores también lo han adoptado para sus servicios en la nube OpenShift. Se puede encontrar en Amazon AWS, Microsoft Azure, RedHat OpenShift e incluso más tecnologías en la nube. Podemos decir que está bien posicionado para convertirse en un estándar para implementar aplicaciones en la nube.

requisitos previos

Ahora que cubrimos los conceptos básicos, verifiquemos si tiene instalado todo el software de requisitos previos. En primer lugar, necesita Docker. Si está utilizando Windows o Mac, necesita Docker Toolbox. Si está utilizando Linux, debe instalar el paquete particular provisto por su distribución o simplemente seguir las instrucciones oficiales.

Vamos a codificar en Scala, que es un lenguaje JVM. Por supuesto, necesita el kit de desarrollo de Java y la herramienta scala SBT instalada y disponible en la ruta global. Si ya es un programador de Scala, es probable que ya tenga esas herramientas instaladas.

Si usa Windows o Mac, Docker creará de manera predeterminada una máquina virtual denominada default con solo 1 GB de memoria, que puede ser demasiado pequeña para ejecutar Kubernetes. En mi experiencia, tuve problemas con la configuración predeterminada. Le recomiendo que abra la GUI de VirtualBox, seleccione el default de su máquina virtual y cambie la memoria a al menos 2048 MB.

Configuración de memoria de VirtualBox

La aplicación para agrupar

Las instrucciones de este tutorial se pueden aplicar a cualquier aplicación o proyecto de Scala. Para que este artículo tenga algo de "carne" en la que trabajar, elegí un ejemplo que se usa muy a menudo para demostrar un microservicio REST simple en Scala, llamado Akka HTTP. Le recomiendo que intente aplicar el kit fuente al ejemplo sugerido antes de intentar usarlo en su aplicación. He probado el kit con la aplicación de demostración, pero no puedo garantizar que no haya conflictos con su código.

Entonces, primero, comenzamos clonando la aplicación de demostración:

 git clone https://github.com/theiterators/akka-http-microservice

A continuación, prueba si todo funciona correctamente:

 cd akka-http-microservice sbt run

Luego, acceda a http://localhost:9000/ip/8.8.8.8 , y debería ver algo como en la siguiente imagen:

El microservicio Akka HTTP se está ejecutando

Adición del kit fuente

Ahora, podemos agregar el kit fuente con algo de magia de Git:

 git remote add ScalaGoodies https://github.com/sciabarra/ScalaGoodies git fetch --all git merge ScalaGoodies/kubernetes

Con eso, tiene la demostración que incluye el kit fuente y está listo para probar. O incluso puede copiar y pegar el código desde allí en su aplicación.

Una vez que haya fusionado o copiado los archivos en sus proyectos, estará listo para comenzar.

Iniciar Kubernetes

Una vez que haya descargado el kit, necesitamos descargar el binario kubectl necesario, ejecutando:

 bin/install.sh

Este instalador es lo suficientemente inteligente (con suerte) para descargar el binario kubectl correcto para OSX, Linux o Windows, según su sistema. Tenga en cuenta que el instalador funcionó en los sistemas que poseo. Informe cualquier problema para que pueda arreglar el kit.

Una vez que haya instalado el binario kubectl , puede iniciar Kubernetes completo en su Docker local. Solo corre:

 bin/start-local-kube.sh

La primera vez que se ejecuta, este comando descargará las imágenes de toda la pila de Kubernetes y se necesita un registro local para almacenar sus imágenes. Puede tomar algún tiempo, así que tenga paciencia. También tenga en cuenta que necesita acceso directo a Internet. Si está detrás de un proxy, será un problema ya que el kit no admite proxies. Para resolverlo, debe configurar las herramientas como Docker, curl, etc. para usar el proxy. Es lo suficientemente complicado como para recomendar obtener un acceso temporal sin restricciones.

Suponiendo que pudo descargar todo con éxito, para verificar si Kubernetes funciona bien, puede escribir el siguiente comando:

 bin/kubectl get nodes

La respuesta esperada es:

 NAME STATUS AGE 127.0.0.1 Ready 2m

Tenga en cuenta que la edad puede variar, por supuesto. Además, dado que iniciar Kubernetes puede llevar algún tiempo, es posible que deba invocar el comando un par de veces antes de ver la respuesta. Si no obtiene errores aquí, felicitaciones, tiene Kubernetes funcionando en su máquina local.

Dockerización de su aplicación Scala

Ahora que tiene Kubernetes en funcionamiento, puede implementar su aplicación en él. En los viejos tiempos, antes de Docker, tenía que implementar un servidor completo para ejecutar su aplicación. Con Kubernetes, todo lo que necesita hacer para implementar su aplicación es:

  • Cree una imagen de Docker.
  • Empújelo en un registro desde donde se puede iniciar.
  • Inicie la instancia con Kubernetes, que tomará la imagen del registro.

Afortunadamente, es mucho menos complicado de lo que parece, especialmente si está utilizando la herramienta de compilación SBT como muchos lo hacen.

En el kit, incluí dos archivos que contenían todas las definiciones necesarias para crear una imagen capaz de ejecutar aplicaciones Scala, o al menos lo que se necesita para ejecutar la demostración HTTP de Akka. No puedo garantizar que funcione con otras aplicaciones de Scala, pero es un buen punto de partida y debería funcionar para muchas configuraciones diferentes. Los archivos a buscar para construir la imagen de Docker son:

 docker.sbt project/docker.sbt

Echemos un vistazo a lo que hay en ellos. El archivo project/docker.sbt contiene el comando para importar el sbt-docker :

 addSbtPlugin("se.marcuslonnberg" % "sbt-docker" % "1.4.0")

Este complemento administra la creación de la imagen de Docker con SBT por usted. La definición de Docker está en el archivo docker.sbt y tiene este aspecto:

 imageNames in docker := Seq(ImageName("localhost:5000/akkahttp:latest")) dockerfile in docker := { val jarFile: File = sbt.Keys.`package`.in(Compile, packageBin).value val classpath = (managedClasspath in Compile).value val mainclass = mainClass.in(Compile, packageBin).value.getOrElse(sys.error("Expected exactly one main class")) val jarTarget = s"/app/${jarFile.getName}" val classpathString = classpath.files.map("/app/" + _.getName) .mkString(":") + ":" + jarTarget new Dockerfile { from("anapsix/alpine-java:8") add(classpath.files, "/app/") add(jarFile, jarTarget) entryPoint("java", "-cp", classpathString, mainclass) } }

Para comprender completamente el significado de este archivo, debe conocer Docker lo suficientemente bien como para comprender este archivo de definición. Sin embargo, no vamos a entrar en los detalles del archivo de definición de Docker, porque no es necesario que lo entienda a fondo para crear la imagen.

La belleza de usar SBT para construir la imagen de Docker es que
el SBT se encargará de recopilar todos los archivos por usted.

Tenga en cuenta que el classpath se genera automáticamente con el siguiente comando:

 val classpath = (managedClasspath in Compile).value

En general, es bastante complicado reunir todos los archivos JAR para ejecutar una aplicación. Con SBT, el archivo Docker se generará con add(classpath.files, "/app/") . De esta forma, SBT recopila todos los archivos JAR por usted y construye un Dockerfile para ejecutar su aplicación.

Los otros comandos reúnen las piezas que faltan para crear una imagen de Docker. La imagen se creará utilizando una imagen APT existente para ejecutar programas Java ( anapsix/alpine-java:8 , disponible en Internet en Docker Hub). Otras instrucciones son agregar los otros archivos para ejecutar su aplicación. Finalmente, al especificar un punto de entrada, podemos ejecutarlo. Tenga en cuenta también que el nombre comienza con localhost:5000 a propósito, porque localhost:5000 es donde instalé el registro en el script start-kube-local.sh .

Creación de la imagen de Docker con SBT

Para construir la imagen de Docker, puede ignorar todos los detalles del Dockerfile. Solo necesitas teclear:

 sbt dockerBuildAndPush

El sbt-docker luego creará una imagen de Docker para usted, descargando de Internet todas las piezas necesarias, y luego lo enviará a un registro de Docker que se inició antes, junto con la aplicación Kubernetes en localhost . Entonces, todo lo que necesita es esperar un poco más para tener su imagen cocinada y lista.

Tenga en cuenta que si tiene problemas, lo mejor que puede hacer es restablecer todo a un estado conocido ejecutando los siguientes comandos:

 bin/stop-kube-local.sh bin/start-kube-local.sh

Esos comandos deberían detener todos los contenedores y reiniciarlos correctamente para que su registro esté listo para recibir la imagen creada y enviada por sbt .

Inicio del servicio en Kubernetes

Ahora que la aplicación está empaquetada en un contenedor y empujada en un registro, estamos listos para usarla. Kubernetes usa la línea de comandos y los archivos de configuración para administrar el clúster. Dado que las líneas de comando pueden volverse muy largas y también poder replicar los pasos, estoy usando los archivos de configuración aquí. Todas las muestras del kit fuente están en la carpeta kube .

Nuestro siguiente paso es lanzar una única instancia de la imagen. Una imagen en ejecución se llama, en el lenguaje de Kubernetes, un pod . Así que vamos a crear un pod invocando el siguiente comando:

 bin/kubectl create -f kube/akkahttp-pod.yml

Ahora puede inspeccionar la situación con el comando:

 bin/kubectl get pods

Deberías ver:

 NAME READY STATUS RESTARTS AGE akkahttp 1/1 Running 0 33s k8s-etcd-127.0.0.1 1/1 Running 0 7d k8s-master-127.0.0.1 4/4 Running 0 7d k8s-proxy-127.0.0.1 1/1 Running 0 7d

El estado en realidad puede ser diferente, por ejemplo, "ContainerCreating", pueden pasar unos segundos antes de que se convierta en "En ejecución". Además, puede obtener otro estado como "Error" si, por ejemplo, olvida crear la imagen antes.

También puede verificar si su pod se está ejecutando con el comando:

 bin/kubectl logs akkahttp

Debería ver una salida que termina con algo como esto:

 [DEBUG] [05/30/2016 12:19:53.133] [default-akka.actor.default-dispatcher-5] [akka://default/system/IO-TCP/selectors/$a/0] Successfully bound to /0:0:0:0:0:0:0:0:9000

Ahora tiene el servicio funcionando dentro del contenedor. Sin embargo, el servicio aún no está disponible. Este comportamiento es parte del diseño de Kubernetes. Su pod se está ejecutando, pero debe exponerlo explícitamente. De lo contrario, el servicio está destinado a ser interno.

Creación de un servicio

Crear un servicio y comprobar el resultado es cuestión de ejecutar:

 bin/kubectl create -f kube/akkahttp-service.yaml bin/kubectl get svc

Debería ver algo como esto:

 NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE akkahttp-service 10.0.0.54 9000/TCP 44s kubernetes 10.0.0.1 <none> 443/TCP 3m

Tenga en cuenta que el puerto puede ser diferente. Kubernetes asignó un puerto para el servicio y lo inició. Si está utilizando Linux, puede abrir directamente el navegador y escribir http://10.0.0.54:9000/ip/8.8.8.8 para ver el resultado. Si usa Windows o Mac con Docker Toolbox, la IP es local para la máquina virtual que ejecuta Docker y, lamentablemente, aún no se puede acceder a ella.

Quiero enfatizar aquí que esto no es un problema de Kubernetes, sino que es una limitación de Docker Toolbox, que a su vez depende de las restricciones impuestas por máquinas virtuales como VirtualBox, que actúan como una computadora dentro de otra computadora. Para superar esta limitación, necesitamos crear un túnel. Para facilitar las cosas, incluí otra secuencia de comandos que abre un túnel en un puerto arbitrario para llegar a cualquier servicio que implementemos. Puede escribir el siguiente comando:

 bin/forward-kube-local.sh akkahttp-service 9000

Tenga en cuenta que el túnel no se ejecutará en segundo plano, debe mantener la ventana de la terminal abierta todo el tiempo que la necesite y cerrarla cuando ya no necesite el túnel. Mientras se ejecuta el túnel, puede abrir: http://localhost:9000/ip/8.8.8.8 y finalmente ver la aplicación ejecutándose en Kubernetes.

Toque final: Escala

Hasta ahora hemos “simplemente” puesto nuestra aplicación en Kubernetes. Si bien es un logro emocionante, no agrega demasiado valor a nuestra implementación. Nos ahorramos el esfuerzo de subir e instalar en un servidor y configurar un servidor proxy para ello.

Donde brilla Kubernetes es en la escalabilidad. Puede implementar dos, diez o cien instancias de nuestra aplicación simplemente cambiando la cantidad de réplicas en el archivo de configuración. Hagamoslo.

Vamos a detener el único pod y comenzar una implementación en su lugar. Así que vamos a ejecutar los siguientes comandos:

 bin/kubectl delete -f kube/akkahttp-pod.yml bin/kubectl create -f kube/akkahttp-deploy.yaml

A continuación, compruebe el estado. Una vez más, puede intentarlo un par de veces porque la implementación puede tardar un tiempo en realizarse:

 NAME READY STATUS RESTARTS AGE akkahttp-deployment-4229989632-mjp6u 1/1 Running 0 16s akkahttp-deployment-4229989632-s822x 1/1 Running 0 16s k8s-etcd-127.0.0.1 1/1 Running 0 6d k8s-master-127.0.0.1 4/4 Running 0 6d k8s-proxy-127.0.0.1 1/1 Running 0 6d

Ahora tenemos dos pods, no uno. Esto se debe a que en el archivo de configuración que proporcioné, está el valor replica: 2 , con dos nombres diferentes generados por el sistema. No voy a entrar en los detalles de los archivos de configuración, porque el alcance del artículo es simplemente una introducción para que los programadores de Scala se inicien en Kubernetes.

De todos modos, ahora hay dos pods activos. Lo interesante es que el servicio es el mismo que antes. Configuramos el servicio para equilibrar la carga entre todos los pods etiquetados como akkahttp . Esto significa que no tenemos que volver a implementar el servicio, pero podemos reemplazar la instancia única con una replicada.

Podemos verificar esto iniciando el proxy nuevamente (si está en Windows y lo ha cerrado):

 bin/forward-kube-local.sh akkahttp-service 9000

Luego, podemos intentar abrir dos ventanas de terminal y ver los registros de cada pod. Por ejemplo, en el primer tipo:

 bin/kubectl logs -f akkahttp-deployment-4229989632-mjp6u

Y en el segundo tipo:

 bin/kubectl logs -f akkahttp-deployment-4229989632-s822x

Por supuesto, edite la línea de comando de acuerdo con los valores que tiene en su sistema.

Ahora, intente acceder al servicio con dos navegadores diferentes. Debería esperar ver que las solicitudes se dividan entre los múltiples servidores disponibles, como en la siguiente imagen:

Kubernets en acción

Conclusión

Hoy apenas arañamos la superficie. Kubernetes ofrece muchas más posibilidades, incluido el escalado y el reinicio automatizados, las implementaciones incrementales y los volúmenes. Además, la aplicación que usamos como ejemplo es muy simple, sin estado y las distintas instancias no necesitan conocerse entre sí. En el mundo real, las aplicaciones distribuidas necesitan conocerse entre sí y deben cambiar las configuraciones de acuerdo con la disponibilidad de otros servidores. De hecho, Kubernetes ofrece un almacén de claves distribuido ( etcd ) para permitir que diferentes aplicaciones se comuniquen entre sí cuando se implementan nuevas instancias. Sin embargo, este ejemplo es lo suficientemente pequeño y simplificado a propósito para ayudarlo a comenzar, centrándose en las funcionalidades principales. Si sigue el tutorial, debería poder obtener un entorno de trabajo para su aplicación Scala en su máquina sin confundirse con una gran cantidad de detalles y perderse en la complejidad.

Relacionados:
  • Desarrollo para la nube en la nube: BigData Development con Docker en AWS
  • K8s/Kubernetes: AWS frente a GCP frente a Azure
  • Una comparación de la malla de servicios de Kubernetes