Docker. Начало

Примерно такие же эмоции я и мои коллеги испытывали, когда начинали работать с Docker. В подавляющем большинстве случаев это происходило от недостатка понимания основных механизмов, поэтому его поведение казалось нам непредсказуемым. Сейчас страсти поутихли и вспышки ненависти происходят все реже и все слабее. Более того, постепенно мы на практике оцениваем его достоинства и он начинает нам нравиться… Чтобы снизить степень первичного отторжения и добиться максимального эффекта от использования, нужно обязательно заглянуть на кухню Docker’a и хорошенько там осмотреться.
Начнем с того, для чего же нам нужен Docker:
- изолированный запуск приложений в контейнерах
- упрощение разработки, тестирования и деплоя приложений
- отсутствие необходимости конфигурировать среду для запуска — она поставляется вместе с приложением — в контейнере
- упрощает масштабируемость приложений и управление их работой с помощью систем оркестрации контейнеров.
Предыстрория
Для изоляции процессов, запущенных на одном хосте, запуска приложений, предназначенных для разных платформ, можно использовать виртуальные машины. Виртуальные машины делят между собой физические ресурсы хоста:

- процессор,
- память,
- дисковое пространство,
- сетевые интерфейсы.
На каждой ВМ устанавливаем нужную ОС и запускаем приложения. Недостатком такого подхода является то, что значительная часть ресурсов хоста расходуется не на полезную нагрузку(работа приложений), а на работу нескольких ОС.
Контейнеры
Альтернативным подходом к изоляции приложений являются контейнеры. Само понятие контейнеров не ново и давно известно в Linux. Идея состоит в том, чтобы в рамках одной ОС выделить изолированную область и запускать в ней приложение. В этом случае говорим о виртуализации на уровне ОС. В отличие от ВМ контейнеры изолированно используют свой кусочек ОС:

- файловая система
- дерево процессов
- сетевые интерфейсы
- и др.
Т.о. приложение, запущенное в контейнере думает, что оно одно во всей ОС. Изоляция достигается за счет использования таких Linux-механизмов, как namespaces и control groups. Если говорить просто, то namespaces обеспечивают изоляцию в рамках ОС, а control groups устанавливают лимиты на потребление контейнером ресурсов хоста, чтобы сбалансировать распределение ресурсов между запущенными контейнерами.
Т.о. контейнеры сами по себе не являются чем-то новым, просто проект Docker, во-первых, скрыл сложные механизмы namespaces, control groups, а во-вторых, он окружен экосистемой, обеспечивающей удобное использование контейнеров на всех стадиях разработки ПО.
Образы
Образ в первом приближении можно рассматривать как набор файлов. В состав образа входит все необходимое для запуска и работы приложения на голой машине с докером: ОС, среда выполнения и приложение, готовое к развертыванию.
Но при таком рассмотрении возникает вопрос: если мы хотим использовать несколько образов на одном хосте, то будет нерационально как с точки зрения загрузки, так и с точки зрения хранения, чтобы каждый образ тащил все необходимое для своей работы, ведь большинство файлов будут повторяться, а различаться — только запускаемое приложение и, возможно, среда выполнения. Избежать дублирования файлов позволяет структура образа.
Образ состоит из слоев, каждый из которых представляет собой неизменяемую файловую систему, а по-простому набор файлов и директорий. Образ в целом представляет собой объединенную файловую систему (Union File System), которую можно рассматривать как результат слияния файловых систем слоев. Объединенная файловая система умеет обрабатывать конфликты, например, когда в разных слоях присутствуют файлы и директории с одинаковыми именами. Каждый следующий слой добавляет или удаляет какие то файлы из предыдущих слоев. В данном контексте «удаляет» можно рассматривать как «затеняет», т.е. файл в нижележащем слое остается, но его не будет видно в объединенной файловой системе.
Можно провести аналогию с Git: слои — это как отдельные коммиты, а образ в целом — результат выполнения операции squash. Как мы увидим дальше, на этом параллели с Git не заканчиваются. Существуют различные реализации объединенной файловой системы, одна из них — AUFS.
Для примера рассмотрим образ произвольного .NET приложения MyApplication: первым слоем является ядро Linux, далее следуют слои ОС, среды исполнения и уже самого приложения.

Слои являются read only и, если в слое MyApplication нужно изменить файл, находящийся в слое dotnet, то файл сначала копируется в нужный слой, а потом в нем изменяется, оставаясь в исходном слое в первозданном виде.

Неизменяемость слоев позволяет использовать их всеми образами на хосте. Допустим MyApplication — это веб-приложение, которое использует БД и взаимодействует также с NodeJS сервером.

