Docker — один из самых известных инструментов по работе с контейнерами. В статье мы расскажем что такое Docker-контейнеры, где они применяются и чем могут быть вам полезны.
Также будет практическая часть: мы создадим небольшое приложение, обернем его в образ и запустим. Все действия будем показывать на примере виртуальной машины на платформе Selectel.
Контейнеры — хорошая альтернатива аппаратной виртуализации. Они позволяют запускать приложения в изолированном окружении, но при этом потребляют намного меньше ресурсов.
В первую очередь эта статья будет полезна тем, кто вообще не знаком с контейнерами или Docker. Мы расскажем самые базовые вещи, а наш пример по созданию приложения будет довольно простым. Но это позволит вам понять основы Docker и затем двигаться дальше — изучать более сложные материалы.
Прежде чем рассказывать про Docker, нужно сказать несколько слов о технологии контейнеризации.
Контейнеры — это способ упаковать приложение и все его зависимости в единый образ. Этот образ запускается в изолированной среде, не влияющей на основную операционную систему. Контейнеры позволяют отделить приложение от инфраструктуры: разработчикам не нужно задумываться, в каком окружении будет работать их приложение, будут ли там нужные настройки и зависимости. Они просто создают приложение, упаковывают все зависимости и настройки в единый образ. Затем этот образ можно запускать на других системах, не беспокоясь, что приложение не запустится.
Docker — это платформа для разработки, доставки и запуска контейнерных приложений. Docker позволяет создавать контейнеры, автоматизировать их запуск и развертывание, управляет жизненным циклом. Он позволяет запускать множество контейнеров на одной хост-машине.
Контейнеризация похоже на виртуализацию, но это не одно и то же. Виртуализация работает как отдельный компьютер, со своим виртуальным оборудованием и операционной системой. При этом внутри одной ОС можно запустить другую ОС. В случае контейнеризации виртуальная среда запускается прямо из ядра основной операционной системы и не виртуализирует оборудование. Это означает, что контейнер может работать только в той же ОС, что и основная. При этом так как контейнеры не виртуализируют оборудование, они потребляют намного меньше ресурсов.
Контейнеры в целом упрощают работу как программистам, так администраторам, которые развертывают эти приложения.
Контейнеры позволяют упаковать в единый образ приложение и все его зависимости: библиотеки, системные утилиты и файлы настройки. Это упрощает перенос приложения на другую инфраструктуру.
Например, разработчики создают приложение в системе разработки, там все настроено и приложение работает. Когда приложение готово, его нужно перенести в систему тестирования и затем в продуктивную среду. И если в этих системах будет не хватать какой-нибудь зависимости, то приложение не будет работать. В этом случае программистам придется отвлечься от разработки и совместно с командой поддержки разбираться в ситуации.
Контейнеры позволяют избежать такой проблемы, потому что они содержат в себе все необходимое для запуска приложения. Программисты смогут сосредоточиться на разработке, а не решении инфраструктурных проблем.
Контейнер — это набор процессов, изолированных от основной операционной системы. Приложения работают только внутри контейнеров, и не имеют доступа к основной операционной системе. Это повышает безопасность приложений, потому что они не смогут случайно или умышленно навредить основной системе. Если приложение в контейнере завершится с ошибкой или зависнет, это никак не затронет основную ОС.
Контейнеры упрощают развертывание приложений. В классическом подходе для установки программы может потребоваться выполнить несколько действий: выполнить скрипт, изменить файлы настроек и так далее. В этом процессе не исключена вероятность человеческой ошибки: пользователь запустит скрипт два раза, перепутает последовательность или что-то не поймет. Контейнеры позволяют полностью автоматизировать этот процесс, так как включают в себя все нужные зависимости и порядок выполнения действий.
Также контейнеры упрощают развертывание на нескольких серверах. В классическом подходе для того, чтобы развернуть одно и то же приложение на нескольких машинах, нужно будет повторять одни и те же действия. Контейнеры избавляют от этой рутинной работы и позволяют автоматизировать развертывание.
Контейнеры хорошо вписываются в микросервисную архитектуру. Это подход к разработке, при котором приложение разбивается на небольшие компоненты, по возможности независимые. Обычно противопоставляется монолитной архитектуре, где все части системы сильно связаны друг с другом.
Это позволяет разрабатывать новую функциональность быстрее, ведь в случае с монолитной архитектурой изменение какой-то части может затронуть всю остальную систему.
Docker-compose позволяет разворачивать и настраивать несколько контейнеров одновременно. Например, для веб-приложения нужно развернуть стек LAMP: Linux, Apache, MySQL, PHP. Каждое из приложений — это отдельный контейнер. Но в этой ситуации нам нужны именно все контейнеры вместе, а не отдельно взятое приложение. Docker-compose позволяет развернуть и настроить все приложения одной командой, а без него пришлось разворачивать и настраивать каждый контейнер отдельно.
Она из главных особенностей контейнеров — эфемерность. Это означает, что контейнеры могут быть в любой момент остановлены, перезапущены или уничтожены. При этом все накопленные данные в контейнере будут потеряны. Поэтому приложения нужно разрабатывать так, чтобы они не полагались на хранилище данных в контейнере, это называется принципом Stateless.
Это хорошо подходит для приложений или сервисов, которые не сохраняют результаты своей работы. Например, функции расчета или преобразования данных: им на вход поступил один набор данных, они его преобразовали или рассчитали и вернули результат. Все, ничего никуда сохранять не нужно.
Но далеко не все приложения такие, и есть много данных, которые нужно сохранить. В контейнерах для этого предусмотрены несколько способов.
Это способ, при котором докер сам создает директории для хранения данных. Их можно сделать доступными для разных контейнеров, чтобы они могли обмениваться данными. По умолчанию эти директории создаются на хост-машине, но можно использовать и удаленные хранилища: файловый сервер или объектное хранилище.
В этом случае директория сначала создается в хост-системе, а уже потом монтируется в докер контейнеры.
Но этот способ не рекомендуется, потому что он усложняет резервное копирование, миграцию и совместное использование данных несколькими контейнерами.
Теперь расскажем подробнее про компоненты, из которых состоит Docker.
Это сервис, через который осуществляется все взаимодействие с контейнерами: создание и удаление, запуск и остановка. Этот сервис работает в фоновом режиме и получает команды от интерфейса командной строки или API.
Это интерфейс командной строки для управления docker daemon. Мы пользуемся этим клиентом, когда создаем и разворачиваем контейнеры, а клиент отправляет эти запросы в docker daemon.
Это неизменяемый файл (образ), из которого разворачиваются контейнеры. Приложения упаковываются именно в образы, из которых потом уже создаются контейнеры.
Приведем аналогию на примере установки операционной системы. В дистрибутиве (образе) ОС есть все, что необходимо для ее установки. Но этот образ нельзя запустить, для начала его нужно «развернуть» в готовую ОС. Так вот, дистрибутив для установки ОС — это Docker image, а установленная и работающая ОС — это Docker container. Но контейнеры обычно разворачиваются одной командой — это намного проще и быстрее, чем установка ОС.
Это уже развернутое из образа и работающее приложение.
Это репозиторий с докер-образами. Разработчики создают образы своих программ и выкладывают их в репозиторий, чтобы их можно было скачать и воспользоваться ими. Распространенный публичный репозиторий — Docker Hub. В нем собраны образы множества популярных программ или платформ: базы данных, веб-серверы, компиляторы, операционные системы и так далее. Также можно создать свой приватный репозиторий, например внутри компании. Разработчики будут размещать там образы, которые будут использоваться всей компанией.
Dockerfile — это инструкция для сборки образа. Это простой текстовый файл, содержащий по одной команде в каждой строке. В нем указываются все программы, зависимости и образы, которые нужны для разворачивания образа.
Для примера рассмотрим dockerfile, который мы будем использовать далее в этой статье чтобы развернуть собственное приложение:
FROM python:3
COPY main.py /
CMD [ "python", "./main.py" ]
Первая строчка означает, что за основу мы берем образ с названием python версии 3 это называется базовый образ. Docker найдет его в docker registry, скачает и будет использовать за основу. Вторая строчка означает, что нужно скопировать файл main.py в корень файловой системы контейнера. Третья строчка означает, что нужно запустить python и передать ему в качестве параметра файл main.py.
Далее рассмотрим примеры нескольких команд докер и что происходит, когда мы их выполняем.
Все эти команды выполняются в Docker client, который отправляет их в docker daemon:
Перейдем к практической части. Мы установим докер, создадим приложение, обернем его в контейнер и запустим. Мы для примера будем использовать виртуальную машину на платформе Selectel.
В панели управления заходим в раздел «Облачная платформа» — «Серверы», нажимаем кнопку «Создать сервер».
На следующем экране выбираем параметры сервера: имя, регион, ОС, параметры производительности и так далее. Сейчас для нас важны параметры «Источник» — выбираем ОС Ubuntu 20.04 и «Конфигурация» — выбираем 2 vCPU и 8 ГБ оперативной памяти.
Далее обратите внимание на разделы «Сеть» и «Доступ». В разделе «Сеть» нужно выбрать подсеть с публичным адресом, чтобы к виртуальной машине можно было подключаться из интернета. В разделе «Доступ» будет указан пароль для root-пользователя, а также необходимо загрузить SSH-ключ, чтобы подключаться к виртуальной машине. Подробную инструкцию о подключении смотрите в Базе знаний.
После этого внизу страницы нажимаем кнопку «Создать». Виртуальная машина создается за несколько минут, и после того, как она перейдет в статус ACTIVE, к ней можно подключаться по SSH.
Мы рассмотрим установку докера на примере Ubuntu. Если у вас другой дистрибутив Linux или операционная система — ищите соответствующую инструкцию на официальном сайте.
Для начала синхронизируем пакетную базу apt и установим нужные зависимости:
sudo apt-get update
sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg \
lsb-release
Далее импортируем GPG-ключ для репозитория docker:
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
Теперь добавим новый репозиторий в список apt:
echo \
"deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Теперь можно устанавливать докер:
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io
По умолчанию, доступ к docker daemon есть только у пользователя root. Чтобы с докером могли работать и другие пользователи, их нужно добавить в специальную группу — docker. Выполните эту команду из под обычного пользователя:
sudo usermod -aG docker $USER
После этого необходимо перелогиниться, чтобы изменение вступило в силу.
Теперь попробуем запустить какое-нибудь готовое приложение. Выполните команду:
docker run ubuntu echo 'hello from ubuntu'
Команда docker run создает и запускает контейнер из образа. В этом примере мы создаем контейнер из образа ubuntu, затем выполняем в нем команду echo ‘hello from ubuntu’. Но так как у нас чистая установка докера и мы не скачали ни одного образа, докер сначала найдет этот образ в публичном репозитории Docker Hub, скачает, а потом создаст из него контейнер. В следующий раз, когда нам понадобится образ ubuntu, докер уже не будет его скачивать.
После выполнения команды в терминале появится строка hello from ubuntu, и контейнер сразу остановится. Теперь выполним другую команду:
docker run -it ubuntu
Эта команда запустит контейнер в интерактивном режиме, то есть контейнер запустится и будет ждать дальнейших команд. При этом мы окажемся внутри операционной системы контейнера: запустится оболочка (bash), и мы сможем выполнять какие-то команды внутри контейнера. Чтобы выйти из контейнера, введите команду exit.
Теперь создадим HelloWorld-приложение на Python, обернем его в образ и запустим.
Для начала создадим директорию, в которой мы будем работать и перейдем в нее:
mkdir first-docker-app
cd first-docker-app
Создадим файл main.py и запишем в него одну строчку кода:
echo 'print("Hello from python");' >> main.py
Проверим, что наша программа работает. Для этого выполним команду:
python main.py
В консоли должно выйти сообщение Hello from python. Это и есть наше простое приложение. Теперь нужно обернуть его в докер-образ. Для этого создадим файл Dockerfile и напишем в нем три строчки:
FROM python:3
COPY main.py /
CMD [ "python", "./main.py" ]
В первой строке мы указываем образ, который берем за основу. Так мы пишем приложение на Python, нужно чтобы в нашем образе он уже был установлен. Самый простой способ это сделать — использовать готовый официальный образ с Docker Hub. Цифра 3 — это тег. Он означает, что нужно использовать третью версию Python. Вместо этого можно было бы использовать тег latest, который означает самую последнюю версию, или можно было указать номер конкретной версии, например 3.8.8.
Во второй строчке мы копируем наш файл main.py в корневую директорию образа.
Третья строчка — запускаем python и передаем ему в качестве параметра имя нашего файла.
Теперь из этого докер-файла можно собирать образ. Выполним команду:
docker build -t first-docker-app .
Параметр -t обозначает имя нашего образа, мы назвали его first-docker-app.
Так как у нас еще нет скачанного образа python, то докер сам скачает его из Docker Hub и затем будет использовать его в качестве основы для создания нашего образа.
Проверим список установленных у нас образов:
docker images
Мы увидим, что у нас установлено три образа:
REPOSITORY TAG IMAGE ID CREATED SIZE
first-docker-app latest 649cceb4dfd2 4 seconds ago 885MB
python 3 b1aa63f57d3c 2 days ago 885MB
ubuntu latest 8e428cff54c8 4 days ago 72.9MB
first-docker-app — это наш образ, который мы только что создали. python — это образ python, который докер автоматически скачал чтобы собрать наш образ. ubuntu — образ, который мы пробовали для запуска готового приложения.
Теперь создадим контейнер из нашего образа и запустим его:
docker run first-docker-app
В результате нам выведется результат: Hello from python.
Итог: Мы создали свое приложение, упаковали его в докер-образ и запустили. Конечно, это очень простой пример. Наша программа состоит всего из одной строчки, а dockerfile из трех. Но это позволяет понять базовые принципы работы докера, как он устроен, как создавать свои образы и запускать контейнеры.
Теперь приведем список полезных команд, которые могут пригодиться при работе с докером.
Эта команда выведет список всех докер контейнеров:
docker ps
Но по умолчанию выводятся только работающие контейнеры. Чтобы вывести все, в том числе и остановленные, используйте опцию -a:
docker ps -a
Чтобы удалить контейнеры, сначала их нужно остановить. Первая команда остановит запущенные контейнеры, если они есть. А вторая команда — удалит их.
docker stop $(docker ps -a -q)
docker rm $(docker ps -a -q)
По умолчанию контейнеры после завершения работы останавливаются, но не удаляются. Они сохраняют свое состояние и при необходимости их можно запустить снова. Чтобы контейнер удалялся сразу после остановки, добавьте к команде docker run параметр —rm, например:
docker run --rm ubuntu echo 'hello from ubuntu'
docker images
docker rmi <имя-образа>
Если у этого образа есть контейнеры, пусть даже остановленные, докер не позволит его удалить. Он выдаст сообщение:
unable to remove repository reference <имя-образа> (must force) - container <id-контейнера> is using its
Чтобы принудительно удалить образ, добавьте флаг -f:
docker rmi -f <имя-образа>
docker ps -a --filter ancestor=<название-образа>
В статье мы рассмотрели, что такое контейнеры и Docker, как они работают и чем отличаются от виртуализации. Также мы создали простое python-приложение, обернули его в докер-образ и запустили контейнер.
Мы рассказали основы технологий, но не затронули более сложные темы, вроде Docker Swarm, настройку сети или настройки процессов CI/CD. Но этого вполне достаточно, чтобы погрузиться в основы технологий.
➕