Статьи

Docker для разработчиков Java: Docker через командную строку

Эта статья является частью нашего курса Академии под названием Docker Tutorial для разработчиков Java .

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

1. Введение

В этом разделе руководства мы собираемся освоить швейцарский армейский нож Docker , его инструмент командной строки с одноименным docker и его лучший друг docker-compose . Чтобы отдать должное этим инструментам, каждый из них поддерживает множество различных аргументов и параметров командной строки, поэтому обсуждение всех из них сделает этот раздел буквально бесконечным. Вместо этого мы сосредоточимся на наиболее полезных классах команд, указывая на соответствующие разделы документации на тот случай, если вы захотите узнать больше сразу.

Docker развивается очень быстро, и как таковые, docker и docker-compose также постоянно меняются, добавляя некоторые новые аргументы командной строки, в то же время осуждая другие. Разрывные изменения не так уж редки, но с реальностью пользователи Docker уже давно сталкиваются.

Одно из самых больших изменений, которые вступили в силу в последнее время, касалось именно инструментов командной строки. В прошлом докер принимал одну команду в качестве первого аргумента, за которым следовал список опций. Тем не менее, количество различных команд стало настолько большим, что инструмент стал действительно запутанным и громоздким в использовании. Было решено разбить команды на классы (например, изображение , контейнер , сеть , том , плагин , система и т. Д.), Чтобы каждой команде предшествовал ее класс (например, сборка docker становится сборкой образа docker ). Это было действительно необходимое изменение, и хотя использование команд в старом стиле все еще поддерживается, мы будем придерживаться рекомендуемых методов в этом руководстве.

Раздел структурирован таким образом, чтобы познакомить вас с наиболее полезными командами, не вдаваясь в подробности и сценарии использования. Смысл довольно прост, хотя, в следующих разделах урока мы собираемся протестировать большинство (если не все) из них, выполняя действительно практичные вещи.

2. Изображения

Логично начать с самого низа и познакомиться с докером , научившись создавать изображения. В первом разделе руководства мы кратко рассмотрели процесс, но пришло время сделать это самостоятельно, используя команду build .

1
docker image build [OPTIONS] PATH | URL | -

Как мы помним, docker строит образы из Dockerfile , выполняя каждую инструкцию в указанном порядке. Параметры PATH или URL — это подсказки, где искать Dockerfile .

Создание небольших и эффективных изображений и их быстрое выполнение — одна из ключевых целей, к которой нужно стремиться, чтобы добиться успеха в Docker . Кэширование изображений, маркировка, тегирование, уменьшение конечного размера изображения и многие другие темы хорошо обобщены в передовых практиках написания Dockerfiles , настоятельно рекомендуется прочитать это чтение.

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

На данный момент, объективно, Alpine Linux является де-факто базовым дистрибутивом Linux для контейнерных приложений. Если вам довелось запускать ваши приложения в OpenJDK , вам действительно повезло, так как официальный репозиторий проекта DockerHub предоставляет множество образов на основе Alpine Linux . Вкратце , вот самый простой пример Dockerfile :

1
2
FROM openjdk:8u131-jdk-alpine
CMD ["java", "-version"]

Предполагая, что ваша оболочка указывает на ту же папку, где находится этот Dockerfile , вы можете построить (и также пометить) образ с помощью этой команды:

1
docker image build . --tag base:openjdk-131-jdk

Если вы делаете ставку на дистрибутивы Oracle JVM , к сожалению, Alpine Linux официально еще не поддерживается (хотя вы можете видеть, что некоторые люди пытаются объединить эти два вместе, имейте в виду, что это возможно, но процесс JVM в контейнере может крушение в любое время). Здесь вы можете использовать официальный образ Oracle Java 8 SE (Server JRE) или создать свой собственный на основе дистрибутивов Ubuntu или Debian .