Совместное использование проявляется также и при скачивании образа. Первым загружается манифест, который описывает какие слои входят в образ. Далее скачиваются только те слои из манифеста, которых еще нет локально. Т.о. если мы для MyApplication уже скачали ядро и ОС, то для PostgreSQL и Node.js эти слои уже загружаться не будут.
- Образ — это набор файлов, необходимых для работы приложения на голой машине с установленным Docker.
- Образ состоит из неизменяемых слоев, каждый из которых добавляет/удаляет/изменяет файлы из предыдущего слоя.
- Неизменяемость слоев позволяет их использовать совместно в разных образах.
Docker-контейнеры
Docker-контейнер строится на основе образа. Суть преобразования образа в контейнер состоит в добавлении верхнего слоя, для которого разрешена запись. Результаты работы приложения (файлы) пишутся именно в этом слое.

Например, мы создали на основе образа с PostgreSQL сервером контейнер и запустили его. Когда мы создаем БД, то соответствующие файлы появляются в верхнем слое контейнера — слое для записи.

Можно провести и обратную операцию: из контейнера сделать образ. Верхний слой контейнера отличается от остальных только лишь разрешением на запись, в остальном это обычный слой — набор файлов и директорий. Делая верхний слой read only, мы преобразуем контейнер в образ.

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

Docker
Когда мы устанавливаем докер на локальную машину, то получаем клиент (CLI) и http-сервер, работающий как демон. Сервер предоставляет REST API, а консоль просто преобразует введенные команды в http-запросы.

Registry
Registry — это хранилище образов. Самым известным является DockerHub. Он напоминает GitHub, только содержит образы, а не исходный код. На DockerHub также есть репозитории, публичные и приватные, можно скачивать образы (pull), заливать изменения образов (push). Скачанные однажды образы и собранные на их основе контейнеры хранятся локально, пока не будут удалены вручную.

Существует возможность создания своего хранилища образов, тогда при необходимости Docker будет искать там образы, которых еще нет локально. Надо сказать, что при использовании Docker хранилище образов становится важнейшим звеном в CI/CD: разработчик делает коммит в репозиторий, запускаются тесты. Если тесты прошли успешно, то на основе коммита обновляется существующий или собирается новый образ с последующим деплоем. Причем в registry обновляются не целые образы, а только необходимые слои.

При этом важно не ограничивать восприятие образа как некой коробки в которой приложение просто доставляется до пункта назначения и потом запускается. Приложение может и собираться внутри образа (правильнее сказать внутри контейнера, но об этом чуть позже). На схеме выше сервер, занимающийся сборкой образов, может иметь только установленный Docker, а не различные среды, платформы и приложения, необходимые для сборки разных компонентов нашего приложения.
Dockerfile
Dockerfile представляет собой набор инструкций, на основе которых строится новый образ. Каждая инструкция добавляет новый слой к образу. Для примера рассмотрим Dockerfile, на основе которого мог бы быть создан образ рассмотренного ранее .NET-приложения MyApplication:
FROM microsoft/aspnetcore WORKDIR /app COPY bin/Debug/publish . ENTRYPOINT["dotnet", "MyApplication.dll"]
Рассмотрим отдельно каждую инструкцию:
- определяем базовый образ, на основе которого будем строить свой. В данном случае берем microsoft/aspnetcore — официальный образ от Microsoft, который можно найти на DockerHub
- задаем рабочую директорию внутри образа
- копируем предварительно спаблишенное приложение MyApplication в рабочую директорию внутри образа. Сначала пишется исходная директория — путь относительно контекста, указанного в команде docker build , а вторым аргументом — целевая директория внутри образа, в данном случае точка обозначает рабочую директорию
- конфигурируем контейнер как исполняемый: в нашем случае для запуска контейнера будет выполнена команда dotnet MyApplication.dll

