Начало работы с Docker: упрощение DevOps
Опубликовано: 2022-03-11Если вам нравятся киты или вы просто заинтересованы в быстрой и безболезненной непрерывной доставке вашего программного обеспечения в производство, то я приглашаю вас прочитать это вводное руководство по Docker. Все указывает на то, что программные контейнеры — это будущее ИТ, так что давайте быстро окунемся в контейнерные киты Moby Dock и Molly.
Docker, представленный логотипом с дружелюбным китом, представляет собой проект с открытым исходным кодом, облегчающий развертывание приложений внутри программных контейнеров. Его базовая функциональность обеспечивается функциями изоляции ресурсов ядра Linux, но поверх этого он предоставляет удобный API. Первая версия была выпущена в 2013 году, и с тех пор она стала чрезвычайно популярной и широко используется многими крупными игроками, такими как eBay, Spotify, Baidu и другими. В последнем раунде финансирования Docker получил огромные 95 миллионов долларов, и он находится на пути к тому, чтобы стать одним из основных сервисов DevOps.
Аналогия с транспортировкой товаров
Философию Docker можно проиллюстрировать следующей простой аналогией. В международной транспортной отрасли товары должны перевозиться различными способами, такими как вилочные погрузчики, грузовики, поезда, краны и корабли. Эти товары бывают разных форм и размеров и имеют разные требования к хранению: мешки с сахаром, бидоны из-под молока, растения и т. д. Исторически сложилось так, что это был болезненный процесс, зависящий от ручного вмешательства на каждом транзитном пункте при погрузке и разгрузке.
Все изменилось с появлением интермодальных контейнеров. Поскольку они имеют стандартные размеры и производятся с учетом транспортировки, все соответствующие механизмы могут быть спроектированы так, чтобы обрабатывать их с минимальным вмешательством человека. Дополнительным преимуществом герметичных контейнеров является то, что они могут сохранять внутреннюю среду, такую как температура и влажность, для чувствительных товаров. В результате транспортная отрасль может перестать беспокоиться о самих товарах и сосредоточиться на их доставке из пункта А в пункт Б.
И вот тут-то и появляется Docker, который приносит аналогичные преимущества индустрии программного обеспечения.
Чем он отличается от виртуальных машин?
На первый взгляд виртуальные машины и контейнеры Docker могут показаться похожими. Однако их основные различия станут очевидны, если вы посмотрите на следующую диаграмму:
Приложения, работающие на виртуальных машинах, помимо гипервизора, требуют полной версии операционной системы и любых вспомогательных библиотек. Контейнеры, с другой стороны, совместно используют операционную систему с хостом. Гипервизор можно сравнить с механизмом контейнеров (обозначенным на изображении как Docker) в том смысле, что он управляет жизненным циклом контейнеров. Важным отличием является то, что процессы, работающие внутри контейнеров, аналогичны собственным процессам на хосте и не создают никаких накладных расходов, связанных с выполнением гипервизора. Кроме того, приложения могут повторно использовать библиотеки и обмениваться данными между контейнерами.
Поскольку обе технологии имеют разные сильные стороны, часто встречаются системы, сочетающие виртуальные машины и контейнеры. Прекрасным примером является инструмент Boot2Docker, описанный в разделе установки Docker.
Докер Архитектура
В верхней части диаграммы архитектуры находятся реестры. По умолчанию основным реестром является Docker Hub, в котором размещаются общедоступные и официальные образы. При желании организации также могут размещать свои частные реестры.
С правой стороны у нас есть изображения и контейнеры. Изображения можно загружать из реестров явно ( docker pull imageName
) или неявно при запуске контейнера. После загрузки изображения оно кэшируется локально.
Контейнеры — это экземпляры образов — они живые существа. Может быть несколько контейнеров, работающих на основе одного и того же образа.
В центре находится демон Docker, отвечающий за создание, запуск и мониторинг контейнеров. Он также заботится о создании и хранении изображений. Наконец, слева есть клиент Docker. Он общается с демоном через HTTP. Сокеты Unix используются на одном компьютере, но возможно удаленное управление через API на основе HTTP.
Установка Докера
Для получения последних инструкций вы всегда должны обращаться к официальной документации.
Docker изначально работает в Linux, поэтому в зависимости от целевого дистрибутива это может быть так же просто, как sudo apt-get install docker.io
. Подробную информацию см. в документации. Обычно в Linux команды Docker добавляются с помощью sudo
, но в этой статье мы пропустим его для ясности.
Поскольку демон Docker использует специфические для Linux функции ядра, запустить Docker изначально в Mac OS или Windows невозможно. Вместо этого вы должны установить приложение под названием Boot2Docker. Приложение состоит из виртуальной машины VirtualBox, самого Docker и утилит управления Boot2Docker. Вы можете следовать официальным инструкциям по установке для MacOS и Windows, чтобы установить Docker на эти платформы.
Использование Докера
Давайте начнем этот раздел с быстрого примера:
docker run phusion/baseimage echo "Hello Moby Dock. Hello Molly."
Мы должны увидеть этот вывод:
Hello Moby Dock. Hello Molly.
Однако за кулисами произошло гораздо больше, чем вы думаете:
- Образ phusion/baseimage был загружен из Docker Hub (если его еще не было в локальном кеше).
- Запущен контейнер на основе этого образа
- Команда echo была выполнена внутри контейнера
- Контейнер был остановлен, когда команда вышла
При первом запуске вы можете заметить задержку перед выводом текста на экран. Если бы изображение было кэшировано локально, все заняло бы доли секунды. Подробности о последнем контейнере можно получить, запустив docker ps -l
:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES af14bec37930 phusion/baseimage:latest "echo 'Hello Moby Do 2 minutes ago Exited (0) 3 seconds ago stoic_bardeen
Следующее погружение
Как видите, запустить простую команду в Docker так же просто, как запустить ее непосредственно на стандартном терминале. Чтобы проиллюстрировать более практический вариант использования, в оставшейся части этой статьи мы увидим, как мы можем использовать Docker для развертывания простого приложения веб-сервера. Для простоты мы напишем программу на Java, которая обрабатывает HTTP-запросы GET к '/ping' и отвечает строкой 'pong\n'.
import java.io.IOException; import java.io.OutputStream; import java.net.InetSocketAddress; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; public class PingPong { public static void main(String[] args) throws Exception { HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0); server.createContext("/ping", new MyHandler()); server.setExecutor(null); server.start(); } static class MyHandler implements HttpHandler { @Override public void handle(HttpExchange t) throws IOException { String response = "pong\n"; t.sendResponseHeaders(200, response.length()); OutputStream os = t.getResponseBody(); os.write(response.getBytes()); os.close(); } } }
Докерфайл
Прежде чем приступить к созданию собственного образа Docker, рекомендуется сначала проверить, существует ли он в Docker Hub или в каких-либо частных реестрах, к которым у вас есть доступ. Например, вместо самостоятельной установки Java мы будем использовать официальный образ: java:8
.
Чтобы создать образ, сначала нам нужно выбрать базовый образ, который мы собираемся использовать. Обозначается инструкцией FROM . Вот официальный образ Java 8 от Docker Hub. Мы собираемся скопировать его в наш файл Java, выполнив инструкцию COPY . Далее мы собираемся скомпилировать его с помощью RUN . Инструкция EXPOSE указывает, что образ будет предоставлять услугу на определенном порту. ENTRYPOINT — это инструкция, которую мы хотим выполнить при запуске контейнера, основанного на этом образе, и CMD указывает параметры по умолчанию, которые мы собираемся ему передать.

