Что такое образ контейнера
Понимая Docker
Уже несколько месяцев использую docker для структуризации процесса разработки/доставки веб-проектов. Предлагаю читателям «Хабрахабра» перевод вводной статьи о docker — «Understanding docker».
Что такое докер?
Докер — это открытая платформа для разработки, доставки и эксплуатации приложений. Docker разработан для более быстрого выкладывания ваших приложений. С помощью docker вы можете отделить ваше приложение от вашей инфраструктуры и обращаться с инфраструктурой как управляемым приложением. Docker помогает выкладывать ваш код быстрее, быстрее тестировать, быстрее выкладывать приложения и уменьшить время между написанием кода и запуска кода. Docker делает это с помощью легковесной платформы контейнерной виртуализации, используя процессы и утилиты, которые помогают управлять и выкладывать ваши приложения.
В своем ядре docker позволяет запускать практически любое приложение, безопасно изолированное в контейнере. Безопасная изоляция позволяет вам запускать на одном хосте много контейнеров одновременно. Легковесная природа контейнера, который запускается без дополнительной нагрузки гипервизора, позволяет вам добиваться больше от вашего железа.
Для чего я могу использовать docker?
Быстрое выкладывание ваших приложений
Docker прекрасно подходит для организации цикла разработки. Docker позволяет разработчикам использовать локальные контейнеры с приложениями и сервисами. Что в последствии позволяет интегрироваться с процессом постоянной интеграции и выкладывания (continuous integration and deployment workflow).
Например, ваши разработчики пишут код локально и делятся своим стеком разработки (набором docker образов) с коллегами. Когда они готовы, отравляют код и контейнеры на тестовую площадку и запускают любые необходимые тесты. С тестовой площадки они могут оправить код и образы на продакшен.
Более простое выкладывание и разворачивание
Основанная на контейнерах docker платформа позволят легко портировать вашу полезную нагрузку. Docker контейнеры могут работать на вашей локальной машине, как реальной так и на виртуальной машине в дата центре, так и в облаке.
Портируемость и легковесная природа docker позволяет легко динамически управлять вашей нагрузкой. Вы можете использовать docker, чтобы развернуть или погасить ваше приложение или сервисы. Скорость docker позволяет делать это почти в режиме реального времени.
Высокие нагрузки и больше полезных нагрузок
Docker легковесен и быстр. Он предоставляет устойчивую, рентабельную альтернативу виртуальным машинам на основе гипервизора. Он особенно полезен в условиях высоких нагрузок, например, при создания собственного облака или платформа-как-сервис (platform-as-service). Но он так же полезен для маленьких и средних приложений, когда вам хочется получать больше из имеющихся ресурсов.
Главные компоненты Docker
Архитектура Docker
Docker использует архитектуру клиент-сервер. Docker клиент общается с демоном Docker, который берет на себя тяжесть создания, запуска, распределения ваших контейнеров. Оба, клиент и сервер могут работать на одной системе, вы можете подключить клиент к удаленному демону docker. Клиент и сервер общаются через сокет или через RESTful API.
Docker-демон
Как показано на диаграмме, демон за пускается на хост-машине. Пользователь не взаимодействует с сервером на прямую, а использует для этого клиент.
Docker-клиент
Docker-клиент, программа docker — главный интерфейс к Docker. Она получает команды от пользователя и взаимодействует с docker-демоном.
Внутри docker-а
Образы
Docker-образ — это read-only шаблон. Например, образ может содержать операционку Ubuntu c Apache и приложением на ней. Образы используются для создания контейнеров. Docker позволяет легко создавать новые образы, обновлять существующие, или вы можете скачать образы созданные другими людьми. Образы — это компонента сборки docker-а.
Реестр
Docker-реестр хранит образы. Есть публичные и приватные реестры, из которых можно скачать либо загрузить образы. Публичный Docker-реестр — это Docker Hub. Там хранится огромная коллекция образов. Как вы знаете, образы могут быть созданы вами или вы можете использовать образы созданные другими. Реестры — это компонента распространения.
Контейнеры
Контейнеры похожи на директории. В контейнерах содержится все, что нужно для работы приложения. Каждый контейнер создается из образа. Контейнеры могут быть созданы, запущены, остановлены, перенесены или удалены. Каждый контейнер изолирован и является безопасной платформой для приложения. Контейнеры — это компонента работы.
Так как же работает Docker?
Как работает образ?
Мы уже знаем, что образ — это read-only шаблон, из которого создается контейнер. Каждый образ состоит из набора уровней. Docker использует union file system для сочетания этих уровней в один образ. Union file system позволяет файлам и директориями из разных файловых систем (разным ветвям) прозрачно накладываться, создавая когерентную файловую систему.
Одна из причин, по которой docker легковесен — это использование таких уровней. Когда вы изменяете образ, например, обновляете приложение, создается новый уровень. Так, без замены всего образа или его пересборки, как вам возможно придётся сделать с виртуальной машиной, только уровень добавляется или обновляется. И вам не нужно раздавать весь новый образ, раздается только обновление, что позволяет распространять образы проще и быстрее.
В основе каждого образа находится базовый образ. Например, ubuntu, базовый образ Ubuntu, или fedora, базовый образ дистрибутива Fedora. Так же вы можете использовать образы как базу для создания новых образов. Например, если у вас есть образ apache, вы можете использовать его как базовый образ для ваших веб-приложений.
Примечание! Docker обычно берет образы из реестра Docker Hub.
Docker образы могут создаться из этих базовых образов, шаги описания для создания этих образов мы называем инструкциями. Каждая инструкция создает новый образ или уровень. Инструкциями будут следующие действия:
Как работает docker реестр?
Реестр — это хранилище docker образов. После создания образа вы можете опубликовать его на публичном реестре Docker Hub или на вашем личном реестре.
С помощью docker клиента вы можете искать уже опубликованные образы и скачивать их на вашу машину с docker для создания контейнеров.
Docker Hub предоставляет публичные и приватные хранилища образов. Поиск и скачивание образов из публичных хранилищ доступно для всех. Содержимое приватных хранилищ не попадает в результат поиска. И только вы и ваши пользователи могут получать эти образы и создавать из них контейнеры.
Как работает контейнер?
Контейнер состоит из операционной системы, пользовательских файлов и метаданных. Как мы знаем, каждый контейнер создается из образа. Этот образ говорит docker-у, что находится в контейнере, какой процесс запустить, когда запускается контейнер и другие конфигурационные данные. Docker образ доступен только для чтения. Когда docker запускает контейнер, он создает уровень для чтения/записи сверху образа (используя union file system, как было указано раньше), в котором может быть запущено приложение.
Что происходит, когда запускается контейнер?
Что же происходит под капотом, когда мы запускаем эту команду?
Используемые технологии
Докер написан на Go и использует некоторые возможности ядра Linux, чтобы реализовать приведенный выше функционал.
Пространство имен(namespaces)
Docker использует технологию namespaces для организации изолированных рабочих пространств, которые мы называем контейнерами. Когда мы запускаем контейнер, docker создает набор пространств имен для данного контейнера.
Это создает изолированный уровень, каждый аспект контейнера запущен в своем простанстве имен, и не имеет доступ к внешней системе.
Control groups (контрольные группы)
Docker также использует технологию cgroups или контрольные группы. Ключ к работе приложения в изоляции, предоставление приложению только тех ресурсов, которые вы хотите предоставить. Это гарантирует, что контейнеры будут хорошими соседями. Контрольные группы позволяют разделять доступные ресурсы железа и если необходимо, устанавливать пределы и ограничения. Например, ограничить возможное количество памяти контейнеру.
Union File System
Union File Sysem или UnionFS — это файловая система, которая работает создавая уровни, делая ее очень легковесной и быстрой. Docker использует UnionFS для создания блоков, из которых строится контейнер. Docker может использовать несколько вариантов UnionFS включая: AUFS, btrfs, vfs и DeviceMapper.
Что такое Docker и как он работает
Под контейнеризацией часто понимается одна конкретная технология — Docker. В этой статье мы подробно расскажем о том, как устроен Docker, как начать им пользоваться и какие у него есть альтернативы.
Также будет практическая часть, где мы создадим собственный докер-образ и запустим контейнер.
Мы не будем рассказывать, что такое контейнеры и для чего они используются. Если вы еще не знакомы с этой темой, прочитайте нашу статью о контейнеризации. А в этой статье мы сосредоточимся именно на докере.
Компоненты Docker
Docker — это платформа для разработки и запуска контейнеров. Докер позволяет создавать контейнеры, автоматизирует их запуск и управляет жизненным циклом. Докер состоит из нескольких компонентов:
Это компьютер, на котором работает докер. Это может быть обычный персональный компьютер, железный сервер или виртуальная машина в облаке.
Это фоновый процесс, который работает постоянно и ожидает команды. Все операции по управлению контейнерами отправляются именно в демон, например: запустить или остановить контейнер, скачать образ. И уже на основе этих команд демон выполняет необходимые действия с контейнерами и образами. Поэтому докер-демон знает все о контейнерах, запущенных на одном хосте: сколько всего контейнеров, какие из них работают, где хранятся образы и так далее.
Это клиент, при помощи которого пользователи взаимодействуют с демоном и отправляют ему команды. Это может быть консоль, API или графический интерфейс.
Это неизменяемый образ, из которого разворачивается контейнер. Его можно рассматривать как набор файлов, необходимых для запуска и работы приложения на другом хосте. Можно привести аналогию из мира установки ПО: образ — это компакт-диск, с которого устанавливается программа.
Это уже развернутое и запущенное приложение. Продолжая аналогию с установкой ПО, контейнер можно сравнить с уже установленной и работающей программой на ПК.
Это репозиторий, в котором хранятся образы. Когда разработчики создают приложения, они размещают свои образы в этих репозиториях, откуда их могут скачать другие люди. Есть публичные репозитории, например Docker Hub. А можно создать свой репозиторий, для использования внутри компании или команды.
Это файл-инструкция для сборки образа.
Рассмотрим несколько примеров команд и посмотрим, как на них реагирует докер и что при этом происходит. Обратите внимание, что все команды от клиента поступают в демон, который выполняет нужное действие.
Компоненты Docker и схема работы команд
Как устроены докер-образы и контейнеры
Докер-образ состоит из слоев. Каждая команда в докер-файле добавляет новый слой, который накладывается на предыдущий. Финальный докер-образ — это объединение всех слоев в один.
Такая структура позволяет использовать уже существующие образы для создания новых. Например, мы разрабатываем приложения на Python. Чтобы наши приложения запускались на других серверах, мы должны в каждый образ устанавливать среду выполнения Python. Чтобы не реализовывать это самостоятельно, мы просто используем готовый официальный образ Python. В свою очередь, этот образ основан на базе образа Debian — дистрибутива Linux. Без него Python не сможет работать.
В результате наш докер-образ упрощенно выглядит вот так:
Также этот подход позволяет один раз скачать образ Python и использовать его для всех наших приложений. Так мы экономим место на диске и не дублируем одни и те же файлы.
Образ — это неизменяемые слои, которые доступны только для чтения. А контейнер — это тот же самый образ, у которого «сверху» есть еще один слой для записи. Контейнер использует его, когда ему нужно сохранить информацию в процессе своей работы: логи, временные файлы и так далее. Если контейнер уничтожится, то этот слой и вся информация на нем тоже пропадут. А когда из этого же образа создастся новый контейнер, слой для записи у него будет новый и пустой.
Когда к Docker-образу добавляется слой для записи, получается Docker-контейнер
Создание и запуск докер-контейнеров
Перейдем к практике. Для начала скачаем готовый образ и запустим из него контейнер. Затем создадим свое приложение, обернем его в образ и запустим в докере.
Для начала нужно установить Docker. Мы не будем описывать этот шаг, потому что он различается в зависимости от ОС и дистрибутива Linux. Инструкция по установке есть на официальном сайте.
Запускаем контейнер
Сначала мы скачаем уже собранный образ и запустим контейнер. Выполним в консоли команду:
Этой командой мы говорим докеру: «Запусти образ debian и выполни в нем вот эту команду». В результате мы должны получить одну строчку с текстом This is Debian.
Смотрим результат в консоли:
Поясним, что тут происходит. Так как у нас свежая установка докера, у нас еще нет ни одного скачанного образа. Поэтому прежде чем использовать образ debian, его нужно откуда-то взять. По умолчанию Docker настроен на публичный репозиторий Docker Hub. Поэтому когда докер не нашел на нашем компьютере запрашиваемый образ, он решил найти его на докер-хабе и скачать. Docker самостоятельно определил, что ему сначала нужно выполнить команду docker pull и лишь затем docker run. И только после этого мы увидели результат в консоли. Если мы запустим эту же команду повторно, докер уже не будет скачивать образ, и мы сразу получим результат.
Создаем собственный образ
Теперь мы напишем простое Python-приложение, упакуем его в образ и запустим контейнер. Для начала создадим директорию для работы и перейдем в нее:
Далее создадим текстовый файл app.py и в любом текстовом редакторе запишем в него одну строчку кода:
Прежде чем упаковывать приложение в контейнер, нужно убедиться, что оно работает. Выполним команду:
В консоли мы увидим результат: This is Python. Это все, что делает наше приложение: выводит одну строку текста. Но сейчас это не важно, ведь главная задача для нас — научиться упаковывать приложения в докер-образ.
Далее в этой же директории создадим файл с названием Dockerfile и запишем в него следующее:
Поясним, что мы сделали:
Но пока это еще не докер-образ, а просто файл с командами. Чтобы из докер-файла собрать образ, запустим команду:
После того как образ собран, мы можем запустить контейнер:
В результате в консоли будет результат: This is Python. Итак, мы создали простое Python-приложение, обернули его в докер-образ и запустили контейнер. Конечно, это очень простой пример, и в настоящих приложениях все намного сложнее. Но это позволяет понять основы технологии и как с ней работать.
Аналоги Docker
Docker стал стандартом де-факто в мире контейнеров. Часто, когда говорят про контейнеры, подразумевают именно Docker-контейнеры. Но это не единственная реализация технологии контейнеризации, есть и другие:
Имеет интерфейс командной строки, который очень похож на команды Docker. Основное отличие от докера заключается в том, что у Podman нет отдельного демона, это самостоятельная утилита. Podman используется как инструмент управления контейнерами по умолчанию в дистрибутиве Fedora Linux.
Система виртуализации на уровне ОС, а не самостоятельная платформа, как Docker. Она создает отдельное виртуальное окружение с собственным пространством процессов и сетевым стеком, в котором все контейнеры используют один экземпляр ядра ОС. LXC часто рассматривается как среднее между chroot и полноценной виртуальной машиной.
Движок, который изначально был ориентирован на современные облачные приложения. В 2019 году разработка прекратилась, и движок переведен в архив. Возможно, еще можно встретить проекты, где он используется. Но в новых разработках rkt уже не стоит выбирать.
Docker подходит для облаков
Докер-контейнеры можно запускать не только на своих серверах, но и в облаке. Во-первых, это позволит не заниматься выбором, покупкой и настройкой серверов. Во-вторых, у облачных провайдеров разработано много готовых сервисов, которые позволяют получить дополнительные преимущества. Например, Kubernetes на платформе VK Cloud Solutions (бывш. MCS) управляет жизненным циклом контейнеров, автоматически масштабируется под изменение нагрузки и помогает построить отказоустойчивую систему.
Образы и контейнеры Docker в картинках
Перевод поста Visualizing Docker Containers and Images, от новичка к новичкам, автор на простых примерах объясняет базовые сущности и процессы в использовании docker.
Если вы не знаете, что такое Docker или не понимаете, как он соотносится с виртуальными машинами или с инструментами configuration management, то этот пост может показаться немного сложным.
Пост предназначен для тех, кто пытается освоить docker cli, понять, чем отличается контейнер и образ. В частности, будет объяснена разница между просто контейнером и запущенным контейнером.
В процессе освоения нужно представить себе некоторые лежащие в основе детали, например, слои файловой системы UnionFS. В течение последней пары недель я изучал технологию, я новичок в мире docker, и командная строка docker показалась мне довольно сложной для освоения.
По-моему, понимание того, как технология работает изнутри — лучший способ быстро освоить новый инструмент и правильно его использовать. Часто новая технология разрабатывает новые модели абстракций и привносит новые термины и метафоры, которые могут быть как будто бы понятны в начале, но без четкого понимания затрудняют последующее использование инструмента.
Хорошим примером является Git. Я не мог понять Git, пока не понял его базовую модель, включая trees, blobs, commits, tags, tree-ish и прочее. Я думаю, что люди, не понимающие внутренности Git, не могут мастерски использовать этот инструмент.
Определение образа (Image)
Визуализация образа представлена ниже в двух видах. Образ можно определить как «сущность» или «общий вид» (union view) стека слоев только для чтения.
Слева мы видим стек слоев для чтения. Они показаны только для понимания внутреннего устройства, они доступны вне запущенного контейнера на хост-системе. Важно то, что они доступны только для чтения (иммутабельны), а все изменения происходят в верхнем слое стека. Каждый слой может иметь одного родителя, родитель тоже имеет родителя и т.д. Слой верхнего уровня может быть использован как UnionFS (AUFS в моем случае с docker) и представлен в виде единой read-only файловой системы, в которой отражены все слои. Мы видим эту «сущность» образа на рисунке справа.
Если вы захотите посмотреть на эти слои в первозданном виде, вы можете найти их в файловой системе на хост-машине. Они не видны напрямую из запущенного контейнера. На моей хост-машине я могу найти образы в /var/lib/docker/aufs.
Определение контейнера (Container)
Контейнер можно назвать «сущностью» стека слоев с верхним слоем для записи.
На изображении выше показано примерно то же самое, что на изображении про образ, кроме того, что верхний слой доступен для записи. Вы могли заметить, что это определение ничего не говорит о том, запущен контейнер или нет и это неспроста. Разделение контейнеров на запущенные и не запущенные устранило путаницу в моем понимании.
Контейнер определяет лишь слой для записи наверху образа (стека слоев для чтения). Он не запущен.
Определение запущенного контейнера
Запущенный контейнер — это «общий вид» контейнера для чтения-записи и его изолированного пространства процессов. Ниже изображен контейнер в своем пространстве процессов.
Изоляция файловой системы обеспечивается технологиями уровня ядра, cgroups, namespaces и другие, позволяют докеру быть такой перспективной технологией. Процессы в пространстве контейнера могут изменять, удалять или создавать файлы, которые сохраняются в верхнем слое для записи. Смотрите изображение:
Чтобы проверить это, выполните команду на хост-машине:
Вы можете найти новый файл в слое для записи на хост-машине, даже если контейнер не запущен.
Определение слоя образа (Image layer)
Наконец, мы определим слой образа. Изображение ниже представляет слой образа и дает нам понять, что слой — это не просто изменения в файловой системе.
Метаданные — дополнительная информация о слое, которая позволяет докеру сохранять информацию во время выполнения и во время сборки. Оба вида слоев (для чтения и для записи) содержат метаданные.
Кроме того, как мы уже упоминали раньше, каждый слой содержит указатель на родителя, используя id (на изображении родительские слои внизу). Если слой не указывает на родительский слой, значит он наверху стека.
Расположение метаданных
На данный момент (я понимаю, что разработчики docker могут позже сменить реализацию), метаданные слоев образов (для чтения) находятся в файле с именем «json» в папке /var/lib/docker/graph/id_слоя:
где «e809f156dc985. » — урезанный id слоя.
Свяжем все вместе
Теперь, давайте посмотрим на команды, иллюстрированные понятными картинками.
docker create
До:
После:
docker start
До:
После:
Команда ‘docker start’ создает пространство процессов вокруг слоев контейнера. Может быть только одно пространство процессов на один контейнер.
docker run
До:
После:
Один из первых вопросов, который задают люди (я тоже задавал): «В чем разница между ‘docker start’ и ‘docker run’?» Одна из первоначальных целей этого поста — объяснить эту тонкость.
Как мы видим, команда ‘docker run’ находит образ, создает контейнер поверх него и запускает контейнер. Это сделано для удобства и скрывает детали двух команд.
Продолжая сравнение с освоением Git, я скажу, что ‘docker run’ очень похожа на ‘git pull’. Так же, как и ‘git pull’ (который объединяет ‘git fetch’ и ‘git merge’), команда ‘docker run’ объединяет две команды, которые могут использоваться и независимо. Это удобно, но поначалу может ввести в заблуждение.
docker ps
Команда ‘docker ps’ выводит список запущенных контейнеров на вашей хост-машине. Важно понимать, что в этот список входят только запущенные контейнеры, не запущенные контейнеры скрыты. Чтобы посмотреть список всех контейнеров, нужно использовать следующую команду.
docker images
Команда ‘docker images’ выводит список образов верхнего уровня (top-level images). Фактически, ничего особенного не отличает образ от слоя для чтения. Только те образы, которые имеют присоединенные контейнеры или те, что были получены с помощью pull, считаются образами верхнего уровня. Это различие нужно для удобства, так как за каждым образом верхнего уровня может быть множество слоев.
docker stop
До:
После:
Команда ‘docker stop’ посылает сигнал SIGTERM запущенному контейнеру, что мягко останавливает все процессы в пространстве процессов контейнера. В результате мы получаем не запущенный контейнер.
docker kill
До:
После:
Команда ‘docker kill’ посылает сигнал SIGKILL, что немедленно завершает все процессы в текущем контейнере. Это почти то же самое, что нажать Ctrl+\ в терминале.
docker pause
До:
После:
В отличие от ‘docker stop’ и ‘docker kill’, которые посылают настоящие UNIX сигналы процессам контейнера, команда ‘docker pause’ используют специальную возможность cgroups для заморозки запущенного пространства процессов. Подробности можно прочитать здесь, если вкратце, отправки сигнала Ctrl+Z (SIGTSTP) не достаточно, чтобы заморозить все процессы в пространстве контейнера.
docker rm
До:
После:
Команда ‘docker rm’ удаляет слой для записи, который определяет контейнер на хост-системе. Должна быть запущена на остановленном контейнерах. Удаляет файлы.
docker rmi
До:
После:
docker commit
До:
или
После:
Команда ‘docker commit’ берет верхний уровень контейнера, тот, что для записи и превращает его в слой для чтения. Это фактически превращает контейнер (вне зависимости от того, запущен ли он) в неизменяемый образ.
docker build
До:
Dockerfile и
После:
Со многими другими слоями.
Команда ‘docker build’ интересна тем, что запускает целый ряд команд:
На изображении выше мы видим, как команда build использует значение инструкции FROM из файла Dockerfile как базовый образ после чего:
1) запускает контейнер (create и start)
2) изменяет слой для записи
3) делает commit
На каждой итерации создается новый слой. При исполнении ‘docker build’ может создаваться множество слоев.
docker exec
До:
После:
Команда ‘docker exec’ применяется к запущенному контейнеру, запускает новый процесс внутри пространства процессов контейнера.
docker inspect |
До:
или
После:
Команда ‘docker inspect’ получает метаданные верхнего слоя контейнера или образа.
docker save
До:
После:
Команда ‘docker save’ создает один файл, который может быть использован для импорта образа на другую хост-систему. В отличие от команды ‘export’, она сохраняет все слои и их метаданные. Может быть применена только к образам.
docker export
До:
После:
Команда ‘docker export’ создает tar архив с содержимым файлов контейнера, в результате получается папка, пригодная для использования вне docker. Команда убирает слои и их метаданные. Может быть применена только для контейнеров.
docker history
До:
После:
Команда ‘docker history’ принимает и рекурсивно выводит список всех слоев-родителей образа (которые тоже могут быть образами)