Рассмотрим еще один Dockerfile, который демонстрирует прекрасную возможность Docker, обеспечивающую легковесность образов. Подобный файл генерирует VisualStudio 2017 для проекта с поддержкой контейнеров и он позволяет собирать образ из исходного кода приложения.
FROM microsoft/aspnetcore-build:2.0 AS publish WORKDIR /src COPY . . RUN dotnet restore RUN dotnet publish -o /publish FROM microsoft/aspnetcore:2.0 WORKDIR /app COPY --from=publish /publish . ENTRYPOINT ["dotnet", "MyApplication.dll"]
Инструкции в файле разбиты на две секции:
- Определение образа для сборки приложения: microsoft/aspnetcore-build. Данный образ предназначен для сборки, паблиша и запуска .NET приложений и согласно DockerHub с тегом 2.0 имеет размер 699 MB. Далее происходит копирование исходных файлов приложения внутрь образа и внутри него выполняются команды dotnet restore и dotnet publish с размещением результатов в директории /publish внутри образа.
- Определяется базовый образ, в данном случае это microsoft/aspnetcore, который содержит в себе только среду исполнения и согласно DockerHub с тегом 2.0 имеет размер всего 141 MB. Далее определяется рабочая директория и в нее копируется результат предыдущей стадии (ее имя указывается в аргументе —from ), определяется команда запуска контейнера и все — образ готов.
Напоследок хочу отметить, что намеренно для простоты оперировал понятием образ, рассматривая работу с Dockerfile. На самом деле изменения, вносимые каждой инструкцией происходят конечно же не в образе (ведь в нем только неизменяемые слои), а в контейнере. Механизм такой: из базового образа создается контейнер (добавляется ему слой для записи), выполняется инструкция в данном слое (она может добавлять файлы в слой для записи: COPY или нет: ENTRYPOINT ), вызывается команда docker commit и получается образ. Процесс создания контейнера и коммита в образ повторяется для каждой инструкции в файле. В итоге в процессе формирования конечного образа создается столько промежуточных образов и контейнеров, сколько инструкций в файле. Все они автоматически удаляются после окончания сборки конечного образа.
Заключение
Конечно же Docker не панацея и его использование должно быть оправдано и мотивировано не только желанием использовать современную технологию, о которой многие говорят. При этом я уверен, что Docker, примененный грамотно и к месту, может принести много пользы на всех стадиях разработки ПО и облегчить жизнь всем участникам процесса.
Надеюсь смог раскрыть базовые моменты и заинтересовать к дальнейшему изучению вопроса. Конечно же для овладения Docker одной этой статьи недостаточно, но, надеюсь, она станет одним из элементов пазла для осознания общей картины происходящего в мире контейнеров под управлением Docker.
Ссылки
- Документация Docker
- Механизм namespaces
- Механизм control groups
- Статья о Docker
- Docker
- Linux контейнеры
Запуск контейнера Docker на Linux
Клиент Docker предназначен для облегчения управления приложениями внутри контейнеров. Так, он дает пользователю возможность отдельно запускать программы в процессах с изолированными ресурсами. Это напоминает работу в виртуальных машинах, однако контейнеры отличаются более низкой требовательностью к ресурсам, высокой портируемостью и большей зависимостью от операционной системы.
Процесс установки Docker
Чтобы выполнить запуск контейнера, понадобится иметь под рукой настроенный сервер с установленным дистрибутивом Linux. В операционной системе необходимо создать обычного пользователя (не root) и установить фаервол.
Работу с Docker рассмотрим на примере Ubuntu, который является одним из наиболее популярных дистрибутивов для сервера.
Заранее стоит отметить, что в репозитории Ubuntu не всегда предлагается последняя версия Docker. Поэтому этот сервис предпочтительнее устанавливать непосредственно из его официального репозитория. Соответственно, следует добавить в систему новый репозиторий, ввести ключ GPG для проверки действительности скачиваемой версии и лишь затем приступать к установке.
![]()
Работа с командой Docker без прав суперпользователя
В дефолтных настройках предусмотрено, что для запуска Docker необходимо иметь права root-пользователя или пользователя из группы docker, создаваемой автоматически во время установки сервиса.
Если попробовать запустить в терминале Docker без этих прав или, не являясь пользователем группы docker, появится ситуация, когда не запускается контейнер.
Поэтому если нет желания постоянно набирать sudo перед запуском команды, стоит добавить своего пользователя в указанную выше группу:
sudo usermod -aG docker $
Чтобы применить изменения, потребуется выйти с заново войти на сервер. Можно также воспользоваться этой командой:
Продолжить работу можно будет после ввода пароля пользователя. Проверить, что добавление пользователя прошло успешно, можно командой (пользователь sammy):
id -nG

При желании добавить выбранного пользователя в группу, можно указать его имя:
sudo usermod -aG docker username
Последующие шаги предполагают, что пользователь уже добавил себя в группу docker. Если нет желания это делать, перед командами необходимо набирать sudo .
Как пользоваться командами Docker
С помощью команды docker можно пользоваться различными опциями, а также командами с аргументами. Ниже показан ее синтаксис:
docker [option] [command] [arguments]
Чтобы ознакомиться со всеми подкомандами, можно набрать следующее:
docker

Здесь показан список из Docker 18.
Можно получить информацию по использованию выбранной команды:
docker docker-subcommand --help

Для получения полной информации по Docker потребуется ввести следующее:
docker info

Как работать с образами Docker
Запуск контейнеров выполняется из образов. Изначально Docker берет образы в хабах Docker Hub (реестр образов, поддерживаемый разработчиком). Любой имеет возможность создавать и загружать собственные образы. Так что для большей части дистрибутивов и программ уже имеются необходимые образы в Docker Hub.
Приведенная ниже команда позволяет проверить, есть ли возможность получить доступ и скачивать образы в Docker Hub (на примере образа hello-world):
docker run hello-world
Этот вывод данных указывает, что Docker работает правильно:

Сначала сервис не мог найти образ hello-world на локальной машине, из-за чего ему приходилось загрузить образ в хабе (репозитории по умолчанию). После его скачивания Docker создал отдельный контейнер из образа и уже затем запускал в нем программу контейнере.
Представленные в хабе образы можно найти при помощи команд docker и search. К примеру, найти образ Ubuntu можно следующим образом:
docker search ubuntu

Далее скрипт просмотрит содержимое хаба и покажет все образы, соответствующие заданным критериям. В предложенном примере результат будет примерно таков:
Строчка OK в столбце OFFICIAL говорит о том, что образ создан и его поддержка осуществляется компанией, ответственной за этот проект. Выбрав требуемый образ, его можно скачать на компьютер, используя подкоманду pull.
Загрузка официального образа Ubuntu на компьютер выполняется такой командой:
docker pull ubuntu

