В наши дни использование Docker стало обычным явлением. В этом руководстве мы узнаем, как научиться докеризовать наши HTTP-приложения Scala и Akka, без даже создания файла Docker.

В рамках этого руководства мы предполагаем, что Docker уже установлен на машине. Если это не так, следуйте официальной документации.

Чтобы автоматизировать создание Dockerfile для нашего проекта, мы будем использовать плагин sbt-native-packager.

Для этого руководства вы можете использовать любой проект Scala или Akka HTTP. Мы будем использовать следующий репозиторий, не стесняйтесь клонировать его и обязательно проверьте ветку 6.5-testing-directives.

Добавление плагина

Во-первых, нам нужно добавить плагин в наш проект в project/plugins.sbt файле. Если файл не существует, нам нужно создать его, а затем добавить следующую строку:

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

Затем нам нужно включить плагин в нашем build.sbt файле. Добавьте следующую строку вверху:

enablePlugins(JavaAppPackaging)

Включение этого плагина также позволяет нам создать исполняемый файл для нашего приложения. Запустите sbt stage в корневом каталоге проекта.

Теперь мы можем выполнить наше приложение, запустив ./target/universal/stage/bin/akkahttp-quickstart. Вы должны увидеть сообщение Success!. Если вы отправите запрос GET на localhost:9000/todos, вы получите пару задач.

Докеризация нашего приложения

Пришло время поиграть с Docker.

Начнем с создания Dockerfile для нашего приложения. Запустите sbt docker:stage, затем запустите cat target/docker/stage/Dockerfile, чтобы увидеть его содержимое:

FROM openjdk:latest
WORKDIR /opt/docker
ADD --chown=daemon:daemon opt /opt
USER daemon
ENTRYPOINT ["/opt/docker/bin/akkahttp-quickstart"]
CMD []

Все очень просто. В итоге он запускает двоичный файл, аналогичный тому, который мы сгенерировали и запустили ранее.

Мы можем создать образ Docker, используя этот файл Docker вручную, но есть более удобный способ сделать это. Давайте запустим sbt docker:publishLocal. Как следует из названия, он опубликует Docker-образ нашего приложения в нашем локальном реестре.

Запустите docker images, и вы должны увидеть следующую запись:

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

Теперь мы можем запустить наше приложение с помощью Docker.

Запустите docker run akkahttp-quickstart:0.1, вы снова должны увидеть сообщение Success!.

Но на этот раз, если мы попытаемся запросить наше приложение, мы получим ошибку:

Давайте запустим docker ps, чтобы получить некоторую информацию о нашем запущенном процессе Docker (вывод сокращен):

CONTAINER ID     IMAGE                       PORTS            NAMES
9746162d4723     akkahttp-quickstart:0.1                      serene_agnesi

Как мы видим, нет открытых портов, поэтому нет возможности взаимодействовать с нашим приложением.

Приложения в Docker по умолчанию запускаются в их сети. Существует несколько способов разрешить обмен данными между процессами Docker и хост-компьютером. Самый простой способ - выставить порт.

Остановите запущенное приложение, нажав Ctrl-C или запустив docker stop $CONTAINER_ID.

На этот раз, когда мы запустим его, мы также выставим соответствующий порт:

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

Теперь мы можем запросить наше докеризованное приложение:

Настройка нашей установки

Есть несколько вещей, которые мы могли бы сделать с текущими настройками, которые у нас есть:

  • Что, если нам нужно другое имя изображения?
  • Что, если мы захотим использовать другой порт?
  • Можно ли сделать изображение светлее?

Давайте рассмотрим эти распространенные варианты использования.

Изменение имени изображения

Если мы заглянем в официальную документацию плагина, то увидим, что есть ряд параметров, которые мы можем изменить.

Прочтите его и посмотрите, что еще вы можете настроить.

Чтобы изменить имя изображения, мы можем изменить свойство packageName в нашем build.sbt файле, добавив следующую строку после свойства scalaVersion:

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

Опубликуем изображение еще раз. Запустите sbt docker:publishLocal. Мы можем проверить наличие нового образа, запустив 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

