Контейнеризация — часто применяемый на практике механизм. Например, при поиске по headhunter вы найдёте на текущий день 477 вакансий, где упоминается Docker. Поэтому, не лишним будет ознакомиться с тем, что это такое. Надеюсь, данный небольшой обзор поможет сформировать первое представление. Ну и подкрепит его дополнительными материалами, вроде курсов на Udemy.
Вступление
В данном небольшом обзоре хочется затронуть такую тему, как контейнеризация. И начать нужно с понимания того, что такое вообще контейнеризация. Согласно википедии "Контейнеризация" — это виртуализация на уровне операционной системы (то есть НЕ аппаратная), при которой ядро операционной системы поддерживает несколько изолированных экземпляров пространства пользователя вместо одного. "Пространство пользователя" — это адресное пространство виртуальной памяти операционной системы, отводимое для пользовательских программ. Экземпляры пространства пользователя (обычно называемые контейнерами) с точки зрения пользователя полностью идентичны отдельному экземпляру операционной системы. Ядро обеспечивает полную изолированность контейнеров, поэтому программы из разных контейнеров не могут воздействовать друг на друга. Получается, что контейнеризация — это программная виртуализация, то есть виртуализация на уровне операционной системы, за которую отвечает ядро операционной системы. Одной из характерных черт такого подхода является использование всеми контейнерами общего ядра, того же, что и у хостовой операционной системы (то есть той, в которой размещены контейнеры). Это позволяет избавиться от накладных расходов на эмуляцию виртуального оборудования и запуска полноценного экземпляра операционной системы. Можно сказать, что это "легковесная" виртуализация. Ядро (kernel) — центральная часть ОС, обеспечивающая приложениям координированный доступ к ресурсам компьютера, таким как процессорное время, память, внешнее аппаратное обеспечение, внешнее устройство ввода и вывода информации. Также обычно ядро предоставляет сервисы файловой системы и сетевых протоколов. В общем, это сердце всей системы. В качестве дополнительной информации может быть полезно ознакомиться с материалом "Общие сведения о контейнерах". И ещё пару слов, чтобы вступление было полным. У нас теперь есть понимание, что в операционной системе есть ядро. Оно обеспечивает изолированность экземпляров пространств пользователей. В этом контексте Вы можете встретить такой термин как "cgroups". Так называется как раз тот самый механизм ядра Linux, который позволяет этого добиться. Поэтому, можно сказать, что путь контейнеризации начался с Linux систем. Однако, начиная с Windows 10 тоже появилась поддержка контейнеризации. Для работы с виртуализацией необходимо в BIOS компьютера настроить поддержку виртуализации. Как это сделать зависит от компьютера. Например, это может выглядеть так:На Windows это можно проверить разными способами.
Например, можно скачать специальную утилиту с сайта Microsoft: Hardware-Assisted Virtualization Detection Tool.
Ну и стоит упоминуть ещё одно важное понятие — Гипервизор. Гипервизор (Hypervisor) — это монитор виртуальных машин, программа для обеспечения параллельного выполнения нескольких операционных система на одном и том же компьютере. Гипервизор обеспечивает изоляцию операционных систем друг от друга, разделяет между запущенными ОС ресурсы. Одним из таких гипервизоров является Oracle VirtualBox.
Docker
Итак, что такое виртуализация — понятно. Но как этим пользоваться? И тут нам на помощь приходит Docker. Docker — программное обеспечение для автоматизации развёртывания и управления приложениями в средах с поддержкой контейнеризации. Стоит начать с того, что Docker представлен таким понятием, как Docker Enginge. И начать стоит с официального сайта докера и раздела "Docker Overview".В документации сказано, что докер состоит из:
- Докер-сервера, который называется Docker Daemon process (dockerd).
- Интерфейс коммандной строки, он же CLI (docker).
- REST API, который описывает то, каким образом программы могут "разговаривать" с deamon и давать ему указания, что делать.
Docker Toolbox
Для установки Docker на старые, не подходящие под системные требования, машины. На сайте так и написано, "Legacy Desktop Solution". Перейдём на страницу "Docker Toolbox" и скачаем его. Весит такой набор около 211 мегабайт. Установим в комплектации по умолчанию, то есть просто будем безропотно соглашаться со всем, не переставляя флаги. После установки проверим, что всё хорошо. В дальнейшем нашим полем боя будет коммандная строка. Рекомендую не использовать коммандную строку Windows, потому что с ней могут быть неочевидные проблемы. Лучше использовать bash shell. На Windows самый рекомендуемый способ получения его - установить себе систему контроля версий git, всё равно она пригодится. Потому что "в комплекте" с ним будет и нужный нам bash. Для данного обзора я буду использовать именно git bash. Так же bash можно поставить вместе с CYGWIN. Запустим bash или git bash. Убедимся, что у нас установилась докер машина, она же Docker Machine:docker-machine -version
Что такое эта Docker Machine?
Docker Machine — это утилита для управления докеризированными хостами (это такие хосты, на которых установлен Docker Engine). Если мы выполним сразу же после установки Docket Toolbox просмотр докер-машин при помощи комманды docker-machine ls
, то увидим пустой список:
Давайте создадим новую машину.
Для этого нам понадобится выполнить комманду create:
docker-machine create -- driver virtualbox javarush
:
Мы увидим лог создания докер машины:
Тут интересно нам следующее. Что за Boot2Docker? Это минималистичный дистрибутив Linux для запуска Docker Engine (мы ведь поним, что Docker работает благодаря средствам виртуализации Linux, а в Windows необходимый механизм появился только начиная с Windows 10). Этот дистрибутив основан на дистрибутиве "Tiny Core Linux".
Так же упоминается про VirtualBox VM. Это потому, что мы указали
--driver virtualbox
. Была создана новая виртуальная машина в VirtualBox из образа Boot2Docker.
После создания мы можем запустить VirtualBox (т.к. VirtualBox установливаяется вместе с Docker Toolbox) и увидеть созданную виртуальную машину для докер машины:
После создания нам предложат выполнить комманду "docker-machine env", чтобы получить переменные среды, которые нужно настроить для подключения к докер машине:
После выполнения этой комманды при помощи docker-machine мы подключаемся к удалённому докеризированному хосту (в данном случае виртуальному, размещённому на Virtual Box) и можем выполнять комманды docker у себя локально так, словно бы выполняли их на удалённом хосте.
Для проверки можем выполнить комманду "docker info". В случае, если подключения к докер машине не установлено, мы получим ошибку. А если всё хорошо — информацию о докере на докер машине.
Теперь самое время разобраться с тем, как вообще работает докер и как его использовать.
Docker Containers
Итак, у нас есть докер. Что такое вообще этот докер? Понять это нам поможет документация Docker и раздел "Get started". В вводной части этого раздела рассказывается по концепцию докера (Docker Concepts). Там указано, что докер — это платформа для разработки, отладки и запуска приложений в контейнерах. Поэтому, главное для докера — это контейнеры. Даже если вы посмотрите на логотип докера - это кит, который держит на своей спине контейнеры. Но что такое контейнер? Далее в разделе "Images and Containers" говорится, что контейнер — это запущенный экземпляр Image. А Image — это выполняем "пакет", который содержит всё необходимое для приложения (код, окружение, библиотеки, настройки и т.д.). Теперь давайте попробуем это сами. На сайте Docker'а есть раздел "Docker samples", в котором есть "Docker for Beginners". Примеры отсюда мне кажутся более интересными. Итак, мы вдруг захотели ознакомиться с Alpine Linux и можем это сделать при помощи контейнеров докера. Чтобы получить образ, мы должны его "вытянуть" или "вытащить". Поэтому, выполняем комманду docker pull:docker pull apline
Как мы видим, мы откуда-то скачиваем. По умолчанию, докер смотрит на свой репозиторий в сети https://hub.docker.com.
После успешного получения image мы можем проверить список доступных образов, выполнив комманду docker images:
Теперь, у нас есть образ apline. Так как контейнер — запущенный экземпляр image, то давайте запустим этот самый образ.
Выполним запуск контейнера при помощи комманды
docker run alpine
. Как мы видим, ничего не произошло. Если мы выполним комманду docker ps
для вывода всех активных контейнеров, то тоже ничего не получим. Но если мы выполним docker ps -a
то увидим все контейнеры:
Всё дело в том, что мы запустили докер не в интерактивном режиме. Поэтому, он выполнил комманду и остановился. Комманда была — открытие терминала.
Давайте выполним тоже самое, но в интерактивном режиме (с флагом -it):
Как видно, преодолев одну ошибку и воспользовавшись подсказкой мы попали на контейнер и можем в нём работать!
Чтобы выйти из контейнера не прекращая его работу можно нажать
Ctrl + p + q
. Если выполнить теперь docker ps
, то мы увидим один активный контейнер.
Чтобы зайти на уже запущенный контейнер, выполним комманду docker exec:
Отличное описание того, как это всё происходит рекомендую прочитать в описании докер сэмпла: "1.0 Running your first container". Он мне нравится за то, что там всё написано очень доступно и понятно.
Если кратко перефразировать, то мы подключены при помощи docker-machine к виртуальной машине, на которой запущен Docker Daemon. При помощи CLI по REST API мы просим запустить alpine образ. Докер его находит и поэтому не скачивает. Докер создаёт новый контейнер и запускает в этом контейнере указанную нами комманду.
И всё это, конечно, хорошо. Но зачем нам это всё? И тут нам надо разобраться, как вообще докер создаёт image. А создаёт он их на основе докерфайла (dockerfile).
Dockerfile
Как сказано в Dockerfile reference, докерфайл — это текстовый файл, в котором указаны все комманды для получения image. На самом деле, все образы, которые мы получаем (даже Alpine, из примера выше) создавались из докерфайла. Давайте соберём свой образ с Java приложением. И для начала нам понадобится это Java приложение. Предлагаю воспользоваться системой сборки Gradle, подробней про которую можно прочитать в кратком обзоре: "Краткое знакомство с Gradle". Поможет нам в создании проекта "Gradle Build init plugin". Выполним создание нового Java приложения при помощи Gradle:gradle init --type java-application
Данная команда создаёт заготовку Java проекта. Это standalone приложение, а нам хотелось бы создать веб-приложение.
Удалим классы App и AppTest (их сгенерировал автоматически Gradle Build Init Plugin). Чтобы быстро создать веб-приложение воспользуемся tutorial'ом от Gradle: "Building Java Web Applications".
Согласно tutorial выполним:
- Откроем билд скрипт build.gradle и поправим его, согласно разделу: "Add a Gradle build file".
- Добавим страницы в src/main/webapp, как указано в разделе "Add JSP pages to the demo application".
- Добавим класс
HelloServlet
с содержимым из раздела "Add a servlet and metadata to the project".
Теперь, для проверки в build.gradle добавим плагин gretty, как указано в разделе "Add the gretty plugin and run the app":
plugins {
id 'war'
id 'org.gretty' version '2.2.0'
}
Интересно, что Gretty не видит ошибку в HelloServlet
, которая описана выше. Это доказывает, что в разном окружении приложение себя может вести по-разному. Gretty может работать там, где обычный standalone сервер выдаст ошибку.
Осталось проверить, что приложение работает правильно. Выполним: gradle appRun
Если всё хорошо, то коммандой
gradle war
соберём архив с расширениеи war (web archive).
По умолчанию gradle собирает его в каталоге \build\libs
. Теперь, мы готовы написать наш докерфайл.
Руководствуясь "Dockerfile reference" создадим докерфайл. Создадим файл с названием "Dockerfile" в корне нашего Java проекта (там же, где build скрипт). Откроем его на редактирование. У данного файла есть свой формат, описанный в разделе "Dockerfile reference : Format".
Любой докерфайл начинается с инструкции FROM, указывая "базовый образ" (base image). Можно сказать, что это родительский образ, на основе которого мы делаем наш image. Родительский образ нам выбрать очень просто. Веб-приложению нужен веб-сервер. Например, мы можем использовать веб-сервер Tomcat. Заходим на официальный репозиторий докера, который называется docker hub.
Ищем там, есть ли нужный нам image:
Стоит так же понимать, что назыается образ tomcat. Но кроме названия у него есть тэг. Тэг - это как версия.
Образы томката разных версий различаются тем, какая версия Tomcat используется, какая версия jre и какой базовый образ. Например, мы можем получить образ
docker pull tomcat:9-jre8-alpine
В нём используется 9 версия tomcat, jre версии 8 и образ alpine как основа. Это может иметь важную роль, чтобы уменьшить размер нашего образа:
Как мы видим, разница огромна. Если мы будем строить наш образ на основе tomcata alpine, то мы начнём всего со 100 мегабайт, а не с 600.
Соответственно, в ранее созданный докерфайл добавим следующее содержимое:
# Базовый образ, "наследуемся" от него
FROM tomcat:9-jre8-alpine
# Копируем из Build Context'а собранный web archive в каталог томката
COPY build/libs/docker.war /usr/local/tomcat/webapps/docker.war
# Меняем рабочий каталог на томкатовский
WORKDIR /usr/local/tomcat
# Открываем порт 8080 для контейнера, т.к. его слушает томкат
EXPOSE 8080
И теперь выполним команду построения образа: docker build -t jrdocker .
.
-t
— это tag, то есть как назвать собранный образ.
Точкой в конце мы обозначаем, что текущий каталог (каталог, где находится докерфайл и откуда мы запускали команду) добавляем в Build context
. Build context
— это контекст тех файлов, которые доступны при создании докерфайла. Как видно, мы смогли скопировать благодаря этому собранный war файл в наш образ, в каталог веб-сервера.
Теперь запустим наш образ: docker run -d --rm -p 8888:8080 jrdocker
Чтобы понять, запустился ли сервер, можно посмотреть лог с контейнера.
Лог можно получить при помощи команды docker logs с указанием контейнера по его ID или названию. Например:
Ну и не забываем, что мы по имени всегда можем зайти на запущенный контейнер командой:
winpty docker exec -it ИмяКонтейнера sh
Теперь осталось подключиться. Ранее мы указали EXPOSE, то есть изнутри контейнера разрешили доступ к порту 8080. Когда же мы запускали сам контейнер, мы указали тэг -p (incoming ports), тем самым соотнесли порт 8080 на контейнере (там ожидает подключения веб-сервер томкат) с портом 8888 на машине с докер демоном.
Как мы помним, докер демон мы запустили не напрямую, а через docker-machine. Поэтому, ещё раз спросим данные по нашим докер машинам при помощи команды docker-machine ls и обратимся на сервер в контейнере:
Таким образом, мы с Вами только что запустили своё веб-приложение в докер контейнере! )
Ещё хотелось бы отметить следующее. В случае возникновения проблем с доступом следует помнить, что докер машина в первую очередь это виртуальная машина Virtual BOx. Возможно, проблемы в настройках сети виртуальной машины. Рабочая конфигурация VMBox может выглядеть следующим образом:
Слои (layers)
Мы с вами уже разобрались, что образы создаются из докерфайлов и что докерфайлы — это набор комманд. Ещё мы разобрались, что у докерфайла есть родительский. И что размер образов разный. Интересно, что можно посмотреть историю того, как собирался образ при помощи команды docker history. Например:Важно об этом сказать для понимание того, что по своей сути каждый образ - это набор образов. Каждое изменение образа (каждая новая команда в докерфайле) создаёт новый слой, который имеет свой ID.
Подробнее про слои можно прочитать в документации "Docker: Images and Layers".
Так же очень рекомендую к ознакомлению статью на хабре: "Образы и контейнеры Docker в картинках".
Заключение
Надеюсь, этот небольшой обзор был достаточен, чтобы заинтересовать в контейнеризации. Ниже приведены ссылки на дополнительный материал, который может быть полезен:- Best practices for writing Dockerfiles
- Контейнер на удалённом хосте с помощью Docker Machine
- Статья на хабре "Docker. Начало"
- Статья на хабре "Оптимизация образов Docker".
- Udemy: "Getting Started With Docker"
- Youtube : "Docker уроки от А до Я"
- Возможности программного обеспечения Docker [GeekBrains]
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