По завершении операции отобразится такой результат:
Когда образ будет скачан, станет возможным выполнить запуск контейнера из образа при помощи команды run. На примере hello-world видно, что если после ввода run образ не был скачан, сначала клиент загрузит его и уже запустит контейнер с образом.
Чтобы посмотреть, какие образы были загружены на компьютер, используется команда:
docker images

Результат будет похожим на следующий:
Ниже можно увидеть, что образы для запуска контейнеров можно менять и использовать при создании других образов. Причем последние можно загрузить в Docker Hub или другие репозитории. Далее будет рассмотрено более подробно, как работать с контейнерами.
Запуск контейнера Docker
Запущенный ранее hello-world представляет собой пример контейнера, который открывается и закрывается после отображения тестового сообщения. При этом контейнеры предназначены для выполнения более полезных задач. Они имеют много общего с виртуальными машинами, однако требуют намного меньше ресурсов для работы.
Можно рассмотреть, как запустить контейнер при помощи последней версии образа Ubuntu. Добавление опций -i и -t предоставляет доступ в интерактивном режиме к командному процессору:
docker run -it ubuntu
Командная строка изменится, указывая на переход в контейнер, и будет выглядеть таким образом:
![]()
Следует обратить внимание, что также отображается идентификатор контейнера. Здесь это 2c88170e5391. Он понадобится позже, чтобы сообщить клиенту, какой именно удалить контейнер.
После этого можно запустить любую команду в контейнере. К примеру, можно обновить базу данных пакетов. Для этого нет необходимости пользоваться командой sudo, поскольку работа ведется от имени пользователя с root-правами:
apt update

Затем появится возможность установки приложений. Рассмотрим пример установки Node.js:
apt install nodejs

Эта команда выполнит установку Node.js в контейнер, используя репозиторий Ubuntu. По завершении установки можно проверить, была ли она успешно выполнена:
node -v
Будет показан номер текущей версии Node.js:
![]()
Проведенные изменения в контейнере распространяются только на него. Завершить работу с контейнером можно командой exit .
Команды для управления контейнерами
Со временем после работы с Docker на локальной машине соберется достаточное количество активных и неактивных контейнеров. Для просмотра запущенных контейнеров применяется команда:
docker ps
![]()
Система выведет примерные результаты:
В этой инструкции разбирался запуск двух контейнеров — с образов hello-world и ubuntu. Хотя сейчас они не активные, но уже расположены в системе. Для просмотра находящихся в системе контейнеров нужно запустить docker ps, добавив параметр -a:
docker ps -a

В терминале отобразится примерный вывод:
Для просмотра последних созданных контейнеров используется опция -l:
docker ps -l

Чтобы запустить остановленный контейнер, необходимо ввести docker start и далее указать идентификатор или имя контейнера. Так выглядит запуск контейнера 2c88170e5391:
docker start 2c88170e5391
Контейнер будет запущен и чтобы просмотреть его статус, используется команда docker ps : 
Чтобы выключить активный контейнер, используется команда docker stop с последующим указанием его идентификатора или имени. Здесь уже потребуется воспользоваться именем, которое предоставил контейнеру Docker (peaceful_minsky):
docker stop peaceful_minsky
Также может потребоваться перезапустить контейнер, не отключая его. Это можно сделать командой:
docker stop 2c88170e5391 && docker start 2c88170e5391
docker restart 2c88170e5391
Отдельного внимания заслуживает запуск контейнера docker compose. Так, после смены настроек в файле docker-compose.yml (например, проброс порта) изменения не выполнятся автоматически. Вдобавок, команда restart также не поможет и потребуется выполнить пересборку контейнера, применив для этого команду build. Другими словами, он будет заново создан. Выполнить операцию можно следующей командой:
docker-compose up -d --no-deps --build
После чего отобразится похожий вывод:
![]()
Потребуется пара секунд, чтобы перезапуск контейнера полностью завершился, хотя в действительности Docker осуществив намного больше операций. То есть, собрал новый образ, создал новый контейнер на его основе, остановил старый, запустил новый и удалил старый.
Когда контейнер уже не нужен для дальнейшей работы, его можно удалить, набрав в терминале docker rm с добавлением его имени или идентификатора. Для поиска этих данных, которые связаны с hello-world, вводится команда:
docker ps -a