Теперь у нас есть два изображения, исходное и новое. Потрясающие!

Смена порта

Мы не можем изменить порт, который прослушивает наше приложение, не внося изменений в код. Порт жестко запрограммирован в нашем приложении. В идеале мы должны читать его из переменной окружения и, возможно, использовать ее по умолчанию.

Но это нормально. Поскольку наше приложение работает в другой сети, мы можем сопоставить другой порт с внутренним портом 9000.

Когда мы указываем флаг -p 9000:9000, мы говорим, что порт 9000 на хост-машине будет отображаться на порт 9000 в сети нашего процесса. Попробуем это изменить.

Запустите docker run -p 5000:9000 dockerised-akka-http:0.1, чтобы запустить наш новый образ с другим портом.

Мы можем запросить todos, чтобы убедиться, что он работает должным образом:

Делаем наш образ светлее

В нашем последнем эксперименте мы постараемся сделать изображение светлее. На данный момент он использует более 700 МБ.

Во-первых, давайте увеличим версию, чтобы получить другой тег и сравнить их. Затем добавьте dockerBaseImage := "openjdk:8-jre-alpine" выше, где мы меняем packageName. Наш build.sbt теперь выглядит так:

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
)

Мы используем другой тег базового образа openjdk, чтобы указать, что мы хотим использовать alpine, который является облегченным дистрибутивом Linux.

Опубликуйте изображение, запустив sbt docker:publishLocal. Получите изображения с docker images. Мы видим, что изображение стало светлее:

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

Убедимся, что он по-прежнему работает.

Запустите docker run -p 5000:9000 dockerised-akka-http:0.2, обращая внимание на номер тега. Не работает, мы получаем следующую ошибку:

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

Очевидно, нашему докеризованному приложению требуется bash для запуска. Читая документацию по плагину, мы можем сказать, что он генерирует bash-скрипт, который выполняет наше приложение.

Итак, давайте установим bash в наш образ и попробуем еще раз.

Добавьте следующие строки ниже, где мы меняем packageName в нашем build.sbt файле:

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

Мы добавляем несколько дополнительных команд в наш Dockefile. Мы меняем пользователя на root для установки пакета, а затем устанавливаем bash.

Попробуем снова запустить приложение, docker run -p 5000:9000 dockerised-akka-http:0.2. И теперь он работает, отлично!

Если мы снова проверим изображения, то изображение на основе alpine будет немного больше, например, 10 МБ. Это ничто по сравнению с примерно 770 МБ других.

Установка bash в alpine - не самое худшее в мире. Некоторые люди все равно добавляют его из-за своих предпочтений и для отладки.

Создание исполняемого файла, совместимого с Ash

Установка bash в наш образ - это своего рода обходной путь. Давайте воспользуемся дополнительным плагином для создания исполняемого файла, совместимого с Alpine. Спасибо Muki Seller за то, что сообщили нам об этом решении!

Согласно официальной документации, нам нужно включить дополнительный плагин AshScriptPlugin.

Измените файл build.sbt, чтобы включить оба плагина, и удалите предыдущий обходной путь:

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
)

Мы также увеличили версию, чтобы мы могли сравнивать и не переопределять предыдущую.

Запустите sbt docker:publishLocal, а затем docker run dockerised-akka-http:0.3.

Вы должны увидеть сообщение об успешном выполнении, и, если вы запросите задачи, вы также должны увидеть их. Большой!

Заключение

В этом руководстве мы докерили приложение Scala и Akka HTTP. Специально для этого приложения ничего не было сделано, а это значит, что установка будет работать в основном без изменений.

Затем мы рассмотрели, как реализовать некоторые распространенные варианты использования, настроив наш Dockerfile через плагин.

Нам даже удалось уменьшить размер изображения почти в семь раз!

Удивительно, не правда ли?

Если вам понравился этот учебник и вы хотите узнать, как создать API для приложения todo, ознакомьтесь с нашим новым бесплатным курсом! 😁👇🏽

Первоначально опубликовано на www.codemunity.io.