Cómo acoplar una aplicación HTTP de Scala y Akka: la forma más fácil

El uso de Docker es un hecho hoy en día. En este tutorial veremos cómo aprender a acoplar nuestras aplicaciones HTTP de Scala y Akka sin ni siquiera crear un Dockerfile nosotros mismos.

Para los propósitos de este tutorial, asumimos que Docker ya está instalado en la máquina. Si no es así, siga la documentación oficial.

Para automatizar la creación del Dockerfile para nuestro proyecto, usaremos el complemento sbt-native-packager.

Puede utilizar cualquier proyecto HTTP de Scala o Akka para este tutorial. Usaremos el siguiente repositorio, no dude en clonarlo y asegúrese de verificar la rama 6.5-testing-directives.

Agregar el complemento

Primero, necesitamos agregar el complemento a nuestro proyecto en el project/plugins.sbtarchivo. Si el archivo no existe, debemos crearlo y luego agregar la siguiente línea:

addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.6")

Entonces necesitamos habilitar el complemento en nuestro build.sbtarchivo. Agregue la siguiente línea en la parte superior:

enablePlugins(JavaAppPackaging)

Habilitar este complemento también nos permite crear un ejecutable para nuestra aplicación. Ejecutar sbt stageen el directorio raíz del proyecto.

Ahora podemos ejecutar nuestra aplicación ejecutando ./target/universal/stage/bin/akkahttp-quickstart. Debería ver un Success!mensaje. Si envía una solicitud GET a localhost:9000/todos, obtendrá un par de tareas pendientes.

Dockerizando nuestra aplicación

Es hora de empezar a jugar con Docker.

Comencemos generando el Dockerfile para nuestra aplicación. Ejecutar sbt docker:stage, luego ejecutar cat target/docker/stage/Dockerfilepara ver su contenido:

FROM openjdk:latestWORKDIR /opt/dockerADD --chown=daemon:daemon opt /optUSER daemonENTRYPOINT ["/opt/docker/bin/akkahttp-quickstart"]CMD []

Es muy sencillo. Termina ejecutando un binario similar al que generamos y ejecutamos anteriormente.

Podemos crear una imagen de Docker usando ese Dockerfile manualmente, pero hay una forma más conveniente de hacerlo. Vamos a ejecutar sbt docker:publishLocal.Como su nombre indica, publicará una imagen de Docker de nuestra aplicación en nuestro registro local.

Ejecutar docker imagesy debería ver la siguiente entrada:

REPOSITORY TAG IMAGE ID CREATED SIZEakkahttp-quickstart 0.1 d03732dd0854 42 seconds ago 774MB

Ahora podemos ejecutar nuestra aplicación usando Docker.

Ejecutar docker run akkahttp-quickstart:0.1, debería ver el Success!mensaje una vez más.

Pero esta vez, si intentamos consultar nuestra aplicación, obtendremos un error:

Ejecutemos docker pspara obtener información sobre nuestro proceso Docker en ejecución (salida abreviada):

CONTAINER ID IMAGE PORTS NAMES9746162d4723 akkahttp-quickstart:0.1 serene_agnesi

Como podemos ver, no hay puertos expuestos, por lo que no hay forma de comunicarse con nuestra aplicación.

Las aplicaciones de Docker se ejecutan en su red de forma predeterminada. Hay varias formas de permitir la comunicación entre los procesos de Docker y la máquina host. La forma más sencilla es exponer un puerto.

Detenga la aplicación en ejecución, ya sea presionando Ctrl-Co ejecutándose docker stop $CONTAINER_ID.

Esta vez, cuando lo ejecutemos, también expondremos el puerto respectivo:

docker run -p 9000:9000 akkahttp-quickstart:0.1

Ahora podemos consultar nuestra aplicación acoplada:

Personalizando nuestra configuración

Hay varias cosas que podríamos querer hacer con la configuración actual que tenemos:

  • ¿Qué pasa si queremos un nombre de imagen diferente?
  • ¿Y si queremos utilizar un puerto diferente?
  • ¿Podemos tener una imagen más clara?

Exploremos estos casos de uso comunes.

Cambiar el nombre de la imagen

Si miramos la documentación oficial del complemento, vemos que hay una serie de opciones que podemos cambiar.

Dale una lectura y mira qué más puedes personalizar.

Para cambiar el nombre de la imagen podemos modificar la packageNamepropiedad en nuestro build.sbtarchivo, agregue la siguiente línea después de la scalaVersionpropiedad:

packageName in Docker := "dockerised-akka-http"

Publiquemos la imagen nuevamente. Corre sbt docker:publishLocal. Podemos comprobar que tenemos una nueva imagen ejecutando docker images:

REPOSITORY TAG IMAGE ID CREATED SIZE akkahttp-quickstart 0.1 d03732dd0854 42 minutes ago 774MB dockerised-akka-http 0.1 d03732dd0854 42 minutes ago 774MB

Ahora tenemos dos imágenes, la original y la nueva. ¡Increíble!

Cambiar el puerto

No podemos cambiar el puerto que escucha nuestra aplicación sin hacer cambios en el código. El puerto está codificado en nuestra aplicación. Idealmente, lo leeríamos desde una variable de entorno y tal vez tengamos una por defecto.

Pero eso está bien. Debido a que nuestra aplicación se ejecuta en una red diferente, podemos asignar un puerto diferente al puerto interno 9000.

When we specify the flag -p 9000:9000 we are saying that the port 9000 in the host machine will map to the port 9000 in our process' network. Let's try changing that.

