Контейнеризация — часто применяемый на практике механизм. Например, при поиске по headhunter вы найдёте на текущий день 477 вакансий, где упоминается Docker. Поэтому, не лишним будет ознакомиться с тем, что это такое. Надеюсь, данный небольшой обзор поможет сформировать первое представление. Ну и подкрепит его дополнительными материалами, вроде курсов на Udemy. Первое знакомство с Docker - 1

Вступление

В данном небольшом обзоре хочется затронуть такую тему, как контейнеризация. И начать нужно с понимания того, что такое вообще контейнеризация. Согласно википедии "Контейнеризация" — это виртуализация на уровне операционной системы (то есть НЕ аппаратная), при которой ядро операционной системы поддерживает несколько изолированных экземпляров пространства пользователя вместо одного. "Пространство пользователя" — это адресное пространство виртуальной памяти операционной системы, отводимое для пользовательских программ. Экземпляры пространства пользователя (обычно называемые контейнерами) с точки зрения пользователя полностью идентичны отдельному экземпляру операционной системы. Ядро обеспечивает полную изолированность контейнеров, поэтому программы из разных контейнеров не могут воздействовать друг на друга. Получается, что контейнеризация — это программная виртуализация, то есть виртуализация на уровне операционной системы, за которую отвечает ядро операционной системы. Одной из характерных черт такого подхода является использование всеми контейнерами общего ядра, того же, что и у хостовой операционной системы (то есть той, в которой размещены контейнеры). Это позволяет избавиться от накладных расходов на эмуляцию виртуального оборудования и запуска полноценного экземпляра операционной системы. Можно сказать, что это "легковесная" виртуализация. Ядро (kernel) — центральная часть ОС, обеспечивающая приложениям координированный доступ к ресурсам компьютера, таким как процессорное время, память, внешнее аппаратное обеспечение, внешнее устройство ввода и вывода информации. Также обычно ядро предоставляет сервисы файловой системы и сетевых протоколов. В общем, это сердце всей системы. В качестве дополнительной информации может быть полезно ознакомиться с материалом "Общие сведения о контейнерах". И ещё пару слов, чтобы вступление было полным. У нас теперь есть понимание, что в операционной системе есть ядро. Оно обеспечивает изолированность экземпляров пространств пользователей. В этом контексте Вы можете встретить такой термин как "cgroups". Так называется как раз тот самый механизм ядра Linux, который позволяет этого добиться. Поэтому, можно сказать, что путь контейнеризации начался с Linux систем. Однако, начиная с Windows 10 тоже появилась поддержка контейнеризации. Для работы с виртуализацией необходимо в BIOS компьютера настроить поддержку виртуализации. Как это сделать зависит от компьютера. Например, это может выглядеть так:
Первое знакомство с Docker - 2
На Windows это можно проверить разными способами. Например, можно скачать специальную утилиту с сайта Microsoft: Hardware-Assisted Virtualization Detection Tool. Ну и стоит упоминуть ещё одно важное понятие — Гипервизор. Гипервизор (Hypervisor) — это монитор виртуальных машин, программа для обеспечения параллельного выполнения нескольких операционных система на одном и том же компьютере. Гипервизор обеспечивает изоляцию операционных систем друг от друга, разделяет между запущенными ОС ресурсы. Одним из таких гипервизоров является Oracle VirtualBox.
Первое знакомство с Docker - 3

Docker

Итак, что такое виртуализация — понятно. Но как этим пользоваться? И тут нам на помощь приходит Docker. Docker — программное обеспечение для автоматизации развёртывания и управления приложениями в средах с поддержкой контейнеризации. Стоит начать с того, что Docker представлен таким понятием, как Docker Enginge. И начать стоит с официального сайта докера и раздела "Docker Overview".
Первое знакомство с Docker - 4
В документации сказано, что докер состоит из:
  • Докер-сервера, который называется Docker Daemon process (dockerd).
  • Интерфейс коммандной строки, он же CLI (docker).
  • REST API, который описывает то, каким образом программы могут "разговаривать" с deamon и давать ему указания, что делать.
