Sådan docker du en Scala- og Akka HTTP-applikation - den nemme måde

Brug af Docker er en given i dag. I denne vejledning vil vi lære at dockerisere vores Scala- og Akka HTTP-applikationer uden selv at oprette en Dockerfile selv.

Med henblik på denne vejledning antager vi, at Docker allerede er installeret på maskinen. Hvis det ikke er tilfældet, skal du følge den officielle dokumentation.

For at automatisere oprettelsen af ​​Dockerfile til vores projekt bruger vi pluginet sbt-native-packager.

Du kan bruge ethvert Scala- eller Akka HTTP-projekt til denne vejledning. Vi bruger følgende arkiv, er du velkommen til at klone det og sørg for at tjekke filialen 6.5-testing-directives.

Tilføjelse af plugin

Først skal vi tilføje pluginet til vores projekt i project/plugins.sbtfilen. Hvis filen ikke findes, skal vi oprette den og derefter tilføje følgende linje:

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

Så er vi nødt til at aktivere pluginet i vores build.sbtfil. Tilføj følgende linje øverst:

enablePlugins(JavaAppPackaging)

Aktivering af dette plugin giver os også mulighed for at oprette en eksekverbar fil til vores applikation. Kør sbt stagei projektets rodmappe.

Nu kan vi udføre vores ansøgning ved at køre ./target/universal/stage/bin/akkahttp-quickstart. Du skal se en Success!besked. Hvis du sender en GET-anmodning til localhost:9000/todos, får du et par todos.

Dockeriserer vores ansøgning

Det er tid til at begynde at lege med Docker.

Lad os starte med at generere Dockerfile til vores applikation. Kør sbt docker:stage, og kør derefter for cat target/docker/stage/Dockerfileat se dens indhold:

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

Det er ret simpelt. Det ender med at køre en lignende binær som den, vi genererede og kørte tidligere.

Vi kan oprette et Docker-billede ved hjælp af den Dockerfile manuelt, men der er en mere bekvem måde at gøre det på. Lad os køre, sbt docker:publishLocal.som navnet antyder, vil det offentliggøre et Docker-billede af vores applikation til vores lokale register.

Kør, docker imagesog du skal se følgende post:

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

Vi kan nu køre vores applikation ved hjælp af Docker.

Kør docker run akkahttp-quickstart:0.1, du skal se Success!meddelelsen igen.

Men denne gang, hvis vi prøver at forespørge vores ansøgning, får vi en fejl:

Lad os løbe for docker psat få nogle oplysninger om vores kørende Docker-proces (output forkortet):

CONTAINER ID IMAGE PORTS NAMES9746162d4723 akkahttp-quickstart:0.1 serene_agnesi

Som vi kan se, er der ingen porte eksponeret, så der er ingen måde at kommunikere med vores applikation på.

Applikationer i Docker køres som standard i deres netværk. Der er flere måder at tillade kommunikation mellem Docker-processer og værtsmaskinen. Den enkleste måde er at udsætte en havn.

Stop den kørende applikation, enten ved at trykke Ctrl-Celler ved at køre docker stop $CONTAINER_ID.

Denne gang, når vi kører det, udsætter vi også den respektive havn:

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

Vi er nu i stand til at forespørge vores dockeriserede applikation:

Tilpasning af vores opsætning

Der er flere ting, vi måske vil gøre med den nuværende opsætning, vi har:

  • Hvad hvis vi vil have et andet billednavn?
  • Hvad hvis vi vil bruge en anden port?
  • Kan vi få et lysere billede?

Lad os undersøge disse almindelige brugssager.

Ændring af billednavnet

Hvis vi ser på plugins officielle dokumentation, ser vi, at der er en række muligheder, vi kan ændre.

Læs det, og se hvad du ellers kan tilpasse.

For at ændre billednavnet kan vi ændre packageNameegenskaben i vores build.sbtfil ved at tilføje følgende linje efter scalaVersionegenskaben:

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

Lad os offentliggøre billedet igen. Kør sbt docker:publishLocal. Vi kan kontrollere, at vi har et nyt billede ved at køre 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

Nu har vi to billeder, den originale og den nye. Fantastisk!

Ændring af havn

Vi kan ikke ændre den port, som vores applikation lytter til, uden at foretage kodeændringer. Havnen er hårdkodet i vores applikation. Ideelt set ville vi læse det fra en miljøvariabel og måske have en som standard.

Men det er okay. Da vores applikation kører i et andet netværk, kan vi kortlægge en anden port til den interne 9000-port.

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?

Hvis du kunne lide denne tutorial og vil lære at oprette en API til en todo-applikation, skal du tjekke vores nye gratis kursus! ???

Oprindeligt offentliggjort på www.codemunity.io.