С точки зрения того, на какую версию JVM опираться, пожалуйста, обязательно используйте хотя бы Java 8 с обновлением 131 или более поздней версии, по причинам, которые мы подробно обсудим в следующих разделах (но если вам интересно, вот краткий обзор позади занавес ).

Наряду со сборкой есть пара очень полезных команд, о которых стоит упомянуть. Команда ls показывает все изображения:

1
docker image ls [OPTIONS] [REPOSITORY[:TAG]]

Команда history показывает историю изображения:

1
docker image history [OPTIONS] IMAGE

Пока команда rm удаляет одно или несколько изображений:

1
docker image rm [OPTIONS] IMAGE [IMAGE...]

Для взаимодействия с реестрами существуют команды pull и push . Первый загружает изображение из реестра, а второй загружает его в реестр.

1
2
docker image pull [OPTIONS] NAME[:TAG|@DIGEST]
docker image push [OPTIONS] NAME[:TAG]

И, наконец, исключительно полезная команда prune удаляет все неиспользуемые изображения (которые лучше использовать с опцией -a постоянно):

1
docker image prune [OPTIONS]

Одной из чрезвычайно полезных функций, представленных Docker в недавнем выпуске, является поддержка многоэтапных сборок, которая позволяет иметь несколько операторов FROM в Dockerfile . Мы бы просто упомянули об этом здесь, но он вернется к нам в следующих разделах руководства.

3. Контейнеры

Функциональное управление контейнером составляет большую часть Docker, и существует множество различных команд для его резервного копирования. Давайте начнем с разбора супер мощной команды run , которая порождает новый контейнер:

1
docker container run [OPTIONS] IMAGE [COMMAND] [ARG...]

Чтобы понять, насколько это просто, мы можем запустить контейнер, используя наше изображение на основе OpenJDK, которое мы создали ранее:

1
2
3
4
5
$ docker container run base:openjdk-131-jdk
 
openjdk version "1.8.0_131"
OpenJDK Runtime Environment (IcedTea 3.4.0) (Alpine 8.131.11-r2)
OpenJDK 64-Bit Server VM (build 25.131-b11, mixed mode)

После запуска контейнеров вы можете присоединиться к ним или использовать команды start / stop / restart для управления их жизненным циклом.

1
2
3
4
docker container attach [OPTIONS] CONTAINER
docker container start [OPTIONS] CONTAINER [CONTAINER...]
docker container stop [OPTIONS] CONTAINER [CONTAINER...]
docker container restart [OPTIONS] CONTAINER [CONTAINER...]

Кроме того, с помощью команд pause / unpause вы можете контролировать состояние процессов в контейнерах.

1
2
docker container pause CONTAINER [CONTAINER...]
docker container unpause CONTAINER [CONTAINER...]

Вероятно, команда ls будет наиболее используемой, поскольку она перечисляет все запущенные контейнеры (и с опцией -a все контейнеры, работающие и остановленные):

1
docker container ls [OPTIONS]

Следовательно, команда inspect отображает подробную информацию об одном или нескольких контейнерах:

1
docker container inspect [OPTIONS] CONTAINER [CONTAINER...]

Команда stats предназначена для предоставления статистики времени выполнения о контейнере. Команда top показывает запущенные процессы контейнера, а команда logs извлекает журналы контейнера.

1
2
3
docker container stats [OPTIONS] [CONTAINER...]
docker container top CONTAINER
docker container logs [OPTIONS] CONTAINER

За время своего существования контейнер мог пройти множество модификаций и значительно отличаться от своего базового образа. Команда diff проверяет все изменения файлов или каталогов в файловой системе контейнера и сообщает о них.

1
docker container diff CONTAINER

В случае, если вам необходимо зафиксировать эти изменения, есть удобная команда commit, которая создает новое изображение из контейнера.

1
docker container commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]

