Каталог статей

The Docker Handbook – Изучение Docker для начинающих

The Docker Handbook – Изучение Docker для начинающих

Фархан Хасин Чоудхури

Сама концепция контейнеризации довольно старая. Но появление в 2013 году движка Docker Engine значительно упростило контейнеризацию приложений.

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

Предварительные условия

Оглавление

Код проекта

Код для примеров проектов можно найти в следующем репозитории:

Полный код можно найти в завершенной ветке.

Вклад

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

Книга по Docker с открытым исходным кодом. Внесите свой вклад в разработку fhsinchy/the-docker-handbook, создав учетную запись на GitHub.

Обычно я сначала вношу изменения и обновления в GitBook-версию книги, а затем публикую их на freeCodeCamp. Вы можете найти постоянно обновляемую и часто нестабильную версию книги по следующей ссылке:

The Docker Handbook

Если вы ищете замороженную, но стабильную версию книги, то freeCodeCamp будет лучшим местом для этого:

Сама концепция контейнеризации довольно стара, но появление движка Docker Engine [https://docs.docker.com/get-started/overview/#docker-engine] в 2013 году значительно упростило контейнеризацию приложений. По данным опроса разработчиков Stack Overflow Developer Survey – 2020[https://insights.stackoverflow.com/survey/2020#overview…

Фархан Хасин Чоудхури freeCodeCamp.org

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

Введение в контейнеризацию и Docker

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

Другими словами, контейнеризация позволяет вам упаковать ваше программное обеспечение вместе со всеми его зависимостями в самодостаточный пакет, чтобы его можно было запускать, не прибегая к

Теоретически это должно быть все. Но практически есть и некоторые другие моменты. Оказывается, Node.js использует инструмент сборки, известный как node-gyp, для создания нативных дополнений. И согласно инструкции по установке в официальном репозитории, этот инструмент сборки требует Python 2 или 3 и соответствующую цепочку инструментов компилятора C/C++.

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

Node.js

Express.js

Независимо от того, какой пакет вы установите, он все равно может сломаться при обновлении ОС. На самом деле, эта проблема настолько распространена, что в официальном репозитории имеются примечания по установке для macOS Catalina.

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

А что если у вас есть товарищ по команде, который использует Windows, в то время как вы используете Linux. Теперь вам придется учитывать несоответствия в том, как эти две разные операционные системы обрабатывают пути. Или тот факт, что популярные технологии, такие как nginx, не очень хорошо оптимизированы для работы под Windows. Некоторые технологии, такие как Redis, даже не поставляются предварительно подготовленными для Windows.

Даже если вы пройдете весь этап разработки, что если человек, ответственный за управление серверами, выполнит неправильную процедуру развертывания?

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

Разработать и запустить приложение в изолированной среде (известной как контейнер), которая соответствует вашей конечной среде развертывания.

Поместить приложение в один файл (известный как образ) вместе со всеми его зависимостями и необходимыми конфигурациями развертывания.

Теперь возникает вопрос: “Какую роль здесь играет Docker?”.

Как я уже объяснил, контейнеризация – это идея, которая решает огромное количество проблем в разработке программного обеспечения путем помещения вещей в коробки.

Эта идея имеет довольно много реализаций. Docker является такой реализацией. Это платформа контейнеризации с открытым исходным кодом, которая позволяет вам контейнеризировать

Кроме того, если вы хотите получить урок истории, вы можете прочитать потрясающую книгу “Краткая история контейнеров: From the 1970s Till Now”, в котором описаны основные поворотные моменты развития технологии.

Как установить Docker

Установка Docker сильно варьируется в зависимости от используемой вами операционной системы. Но во всех случаях она проста.

Docker безупречно работает на всех трех основных платформах – Mac, Windows и Linux. Из этих трех платформ процесс установки на Mac самый простой, поэтому мы начнем с него.

Как установить Docker на macOS

На mac все, что вам нужно сделать, это перейти на официальную страницу загрузки и нажать кнопку Download for Mac (stable).

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

Вы можете запустить Docker, просто дважды щелкнув по значку приложения. Как только приложение запустится, вы увидите значок Docker на панели меню.

Теперь откройте терминал и выполните docker –version и docker-compose –version, чтобы убедиться в успехе установки.

Как установить Docker на Windows

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

Перейдите на этот сайт и следуйте инструкциям по установке WSL2 на Windows 10.

Затем перейдите на официальную страницу загрузки и нажмите кнопку Загрузить для Windows (стабильная версия).

Дважды щелкните загруженную программу установки и пройдите установку с настройками по умолчанию.

  1. После завершения установки запустите Docker Desktop из стартового меню или с рабочего стола. На панели задач должен появиться значок докера.
  2. Теперь откройте Ubuntu или любой другой дистрибутив, который вы установили из Microsoft Store. Выполните команды docker –version и docker-compose –version, чтобы убедиться, что установка прошла успешно.
  3. Вы можете получить доступ к Docker и из обычной командной строки или PowerShell. Просто я предпочитаю использовать WSL2, а не любую другую командную строку в Windows.

Как установить Docker на Linux

Установка Docker в Linux – это немного другой процесс, и в зависимости от дистрибутива, на котором вы работаете, он может отличаться еще больше. Но, честно говоря, установка так же проста (если не проще), как и на двух других платформах.

Пакет Docker Desktop на Windows или Mac представляет собой набор таких инструментов, как Docker Engine, Docker Compose, Docker Dashboard, Kubernetes и некоторые другие.

В Linux, однако, вы не получаете такого пакета. Вместо этого вы устанавливаете все необходимые инструменты вручную. Процедуры установки для различных дистрибутивов выглядят следующим образом:

Если вы используете Ubuntu, вы можете следовать разделу Install Docker Engine on Ubuntu из официальной документации.

Для других дистрибутивов в официальных документах доступны руководства по установке для каждого дистрибутива.

После завершения установки откройте терминал и выполните docker –version и docker-compose –version, чтобы убедиться в успехе установки.

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

Hello World in Docker – введение в основы Docker

Теперь, когда на вашей машине установлен и работает Docker, пришло время запустить ваш первый контейнер. Откройте терминал и выполните следующую команду:

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

Теперь в терминале вы можете использовать команду docker p s-a, чтобы посмотреть на все контейнеры, которые запущены в данный момент или запускались в прошлом:

В выводе контейнер с именем exciting_chebyshev был запущен с идентификатором контейнера 128ec8ceab71 с использованием образа hello-world. Он завершился (0) 13 секунд назад, где код завершения (0) означает, что во время выполнения контейнера не возникло никаких ошибок.

Теперь, чтобы понять, что только что произошло за кулисами, вам придется познакомиться с архитектурой Docker и тремя очень фундаментальными концепциями контейнеризации в целом, которые заключаются в следующем:

Я перечислил эти три концепции в алфавитном порядке и начну свои объяснения с первой в списке.

Что такое контейнер?

В мире контейнеризации не может быть ничего более фундаментального, чем понятие контейнера.

На официальном сайте ресурсов Docker сказано следующее.

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

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

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

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

Виртуальные машины обычно создаются и управляются программой, известной как гипервизор, например, Oracle VM VirtualBox, VMware Workstation, KVM, Microsoft Hyper-V и так далее. Программа гипервизора обычно располагается между операционной системой хоста и виртуальными машинами, выполняя роль средства связи.

Каждая виртуальная машина поставляется со своей собственной гостевой операционной системой, которая так же тяжела, как и операционная система хоста.

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

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

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

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

В качестве демонстрации этого тезиса посмотрите на следующий блок кода:

В приведенном выше блоке кода я выполнил команду unam e-a на моей хостовой операционной системе, чтобы вывести подробную информацию о ядре. Затем в следующей строке я выполнил ту же команду внутри контейнера под управлением Alpine Linux.

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

Если вы работаете на машине с Windows, вы обнаружите, что все контейнеры используют ядро WSL2. Это происходит потому, что WSL2 выступает в качестве back-end для Docker в Windows. На macOS по умолчанию используется виртуальная машина, работающая на гипервизоре HyperKit.

Что такое образ Docker?

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

В прошлом различные контейнерные движки имели разные форматы образов. Но позже Open Container Initiative (OCI) определила стандартную спецификацию для образов контейнеров, которую соблюдают основные движки контейнеризации. Это означает, что образ, созданный с помощью Docker, может быть использован с другой средой исполнения, такой как Podman, без каких-либо дополнительных проблем.

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

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

Что такое реестр Docker?

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

Реестр образов – это централизованное место, куда вы можете загружать свои образы, а также скачивать образы, созданные другими. Docker Hub является публичным реестром по умолчанию для Docker. Другим очень популярным реестром образов является Quay от Red Hat.

На протяжении всей этой книги я буду использовать Docker Hub как наиболее предпочтительный реестр.

Вы можете бесплатно поделиться любым количеством общедоступных изображений на Docker Hub. Люди по всему миру смогут скачать их и свободно использовать. Загруженные мной изображения доступны на странице моего профиля (fhsinchy).

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

Обзор архитектуры Docker

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

Движок состоит из трех основных компонентов:

Демон Docker: Демон ( dockerd ) – это процесс, который работает в фоновом режиме и ожидает команд от клиента. Демон способен управлять различными объектами Docker.

Клиент Docker: Клиент ( docker ) – это программа интерфейса командной строки, отвечающая в основном за передачу команд, отдаваемых пользователями.

REST API: REST API действует как мост между демоном и клиентом. Любая команда, отданная с помощью клиента, проходит через API, чтобы в конечном итоге достичь демона.

Согласно официальной документации,

  1. “Docker использует архитектуру клиент-сервер. Клиент Docker общается с демоном Docker, который выполняет всю работу по созданию, запуску и распространению ваших контейнеров Docker”.
  2. Вы, как пользователь, обычно выполняете команды с помощью клиентского компонента. Затем клиент использует REST API, чтобы связаться с долго работающим демоном и выполнить свою работу.
  3. Полная картина

Ладно, достаточно разговоров. Теперь пришло время понять, как гармонично работают все эти части головоломки, о которой вы только что узнали. Прежде чем я погружусь в объяснение того, что на самом деле происходит, когда вы выполняете команду docker run hello-world, позвольте мне показать вам небольшую диаграмму, которую я сделал:

Это изображение – слегка измененная версия того, что можно найти в официальной документации. События, происходящие при выполнении команды, выглядят следующим образом:

Вы выполняете команду docker run hello-world, где hello-world – это имя образа.

Клиент Docker обращается к демону, говорит ему получить образ hello-world и запустить контейнер из него.

Демон Docker ищет образ в локальном репозитории и понимает, что его там нет, в результате чего на терминале появляется сообщение Unable to find image ‘hello-world:latest’.

Затем демон обращается к публичному реестру по умолчанию, которым является Docker Hub, и извлекает последнюю копию образа hello-world, обозначенную как latest: Pulling from library/hello-world в вашем терминале.

  1. Затем демон Docker создает новый контейнер из свежего образа.
  2. Наконец, демон Docker запускает контейнер, созданный с использованием образа hello-world, выводя стену текста на ваш терминал.
  3. Это стандартное поведение демона Docker – искать в хабе образы, которых нет локально. Но если образ был получен, он останется в локальном кэше. Поэтому, если вы выполните команду снова, вы не увидите следующих строк в выводе:
  4. Если в публичном реестре доступна более новая версия образа, демон получит образ снова. Это :latest – метка. Образы обычно имеют значимые теги, указывающие на версии или сборки. Более подробно об этом вы узнаете позже.
  5. Основы манипулирования контейнерами Docker
  6. В предыдущих разделах вы познакомились со строительными блоками Docker, а также запустили контейнер с помощью команды docker run.

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

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

Как запустить контейнер

Ранее вы уже использовали команду docker run для создания и запуска контейнера с использованием образа hello-world. Общий синтаксис этой команды выглядит следующим образом:

Хотя это вполне допустимая команда, существует лучший способ передачи команд демону docker.

До версии 1.13 Docker имел только вышеупомянутый синтаксис команд. Позже командная строка была реструктурирована и стала иметь следующий синтаксис:

object указывает на тип объекта Docker, которым вы будете управлять. Это может быть контейнер, образ, сеть или объект тома.

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

options может быть любым допустимым параметром, который может отменить поведение команды по умолчанию, например, параметр –publish для отображения портов.

Теперь, следуя этому синтаксису, команда run может быть записана следующим образом:

Как опубликовать порт

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

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

Когда вы написали –publish 8080:80 в предыдущем подразделе, это означало, что любой запрос, отправленный на порт 8080 вашей хост-системы, будет перенаправлен на порт 80 внутри контейнера.

Теперь, чтобы получить доступ к приложению в браузере, зайдите на сайт .

Вы можете остановить контейнер, просто нажав комбинацию клавиш ctrl + c, когда окно терминала находится в фокусе, или полностью закрыв окно терминала.

Как использовать отсоединенный режим

Еще одной очень популярной опцией команды run является опция –detach ил и-d. В приведенном выше примере для того, чтобы контейнер продолжал работать, нужно было держать окно терминала открытым. Закрытие окна терминала также останавливало работающий контейнер.

Это происходит потому, что по умолчанию контейнеры запускаются на переднем плане и прикрепляются к терминалу, как любая другая обычная программа, вызываемая из терминала.

Чтобы отменить это поведение и сохранить контейнер запущенным в фоновом режиме, вы можете включить опцию –detach в команду run следующим образом:

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

Порядок опций, которые вы указываете, не имеет значения. Если вы поставите опцию –publish перед опцией

Команда container ls может быть использована для получения списка контейнеров, которые запущены в данный момент. Для этого выполните следующую команду:

Запущен контейнер с именем gifted_sammet. Он был создан 5 секунд назад, его статус Up 5 seconds, что указывает на то, что контейнер работает нормально с момента его создания.

ID КОНТЕЙНЕРА – 9f21cb777058 – это первые 12 символов полного ID контейнера. Полный идентификатор контейнера – 9f21cb77705810797c4b847dbd330d9c732ffddba14fb435470567a7a3f46cdc длиной 64 символа. Этот полный идентификатор контейнера был выведен в качестве вывода команды docker container run в предыдущем разделе.

В колонке PORTS порт 8080 из локальной сети указывает на порт 80 внутри контейнера. Имя gifted_sammet генерируется Docker и может быть совершенно другим на вашем компьютере.

Команда container ls выводит список только тех контейнеров, которые в данный момент запущены в вашей системе. Чтобы получить список контейнеров, которые были запущены в прошлом, можно использовать опцию –all ил и-a.

Как видите, второй контейнер в списке reverent_torvalds был создан ранее и завершился с кодом состояния 0, что указывает на отсутствие ошибок во время работы контейнера.

Как назвать или переименовать контейнер

По умолчанию каждый контейнер имеет два идентификатора. Они следующие:

CONTAINER ID – случайная строка длиной 64 символа.

ИМЯ – комбинация двух случайных слов, соединенных знаком подчеркивания.

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

Назвать контейнер можно с помощью опции –name. Чтобы запустить другой контейнер, использующий образ fhsinchy/hello-dock с именем hello-dock-container, можно выполнить следующую команду:

Порт 8080 в локальной сети занят контейнером gifted_sammet (контейнер, созданный в предыдущем подразделе). Поэтому вам придется использовать другой номер порта, например 8888. Теперь для проверки выполните команду container ls:

Чтобы переименовать контейнер gifted_sammet в hello-dock-container-2, выполните следующую команду:

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

Как остановить или уничтожить запущенный контейнер

Контейнеры, запущенные на переднем плане, можно остановить, просто закрыв окно терминала или нажав ctrl + c . Однако контейнеры, работающие в фоновом режиме, не могут быть остановлены таким же образом.

Есть две команды, которые решают эту задачу. Первая – это команда остановки контейнера. Общий синтаксис команды выглядит следующим образом:

где идентификатор контейнера может быть либо идентификатором, либо именем контейнера.

Если вы используете имя в качестве идентификатора, вы получите имя обратно в качестве вывода. Команда stop завершает работу контейнера изящно, посылая сигнал SIGTERM. Если контейнер не останавливается в течение определенного периода времени, посылается сигнал SIGKILL, который немедленно закрывает контейнер.

В случаях, когда вы хотите послать сигнал SIGKILL вместо сигнала SIGTERM, вы можете использовать команду container kill. Команда container kill имеет тот же синтаксис, что и команда stop.

Как перезапустить контейнер

Когда я говорю “перезапустить”, я имею в виду два конкретных сценария. Они следующие:

Перезапуск контейнера, который был ранее остановлен или убит.

Перезагрузка работающего контейнера.

Как вы уже узнали из предыдущего подраздела, остановленные контейнеры остаются в вашей системе. Если вы хотите, вы можете перезапустить их. Команда container start может быть использована для запуска любого остановленного или убитого контейнера. Синтаксис команды следующий:

Вы можете получить список всех контейнеров, выполнив команду container ls –all. Затем найдите контейнеры со статусом Exited.

Теперь, чтобы перезапустить контейнер hello-dock-container, вы можете выполнить следующую команду:

Теперь вы можете убедиться, что контейнер запущен, просмотрев список запущенных контейнеров с помощью команды container ls.

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

В случае остановленного контейнера обе команды абсолютно одинаковы. Но в случае работающего контейнера необходимо использовать команду перезапуска контейнера.

Как создать контейнер без запуска

До сих пор в этом разделе вы запускали контейнеры с помощью команды container run, которая на самом деле является комбинацией двух отдельных команд. Эти команды выглядят следующим образом:

команда container create создает контейнер из заданного образа.

команда container start запускает уже созданный контейнер.

Теперь, чтобы выполнить демонстрацию, показанную в разделе “Запуск контейнеров”, используя эти две команды, вы можете сделать примерно следующее:

Как видно из вывода команды container ls –all, контейнер с именем hello-dock был создан с помощью образа fhsinchy/hello-dock. На данный момент СТАТУС контейнера – Created, и, поскольку он не запущен, он не будет отображен в списке без использования опции –all.

После создания контейнера его можно запустить с помощью команды container start.

Контейнер

Чтобы узнать, какие контейнеры не запущены, используйте команду container ls –all и поищите контейнеры со статусом Exited.

Как видно из вывода, контейнеры с ID 6cf52771dde1 и 128ec8ceab71 не запущены. Чтобы удалить 6cf52771dde1, можно выполнить следующую команду:

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

Или, вместо удаления отдельных контейнеров, если вы хотите удалить все висящие контейнеры за один раз, вы можете использовать команду container prune.

Вы можете проверить список контейнеров с помощью команды container ls –all, чтобы убедиться, что висячие контейнеры были удалены:

Если вы следуете книге в точности, как написано до сих пор, вы должны видеть в списке только hello-dock-container и hello-dock-container-2. Я бы посоветовал остановиться и удалить оба контейнера, прежде чем переходить к следующему разделу.

Существует также опция –rm для команд container run и container start, которая указывает, что вы хотите удалить контейнеры сразу после их остановки. Чтобы запустить другой контейнер hello-dock с опцией –rm, выполните следующую команду:

Вы можете использовать команду container ls, чтобы убедиться, что контейнер запущен:

Теперь, если вы остановите контейнер, а затем снова проверите его с помощью команды container ls –all:

Контейнер был удален автоматически. С этого момента я буду использовать опцию –rm для большинства контейнеров. Я буду явно указывать, где она не нужна.

Как запустить контейнер в интерактивном режиме

До сих пор вы запускали только контейнеры, созданные из образа hello-world или образа fhsinchy/hello-dock. Эти образы созданы для выполнения простых программ, которые не являются интерактивными.

Однако все образы не так просты. Образы могут заключать в себе целый дистрибутив Linux.

Популярные дистрибутивы, такие как Ubuntu, Fedora и Debian, имеют официальные образы Docker, доступные в хабе. Языки программирования, такие как python, php, go или время выполнения, такое как node и deno, имеют свои официальные образы.

Эти образы не просто запускают какую-то предварительно сконфигурированную программу. Вместо этого они настроены на запуск оболочки по умолчанию. В случае образов операционной системы это может быть что-то вроде sh или bash, а в случае языков программирования или времени выполнения это обычно языковая оболочка по умолчанию.

Как вы, возможно, уже поняли из своего предыдущего опыта работы с компьютерами, оболочки – это интерактивные программы. Образ, настроенный на выполнение такой программы, является интерактивным образом. Эти образы требуют передачи специальной опци и-it в команде запуска контейнера.

Например, если вы запускаете образ con

Опци я-it задает условия для взаимодействия с любой интерактивной программой внутри контейнера. Эта опция на самом деле представляет собой две отдельные опции, объединенные вместе.

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

Опци я-t или –tty обеспечивает хорошее форматирование и работу с терминалом, выделяя псевдо-tty.

Опци ю-it нужно использовать всякий раз, когда вы хотите запустить контейнер в интерактивном режиме. Другим примером может быть запуск образа node следующим образом:

Любой корректный код JavaScript может быть выполнен в оболочке node. Вместо того чтобы писат ь-it, можно быть более многословным, написав отдельно –interactive –tty.

Как выполнять команды внутри контейнера

В разделе “Hello World in Docker” этой книги вы видели, как я выполнял команду внутри контейнера Alpine Linux. Это выглядело примерно так:

В этой команде я выполнил команду unam e-a внутри контейнера Alpine Linux. Сценарии, подобные этому (когда все, что вы хотите сделать, это выполнить определенную команду внутри определенного контейнера), довольно распространены.

Предположим, что вы хотите закодировать строку с помощью программы base64. Это то, что доступно практически в любой операционной системе на базе Linux или Unix (но не в Windows).

В этой ситуации вы можете быстро запустить контейнер с помощью образов типа busybox и позволить ему выполнить эту работу.

Чтобы выполнить кодирование base64 с помощью образа busybox, вы можете выполнить следующую команду:

Здесь происходит следующее: в команде запуска контейнера все, что вы передаете после имени образа, передается точке входа образа по умолчанию.

Точка входа – это как шлюз в образ. Большинство образов, за исключением исполняемых образов (о них рассказывается в подразделе “Работа с исполняемыми образами”), используют shell или sh в качестве точки входа по умолчанию. Поэтому в качестве аргументов им можно передать любую допустимую команду shell.

Как работать с исполняемыми образами

В предыдущем разделе я кратко упомянул об исполняемых образах. Эти образы созданы для того, чтобы вести себя как исполняемые программы.

Возьмем, к примеру, мой проект rmbyext. Это простой Python-скрипт, способный рекурсивно удалять файлы заданных расширений. Чтобы узнать больше о проекте, вы можете ознакомиться с репозиторием:

Если у вас установлены Git и Python, вы можете установить этот скрипт, выполнив следующую команду:

Если Python был правильно настроен в вашей системе, скрипт должен быть доступен в любом месте через терминал. Общий синтаксис для использования этого скрипта следующий:

Чтобы проверить его, откройте терминал в пустом каталоге и создайте в нем несколько файлов с разными расширениями. Для этого можно использовать команду touch. Теперь на моем компьютере есть каталог со следующими файлами:

Чтобы удалить все файлы pdf из этого каталога, вы можете выполнить следующую команду co

Проблема в том, что контейнеры изолированы от вашей локальной системы, поэтому программа rmbyext, запущенная внутри контейнера, не имеет доступа к вашей локальной файловой системе. Поэтому, если каким-то образом сопоставить локальный каталог, содержащий pdf-файлы, с каталогом /zone внутри контейнера, файлы должны быть доступны контейнеру.

Одним из способов предоставления контейнеру прямого доступа к локальной файловой системе является использование связывающего монтирования.

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

Давайте посмотрим на монтирование с привязкой в действии. Чтобы удалить файлы, используя этот образ вместо самой программы, вы можете выполнить следующую команду:

Как вы уже догадались, увидев в команде част ь-v $(pwd):/zone, опци я-v или –volume используется для создания bind mount для контейнера. Эта опция может принимать три поля, разделенные двоеточиями ( : ). Общий синтаксис опции следующий:

Третье поле необязательно, но вы должны передать абсолютный путь к вашей локальной директории и абсолютный путь к директории внутри контейнера.

В моем случае исходным каталогом является /home/fhsinchy/the-zone . Учитывая, что мой терминал открыт внутри каталога, $(pwd) будет заменен на /home/fhsinchy/the-zone, который содержит ранее упомянутые файлы .pdf и .txt.

При желании вы можете узнать больше о подстановке команд здесь.

Опция –volume ил и-v действительна как для команд запуска контейнера, так и для команд создания контейнера. Мы рассмотрим тома более подробно в следующих разделах, поэтому не волнуйтесь, если вы не очень хорошо поняли их здесь.

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

Так что в итоге команда docker container run –r m-v $(pwd):/zone fhsinchy/rmbyext pdf переводится как rmbyext pdf внутри контейнера. Исполняемые образы не так часто встречаются в природе, но могут быть очень полезны в некоторых случаях.

Основы манипулирования образами Docker

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

В этом разделе вы узнаете основы создания образов, запуска контейнеров с их помощью и публикации их в Интернете.

Я бы посоветовал вам установить Visual Studio Code с официальным расширением Docker Extension из маркета. Это значительно облегчит вашу разработку.

Как создать образ Docker

Как я уже объяснял в разделе “Hello World in Docker”, образы – это многослойные самодостаточные файлы, которые выступают в качестве исходного кода.

Это все хорошо, но что если вы хотите создать собственный образ NGINX, который функционирует точно так же, как официальный, но собранный вами? Честно говоря, это вполне приемлемый сценарий. Собственно, давайте так и сделаем.

Для того чтобы сделать пользовательский образ NGINX, вы должны иметь четкое представление о том, каким будет конечное состояние образа. На мой взгляд, образ должен выглядеть следующим образом:

Образ должен иметь предустановленный NGINX, что можно сделать с помощью менеджера пакетов или собрать из исходников.

Образ должен автоматически запускать NGINX при запуске.

Это просто. Если вы клонировали репозиторий проекта, ссылки на который приведены в этой книге, зайдите в корень проекта и найдите там каталог с именем custom-nginx.

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

Образы представляют собой многослойные файлы, и в этом файле каждая написанная вами строка (известная как инструкции) создает слой для вашего образа.

Каждый правильный Dockerfile начинается с инструкции FROM. Эта инструкция задает базовый образ для вашего результирующего образа. Установив здесь ubuntu:latest в качестве базового образа, вы получите все преимущества Ubuntu, уже доступные в вашем пользовательском образе, и сможете использовать такие вещи, как команда apt-get для простой установки пакетов.

Инструкция EXPOSE используется для указания порта, который должен быть опубликован. Использование этой инструкции не означает, что вам не нужно будет публиковать порт. Вам все равно придется использовать опцию –publish в явном виде. Эта инструкция EXPOSE работает как документация для тех, кто пытается запустить контейнер, используя ваш образ. У нее также есть несколько других применений, которые я не буду здесь обсуждать.

Инструкция RUN в Dockerfile выполняет команду внутри оболочки контейнера. Команда apt-get update && apt-get install ngin x-y проверяет наличие обновленных версий пакетов и устанавливает NGINX. Команда apt-get clean && r m-rf /var/lib/apt/lists/* используется для очистки кэша пакетов, поскольку вы не хотите иметь в образе ненужный багаж. Эти две команды – простые вещи в Ubuntu, ничего навороченного. Инструкции RUN здесь написаны в форме shell. Они также могут быть написаны в форме exec. За дополнительной информацией вы можете обратиться к официальной справке.

Наконец, команда CMD устанавливает команду по умолчанию для вашего образа. Эта инструкция записана в форме exec и состоит из трех отдельных частей. Здесь nginx означает исполняемый файл NGINX. Опци и-g и daemon off являются опциями для NGINX. Запуск NGINX как одного процесса внутри контейнеров считается лучшей практикой, поэтому используется эта опция. Инструкция CMD также может быть написана в форме shell. Для получения дополнительной информации вы можете обратиться к официальной справке.

Теперь, когда у вас есть правильный Dockerfile, вы можете собрать из него образ. Как и команды, связанные с контейнерами, команды, связанные с образами, могут быть выполнены с использованием следующего синтаксиса:

Чтобы создать образ, мы

Символ . в конце задает контекст для этой сборки. Контекст означает каталог, доступный демону во время процесса сборки.

Теперь, чтобы запустить контейнер с помощью этого образа, вы можете использовать команду container run в сочетании с ID образа, который вы получили в результате процесса сборки. В моем случае идентификатором является 3199372aa3fc, о чем свидетельствует строка Successfully built 3199372aa3fc в предыдущем блоке кода.

Как и контейнерам, образам можно присваивать собственные идентификаторы, а не полагаться на случайно сгенерированный ID. В случае с образами это называется тегированием, а не именованием. В таких случаях используется опция –tag ил и-t.

Общий синтаксис опции следующий:

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

Как перечислить и удалить образы Docker

Подобно команде container ls, вы можете использовать команду image ls, чтобы перечислить все образы в вашей локальной системе:

Перечисленные здесь образы могут быть удалены с помощью команды image rm. Общий синтаксис выглядит следующим образом:

Опция –force ил и-f пропускает любые вопросы подтверждения. Вы также можете использовать опцию –all ил и-a для удаления всех кэшированных образов в локальном реестре.

Как понять множество слоев образа Docker

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

Для этой демонстрации я буду использовать образ custom-nginx:packaged из предыдущего подраздела.

Для визуализации множества слоев образа можно использовать команду image history. Различные слои изображения custom-nginx:packaged можно представить следующим образом:

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

Теперь давайте подробнее рассмотрим образы, начиная с imag

587c805fe8df был создан командой /bin/s h-c apt-get update && apt-get install ngin x-y && apt-get clean && r m-rf /var/lib/apt/lists/*, которая была третьей инструкцией в вашем коде. Вы также можете видеть, что этот образ имеет размер 60MB, поскольку все необходимые пакеты были установлены во время выполнения этой инструкции.

Наконец, самый верхний слой 7f16387f7307 был создан командой /bin/s h-c #(nop) CMD [“nginx”, “-g”, “daemon off;”], которая устанавливает команду по умолчанию для этого образа.

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

Это явление наслоения, которое происходит каждый раз, когда вы работаете с Docker, стало возможным благодаря удивительной технической концепции, называемой файловой системой union. Здесь союз означает объединение в теории множеств. Согласно Википедии –

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

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

Как собрать NGINX из исходного кода

В предыдущем подразделе вы познакомились с инструкциями FROM , EXPOSE , RUN и CMD. В этом подразделе вы узнаете гораздо больше о других инструкциях.

В этом подразделе вы снова создадите пользовательский образ NGINX. Но изюминка в том, что вы будете собирать NGINX из исходников, а не устанавливать его с помощью менеджера пакетов, такого как apt-get, как в предыдущем примере.

Для того чтобы собрать NGINX из исходников, вам сначала нужен исходник NGINX. Если вы клонировали репозиторий моих проектов, вы увидите файл с именем nginx-1.19.2.tar.gz в каталоге custom-nginx. Вы будете использовать этот архив в качестве источника для сборки NGINX.

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

Возьмите хороший базовый образ для сборки приложения, например ubuntu.

Установите необходимые зависимости сборки на базовый образ.

Скопируйте файл nginx-1.19.2.tar.gz внутрь образа.

Распакуйте содержимое архива и избавьтесь от него.

Как вы можете видеть, код внутри Dockerfile отражает семь шагов, о которых я говорил выше.

Инструкция FROM устанавливает Ubuntu в качестве базового образа, создавая идеальную среду для создания любого приложения.

Инструкция RUN устанавливает стандартные пакеты, необходимые для сборки NGINX из исходного кода.

Инструкция COPY – это нечто новое. Эта инструкция отвечает за копирование файла nginx-1.19.2.tar.gz внутрь образа. Общий синтаксис инструкции COPY – COPY, где источник находится в вашей локальной файловой системе, а место назначения – внутри образа. Символ . в качестве места назначения означает рабочий каталог внутри образа, который по умолчанию /, если не задано иное.

Вторая инструкция RUN здесь извлекает содержимое из архива с помощью tar и после этого избавляется от него.

Архивный файл содержит каталог nginx-1.19.2 с исходным кодом. Поэтому на следующем шаге вам нужно будет cd внутрь этого каталога и выполнить процесс сборки. Вы можете прочитать статью Как установить программное обеспечение из исходного кода… и удалить его после этого, чтобы узнать больше по этой теме.

После завершения сборки и установки удалите каталог nginx-1.19.2 с помощью команды rm.

На последнем шаге вы запускаете NGINX в однопроцессном режиме, как и раньше.

Теперь, чтобы собрать образ с помощью этого кода, выполните следующую команду:

Инструкция ADD по умолчанию не извлекает файлы, полученные из Интернета, поэтому в строке 18 используется tar.

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

Как вы можете видеть в строке 3, инструкция RUN устанавливает множество пакетов. Хотя эти пакеты необходимы для сборки NGINX из исходников, они не нужны для его запуска.

Из 6 пакетов, которые мы установили, только два необходимы для работы NGINX. Это libpcre3 и zlib1g . Поэтому лучшей идеей будет удалить остальные пакеты после завершения процесса сборки.

Со строки 10 по строку 17 устанавливаются все необходимые пакеты.

В строке 18 происходит извлечение исходного кода и удаление загруженного архива.

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

Если вы установите пакеты, а затем удалите их в отдельных инструкциях RUN, они будут жить в отдельных слоях образа. Хотя в конечном образе не будет удаленных пакетов, их размер все равно будет добавлен к конечному образу, поскольку они существуют в одном из слоев, из которых состоит образ. Поэтому убедитесь, что вы делаете подобные изменения на одном слое.

Давайте создадим образ с помощью этого Dockerfile и посмотрим на различия.

Как вы можете видеть, размер образа увеличился с 343 МБ до 81,6 МБ. Официальный образ составляет 133 МБ. Это довольно оптимизированная сборка, но мы можем пойти немного дальше в следующем подразделе.

Освоение Alpine Linux

Если вы уже некоторое время возитесь с контейнерами, вы, возможно, слышали о так называемом Alpine Linux. Это полнофункциональный дистрибутив Linux, подобный Ubuntu, Debian или Fedora.

Но Alpine хорош тем, что он построен вокруг musl libc и busybox и является легковесным. Если последний образ ubuntu весит около 28 МБ, то Alpine – 2,8 МБ.

Помимо легкости, Alpine также безопасен и гораздо лучше подходит для создания контейнеров, чем другие дистрибутивы.

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

Откройте свой Dockerfile и обновите его содержимое следующим образом:

Код практически идентичен, за исключением нескольких изменений. Я буду перечислять изменения и объяснять их по ходу дела:

Вместо использования apt-get install для установки пакетов мы используем apk add . Опция –no-cache означает, что загруженный пакет не будет кэшироваться. Аналогично мы будем использовать apk del вместо apt-get remove для удаления пакетов.

Для начала откройте каталог, в который вы клонировали репозиторий, прилагаемый к этой книге. Код приложения rmbyext находится в одноименном подкаталоге.

Прежде чем начать работу над Docker-файлом, уделите время планированию того, каким должен быть конечный результат. На мой взгляд, он должен быть примерно таким:

На образе должен быть предустановлен Python.

Он должен содержать копию моего скрипта rmbyext.

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

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

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

Возьмите хороший базовый образ для выполнения сценариев Python, например python.

Установите рабочий каталог в легкодоступную директорию.

Установите Git, чтобы скрипт можно было установить из моего репозитория GitHub.

Установите скрипт с помощью Git и pip.

Инструкции в этом файле объясняются следующим образом:

Инструкция FROM устанавливает python в качестве базового образа, создавая идеальную среду для запуска сценариев Python. Метка 3-alpine указывает, что вы хотите использовать Alpine-вариант Python 3.

Инструкция WORKDIR устанавливает рабочий каталог по умолчанию на /zone here. Имя рабочего каталога здесь совершенно произвольное. Я счел zone подходящим именем, вы можете использовать любое другое.

Поскольку скрипт rmbyext устанавливается с GitHub, git является зависимостью во время установки. Инструкция RUN в строке 5 устанавливает git, затем устанавливает скрипт rmbyext с помощью Git и pip. После этого она также избавляется от git.

Наконец, в строке 9 инструкция ENTRYPOINT устанавливает скрипт rmbyext в качестве точки входа для этого образа.

Во всем этом файле строка 9 – это то волшебство, которое превращает этот, казалось бы, обычный образ в исполняемый. Теперь, чтобы собрать образ, вы можете выполнить следующую команду:

После создания учетной записи вам нужно будет войти в нее с помощью docker CLI. Для этого откройте терминал и выполните следующую команду:

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

После того как образ будет создан, вы можете загрузить его, выполнив следующую команду:

В этом подразделе вы будете работать с исходным кодом изображения fhsinchy/hello-dock, с которым вы работали в предыдущем разделе. В процессе контейнеризации этого очень простого приложения вы познакомитесь с томами и многоступенчатыми сборками, двумя наиболее важными концепциями в Docker.

Как написать Docker-файл для разработки

Для начала откройте каталог, в который вы клонировали репозиторий, прилагаемый к этой книге. Код для приложения hello-dock находится в одноименном подкаталоге.

Это очень простой JavaScript-проект на базе проекта vitejs/vite. Однако не волнуйтесь, вам не нужно знать JavaScript или vite для того, чтобы пройти этот подраздел. Достаточно иметь базовое представление о Node.js и npm.

Как и любой другой проект

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

Теперь, если вы поместите вышеупомянутый план в Dockerfile.dev, файл должен выглядеть следующим образом:

Объяснение этого кода следующее:

Инструкция FROM здесь устанавливает официальный образ Node.js в качестве базового, предоставляя вам все преимущества Node.js, необходимые для запуска любого приложения JavaScript. Тег lts-alpine указывает, что вы хотите использовать вариант Alpine, версию образа с долгосрочной поддержкой. Доступные теги и необходимую документацию к образу можно найти на странице узла node.

Инструкция USER устанавливает пользователя по умолчанию для образа на node . По умолчанию Docker запускает контейнеры от имени пользователя root. Но согласно Docker и Node.js Best Practices это может представлять угрозу безопасности. Поэтому лучше по возможности запускать контейнеры от имени не root-пользователя. Образ node поставляется с нерутовым пользователем node, которого вы можете установить в качестве пользователя по умолчанию с помощью инструкции USER.

Инструкция RUN mkdi r-p /home/node/app создает каталог app в домашнем каталоге пользователя node. Домашним каталогом для любого не корневого пользователя в Linux по умолчанию обычно является /home/.

Затем инструкция WORKDIR устанавливает рабочий каталог по умолчанию в только что созданный каталог /home/node/app. По умолчанию рабочим каталогом любого образа является корневой. Вы же не хотите, чтобы ненужные файлы были распылены по всему корневому каталогу? Поэтому вы меняете рабочий каталог по умолчанию на что-то более разумное, например /home/node/app или любое другое. Этот рабочий каталог будет применяться ко всем последующим инструкциям COPY, ADD, RUN и CMD.

Инструкция COPY здесь копирует файл package.json, который содержит информацию обо всех необходимых зависимостях для данного приложения. Инструкция RUN выполняет команду npm install, которая является командой по умолчанию для установки зависимостей с помощью файла package.json в проектах Node.js. Символ . в конце обозначает рабочий каталог.

Вторая инструкция COPY копирует остальное содержимое из текущего каталога ( .) файловой системы хоста в рабочий каталог ( .) внутри образа.

Наконец, инструкция CMD здесь устанавливает команду по умолчанию для этого образа, которая представляет собой npm run dev, записанную в форме exec.

Сервер разработки vite по умолчанию работает на порту 3000, и добавление команды EXPOSE показалось хорошей идеей, так что вот так.

Теперь, чтобы собрать образ из этого Dockerfile.dev, вы можете выполнить следующую команду:

Учитывая, что имя файла не является именем Dockerfile, вы должны явно передать имя файла с помощью опции –file. Контейнер можно запустить с помощью этого образа, выполнив следующую команду:

Теперь посетите сайт , чтобы увидеть приложение hello-dock в действии.

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

Как работать с Bind Mounts в Docker

Если вы уже работали с каким-либо front-end JavaScript фреймворком, вы должны знать, что серверы разработки в этих фреймворках обычно поставляются с функцией горячей перезагрузки. То есть если вы внесете изменения в свой код, сервер перезагрузится, автоматически отражая все сделанные вами изменения.

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

Теперь, когда вы монтируете корень проекта в локальной файловой системе как том внутри контейнера, содержимое внутри контейнера заменяется вместе с каталогом node_modules, содержащим все зависимости. Это означает, что пакет vite пропал.

Как работать с анонимными томами в Docker

Эту проблему можно решить с помощью анонимного тома. Анонимный том идентичен bind mount, за исключением того, что здесь не нужно указывать исходный каталог. Общий синтаксис для создания анонимного тома выглядит следующим образом:

Установите nginx внутри образа node и используйте его для обслуживания статических файлов.

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

Используйте образ узла в качестве основы и создайте приложение.

Скопируйте файлы, созданные с помощью образа node, в образ nginx.

Создайте окончательный образ на основе nginx и отбросьте все, что связано с node.

Таким образом, ваш образ содержит только те файлы, которые необходимы, и становится действительно удобным.

Этот подход представляет собой многоступенчатую сборку. Чтобы выполнить такую сборку, создайте новый Dockerfile внутри каталога проекта hello-dock и поместите в него следующее содержимое:

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

Строка 1 запускает первый этап сборки, используя node:lts-alpine в качестве базового образа. Синтаксис as builder присваивает имя этому этапу, чтобы на него можно было ссылаться в дальнейшем.

Со строки 3 по строку 9 – это стандартные действия, которые вы уже много раз видели. Команда RUN npm run build фактически компилирует все приложение и помещает его в каталог /app/dist, где /app – рабочий каталог, а /dist – каталог вывода по умолчанию для vite-приложений.

Строка 11 запускает второй этап сборки, используя nginx:stable-alpine в качестве базового образа.

Сервер NGINX по умолчанию работает на порту 80, поэтому добавляется строка EXPOSE 80.

Последняя строка – это инструкция COPY. Часть –from=builder указывает на то, что вы хотите скопировать некоторые файлы из стадии builder. После этого это стандартная инструкция копирования, где /app/dist – источник, а /usr/share/nginx/html – место назначения. Здесь используется путь к сайту по умолчанию для NGINX, поэтому любой статический файл, который вы поместите туда, будет автоматически обслуживаться.

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

После того как образ будет создан, вы можете запустить новый контейнер, выполнив следующую команду:

Запущенное приложение должно быть доступно на :

Здесь вы можете увидеть мое приложение hello-dock во всей его красе. Многоэтапные сборки могут быть очень полезны, если вы создаете большие приложения с большим количеством зависимостей. При правильной настройке образы, собранные в несколько этапов, могут быть очень оптимизированными и компактными.

Как игнорировать ненужные файлы

Если вы уже некоторое время работаете с git, вы можете знать о файлах .gitignore в проектах. Они содержат список файлов и каталогов, которые должны быть исключены из репозитория.

В Docker есть похожая концепция. Файл .dockerignore содержит список файлов и каталогов, которые необходимо исключить из сборки образа. Вы можете найти предварительно созданный файл .dockerignore в каталоге hello-dock.

Этот файл .dockerignore должен находиться в контексте сборки. Файлы и каталоги, упомянутые здесь, будут проигнорированы инструкцией COPY. Но если вы выполните bind mount, файл .dockerignore не будет иметь никакого эффекта. Я добавил файлы .dockerignore, где это необходимо, в репозиторий проекта.

Основы сетевых манипуляций в Docker

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

Эти два контейнера полностью изолированы друг от друга и не подозревают о существовании друг друга. Как же соединить эти два контейнера? Не будет ли это сложной задачей?

Первый вариант предполагает открытие порта из контейнера postgres, через который будет подключаться notes-api. Предположим, что открытый порт контейнера postgres равен 5432. Теперь, если вы попытаетесь подключиться к 127.0.0.1:5432 из контейнера notes-api, вы обнаружите, что notes-api вообще не может найти сервер базы данных.

Причина в том, что когда вы говорите 127.0.0.1 внутри контейнера notes-api, вы просто ссылаетесь на localhost этого контейнера и только этого контейнера. Сервер postgres там просто не существует. В результате приложение notes-api не смогло подключиться.

Второе решение, о котором вы можете подумать, – это найти точный IP-адрес контейнера postgres с помощью команды container inspect и использовать его вместе с портом. Предположив, что имя контейнера postgres – notes-api-db-server, вы можете легко получить IP-адрес, выполнив следующую команду:

Чтобы получить список сетей в вашей системе, выполните следующую команду:

Вы должны увидеть три сети в вашей системе. Теперь посмотрите на столбец DRIVER в таблице. Эти драйверы можно рассматривать как тип сети.

По умолчанию Docker имеет пять сетевых драйверов. Они следующие:

bridge – сетевой драйвер по умолчанию в Docker. Его можно использовать, когда несколько контейнеров работают в стандартном режиме и им необходимо взаимодействовать друг с другом.

host – полностью снимает сетевую изоляцию. Любой контейнер, запущенный в сети хоста, по сути, подключен к сети хост-системы.

none – Этот драйвер полностью отключает сетевое взаимодействие для контейнеров. Я пока не нашел для него применения.

overlay – используется для соединения нескольких демонов Docker между компьютерами и выходит за рамки данной книги.

macvlan – Позволяет назначать MAC-адреса контейнерам, делая их похожими на физические устройства в сети.

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

Как создать пользовательский мост в Docker

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

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

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

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

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

Теперь, когда вы узнали достаточно много о пользовательской сети, пришло время создать ее для себя. Сеть можно создать с помощью команды network create. Общий синтаксис команды выглядит следующим образом:

Чтобы создать сеть с именем skynet, выполните следующую команду:

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

Как присоединить контейнер к сети в Docker

Существует два способа присоединения контейнера к сети. Во-первых, вы можете использовать команду network connect для присоединения контейнера к сети. Общий синтаксис команды выглядит следующим образом:

Чтобы подключить контейнер hello-dock к сети skynet, вы можете выполнить следующую команду:

Как видно из результатов двух команд network inspect, контейнер hello-dock теперь подключен и к сети skynet, и к сети default bridge.

Второй способ присоединения контейнера к сети – это использование опции –network в командах container run или container create. Общий синтаксис для этой опции следующий:

Чтобы запустить другой контейнер hello-dock, подключенный к той же сети, вы можете выполнить следующую команду:

Как вы можете видеть, выполнение команды ping hello-dock из контейнера alpine-box работает, поскольку оба контейнера находятся в одной пользовательской сети bridge и работает автоматическое разрешение DNS.

Как и команда network connect, команда network disconnect не дает никаких результатов.

Как избавиться от сетей в Docker

Как и другие логические объекты в Docker, сети могут быть удалены с помощью команды network rm. Общий синтаксис команды выглядит следующим образом:

Чтобы удалить сеть skynet из вашей системы, вы можете выполнить следующую команду:

Вы также можете использовать команду network prune, чтобы удалить все неиспользуемые сети из системы. Команда также имеет опци и-f или –force и-a или –all.

Как контейнеризировать многоконтейнерное приложение JavaScript

Сервер базы данных в этом проекте представляет собой простой сервер PostgreSQL и использует официальный образ postgres.

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

Чтобы запустить сервер базы данных, можно выполнить следующую команду:

Опция –env для команд container run и container create может быть использована для задания переменных окружения контейнера. Как вы можете видеть, контейнер базы данных был успешно создан и сейчас запущен.

Хотя контейнер запущен, существует небольшая проблема. Такие базы данных, как PostgreSQL, MongoDB и MySQL, хранят свои данные в каталоге. PostgreSQL использует каталог /var/lib/postgresql/data внутри контейнера для сохранения данных.

А что если контейнер по какой-то причине будет уничтожен? Вы потеряете все свои данные. Для решения этой проблемы можно использовать именованный том.

Как работать с именованными томами в Docker

Ранее вы уже работали с bind mounts и анонимными томами. Именованный том очень похож на анонимный том, за исключением того, что вы можете ссылаться на именованный том, используя его имя.

Тома также являются логическими объектами в Docker, и ими можно управлять с помощью командной строки. Для создания именованного тома можно использовать команду volume create.

Общий синтаксис команды следующий

Теперь данные будут надежно храниться внутри тома notes-db-data и могут быть использованы повторно в будущем. Вместо именованного тома здесь также можно использовать bind mount, но я предпочитаю именованный том в таких сценариях.

Как получить доступ к журналам из контейнера в Docker

Чтобы просмотреть журналы из контейнера, вы можете использовать команду container logs. Общий синтаксис команды выглядит следующим образом:

Чтобы получить доступ к журналам из контейнера notes-db, вы можете выполнить следующую команду:

Судя по тексту в строке 57, база данных запущена и готова принимать соединения извне. Существует также опция –follow ил и-f для команды, которая позволяет подключить консоль к выводу журналов и получить непрерывный поток текста.

Как создать сеть и подключить сервер базы данных в Docker

Как вы узнали из предыдущего раздела, контейнеры должны быть присоединены к пользовательской мостовой сети, чтобы общаться друг с другом с помощью имен контейнеров. Для этого создайте в своей системе сеть с именем notes-api-network:

Теперь присоедините контейнер notes-db к этой сети, выполнив следующую команду:

Как написать Dockerfile

Перейдите в каталог, куда вы клонировали код проекта. Внутри нее перейдите в каталог notes-api/api и создайте новый Dockerfile. Поместите в файл следующий код:

Это многоэтапная сборка. Первый этап используется для сборки и установки зависимостей с помощью node-gyp, а второй этап – для запуска приложения. Я кратко пройдусь по этапам:

Этап 1 использует node:lts-alpine в качестве базы и использует builder в качестве имени этапа.

В строке 5 мы устанавливаем python, make и g++. Для работы инструмента node-gyp требуются эти три пакета.

В строке 7 мы устанавливаем каталог /app в качестве WORKDIR.

В строках 9 и 10 мы копируем файл package.json в WORKDIR и устанавливаем все зависимости.

Этап 2 также использует node-lts:alpine в качестве базы.

В строке 16 мы устанавливаем переменную окружения NODE_ENV на production . Это важно для правильной работы API.

Со строки 18 по строку 20 мы устанавливаем пользователя по умолчанию на node , создаем каталог /home/node/app и устанавливаем его в качестве WORKDIR .

В строке 22 мы копируем все файлы проекта, а в строке 23 мы копируем каталог node_modules из стадии builder. Этот каталог содержит все собранные зависимости, необходимые для запуска приложения.

В строке 25 мы задаем команду по умолчанию.

Чтобы собрать образ из этого Dockerfile, вы можете выполнить следующую команду:

Перед запуском контейнера, использующего этот образ, убедитесь, что контейнер базы данных запущен и подключен к сети notes-api-network .

Я сократил вывод для удобства просмотра. В моей системе контейнер notes-db запущен, использует том notes-db-data и подключен к мосту notes-api-network.

Когда вы убедитесь, что все на месте, вы можете запустить новый контейнер, выполнив следующую команду:

Вы должны быть в состоянии понять эту длинную команду самостоятельно, поэтому я пройдусь по переменным окружения

DB_DATABASE – База данных, которую будет использовать этот API. При запуске сервера базы данных мы задали имя базы данных по умолчанию notesdb с помощью переменной окружения POSTGRES_DB. Мы будем использовать это имя здесь.

DB_PASSWORD – Пароль для подключения к базе данных. Он также был задан в подразделе “Запуск сервера баз данных” с помощью переменной окружения POSTGRES_PASSWORD.

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

Контейнер запущен. Вы можете посетить сайт , чтобы увидеть API в действии.

API имеет пять маршрутов, которые вы можете увидеть в файле /notes-api/api/api/routes/notes.js.

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

Как выполнять команды в запущенном контейнере

Вы уже узнали о выполнении команд в остановленном контейнере. Другой сценарий – выполнение команды внутри работающего контейнера.

Для этого вам потребуется использовать команду exec для выполнения пользовательской команды внутри запущенного контейнера.

Общий синтаксис команды exec выглядит следующим образом:

Чтобы выполнить npm run db:migrate внутри контейнера notes-api, вы можете выполнить следующую команду:

В случаях, когда вы хотите запустить интерактивную команду внутри работающего контейнера, вам придется использовать фла г-it. Например, если вы хотите получить доступ к оболочке, запущенной внутри контейнера notes-api, вы можете выполнить следующую команду:

Как писать сценарии управления в Docker

Управление многоконтейнерным проектом вместе с сетью, томами и прочим означает написание большого количества команд. Чтобы упростить этот процесс, мне обычно помогают простые сценарии оболочки и Makefile.

Как составлять проекты с помощью

Compose – это инструмент для определения и запуска многоконтейнерных приложений Docker. С помощью Compose вы используете YAML-файл для настройки сервисов вашего приложения. Затем, с помощью одной команды, вы создаете и запускаете все службы из вашей конфигурации.

Хотя Compose работает во всех средах, он больше ориентирован на разработку и тестирование. Использование Compose в производственной среде вообще не рекомендуется.

Основы работы с Docker Compose

Перейдите в каталог, куда вы клонировали репозиторий, прилагаемый к этой книге. Перейдите в каталог notes-api/api и создайте файл Dockerfile.dev. Поместите в него следующий код:

Этот код практически идентичен Dockerfile, с которым вы работали в предыдущем разделе. Три отличия этого файла заключаются в следующем:

Вы уже знаете, что в этом проекте есть два контейнера:

notes-db – сервер базы данных на базе PostgreSQL.

notes-api – REST API на базе Express.js.

В мире Compose каждый контейнер, составляющий приложение, называется сервисом. Первым шагом в создании многоконтейнерного проекта является определение этих сервисов.

Подобно тому, как демон Docker использует Dockerfile для создания образов, Docker Compose использует файл docker-compose.yaml для чтения определений сервисов.

Перейдите в каталог notes-api и создайте новый файл docker-compose.yaml. Поместите следующий код во вновь созданный файл:

Каждый правильный файл docker-compose.yaml начинается с определения версии файла. На момент написания статьи последней версией является 3.8. Вы можете посмотреть последнюю версию здесь.

Блоки в YAML-файле определяются отступами. Я пройдусь по каждому из блоков и объясню, что они делают.

Блок services содержит определения для каждого из сервисов или контейнеров в приложении. db и api – это два сервиса, которые входят в данный проект.

Блок db определяет новый сервис в приложении и содержит необходимую информацию для запуска контейнера. Каждый сервис требует либо предварительно созданного образа, либо Dockerfile для запуска контейнера. Для службы db мы используем официальный образ PostgreSQL.

В отличие от службы db, для службы api не существует готового образа. Поэтому мы будем использовать файл Dockerfile.dev.

Блок volumes определяет любой именной том, необходимый любой из служб. На данный момент в нем указан только том notes-db-dev-data, используемый службой db.

Теперь, когда мы имеем высокоуровневый обзор файла docker-compose.yaml, давайте подробнее рассмотрим отдельные сервисы.

Ключ image содержит имя собираемого образа. Если он не задан, образ будет назван в соответствии с синтаксисом _.

Внутри карты окружения переменная DB_HOST демонстрирует особенность Compose. То есть, вы можете ссылаться на другой сервис в том же приложении, используя его имя. Таким образом, db здесь будет заменено на IP-адрес контейнера api сервиса. Переменные DB_DATABASE и DB_PASSWORD должны совпадать с POSTGRES_DB и POSTGRES_PASSWORD соответственно из определения службы db.

В карте томов вы можете увидеть анонимный том и описанный bind mount. Синтаксис идентичен тому, что вы видели в предыдущих разделах.

Карта портов определяет любое сопоставление портов. Синтаксис, : идентичен опции –publish, которую вы использовали ранее.

Наконец, код для томов выглядит следующим образом:

Любой именованный том, используемый в любой из служб, должен быть определен здесь. Если вы не определите имя, то том будет назван после символа _, и ключевым здесь является db-data .

Вы можете узнать о различных вариантах конфигурации тома в официальной документации.

Как запустить сервисы в Docker Compose

Существует несколько способов запуска сервисов, определенных в YAML-файле. Первая команда, о которой вы узнаете, это команда up. Команда up собирает все недостающие образы, создает контейнеры и запускает их одним махом.

Перед выполнением этой команды убедитесь, что вы открыли терминал в той же директории, где находится файл docker-compose.yaml. Это очень важно для каждой команды docker-compose, которую вы выполняете.

Опция –detach ил и-d здесь функционирует так же, как и та, которую вы видели раньше. Опция –file ил и-f нужна только в том случае, если YAML-файл не имеет имени docker-compose.yaml (но я использовал его в демонстрационных целях).

Помимо команды up существует команда start. Основное различие между ними заключается в том, что команда start не создает отсутствующие контейнеры, а только запускает существующие. По сути, это то же самое, что и команда запуска контейнера.

Она не так информативна, как вывод container ls, но полезна, когда у вас есть множество одновременно запущенных контейнеров.

Подобно команде exec контейнера, существует команда exec для docker-compose . Общий синтаксис команды выглядит следующим образом:

Чтобы выполнить команду npm run db:migrate внутри службы api, вы можете выполнить следующую команду:

В отличие от команды exec контейнера, вам не нужно передавать фла г-it для интерактивных сессий. docker-compose делает это автоматически.

Как получить доступ к журналам запущенной службы в Docker Compose

Вы также можете использовать команду logs для получения журналов из запущенной службы. Общий синтаксис команды выглядит следующим образом:

Опция –volumes указывает, что вы хотите удалить любой именованный том (тома), определенный в блоке томов. О дополнительных опциях для команды down можно узнать в официальной документации.

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

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

Давайте начнем писать файл docker-compose.yaml. Помимо сервисов api и db, здесь будут клиентский и nginx сервисы. Также будут некоторые сетевые определения, которые я рассмотрю в ближайшее время.

Файл почти идентичен предыдущему, с которым вы работали. Единственное, что требует пояснений, это конфигурация сети. Код блока networks выглядит следующим образом:

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

Я также добавил блок networks в каждое из определений служб. Таким образом, службы api и db будут подключены к одной сети, а клиентская служба будет подключена к отдельной сети. Но служба nginx будет подключена к обеим сетям, чтобы она могла выполнять роль маршрутизатора между внешними и внутренними службами.

Запустите все службы, выполнив следующую команду:

Теперь зайдите на сайт http://localhost:8080 и вуаля!

Попробуйте добавлять и удалять заметки, чтобы проверить, правильно ли работает приложение. Проект также поставляется со сценариями оболочки и Makefile. Изучите их, чтобы узнать, как можно запустить этот проект без помощи docker-compose, как это было в предыдущем разделе.

Заключение

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

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

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

Если вам понравилась моя работа и вы хотите поддержать мою мотивацию, оставьте стартовый отзыв на GitHub и одобряйте меня за соответствующие навыки на LinkedIn. Я также принимаю спонсорскую помощь, поэтому вы можете рассмотреть возможность купить мне кофе, если хотите.

Я всегда открыт для предложений и обсуждений в Twitter или LinkedIn. Пишите мне прямые сообщения.

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

Делиться знаниями – это самый главный акт дружбы. Потому что это способ дать что-то, не теряя ничего. – Ричард Столлман

Фархан Хасин Чоудхури

The Docker Handbook – Изучение Docker для начинающих

-MD_PXBw_anEnk5G-Lck

docker-handbook-preview

drag-docker-in-applications-directory

docker-and-compose-version-on-windows

docker-and-compose-version-on-linux

docker-hub

my-images-on-docker-hub

hello-dock

hello-dock

nginx-default

nginx-default

nginx-default

hello-dock-dev

hello-dock

bonjour-mon-ami

notes-application

Exit mobile version