После чего можно приступать к удалению контейнера.
docker rm hello/root_my-test_1
![]()
Чтобы осуществить запуск нового контейнера с присвоением ему имени, предусмотрена опция —name. Также можно воспользоваться опцией —rm, позволяющей создавать контейнер, который будет автоматически удален после его остановки. Более подробную информацию о данных и других параметрах можно получить после ввода docker run help.
Помимо указанных выше команд из существующих контейнеров можно создавать образы для создания новых. Об этом речь пойдет далее.
Как сохранить изменения в новый образ?
Во время запуска контейнера из существующего образа у пользователя есть возможность создавать или удалять файлы, аналогично работе на виртуальной машине. При этом изменения будут распространяться только в определенном контейнере. Доступна и возможность запуска с последующей остановкой контейнера, но после его удаления с помощью docker rm будут утеряны внесенные изменения.
Соответственно, следует ознакомиться со способом сохранения текущего контейнера как нового образа.
По завершении инсталляции Node.js в контейнере Ubuntu, на компьютере работает загруженный из образа контейнер. При этом он будет отличаться от использованного для его создания образа. В свою очередь, пользователю может понадобиться уже контейнер Node.js, чтобы использовать его при создании для новых образов.
Соответственно, следует сохранить результаты в текущем образе предложенной ниже командой:
docker commit -m "What you did to the image" -a "Author Name" container_id repository/new_image_name
Добавление опции -m дает возможность указать сообщение подтверждения. Это позволит будущим пользователям образа понять, что именно было изменено. Что касается параметра -a — с его помощью можно указать, кто его создатель. container_id является тем же идентификатором, который был использован ранее, во время запуска интерактивной сессии в Docker.
Если вы не занимались до этого созданием новых репозиториев в Docker Hub, имя создаваемого репозитория по умолчанию будет названо именем вашего пользователя в Docker Hub.
К примеру, с именем пользователя admin и идентификатором 2c8ec46adae1 команда должна иметь следующий вид:
docker commit -m "added Node.js" -a "admin" 2c8ec46adae1 admin/ubuntu-nodejs
![]()
После того, как образ будет подтвержден (commit) он сохраняется на компьютере локально. Далее будет рассмотрен пример его отправки в репозиторий Docker, чтобы он стал доступным и для других пользователей. Если же посмотреть список образов по завершении этой операции, в нем будет добавлен новый образ, причем исходный также останется на месте:
docker images
![]()
Ниже показан примерный результат проверки:
В предложенном выводе ubuntu-nodejs является созданным образом на базе уже имеющегося ubuntu, загруженного в Docker Hub. Проведенные изменения можно увидеть по тому, насколько изменился размер образа. Поскольку изменение касалось только инсталляции NodeJS, при необходимости запуска Ubuntu с этим предустановленным приложением, можно воспользоваться созданным образом.
Остается отправить свой образ в репозиторий, чтобы остальные пользователи имели возможность создать контейнеры на его базе.
Отправка контейнеров в реестр
Завершающий этап — сохранение созданных образов в базу Docker Hub или другой репозиторий, откуда их может скачать любой желающий. Чтобы получить такую возможность, предварительно нужно создать аккаунт.
Отправка образов в репозиторий начинается с авторизации на Docker Hub.
docker login -u docker-registry-username
Чтобы вход был успешно осуществлен, потребуется ввести пароль Docker Hub. Если он правильный, авторизация пройдет успешно.
Здесь важно знать, что если в реестре Docker имя пользователя отличается от локального, используемого при создании образа, обязательно нужно привязать этот образ к имени учетной записи в хабе. На примере контейнера с NodeJS команда привязки будет выглядеть так:
docker tag admin/ubuntu-nodejs docker-registry-username/ubuntu-nodejs
После чего можно приступать к загрузке образа на сервер:
docker push docker-registry-username/docker-image-name
Что касается разбираемого примера ubuntu-nodejs, для отправки его в реестр sammy следует ввести эту команду:
docker push admin/ubuntu-nodejs
Загрузка образа может занять время. Когда образ будет находиться в репозитории, его название появится в списке, открываемом в панели управления вашего аккаунта.
В таком случае необходимо снова попытаться сделать вход с использованием docker login и загрузить образ. Наконец, остается проверить, что он был добавлен успешно. Затем можно набрать в терминале docker pull admin/ubuntu-nodejs, чтобы воспользоваться образом на новой машине и пользоваться им при создании новых контейнеров.
Автозагрузка контейнеров
Часто встречается ситуация, когда контейнеры останавливаются вследствие определенных факторов. Простейший пример – произошла перезагрузка сервера. Чтобы избавиться от необходимости вручную запускать их, можно настроить автозапуск контейнеров. Для этого следует создать текстовые файлы со специальным форматом для сервиса systemcmd. Рассмотрим пример их создания на примере контейнера my-db, введя в терминал команду:
cat /etc/systemd/system/my-db.service
В пустой файл необходимо добавить следующий код и сохранить его:
[Unit] Description=MY DB (PG) docker container Requires=docker.service After=docker.service [Service] Restart=always ExecStart=/usr/bin/docker start -a my-db ExecStop=/usr/bin/docker stop -t 2 my-db TimeoutSec=30 [Install] WantedBy=multi-user.target
После этого остается перезапустить демон systemcmd и включить автозагрузку контейнера mydb, набрав в терминале поочередно команды:
systemctl daemon-reload
systemctl start my-db.service
systemctl enable my-db.service
Как создать и запустить Docker контейнер из образа.