Если контейнер больше не нужен, его можно завершить с помощью команды rm . Пожалуйста, будьте осторожны, так как все данные, хранящиеся в контейнере, исчезнут (если они не поддерживаются томами ).

1
docker container rm [OPTIONS] CONTAINER [CONTAINER...]

Более экстремальная версия команды rm, команда prune , массово удаляет все остановленные контейнеры.

1
docker container prune [OPTIONS]

4. Порты

Каждый контейнер может предоставлять порты для прослушивания во время выполнения, либо с помощью инструкций образа Dockerfile, либо с помощью параметров команды run . Команда port выводит список всех отображений портов (или определенных отображений) для контейнера.

1
docker container port CONTAINER [PRIVATE_PORT[/PROTO]]

5. Объемы

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

Чтобы заполнить этот пробел, Docker вводит тома в качестве предпочтительного механизма для сохранения данных, используемых контейнерами, и, очевидно, у Docker есть специальный класс команд для этого ( create , inspect , ls , prune и rm ).

1
2
3
4
5
docker volume create [OPTIONS] [VOLUME]
docker volume inspect [OPTIONS] VOLUME [VOLUME...]
docker volume ls [OPTIONS]
docker volume prune [OPTIONS]
docker volume rm [OPTIONS] VOLUME [VOLUME...]

6. Сети

Docker имеет довольно хорошую сетевую поддержку для контейнеров с несколькими сетевыми драйверами, доступными из коробки. Конечно, стандартный набор команд create , inspect , ls , prune и rm доступен для управления сетями.

1
2
3
4
5
docker network create [OPTIONS] NETWORK
docker network inspect [OPTIONS] NETWORK [NETWORK...]
docker network ls [OPTIONS]
docker network prune [OPTIONS]
docker network rm NETWORK [NETWORK...]

Отличительной чертой сети Docker является то, что демон dockerd содержит встроенный DNS-сервер, который обеспечивает разрешение имен между контейнерами, подключенными к одной и той же пользовательской сети (так что на контейнеры можно ссылаться по их именам, а не только по IP-адресам).

Любой работающий контейнер может быть подключен к сети или отключен от сети с помощью команд подключения и отключения соответственно.

1
2
docker network connect [OPTIONS] NETWORK CONTAINER
docker network disconnect [OPTIONS] NETWORK CONTAINER

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

1
docker network prune [OPTIONS]

7. Связывание

Чаще всего ваш стек приложений будет состоять из множества связанных компонентов, а не автономных компонентов (типичным примером может быть приложение на стороне сервера Java, которое взаимодействует с хранилищем данных MySQL ). Проецируя это в мир контейнеров, вам понадобится группа контейнеров, которые могли бы как-то обнаружить их исходные зависимости и общаться друг с другом. В Docker это раньше называлось связыванием, но в настоящее время это может быть легко достигнуто с помощью пользовательских сетей (что также является рекомендуемой практикой).

8. Проверки здоровья

Обычно демон Docker готовит контейнеры довольно быстро, однако это не означает, что приложения, упакованные внутри контейнеров, готовы и полностью функциональны. Для многих пользователей Docker это было одной из самых раздражающих проблем, заставляющих сообщество придумывать множество специальных решений этой проблемы.

Но, спасибо команде Docker , теперь у нас есть проверки работоспособности (которые можно указать в Dockerfile или используя параметры команды run ). Это дополнительный уровень проверки, который инструктирует Docker о том, как проверить, работает ли приложение внутри контейнера. Это привело к добавлению нового свойства состояния работоспособности для дополнения обычного состояния контейнера.

9. Ресурсные ограничения

Интересно, что по умолчанию контейнер не имеет ограничений по ресурсам и может использовать все ресурсы своей операционной системы. Это могло бы быть шоу-стопором, но, к счастью, Docker предоставляет способ контролировать, сколько памяти, ЦП или блоков ввода / вывода может использовать конкретный контейнер, передавая несколько параметров команде run . В качестве альтернативы, для запущенных контейнеров команда update позволяет динамически настраивать конфигурацию контейнера (основной, ограничения ресурсов).