Run docker run -p 5000:9000 dockerised-akka-http:0.1 to run our new image with a different port.

We can query the todos to make sure it works as expected:

Making our image lighter

For our last experiment, we will try to make our image lighter. At this point it uses over 700MB.

First, let’s increase the version so we get a different tag and can compare them. Then add dockerBaseImage := "openjdk:8-jre-alpine" above where we change the packageName . Our build.sbt now looks like:

enablePlugins(JavaAppPackaging)
name := "akkahttp-quickstart"version := "0.2"scalaVersion := "2.12.6"
dockerBaseImage := "openjdk:8-jre-alpine"packageName in Docker := "dockerised-akka-http"
val akkaVersion = "2.5.13"val akkaHttpVersion = "10.1.3"val circeVersion = "0.9.3"
libraryDependencies ++= Seq( "com.typesafe.akka" %% "akka-actor" % akkaVersion, "com.typesafe.akka" %% "akka-testkit" % akkaVersion % Test, "com.typesafe.akka" %% "akka-stream" % akkaVersion, "com.typesafe.akka" %% "akka-stream-testkit" % akkaVersion % Test, "com.typesafe.akka" %% "akka-http" % akkaHttpVersion, "com.typesafe.akka" %% "akka-http-testkit" % akkaHttpVersion % Test, "io.circe" %% "circe-core" % circeVersion, "io.circe" %% "circe-generic" % circeVersion, "io.circe" %% "circe-parser" % circeVersion, "de.heikoseeberger" %% "akka-http-circe" % "1.21.0", "org.scalatest" %% "scalatest" % "3.0.5" % Test)

We are using a different tag of the openjdk base image to specify that we want to use alpine, which is a lightweight Linux distribution.

Publish the image by running sbt docker:publishLocal. Get the images with docker images . We can see that the image is lighter now:

REPOSITORY TAG IMAGE ID CREATED SIZE dockerised-akka-http 0.2 4688366e70bb 32 seconds ago 119MB akkahttp-quickstart 0.1 d03732dd0854 2 hours ago 774MBdockerised-akka-http 0.1 d03732dd0854 2 hours ago 774MB

Let’s make sure that it still works.

Run docker run -p 5000:9000 dockerised-akka-http:0.2, minding the tag number. It's not working, ad we get the following error:

env: can't execute 'bash': No such file or directory

Apparently, our dockerised application needs bash to run. Reading the plugin’s documentation, we can tell that it generates a bash script that executes our application.

So let’s install bash in our image and try again.

Add the following lines below where we change the packageName in our build.sbt file:

import com.typesafe.sbt.packager.docker._dockerCommands ++= Seq( Cmd("USER", "root"), ExecCmd("RUN", "apk", "add", "--no-cache", "bash"))

We are adding some extra commands to our Dockefile. We are changing the user to root to install the package, and then we install bash.

Let’s try running the application again, docker run -p 5000:9000 dockerised-akka-http:0.2. And it's working now, great!

If we check the images again, the alpine-based one is a bit bigger, like 10MB. That’s nothing compared to the roughly 770MB of the others.

Installing bash in alpine isn’t the worst thing in the world. Some people end up adding it anyway due to their preference and for debugging.

Generating an Ash-compatible executable

Installing bash on our image is a bit of a workaround. Let’s use an additional plugin to generate an executable that is compatible with Alpine. Thanks to Muki Seller for letting us know about this solution!

According to the official documentation, we need to enable the extra plugin AshScriptPlugin.

Modify the build.sbt file to enable both plugins, and remove the previous workaround:

enablePlugins(JavaAppPackaging, AshScriptPlugin)
name := "akkahttp-quickstart"version := "0.3"scalaVersion := "2.12.6"
dockerBaseImage := "openjdk:8-jre-alpine"packageName in Docker := "dockerised-akka-http"
val akkaVersion = "2.5.13"val akkaHttpVersion = "10.1.3"val circeVersion = "0.9.3"
libraryDependencies ++= Seq( "com.typesafe.akka" %% "akka-actor" % akkaVersion, "com.typesafe.akka" %% "akka-testkit" % akkaVersion % Test, "com.typesafe.akka" %% "akka-stream" % akkaVersion, "com.typesafe.akka" %% "akka-stream-testkit" % akkaVersion % Test, "com.typesafe.akka" %% "akka-http" % akkaHttpVersion, "com.typesafe.akka" %% "akka-http-testkit" % akkaHttpVersion % Test,
 "io.circe" %% "circe-core" % circeVersion, "io.circe" %% "circe-generic" % circeVersion, "io.circe" %% "circe-parser" % circeVersion, "de.heikoseeberger" %% "akka-http-circe" % "1.21.0",
 "org.scalatest" %% "scalatest" % "3.0.5" % Test)

We also increased the version so we can compare and avoid overriding the previous one.

Run sbt docker:publishLocal, and then docker run dockerised-akka-http:0.3.

You should see the success message and, if you query for the todos, you should see them as well. Great!

Conclusion

In this tutorial we dockerised a Scala and Akka HTTP application. There was nothing done specifically for this application which means that the setup will work pretty much as it is.

Then we looked at how to accomplish some common use cases by customising our Dockerfile through the plugin.

We even managed to reduce the image size by nearly seven times!

Amazing, isn’t it?

If you liked this tutorial and want to learn how to build an API for a todo application, check out our new free course! ???

Originally published at www.codemunity.io.