FROM java:8 COPY PingPong.java / RUN javac PingPong.java EXPOSE 8080 ENTRYPOINT ["java"] CMD ["PingPong"]
После сохранения этих инструкций в файле с именем «Dockerfile» мы можем создать соответствующий образ Docker, выполнив:
docker build -t toptal/pingpong .
В официальной документации по Docker есть раздел, посвященный рекомендациям по написанию Dockerfile.
Запуск контейнеров
Когда образ создан, мы можем воплотить его в жизнь как контейнер. Есть несколько способов запуска контейнеров, но давайте начнем с простого:
docker run -d -p 8080:8080 toptal/pingpong
где -p [порт-на-узле]:[порт-в-контейнере] обозначает сопоставление портов на узле и в контейнере соответственно. Кроме того, мы сообщаем Docker о запуске контейнера как процесса демона в фоновом режиме, указав -d . Вы можете проверить, запущено ли приложение веб-сервера, попытавшись получить доступ к «http://localhost:8080/ping». Обратите внимание, что на платформах, где используется Boot2docker, вам нужно будет заменить «localhost» на IP-адрес виртуальной машины, на которой работает Docker.
В Linux:
curl http://localhost:8080/ping
На платформах, требующих Boot2Docker:
curl $(boot2docker ip):8080/ping
Если все пойдет хорошо, вы должны увидеть ответ:
pong
Ура, наш первый пользовательский контейнер Docker жив и плавает! Мы также можем запустить контейнер в интерактивном режиме -i -t . В нашем случае мы переопределим команду точки входа , чтобы получить терминал bash. Теперь мы можем выполнять любые команды, которые захотим, но выход из контейнера остановит их:
docker run -i -t --entrypoint="bash" toptal/pingpong
Существует множество других вариантов запуска контейнеров. Давайте рассмотрим еще несколько. Например, если мы хотим сохранить данные вне контейнера, мы можем поделиться файловой системой хоста с контейнером, используя -v . По умолчанию режим доступа — чтение-запись, но его можно изменить на режим только для чтения, добавив :ro
к пути тома внутри контейнера. Тома особенно важны, когда нам нужно использовать какую-либо информацию о безопасности, такую как учетные данные и закрытые ключи внутри контейнеров, которые не должны храниться в образе. Кроме того, это также может предотвратить дублирование данных, например, путем сопоставления вашего локального репозитория Maven с контейнером, чтобы вам не приходилось дважды загружать Интернет.
Docker также имеет возможность связывать контейнеры вместе. Связанные контейнеры могут взаимодействовать друг с другом, даже если ни один из портов не открыт. Этого можно добиться с помощью --link other-container-name . Ниже приведен пример сочетания указанных выше параметров:
docker run -p 9999:8080 --link otherContainerA --link otherContainerB -v /Users/$USER/.m2/repository:/home/user/.m2/repository toptal/pingpong
Другие операции с контейнерами и изображениями
Неудивительно, что список операций, которые можно применить к контейнерам и изображениям, довольно длинный. Для краткости рассмотрим лишь некоторые из них:
- stop — останавливает работающий контейнер.
- start - Запускает остановленный контейнер.
- commit — создает новый образ из изменений контейнера.
- rm - Удаляет один или несколько контейнеров.
- rmi - Удаляет одно или несколько изображений.
- ps — список контейнеров.
- images — список изображений.
- exec — запускает команду в работающем контейнере.
Последняя команда может быть особенно полезна для целей отладки, так как позволяет подключиться к терминалу работающего контейнера:
docker exec -i -t <container-id> bash
Docker Compose для мира микросервисов
Если у вас больше, чем пара взаимосвязанных контейнеров, имеет смысл использовать такой инструмент, как docker-compose. В файле конфигурации вы описываете, как запускать контейнеры и как они должны быть связаны друг с другом. Независимо от количества задействованных контейнеров и их зависимостей, вы можете запустить и запустить их все с помощью одной команды: docker-compose up
.
Докер в дикой природе
Давайте рассмотрим три этапа жизненного цикла проекта и посмотрим, чем может быть полезен наш дружелюбный кит.
Разработка
Docker помогает поддерживать чистоту локальной среды разработки. Вместо того, чтобы устанавливать несколько версий различных сервисов, таких как Java, Kafka, Spark, Cassandra и т. д., вы можете просто запускать и останавливать требуемый контейнер, когда это необходимо. Вы можете сделать еще один шаг вперед и запускать несколько программных стеков одновременно, избегая смешивания версий зависимостей.
С Docker вы можете сэкономить время, усилия и деньги. Если ваш проект очень сложен в настройке, «докеризируйте» его. Пройдите через все трудности создания образа Docker один раз, и с этого момента каждый может просто запустить контейнер в одно мгновение.
Вы также можете иметь «среду интеграции», работающую локально (или в CI), и заменить заглушки реальными службами, работающими в контейнерах Docker.
Тестирование/непрерывная интеграция
С Dockerfile легко получить воспроизводимые сборки. Jenkins или другие решения CI можно настроить для создания образа Docker для каждой сборки. Вы можете сохранить некоторые или все образы в частном реестре Docker для дальнейшего использования.
С Docker вы тестируете только то, что нужно протестировать, и исключаете окружающую среду из уравнения. Выполнение тестов на работающем контейнере может помочь сделать ситуацию более предсказуемой.
Еще одна интересная особенность программных контейнеров заключается в том, что легко создавать подчиненные машины с идентичными настройками разработки. Это может быть особенно полезно для нагрузочного тестирования кластерных развертываний.
Производство
Docker может быть общим интерфейсом между разработчиками и операционным персоналом, устраняющим источник разногласий. Это также поощряет использование одних и тех же изображений/двоичных файлов на каждом этапе конвейера. Кроме того, возможность развертывания полностью протестированного контейнера без различий в среде помогает гарантировать отсутствие ошибок в процессе сборки.
Вы можете легко перенести приложения в рабочую среду. То, что когда-то было утомительным и ненадежным процессом, теперь может быть таким же простым, как:
docker stop container-id; docker run new-image
А если что-то пойдет не так при развертывании новой версии, вы всегда сможете быстро откатиться или перейти на другой контейнер:
docker stop container-id; docker start other-container-id
… гарантированно не оставит после себя никакого беспорядка или оставит вещи в несовместимом состоянии.
Резюме
Хороший обзор того, что делает Docker, содержится в его собственном девизе: Build, Ship, Run.
- Сборка — Docker позволяет создавать приложения из микросервисов, не беспокоясь о несоответствиях между средами разработки и производства и не привязываясь к какой-либо платформе или языку.
- Ship — Docker позволяет вам спланировать весь цикл разработки, тестирования и распространения приложений, а также управлять им с помощью единого пользовательского интерфейса.
- Запуск — Docker предлагает вам возможность безопасного и надежного развертывания масштабируемых сервисов на самых разных платформах.
Приятного плавания с китами!
Часть этой работы вдохновлена отличной книгой Адриана Муата «Использование Docker».