1
docker container update [OPTIONS] CONTAINER [CONTAINER...]

Что касается JVM, работающего внутри контейнера, тема ограничений процессора и памяти становится немного сложнее. Начиная с Java SE 8u131 (и, конечно, в JDK 9 ) и более поздних версий , JVM поддерживает Docker и с небольшой настройкой может нормально играть в соответствии с правилами.

10. Очистить

Как мы уже видели, с Docker вы можете управлять множеством абстракций. Однако со временем Docker генерирует много мусора (например, неиспользуемые слои, изображения, контейнеры, тома и т. Д.), Поглощая драгоценное дисковое пространство. Это известная проблема в течение многих лет, но с недавних пор у нас есть специальная команда удаления, которая очищает все неиспользуемые данные:

1
docker system prune [OPTIONS]

Обратите внимание, что по умолчанию команда не будет очищать тома, если не указана опция –volumes .

11. Все в одном: стек развертывания

Как мы видели до этого момента, вы можете выполнить любую задачу, используя только инструмент командной строки Docker. Но управление жизненным циклом нескольких контейнеров, соединенных вместе, быстро станет бременем и заставит вас задуматься об автоматизации процесса, например, с помощью набора сценариев оболочки или тому подобного.

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

Итак, как это работает? Это всего лишь три простых шага, некоторые из которых мы уже хорошо знаем:

  • Подготовьте изображения для ваших приложений, обычно с помощью Dockerfile s
  • Используйте спецификацию docker-compose.yml, чтобы очертить ваш стек в терминах контейнеров
  • Используйте инструмент командной строки docker-compose для материализации спецификации в набор работающих (и обычно подключенных) контейнеров

Давайте base:openjdk-131-jdk рассмотрим воображаемый стек развертывания, который включает в себя образ JDK, который мы создали ранее, base:openjdk-131-jdk и образ базы данных MySQL mysql:8.0.2 , все они помещены в файл docker-compose.yml .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
version: '2.1'
 
services:
  mysql:
    image: mysql:8.0.2
    environment:
      - MYSQL_ROOT_PASSWORD=p$ssw0rd
      - MYSQL_DATABASE=my_app_db
    expose:
      - 3306
    networks:
      - my-app-network
 
  java-app:
    image: base:openjdk-131-jdk
    mem_limit: 256M
    environment:
      - DB=mysql:3306
    ports:
      - 8080
    depends_on:
      - mysql
    networks:
      - my-app-network
 
networks:
    my-app-network:
       driver: bridge

Довольно аккуратно и просто, не правда ли? Управление версиями форматов спецификации docker-compose.yml требует особого обсуждения. Последний и рекомендуемый формат спецификации — 3.x , но 2.x который мы использовали в приведенном выше примере, также поддерживается и развивается независимо . Почему это?

3.x разработан для обеспечения кросс-совместимости между Docker Compose и Docker Swarm (решение для кластеризации, о котором мы кратко расскажем позже в руководстве), но, к сожалению, он также удаляет несколько очень полезных опций (наряду с добавлением еще нескольких ). В общем, мы будем придерживаться версии 3.x когда сможем, время от времени возвращаясь к версии 2.x чтобы продемонстрировать некоторые действительно интересные функции.

12. Выводы

В этом разделе мы рассмотрели потрясающие инструменты командной строки Docker , docker и docker-compose , выделив наиболее полезные и важные команды. Мы еще не видели большинство из них в действии, но в следующих разделах урока каждый из них найдет время для появления на сцене.

12. Что дальше

Хотя вполне возможно, что эти инструменты будут вашим основным (если не единственным) способом работы с Docker , в следующем разделе руководства мы собираемся изучить другой вариант, используя API-интерфейсы Docker Engine REST (ful) .