Docker: что это и как используется в разработке
Docker — это набор инструментов для ускорения разработки. Из статьи вы узнаете на примерах о Докере, контейнерах и научитесь работать с ними.
45К открытий56К показов
На дворе закат 2022-го, и большая часть IT-индустрии только и делает, что работает с контейнерами. Откуда они появились, как добились глобального признания и при чём тут Docker? Расскажет разработчица в команде инфраструктуры Яндекса, действующий автор курса «DevOps для эксплуатации и разработки» Дарья Меленцова.
- Начнём с основ
- Благодаря каким механизмам работает Docker
- Терминология
- Запуск и начальная настройка Docker
- Развёртывание веб-приложения
- Создание Docker Image
- Выводы
- Дополнительные материалы по Docker
Дарья Меленцова
разработчица в команде инфраструктуры Яндекса, действующий автор курса «DevOps для эксплуатации и разработки»
Из этой статьи вы узнаете:
- что такое Docker и его главные возможности;
- почему Docker стал де-факто современной индустрией программного обеспечения;
- как создавать и развёртывать Docker-контейнеры.
Начнём с основ
Что такое Docker
Разработчики Docker дают ему такое определение: «Docker helps developers bring their ideas to life by conquering the complexity of app development», что можно перевести как «Docker помогает разработчикам воплощать свои идеи в жизнь, преодолевая сложность разработки приложений». Звучит многообещающе, не правда ли?
Если конкретнее, Docker — это инструмент, с помощью которого разработчики, системные администраторы и все желающие могут легко запускать разные приложения в изолированных контейнерах на одном сервере.
Контейнеры не знают, что рядом развёрнуты другие контейнеры с приложениями, они полностью изолированы друг от друга. В каждом контейнере можно настроить окружение, необходимое именно для этого приложения.
В отличие от виртуальных машин, контейнеры не требуют серьёзных мощностей, что позволяет более эффективно использовать ресурсы сервера.
Что такое контейнер
Ещё недавно приложения разворачивали на физических серверах, поэтому возникали сложности, когда это нужно было сделать быстро.
- Все серверы настраивались вручную (или почти вручную). Подключение сервера, установка ОС, настройка правильного окружения, сети и других параметров занимали много времени.
- Были проблемы с гибким масштабированием. Представьте, что у вас на сервере развёрнут интернет-магазин. В обычное время приложение справляется с потоком пользователей, но в канун Нового года аудитория возрастает, ведь все хотят закупиться подарками. И тут оказывается, что интернет-магазин не справляется с нагрузкой и надо либо добавить ресурсы на сервер, либо поднять ещё несколько экземпляров сервиса. Да, мы можем заранее подумать о празднике и предвидеть наплыв покупателей, но что делать с теми ресурсами, которые будут простаивать после Нового года?
- Требовалось эффективнее использовать ресурсы. Если на большом и мощном физическом сервере разместить какое-нибудь скромное приложение, которому нужно от силы 20% всех мощностей, что делать с остальным запасом? Может быть, подселить к этому приложению ещё одно или несколько? Казалось бы, вариант, пока вы не узнаете, что для работы приложений нужны разные версии одного и того же пакета.
Программисты — умные и творческие люди, поэтому они начали думать, как можно избежать этих сложностей. Так родилась виртуализация!
Виртуализация — технология, которая позволяет создавать виртуальное представление ресурсов отдельно от аппаратных. Например, под операционную систему (далее — ОС) можно отдать не весь диск, а только часть, создав его виртуальное представление.
Есть много разных видов виртуализации, и один из них — аппаратная виртуализация.
Идея в том, чтобы взять сервер и разделить его на кусочки. Допустим, у вас есть сервер, на котором установлена хостовая ОС, и внутри неё запускаются виртуальные машины (далее — ВМ) с гостевыми ОС. Между хостовой ОС и ВМ есть прослойка — гипервизор, который управляет разделением ресурсов, а также изоляцией гостевых ОС.
У аппаратной виртуализации есть большой плюс: внутри ВМ можно запускать абсолютно разные ОС, отличные от хостовой, но ценой дополнительных расходов на гипервизор.
Казалось бы, проблемы с утилизацией ресурсов и изоляцией приложений решены, но как быть с установкой ОС и настройкой окружения: всё ещё делаем вручную и на каждой ВМ? Да и зачем платить за гипервизор, если не нужно держать на одном сервере Windows и Linux — достаточно ядра хостовой ОС?
На этот случай придумали контейнерную виртуализацию. При таком типе виртуализация происходит на уровне ОС: есть хостовая ОС и специальные механизмы, которые позволяют создавать изолированные контейнеры. В роли гипервизора выступает хостовая ОС — она отвечает за разделение ресурсов между контейнерами и обеспечивает их изолированность.
Контейнер — это изолированный процесс, который использует основное ядро ОС. Работа с контейнерами помогает решить следующие проблемы:
- утилизации ресурсов (на одном сервере можно запустить несколько контейнеров);
- изоляции приложений;
- установки ОС (по сути, мы используем хостовую ОС);
- настройки окружения для приложения (можно один раз настроить окружение и быстро клонировать его между контейнерами).
Почему контейнеры и Docker
Как мы уже знаем, контейнер — это изолированный процесс, который работает со своим кусочком файловой системы, памятью, ядром и другими ресурсами. При этом он думает, что все ресурсы принадлежат только ему.
Все механизмы для создания контейнеров заложены в ядро Linux, но на практике обычно используют готовые среды выполнения вроде Docker, containerd и cri-o, которые помогают автоматизировать развёртывание и управление контейнерами.
Особенности контейнеров:
- Короткий жизненный цикл. Любой контейнер можно остановить, перезапустить или удалить. Данные, которые содержатся в контейнере, тоже пропадут. Поэтому при проектировании приложений, которые подходят для контейнеризации, используют правило: не хранить важные данные в контейнере. Такой подход проектирования называют Stateless.
- Контейнеры маленькие и лёгкие, их объём измеряется в мегабайтах. Так получается, потому что в контейнер упаковывают лишь те процессы и зависимости ОС, которые необходимы для приложения. Легковесные контейнеры занимают мало места на диске и быстро запускаются.
- Контейнеризация обеспечивает изоляцию процессов. Приложения, которые работают внутри контейнера, не имеют доступа к основной ОС.
- Благодаря контейнерам можно перейти с монолита на микросервисную архитектуру.
- Не нужно тратиться на гипервизор, и можно запустить больше контейнеров, чем ВМ на одних и тех же ресурсах.
- Контейнеры хранятся в специальных репозиториях, и каждый контейнер содержит всё необходимое окружение для запуска приложения, благодаря чему можно автоматизировать развёртывание приложения на разных хостах.
Теперь обсудим, какие преимущества даёт Docker.
- Сообщество. Существует огромное хранилище контейнеров с открытым исходным кодом, и вы можете скачать готовый образ для конкретной задачи.
- Гибкость. Docker позволяет создавать базовые шаблоны контейнеров (image) и использовать их повторно на различных хостах. Docker-контейнеры можно легко запустить как на локальном устройстве, так и в любой облачной инфраструктуре.
- Скорость развёртывания. Шаблон контейнера содержит всё необходимое окружение и настройки для работы приложения, нам не нужно настраивать всё это каждый раз с нуля.
- Нет проблемы с зависимостями и версиями пакетов. Docker позволяет упаковывать различные языки программирования и стек технологий в контейнер, чем избавляет от проблемы несовместимости разных библиотек и технологий в рамках одного хоста.
Благодаря каким механизмам работает Docker
Как вы уже знаете, в ядре Linux из коробки есть все необходимые механизмы для создания контейнеров:
- capabilities — позволяет выдать процессу часть расширенных прав, которые доступны только
root
. Например, разрешить удалять чужие файлы, завершать другие процессы (командаkill
) или изменять атрибуты у файлов (командаchown
); - namespace — это абстракция в Linux, с помощью которой можно создавать своё изолированное окружение в ОС. То есть такую коробочку, в которой свои пользователи, своя сеть, свои процессы и всё остальное. При этом изменения в namespace видны только членам этого namespace. Есть шесть типов пространств имён (namespaces): IPC, Network, Mount, PID, User, UTS.
Например:
- Network namespace отвечает за ресурсы, связанные с сетью. У каждого namespace будут свои сетевые интерфейсы, свои таблицы маршрутизации.
- User namespace специализируется на пользователях и группах в рамках namespace.
- PID namespace заведует набором ID процессов. Первый процесс, созданный в новом namespace, имеет
PID = 1
, а дочерним процессам назначаются следующие PID.
- cgroup объединяет несколько процессов в группу и управляет ресурсами для этой группы.
Традиционно лимиты в Linux можно задавать для одного процесса, и это неудобно: вы могли задать какому-то процессу не больше n мегабайт памяти, но как указывать лимиты на приложение, если у него больше одного процесса? Поэтому появились cgroups
, позволяющие объединить процессы в группу и навесить на неё лимиты.
Давайте разберёмся, как Docker создаёт контейнер из capabilities, namespace и cgroup.
Docker — это очень тонкая прослойка вокруг ядра. Он создаёт контейнер на основе docker image c заданными настройками. Когда вы попросите Docker создать контейнер, он автоматически создаст набор namespaces и cgroup для этого контейнера.
PID Namespace нужны для того, чтобы процессы внутри контейнера не могли видеть другие процессы, которые работают в другом контейнере или на хостовой системе, и влиять на них.
Network namespace — контейнер получит свой сетевой стек, а значит, он не сможет получить доступ к сокетам или сетевым интерфейсам другого контейнера.
Аналогичная история со всеми остальными пространствами имён — для каждого контейнера своё дерево каталогов, хостнеймы и прочее.
При создании Docker-контейнера мы можем указать, сколько памяти или cpu выдать конкретному контейнеру, и ОС будет следить за этим лимитом. Такой контроль нужен, чтобы один контейнер случайно не убил всю систему, съев всю память или перегрузив процессор.
По умолчанию Docker при создании контейнера урезает все capabilites внутри него, оставляя только часть возможностей — смену атрибутов UID
и GID
(chown
), kill
, chroot
и несколько других. Это сделано в целях безопасности, чтобы злоумышленнику не достались все root-права, если бы он смог выбраться из контейнера.
Терминология
Прежде чем начать работу с Docker, нужно изучить несколько терминов.
Docker Image
Образ — это шаблон для ваших будущих контейнеров. В образе описывается, что должно быть установлено в контейнере и какие действия нужно выполнить при старте контейнера.
В практической части вы будете использовать команду docker pull
, чтобы загрузить busybox image из специального хранилища Docker образов — docker hub.
Docker Container
Контейнер — это исполняемый экземпляр образа (image). Его можно создавать, запускать, останавливать и удалять. Также можно подключать к контейнеру хранилище, объединять контейнеры одной или несколькими сетями и общаться с контейнерами, используя Docker API или CLI.
Увидеть список запущенных контейнеров можно через команду docker ps
.
Docker Daemon
Docker-демон (dockerd) — фоновый процесс в операционной системе, который обрабатывает запросы Docker API и управляет объектами Docker: образами, контейнерами, сетями и томами.
Docker Client
Docker-клиент — инструмент командной строки (Comand Line Interface — CLI), через который пользователь взаимодействует с демоном.
Когда вы используете команду docker run
, то Docker-клиент отправляет команду dockerd. Аналогичная история с другими командами docker <команда>
.
Docker Hub
Docker Hub — это общедоступный Docker registry, то есть хранилище всех доступных Docker-образов. При необходимости можно разворачивать свои приватные Docker registry, размещать собственные реестры Docker и использовать их для извлечения образов.
Запуск и начальная настройка Docker
Для работы потребуются:
- базовые навыки работы с командной строкой;
- Git;
- Docker.
Docker — довольно популярный инструмент, и установить его на любую ОС не составит труда. В руководстве «Начало работы с Docker» есть подробные инструкции по настройке Docker на Mac, Linux и Windows.
После установки Docker стоит проверить, что он работает.
Для этого выполните:
Запускаем Busybox
Теперь, когда Docker установлен, запустим в нём первый контейнер. За основу контейнера возьмите Busybox image. Введите в терминале команду:
$ docker pull busybox
ПримечаниеВы можете увидеть ошибку permission denied
после выполнения команды. Если вы работаете на Mac, убедитесь, что ядро Docker (engine) запущено. Если вы работаете в Linux, добавьте к командам docker
префикс sudo
. Кроме того, вы можете создать docker group, чтобы избавиться от этой проблемы.
Команда pull
скачает (спулит) busybox image из Docker registry и сохранит его в вашей системе.
Чтобы увидеть список всех образов в вашей системе, используйте команду docker images
:
Docker Run
Отлично! Давайте теперь запустим Docker-контейнер на основе этого образа. Используйте команду docker run
:
Пусть вас не смущает, что ничего не произошло. Ошибки здесь нет, и всё идёт по плану. Когда вы вызываете run
, Docker-клиент находит образ (в нашем случае busybox), загружает контейнер и запускает в нём команду.
Когда вы запустили docker run busybox
, то не передали команду, поэтому контейнер загрузился, выполнил ничего и затем вышел.
Давайте передадим команду и посмотрим, что будет:
Ура, хоть какой-то результат! Docker клиент выполнил команду echo
в busybox-контейнере, а затем вышел из него. И всё это произошло довольно быстро.
Хорошо, контейнер вы запустили, а как посмотреть, какие контейнеры запущены на сервере прямо сейчас? Для этого есть команда docker ps
:
Сейчас нет запущенных контейнеров, и вы видите пустую строку. Попробуйте более полезный вариант — docker ps -a
:
Появился список всех контейнеров, которые вы запускали. Заметьте, столбец STATUS
показывает, что эти контейнеры были закрыты несколько минут назад.
Итак, вы запустили контейнер, выполнили одну команду, и контейнер завершился. Какой в этом смысл? Может быть, есть способ запускать больше одной команды?
Конечно, есть! Давайте выполним docker run -it busybox sh
:
run
с флагами -it
подключит вас к интерактивному терминалу в контейнере. Теперь можно запускать в контейнере столько команд, сколько захотите.
Попробуйте выполнить ваши любимые команды в контейнере. А ещё стоит потратить немного времени на изучение возможностей команды run
, так как именно её вы будете использовать чаще всего.
Чтобы увидеть список всех флагов, которые поддерживает run
, выполните docker run --help
.
Docker rm
Раз вы научились создавать контейнеры, нужно потренироваться их удалять. Вы сами видели, что даже после остановки контейнера информация о нём остаётся на хосте. Можно запускать docker run
несколько раз и получать бесхозные контейнеры, которые будут занимать место на диске.
Место на диске нерезиновое, поэтому надо прибираться и удалять ненужные контейнеры. В этом поможет команда docker rm
:
Если на хосте много контейнеров, которые надо удалить, то придётся копировать много CONTAINER ID
, а это может быть утомительно. Чтобы облегчить себе жизнь, можно использовать docker container prune
:
Команда удалит все остановленные контейнеры.
Чтобы удалить образы, которые больше не нужны, запустите docker image prune
.
Развёртывание веб-приложения
Static-site
Итак, вы рассмотрели запуск docker
и поиграли с контейнером. Настало время перейти к более реальным вещам и развернуть веб-приложение с помощью Docker.
Первым делом запустите очень простой статический сайт. Для этого заберите Docker-образ из Docker Hub, запустите его и проверьте, что у вас есть рабочий веб-сервер.
Образ, который вы будете использовать, — одностраничный веб-сайт, специально созданный для демонстрации и размещённый в registry — ifireice/static-site
.
Вы можете загрузить и запустить образ сразу, используя docker run
, флаг --rm
автоматически удалит контейнер при выходе из него, а флаг -it
запустит интерактивный терминал, из которого можно выйти с помощью Ctrl+C. Контейнер при этом будет уничтожен.
Так как образа ещё нет на хосте, Docker-клиент сначала скачает образ с registry, а потом запустит его. Если всё пойдёт по сценарию, вы должны увидеть сообщение Nginx is running...
в терминале.
Сервер запущен, но как увидеть сайт? На каком порту работает сайт? Как получить доступ к контейнеру?
Клиент не предоставляет никаких портов, поэтому вам нужно повторно запустить docker run
и опубликовать порты. Нажмите Ctrl+C, чтобы остановить контейнер.
Также вам надо сделать так, чтобы работающий контейнер не был привязан к терминалу. Это нужно для того, чтобы после закрытия терминала контейнер продолжил работать, — принцип действия detached mode:
-d
— отсоединить терминал,-P
— опубликовать все открытые порты на случайные порты,--name
— задать имя контейнеру.
Теперь вы можете увидеть порты, запустив команду docker port [CONTAINER]
:
Откройте http://localhost:55000 в браузере. Также можно указать собственный порт, на который Docker-клиент будет перенаправлять подключения к контейнеру.
Чтобы остановить контейнер, запустите docker stop
, указав идентификатор контейнера. В этом случае можно использовать имя static-site
, которое вы задали контейнеру при запуске.
Чтобы развернуть этот же сайт на удалённом сервере, вам нужно установить Docker и запустить указанную выше команду.
Создание Docker Image
Теперь, когда вы посмотрели, как запустить веб-сервер внутри образа Docker, наверное, хочется создать собственный Docker-образ?
Помните команду docker images
, которая выводит список образов, располагающихся локально?
Перед вами список образов, скачанных из registry, а также образы, которые созданы нами:
TAG
— относится к конкретному снимку изображения;IMAGE ID
— уникальный идентификатор этого image.
Образы могут быть зафиксированы с изменениями и иметь несколько версий. Если вы не укажете конкретный номер версии, по умолчанию для клиента будет установлена последняя — latest
. Например, вы можете вытащить конкретную версию образа ubuntu
:
Новый образ можно или скачать из registry, или создать собственный.
Первый image
Допустим, вы хотите создать образ, который засунет в контейнер простое приложение на Django, отображающее случайную картинку с котиком. Для начала клонируйте это приложение к себе на локальный компьютер (не в Docker-контейнер):
Теперь это приложение нужно упаковать в image. Здесь пригодятся определения про образы.
- Базовые образы — это образы, у которых нет родительского образа. Обычно это образы ОС — ubuntu, busybox или debian;
- Дочерние образы — это образы, созданные на основе базовых образов с дополнительной функциональностью.
Также есть такие понятия, как официальный и пользовательский образы.
- Официальные образы поддерживаются Docker-сообществом. Обычно их имя состоит из одного слова, например, python, ubuntu, busybox и hello-world.
- Пользовательские образы созданы пользователями. Они строятся на основе базового и содержат дополнительную функциональность. Только поддерживаются уже не сообществом, а пользователем, который его создал. Имя у таких образов обычно имеет вид имя пользователя/изображения.
Вы будете создавать пользовательский образ, основанный на Python, потому что используете приложение на Django. Также вам понадобится Dockerfile.
Dockerfile
Dockerfile — это простой текстовый файл со списком команд, которые Docker-клиент вызывает при создании образа. Команды почти как в Linux, а значит, не нужно изучать ещё один язык для создания Dockerfile.
В директории приложения уже есть Dockerfile
, но вы будете создавать его с нуля. Поэтому переименуйте его и создайте пустой файл с именем Dockerfile
в директории Django-приложения.
Начните с определения базового image. Для этого используйте ключевое слово FROM
:
Потом задайте рабочую директорию и скопируйте все файлы приложения:
Теперь, когда у вас есть файлы, можете установить зависимости:
Добавьте порт, который нужно открыть. Приложение работает на порту 5000, его и укажите:
Последний шаг — написать очень простую команду для запуска приложения: python ./manage.py runserver 0.0.0.0:5000
. Для этого используйте команду CMD
. Она говорит, какую команду должен запустить контейнер при старте.
Теперь ваш Dockerfile
готов и выглядит вот так:
Раз у вас есть Dockerfile
, нужно собрать образ. Для этого используйте docker build
и передайте необязательный флаг -t
— имя тега и расположение каталога, содержащего Dockerfile
.
Чтобы сохранить (запушить) готовый image на Docker Hub, нужно создать там учётную запись. Сохранитесь, чтобы потом вы могли получить образ и развернуть контейнер на его основе на любом сервере.
В теге yourusername
должно быть имя вашей учетной записи в Docker Hub, иначе ничего не сработает.
Если на локальной машине нет образа python:3.8
, Docker-клиент сначала скачает образ, а затем создаст ваш. Тогда вывод команды может отличаться.
Если всё прошло хорошо, то image готов! Запустите его, не забыв изменить yourusername
на правильный:
Команда взяла порт 5000
внутри контейнера и сопоставила его с портом 8888
на хосте. И теперь, если вы обратитесь на порт 8888
хостовой машины, запрос будет перенаправлен в контейнер на порт 5000
. Узнать, что вернёт приложение на запрос, можно с помощью пути: http://0.0.0.0:8888
.
Поздравляем! Вы успешно создали свой первый Docker-образ.
Docker push
Осталось дело за малым — сохранить ваш образ в registry. Сначала авторизуйтесь в Docker Hub? Не забудьте про логин из yourusername
.
Когда будете вводить пароль, он не отобразится в консоли. Это норма, он и не должен быть виден всем подряд.
Чтобы сохранить образ в registry, просто введите docker push yourusername/cats
. Важно, чтобы тег имел формат yourusername/image_name
. Тогда Docker-клиент будет знать, куда сохранять образ:
Как только образ сохранится в registry, его можно увидеть в Docker Hub по адресу https://hub.docker.com/r/yourusername/cat.
Ну а забирать и запускать image с registry вы уже умеете!
Выводы
Главные мысли этой работы:
- разобрались с виртуальными машинами, контейнерами и поняли, чем они отличаются;
- узнали, что такое Docker, и обсудили основную терминологию: image, container, docker daemon, docker client, registry;
- запустили Hello Docker;
- запустили контейнер на базе образа из Docker Hub;
- создали свой контейнер и сохранили на Docker Hub.
Дополнительные материалы по Docker
Если хочется изучить Docker глубже, отправляйтесь по ссылкам:
45К открытий56К показов