Предположим, что вы нашли в Docker репозитории какой-то образ. Первый вопрос, который возникает, как создать и запустить Docker контейнер из этого образа?
В этой статье я хотел бы обзорно рассмотреть этот вопрос и дать несколько рекомендаций, где можно найти более подробную информацию. Дело в том, что процесс создания Docker контейнера из образа имеет очень много особенностей.
Если вы только начинаете знакомиться с докер, рекомендую посмотреть мой курс «Основы Docker для веб-программистов.»
Там есть объяснение многих базовых понятий, которые нужно знать прежде чем приступать работать с Docker.
Существует несколько способов создания контейнера. Чтобы во всем этом разобраться, нам нужно понять как происходит весь процесс.

Для того, чтобы запустить какой-либо контейнер, мы должны сначала скачать его образ с репозитория Docker на свой локальный компьютер и затем уже происходит процесс запуска.
Здесь нужно учитывать такой момент, что не все образы после их запуска оставляют после себя запущенный в системе контейнер. Некоторые запускаются, выполняют какую-то работу и закрываются.
Например, так происходит с образом hello-world.
Когда мы выполним команду
docker run hello-world
Эта команда автоматически скачает образ из репозитория Docker на ваш локальный компьютер (вручную скачать его можно с помощью docker pull hello-world) и запустит его в контейнере и автоматически закроет и удалит этот контейнер.
Пожалуй, команда «docker run имя_образа» является одним из самых простых способов как можно создать и запустить контейнер Docker на вашем компьютере.
В зависимости от образа, с которым мы работаем, в этой команде можно указывать дополнительные аргументы и настройки, которые позволят запустить Docker контейнер каким-то определенным способом с какими-то определенными настройками.
Например, чтобы запустить контейнер базы данных mysql мы можем воспользоваться командой.
docker run --name ms -p 3307:3306 -e MYSQL_ROOT_PASSWORD=12345 mysql
Это команда скачает образ mysql на наш локальный компьютер, присвоит имя для нового создаваемого контейнера как «ms», назначит порт 3307 на нашем локальном компьютере для работы сервера баз данных, назначит пароль 12345 для пользователя root.
О том, какие аргументы мы можем передавать в команду docker run можно почитать в документации того образа, с которым вы работаете.
После выполнения этой команды вы должны увидеть созданный и запущенный контейнер, который будет работать в вашей операционной системе.