Прежде, чем мы перейдём к дальнейшему погружению установим докер, то есть установим докер демона. На сайте Docker есть инструкция по установке "Docker для Windows". Интересно, что у Docker есть свои системные требования. И если у Вас, как и у меня, старая Windows, например Windows 7, то необходимо использовать Docker Toolbox.
Первое знакомство с Docker - 5

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, то увидим пустой список:
Первое знакомство с Docker - 6
Давайте создадим новую машину. Для этого нам понадобится выполнить комманду create: docker-machine create -- driver virtualbox javarush: Мы увидим лог создания докер машины:
Первое знакомство с Docker - 7
Тут интересно нам следующее. Что за Boot2Docker? Это минималистичный дистрибутив Linux для запуска Docker Engine (мы ведь поним, что Docker работает благодаря средствам виртуализации Linux, а в Windows необходимый механизм появился только начиная с Windows 10). Этот дистрибутив основан на дистрибутиве "Tiny Core Linux". Так же упоминается про VirtualBox VM. Это потому, что мы указали --driver virtualbox. Была создана новая виртуальная машина в VirtualBox из образа Boot2Docker. После создания мы можем запустить VirtualBox (т.к. VirtualBox установливаяется вместе с Docker Toolbox) и увидеть созданную виртуальную машину для докер машины:
Первое знакомство с Docker - 8
После создания нам предложат выполнить комманду "docker-machine env", чтобы получить переменные среды, которые нужно настроить для подключения к докер машине:
Первое знакомство с Docker - 9
После выполнения этой комманды при помощи docker-machine мы подключаемся к удалённому докеризированному хосту (в данном случае виртуальному, размещённому на Virtual Box) и можем выполнять комманды docker у себя локально так, словно бы выполняли их на удалённом хосте. Для проверки можем выполнить комманду "docker info". В случае, если подключения к докер машине не установлено, мы получим ошибку. А если всё хорошо — информацию о докере на докер машине. Теперь самое время разобраться с тем, как вообще работает докер и как его использовать.
Первое знакомство с Docker - 10

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
Первое знакомство с Docker - 11
Как мы видим, мы откуда-то скачиваем. По умолчанию, докер смотрит на свой репозиторий в сети https://hub.docker.com. После успешного получения image мы можем проверить список доступных образов, выполнив комманду docker images:
Первое знакомство с Docker - 12
Теперь, у нас есть образ apline. Так как контейнер — запущенный экземпляр image, то давайте запустим этот самый образ. Выполним запуск контейнера при помощи комманды docker run alpine. Как мы видим, ничего не произошло. Если мы выполним комманду docker ps для вывода всех активных контейнеров, то тоже ничего не получим. Но если мы выполним docker ps -a то увидим все контейнеры:
Первое знакомство с Docker - 13
Всё дело в том, что мы запустили докер не в интерактивном режиме. Поэтому, он выполнил комманду и остановился. Комманда была — открытие терминала. Давайте выполним тоже самое, но в интерактивном режиме (с флагом -it):
Первое знакомство с Docker - 14
Как видно, преодолев одну ошибку и воспользовавшись подсказкой мы попали на контейнер и можем в нём работать! Чтобы выйти из контейнера не прекращая его работу можно нажать Ctrl + p + q. Если выполнить теперь docker ps, то мы увидим один активный контейнер. Чтобы зайти на уже запущенный контейнер, выполним комманду docker exec:
Первое знакомство с Docker - 15
Отличное описание того, как это всё происходит рекомендую прочитать в описании докер сэмпла: "1.0 Running your first container". Он мне нравится за то, что там всё написано очень доступно и понятно. Если кратко перефразировать, то мы подключены при помощи docker-machine к виртуальной машине, на которой запущен Docker Daemon. При помощи CLI по REST API мы просим запустить alpine образ. Докер его находит и поэтому не скачивает. Докер создаёт новый контейнер и запускает в этом контейнере указанную нами комманду. И всё это, конечно, хорошо. Но зачем нам это всё? И тут нам надо разобраться, как вообще докер создаёт image. А создаёт он их на основе докерфайла (dockerfile).
Первое знакомство с Docker - 16

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 выполним: Тут надо быть внимательным. Как обычно, в примерах могут быть ошибки. Тут она и есть:
Первое знакомство с Docker - 17
Теперь, для проверки в 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
Первое знакомство с Docker - 18
Если всё хорошо, то коммандой gradle war соберём архив с расширениеи war (web archive). По умолчанию gradle собирает его в каталоге \build\libs. Теперь, мы готовы написать наш докерфайл. Руководствуясь "Dockerfile reference" создадим докерфайл. Создадим файл с названием "Dockerfile" в корне нашего Java проекта (там же, где build скрипт). Откроем его на редактирование. У данного файла есть свой формат, описанный в разделе "Dockerfile reference : Format". Любой докерфайл начинается с инструкции FROM, указывая "базовый образ" (base image). Можно сказать, что это родительский образ, на основе которого мы делаем наш image. Родительский образ нам выбрать очень просто. Веб-приложению нужен веб-сервер. Например, мы можем использовать веб-сервер Tomcat. Заходим на официальный репозиторий докера, который называется docker hub. Ищем там, есть ли нужный нам image:
Первое знакомство с Docker - 19
Стоит так же понимать, что назыается образ tomcat. Но кроме названия у него есть тэг. Тэг - это как версия. Образы томката разных версий различаются тем, какая версия Tomcat используется, какая версия jre и какой базовый образ. Например, мы можем получить образ docker pull tomcat:9-jre8-alpine В нём используется 9 версия tomcat, jre версии 8 и образ alpine как основа. Это может иметь важную роль, чтобы уменьшить размер нашего образа:
Первое знакомство с Docker - 20
Как мы видим, разница огромна. Если мы будем строить наш образ на основе 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 ..
Первое знакомство с Docker - 21
-t — это tag, то есть как назвать собранный образ. Точкой в конце мы обозначаем, что текущий каталог (каталог, где находится докерфайл и откуда мы запускали команду) добавляем в Build context. Build context — это контекст тех файлов, которые доступны при создании докерфайла. Как видно, мы смогли скопировать благодаря этому собранный war файл в наш образ, в каталог веб-сервера. Теперь запустим наш образ: docker run -d --rm -p 8888:8080 jrdocker
Первое знакомство с Docker - 22
Чтобы понять, запустился ли сервер, можно посмотреть лог с контейнера. Лог можно получить при помощи команды docker logs с указанием контейнера по его ID или названию. Например:
Первое знакомство с Docker - 23
Ну и не забываем, что мы по имени всегда можем зайти на запущенный контейнер командой: winpty docker exec -it ИмяКонтейнера sh Теперь осталось подключиться. Ранее мы указали EXPOSE, то есть изнутри контейнера разрешили доступ к порту 8080. Когда же мы запускали сам контейнер, мы указали тэг -p (incoming ports), тем самым соотнесли порт 8080 на контейнере (там ожидает подключения веб-сервер томкат) с портом 8888 на машине с докер демоном. Как мы помним, докер демон мы запустили не напрямую, а через docker-machine. Поэтому, ещё раз спросим данные по нашим докер машинам при помощи команды docker-machine ls и обратимся на сервер в контейнере:
Первое знакомство с Docker - 24
Таким образом, мы с Вами только что запустили своё веб-приложение в докер контейнере! ) Ещё хотелось бы отметить следующее. В случае возникновения проблем с доступом следует помнить, что докер машина в первую очередь это виртуальная машина Virtual BOx. Возможно, проблемы в настройках сети виртуальной машины. Рабочая конфигурация VMBox может выглядеть следующим образом:
Первое знакомство с Docker - 25
Первое знакомство с Docker - 26

Слои (layers)

Мы с вами уже разобрались, что образы создаются из докерфайлов и что докерфайлы — это набор комманд. Ещё мы разобрались, что у докерфайла есть родительский. И что размер образов разный. Интересно, что можно посмотреть историю того, как собирался образ при помощи команды docker history. Например:
Первое знакомство с Docker - 27
Важно об этом сказать для понимание того, что по своей сути каждый образ - это набор образов. Каждое изменение образа (каждая новая команда в докерфайле) создаёт новый слой, который имеет свой ID. Подробнее про слои можно прочитать в документации "Docker: Images and Layers". Так же очень рекомендую к ознакомлению статью на хабре: "Образы и контейнеры Docker в картинках".

Заключение

Надеюсь, этот небольшой обзор был достаточен, чтобы заинтересовать в контейнеризации. Ниже приведены ссылки на дополнительный материал, который может быть полезен: #Viacheslav