Альтернативой команде docker run является специальный инструмент для Docker, который называется Docker Compose. По сути, это отдельная утилита для докер, которая позволяет собирать контейнера сразу из множества образов и настраивать взаимодействие между ними.
Например, для того, чтобы создать и запустить контейнер Docker с образом языка программирования php и веб-сервером apache, нужно создать специальный файл, который будет называться docker-composer.yaml в нашем проекте. Подробнее об этом файле вы можете посмотреть в этом моем курсе.
И добавить в этот файл следующее содержимое:
version: "3" services: myproject: image: php:7.4-apache volumes: - D:\Sites\tmp6:/var/www/html ports: - "8080:80"
Теперь нужно дать команду утилите docker compose, чтобы она создала и запустила на основе этого образа новый контейнер. Сделать это можно с помощью команды
docker-compose up --build
Это основные способы, как можно создать и запустить Docker контейнер. Конечно, здесь есть много моментов и тонкостей, с которыми нужно разбираться отдельно. Но, надеюсь, что общий смысл для вас стал понятен и изучая дополнительные материалы вы сможете разобраться в своей конкретной ситуации.
Чтобы оставить сообщение, зарегистрируйтесь/войдите на сайт через:
Руководство по Docker. Часть 1: образ, контейнер, сопоставление портов и основные команды
Docker — платформа с открытым исходным кодом для создания, развертывания и управления контейнеризированными приложениями.
Представьте, как вы устанавливаете программное обеспечение, требующее установку всех его зависимостей. Придется столкнуться со множеством ошибок, вручную выяснить и устранить все их причины. Каждый раз — попытки запустить всю систему заново, чтобы наконец-то правильно завершить установку. Именно в этот момент на помощь приходит Docker, пытаясь серьезно облегчить жизнь.
Содержание:
- Образы и контейнеры.
- Запуск первой программы в Docker.
- Основные команды контейнеров Docker.
- Командная строка Docker-контейнера.
- Пользовательские образы Docker.
- Запуск веб-приложения в Docker.
- Docker и сопоставление портов.
- Выводы.
1. Образы и контейнеры
Самые важные концепции Docker!
Образ — это файл, хранящий всю конфигурацию запуска программы в контейнере Docker. Посмотреть на список образов, созданных в системе, можно при помощи следующей команды:
docker images
Контейнер — это экземпляр образа. Docker-контейнер действует как виртуальная машина, но у него нет отдельной операционной системы. Каждый контейнер устанавливает приложения или программное обеспечение, следуя инструкциям, записанным в файлах образа.
Количество контейнеров не ограничено, все они работают на общей инфраструктуре хоста и общей операционной системе. Посмотреть на список контейнеров можно с помощью команды:
docker ps -a
2. Запуск первой программы в Docker
Первым делом нужно вывести на экран фразу “Hello, world!”.
В Docker каждому контейнеру нужен образ, поэтому, чтобы запустить hello-world , введите в консоль или терминал команду:
docker run hello-world
Попытка создания и запуска контейнера. Согласно документации API-клиента Docker, в установленном порядке выполняются следующие действия.
- Консоль: выполняет команду docker run hello-world .
- Docker client (CLI): получает команду и выполняет действия на ее основе.
- Сервер Docker: связывается с CLI, чтобы выдать клиенту из кэша образов (Image Cache) нужный образ с названием hello-world . Если такого образа нет, то сервер Docker связывается с хабом Docker, где скачивает образ с указанным именем.
- Docker Hub: хаб Docker получает запрос от сервера Docker на загрузку конкретного файла образа из общедоступного хранилища.
- Сервер Docker: снова ищет образ в разделе “Image Cache”, затем создает контейнер — экземпляр образа hello-world .
- Контейнер Docker: согласно первоначальным установкам из образа, в контейнере запускается простая программа hello-world .
- Консоль: в результате успешного выполнения всех вышеизложенных этапов отобразит информацию со скриншота.
3. Основные команды контейнеров Docker
Прочитайте перечень команд управления поведением контейнеров без изменения образов.
- Удалить все неактивные контейнеры Docker:
docker system prune
- Вывести на экран stderr и stdout от запущенных в контейнере программ, логирование контейнера Docker:
docker logs
- Индекс активных контейнеров:
docker ps
- Индекс всех контейнеров, включая неактивные:
docker ps -a
- Индекс образов, установленных в системе на данный момент:
docker images
- Деактивировать Docker-контейнер по идентификатору:
docker stop
- Удалить неактивный контейнер по идентификатору:
docker rm
- Удалить конкретный образ по его названию:
docker rmi
4. Командная строка Docker-контейнера
Рассмотрим набор самых часто задаваемых вопросов о пользовательском вводе команд с клавиатуры напрямую в контейнеры Docker.
- Как выполнить команду внутри контейнера Docker?
docker exec -it
Здесь exec позволяет выполнять команды в контейнере, а флаг -it разрешает прием ввода от пользователя к процессу, а также вывод в терминал как результата выполнения процесса, так и сообщений об ошибках.
- Как получить доступ к командной строке контейнера Docker?
Иногда удобно напрямую взаимодействовать с командной строкой контейнера. Для поиска ошибок и отладки, или вы можете запустить там все команды Linux одновременно. Убедитесь, что нужный контейнер запущен, а потом введите следующую команду:
docker exec -it sh
sh выдаст разрешение на ввод подсказок (prompt), и вы сможете выполнять внутри контейнера различные задачи, например:
- Как разрешить ввод при запуске контейнера из образа Docker?
Допустим, что вы хотите сразу после скачивания из хаба Docker запустить образ под названием busybox . Что вы сделаете?
docker run busybox
Теперь, чтобы ответить на вышеуказанный вопрос, выполните следующую команду:
docker run -it busybox sh
Теперь все знания о полезных командах из двух пунктов руководства помогут вам подготовиться к следующему шагу.
5. Пользовательские образы Docker
Как добавить файлы в контейнер и какую операционную систему выбрать для образа?
Основной файл, хранящий все инструкции. Каждый dockerfile одинаково структурирован. Чтобы создать dockerfile для пользовательского образа необходимо указать:
- Базовый образ.
- Все зависимости и условия запуска программы.
- Команду для запуска программы при старте контейнера.
- Docker Client
После создания dockerfile вы сразу попытаетесь собрать образ. Следовательно, Dockerfile отправляется клиенту Docker.
Далее клиент Docker передает все инструкции серверу Docker, который, в свою очередь, выполняет всю грязную работу, а именно все команды внутри dockerfile , и создает образ. Затем образ запускается, в результате чего создается контейнер, экземпляр образа.
- Как же создать образ Docker?
- Сначала напишите dockerfile .
- Затем выполните команду docker build .
- Теперь запустите образ командой docker run .
Рассмотрим простой dockerfile , в котором установлен сервер базы данных Redis :
Теперь, глядя на Dockerfile, у вас наверняка возникли вопросы! Ответим на них сейчас:
- Что такое базовый образ?
Ни у одного контейнера нет собственной операционной системы. Если вы хотите создать контейнер, то ему нужна отправная точка или базовый образ, с которого он может начать работу.
Alpine Linux — это как Windows и macOS, где можно установить почти все, что вам нужно. Alpine занимает мало памяти, что хорошо подходит для установки Redis. Базовый образ загружается через инструкцию FROM .
- Как установить зависимости?
Посмотрите, в dockerfile написана инструкция ‘RUN’, которая выполняет команды внутри контейнера. APK означает менеджер пакетов Alpine Linux . Команды apk применяются в любой работающей системе на базе Alpine Linux для удаления, установки, обновления программного обеспечения.
- Как указать команду для запуска приложения в контейнере?
Инструкцией CMD задается команда по умолчанию, которая будет выполняться только при запуске контейнера без указания команды. Приведенный выше пример запустит сервер Redis.
Теперь поговорим детальнее о команде docker build . Что происходит перед тем, как результат ее выполнения появляется на экране?
- Загружается контейнер с образом alpine .
- Файловая система из образа alpine переносится во временный контейнер, в нем и выполняются инструкции по установке базы данных Redis. После завершения установки временный контейнер удаляется, а файловая система переносится обратно в образ alpine , тем самым обновляя его. Теперь в новом образе установлен Redis.
- По аналогии создается еще один временный контейнер для установки команды запуска сервера Redis при каждом запуске образа в контейнере. Следом временный контейнер удаляется, а обновленная файловая система устанавливается в конечном пользовательском образе.
- Наконец, для запуска контейнера по шаблону пользовательского образа выполняется команда docker run .
Примечание: если вы обновите dockerfile образа и попытаетесь пересобрать его, то Docker получит кэш из предыдущего образа, чтобы пропустить тот же процесс. Он обновит только вновь добавленный раздел в Dockerfile. Порядок в Dockerfile также важен. Если вы измените порядок команд, то кэш обнулится!
- Как установить специфическое имя для образа?
6. Запуск веб-приложения в Docker
В этом разделе создадим и докеризируем простое приложение на Node.js на основе пользовательского образа. Не беспокойтесь, вам не нужны знания о Node.js для выполнения пунктов руководства.
- Шаг 1
Напишите файл package.json , ведь Express.js — это бэкенд-фреймворк для веб-приложений на Node.js. Укажите в нем последнюю версию при помощи символа звездочки * , а затем укажите запуск по умолчанию для команды node index.js , где index.js — это основной веб-файл.
- Шаг 2
Напишите простой файл index.json . Планируется, что фреймворк Express.js через / получает запросы и отправляет обратно ответ. Чтобы получить запрос и ответ, необходимо установить порт для прослушивания.
- Шаг 3
Напишите dockerfile . Помните все ключевые шаги? Если забыли, то поднимитесь вверх по разделам и освежите знания. Тем не менее совершенно очевидно, что для запуска приложения понадобятся установленные, готовые к работе Node.js и NPM.
- Шаг 4
Постройте пользовательский образ, присвойте ему теги.
- Шаг 5
Запустите контейнер с приложением, запомните прослушиваемый порт.
- Шаг 5
Проверьте, можно ли получить доступ к приложению из браузера, действительно ли оно работает?
О, нет! Что-то пошло не так? Пришло время выяснить.
Возможно, у вас сейчас на уме один вопрос: зачем в dockerfile написана команда WORKDIR /usr/app ?
Основная цель — разделение рабочих файлов, чтобы строго необходимые для работы приложения файлы не смешивались с другими директориями, а легко отделялись от остальных при необходимости, если возникнут проблемы. Следовательно, внутри контейнера файлы веб-приложения сохраняются в каталоге /usr/app .
7. Docker и сопоставление портов
Рассмотрим, как разобраться в настройках и правильно установить сопоставление портов!
При отправке запроса на порт 8080 вашей локальной машины, он не перенаправляется автоматически на контейнер, так как у контейнера собственное сетевое отображение.
Сопоставление портов позволит запросу на порт 8080 с локальной машины перенаправить запрос на порт 8080 Docker-контейнера, только для входящих запросов.
Однако по умолчанию Docker также позволяет исходящие запросы. Интересно, как это сделать? Проверьте зависимости в dockerfile , где NPM из контейнера напрямую обращается к интернету.
- В команде запуска контейнера первый порт — это порт хост-машины или локальной машины, а второй порт — это порт контейнера:
docker run -p 8080:8080 mahedi/simpleweb
Выводы
Веб-приложение запущено и доступно при обращении к порту 8080 локальной машины.
Несмотря на успех, стоит упомянуть три распространенные ошибки, допускаемые в процессе работы с Docker Server и Docker Client.
- Неправильный выбор базового образа.
- Отсутствие в dockerfile команд ADD или COPY для переноса файлов хост-машины в контейнер.
- Неправильное сопоставление портов при создании контейнера.
Далее прилагаются скриншоты правильно запущенного в Docker веб-приложения Node.js, чтобы вы могли с ними свериться.
Самостоятельно ознакомьтесь с файлом dockerfile для построения пользовательского образа и запуска веб-приложения на Node.js.
Проанализируйте изображенный на скриншоте файл index.js с простым примером веб-приложения на Node.js. Данный файл запускается внутри контейнера Docker при помощи соответствующей команды из dockerfile .
На сегодня все! В следующей части руководства пойдет речь о последовательном создании нескольких контейнеров и докеризации сложных веб-систем со множеством зависимостей.
- Среда разработки Entity Framework в Docker
- Перенос сценариев CI в docker build
- Тенденции в области программного обеспечения в 2022 году: 22 прогноза