Привет, Хабр! К 2022 году о контейнеризации не слышал только ленивый. Большинство специалистов, так или иначе имеющих отношение к ИТ, хотя бы раз в жизни запускали программное обеспечение в контейнерах. Однако так ли эта технология проста и понятна? Давайте разбираться вместе! Главная задача данной статьи – рассказать о контейнеризации, дать ключевые понятия для дальнейшего изучения и показать несколько простых практических приемов. По этой причине (а еще, безусловно, вследствие недостаточной квалификации автора) теоретический материал достаточно  упрощен.   Идея изоляции пользовательских пространств берет свое начало в 1979 году, когда в ядре UNIX появился системный вызов chroot. Он позволял изменить путь каталога корня / для группы процессов на новую локацию в файловой системе, то есть фактически создавал новый корневой каталог, который был изолирован от первого. Следующим шагом и логическим продолжением chroot стало создание в 2000 году FreeBSD jails («тюрем»), в которых изначально появилась частичная изоляция сетевых интерфейсов. В первой половине нулевых технологии виртуализации на уровне ОС активно развивались – появились Linux VServer (2001), Solaris Containers (2004) и OpenVZ (2005). В операционной системе Linux технологии изоляции и виртуализации ресурсов вышли на новый этап в 2002 году, когда в ядро было добавлено первое пространство имен для изоляции файловой системы – mount. В 2006-2007 годах компанией Google был разработан механизм Process Containers (позднее переименованный в cgroups), который позволил ограничить и изолировать использование группой процессов ЦПУ, ОЗУ и др. аппаратных ресурсов. В 2008 году функционал cgroups был добавлен в ядро Linux. Достаточная функциональность для полной изоляции и безопасной работы контейнеров была завершена в 2013 году с добавлением в ядро пространства имен пользователей – user. В 2008 году была представлена система LXC (LinuX Containers), которая позволила запускать несколько изолированных Linux систем (контейнеров) на одном сервере. LXC использовала для работы механизмы изоляции ядра – namespaces и cgroups. В 2013 году на свет появилась платформа Docker, невиданно популяризовавшая контейнерные технологии за счет простоты использования и широкого функционала. Изначально Docker использовал LXC для запуска контейнеров, однако позднее перешел на собственную библиотеку libcontainer, также завязанную на функционал ядра Linux. Наконец, в 2015 появился проект Open Container Initiative (OCI), который регламентирует и стандартизирует развитие контейнерных технологий по сей день. Читать подробнее: Недостающее введение в контейнеризацию Контейнеризация (виртуализация на уровне ОС) – технология, которая позволяет запускать программное обеспечение в изолированных на уровне операционной системы пространствах. Контейнеры являются наиболее распространенной формой виртуализации на уровне ОС. С помощью контейнеров можно запустить несколько приложений на одном сервере (хостовой машине), изолируя их друг от друга. Процесс, запущенный в контейнере, выполняется внутри операционной системы хостовой машины, но при этом он изолирован от остальных процессов. Для самого процесса это вылядит так, будто он единственный работает в системе. Изоляция процессов в контейнерах осуществляется благодаря двум механизмам ядра Linux – пространствам имен (namespaces) и контрольным группам (cgroups). Пространства имен гарантируют, что процесс будет работать с собственным представлением системы. Существует несколько типов пространств имен: файловая система (mount, mnt) – изолирует файловую систему UTS (UNIX Time-Sharing, uts) – изолирует хостнейм и доменное имя идентификатор процессов (process identifier, pid) – изолирует процессы сеть (network, net) – изолирует сетевые интерфейсы межпроцессное взаимодействие (ipc) – изолирует конкурирующее взаимодействие процессами пользовательские идентификаторы (user) – изолирует ID пользователей и групп Процесс принадлежит не одному пространству имен, а одному пространству имен каждого типа. Контрольные группы гарантируют, что процесс не будет конкурировать за ресурсы, зарезервированные за другими процессами. Они ограничивают (контролируют) объем ресурсов, который процесс может потреблять – ЦПУ, ОЗУ, пропускную способность сети и др. Читать подробнее: Механизмы контейнеризации: namespaces Механизмы контейнеризации: cgroups Container image (образ) – файл, в который упаковано приложение и его среда. Он содержит файловую систему, которая будет доступна приложению, и другие метаданные (например команды, которые должны быть выполнены при запуске контейнера). Образы контейнеров состоят из слоев (как правило один слой – одна инструкция). Разные образы могут содержать одни и те же слои, поскольку каждый слой надстроен поверх другого образа, а два разных образа могут использовать один и тот же родительский образ в качестве основы. Образы хранятся в Registry Server (реестре) и версионируются с помощью tag (тегов). Если тег не указан, то по умолчанию используется latest. Примеры: Ubuntu, Postgres, NGINX. Registry Server (реестр, хранилище) – это репозиторий, в котором хранятся образы. После создания образа на локальном компьютере его можно отправить (push) в хранилище, а затем извлечь (pull) на другом компьютере и запустить его там. Существуют общедоступные и закрытые реестры образов. Примеры: Docker Hub (репозитории docker.io), RedHat Quay.io (репозитории quay.io). Container (контейнер) – это экземпляр образа контейнера. Выполняемый контейнер – это запущенный процесс, изолированный от других процессов на сервере и ограниченный выделенным объемом ресурсов (ЦПУ, ОЗУ, диска и др.). Выполняемый контейнер сохраняет все слои образа с доступом на чтение и формирует сверху свой исполняемый слой с доступом на запись. Container Engine (движок контейнеризации) – это программная платформа для упаковки, распространения и выполнения приложений, которая скачивает образы и с пользовательской точки зрения запускает контейнеры (на самом деле за создание и запуск контейнеров отвечает Container Runtime). Примеры: Docker, Podman. Container Runtime (среда выполнения контейнеров) – программный компонент для создания и запуска контейнеров. Примеры: runc (инструмент командной строки, основанный на упоминавшейся выше библиотеке libcontainer), crun. Host (хост) – сервер, на котором запущен Container Engine и выполняются контейнеры. Open Container Initiative (OCI) – это проект Linux Foundation, основанный в 2015 году компанией Docker, Inc, целью которого является разработка стандартов контейнеризации. В настоящее время в проекте участвуют такие компании, как Google, RedHat, Microsoft и др. OCI поддерживает спецификации image-spec (формат образов) и runtime-speс (Container Runtime). Читать подробнее:  A Practical Introduction to Container Terminology A Comprehensive Container Runtime Comparison Различия между Docker, containerd, CRI-O и runc На практике при работе с контейнерами могут быть полезны следующие советы: Простейший сценарий – скачать образ, создать контейнер и запустить его (выполнить команду внутри) Документацию по запуску контейнера (путь к образу и необходимые команды с ключами) как правило можно найти в реестре образов (например, у Docker Hub есть очень удобный поисковик) или в ReadMe репозитория с исходным кодом проекта. Создать образ и сохранить его в публичный реестр может практически каждый, поэтому старайтесь пользоваться только официальной документацией и проверенными образами! Примеры: Docker Hub/nginx, Docker Hub/debian, GitHub Readme/prometheus Для скачивания образов используется команда pull, однако в целом она необязательна – при выполнении большинства команд (create, run и др.) образ скачается автоматически, если не будет обнаружен локально При выполнении команд pull, create, run и др. следует указывать репозиторий и тег образа. Если этого не делать, то будут использоваться значения по умолчанию – репозиторий как правило docker.io, а тег latest При запуске контейнера выполняется команда по умолчанию (точка входа), однако можно выполнить и другую команду Docker – это открытая платформа для разработки, доставки и запуска приложений. Состоит из утилиты командной строки docker, которая вызывает одноименный сервис (сервис является потенциальной единой точкой отказа) и требует права доступа root. По умолчанию использует в качестве Container Runtime runc. Все файлы Docker (образы, контейнеры и др.) по умолчанию хранятся в каталоге /var/lib/docker. Для установки необходимо воспользоваться официальным руководством – Download and install Docker, которое содержит подробные инструкции для Linux, Windows и Mac. Стоит сразу отметить, что контейнерам для работы необходимы функции ядра Linux, поэтому они работают нативно под Linux, почти нативно в последних версиях Windows благодаря WSL2 (через Docker Desktop или Linux диструбутив) и не нативно под Mac (используется виртуализация). Автор рекомендует использовать в тестовой и особенно в промышленной эксплуатации только Linux.  Ниже приведены примеры наиболее распространенных команд: Обязательно пробуйте команды на практике, при необходимости прибегая к help или руководствам в Интернете.  При запуске контейнер получает доступ на чтение ко всем слоям образа, а также создает свой исполняемый слой с возможностью создавать, обновлять и удалять файлы. Все эти изменения не будут видны для файловой системы хоста и других контейнеров, даже если они используют тот же базовый образ. При удалении контейнера все измененные данные также будут удалены. В большинстве случаев это предпочтительное поведение, однако иногда данные необходимо расшарить между несколькими контейнерами или просто сохранить.  Рассмотрим два способа хранения данных контейнеров: named volumes – именованные тома хранения данныхПозволяет сохранять данные в именованный том, который располагается в каталоге в /var/lib/docker/volumes и не удаляется при удалении контейнера. Том может быть подключен к нескольким контейнерам bind mount – монтирование каталога с хостаПозволяет монтировать файл или каталог с хоста в контейнер. На практике используется для проброса конфигурационных файлов или каталога БД внутрь контейнера Ниже приведены примеры использования named volume и bind mount: Обязательно пробуйте команды на практике, при необходимости прибегая к help или руководствам в Интернете.  Читать подробнее: Хранение данных в Docker Создание и распространение образов – одна из основных задач Docker. Рассмотрим два способа создания образа: commit изменений из контейнераНеобходимо запустить контейнер из базового образа в интерактивном режиме, внести изменения и сохранить результат в образ с помощью команды commit. На практике способ удобен для небольших быстрых доработок декларативное описание через DockerfileОсновной способ создания образов. Необходимо создать файл Dockerfile с декларативным описанием в формате yaml через текстовый редактор и запустить сборку образа командой build Ниже приведены примеры использования commit и build: Обязательно пробуйте команды на практике, при необходимости прибегая к help или руководствам в Интернете. Читать подробнее: Изучаем Docker, часть 3: файлы Dockerfile. Docker Compose – это инструмент для декларативного описания и запуска приложений, состоящих из нескольких контейнеров. Он использует yaml файл для настройки сервисов приложения и выполняет процесс создания и запуска всех контейнеров с помощью одной команды. Утилита docker-compose позволяет выполнять команды на нескольких контейнерах одновременно – создавать образы, масштабировать контейнеры, запускать остановленные контейнеры и др. Читать подробнее: Руководство по Docker Compose для начинающих Podman – это инструмент с открытым исходным кодом для поиска, сборки, передачи и запуска приложений. Является утилитой командной строки с аналогичными docker командами, однако не требует дополнительный сервис для работы и может работать без прав доступа root. По умолчанию использует в качестве Container Runtime crun (ранее runc). Возможность работать с контейнерами без прав root приводит к нескольким особенностям: все файлы Podman (образы, контейнеры и др.) пользователей с правами доступа root хранятся в каталоге /var/lib/containers, без прав доступа root – в ~/.local/share/containers пользователи без root прав по умолчанию не могут использовать привилегированные порты и полноценно использовать некоторые команды Для установки необходимо воспользоваться официальным руководством – Podman Installation Instructions, которое содержит инструкции для Linux, Windows и Mac. Стоит сразу отметить, что контейнерам для работы необходимы функции ядра Linux, поэтому они работают нативно под Linux, почти нативно в последних версиях Windows благодаря WSL2 (через Linux дистрибутив – не забудьте про wsl --set-default-version 2) и не нативно под Mac (используется виртуализация). Автор рекомендует использовать в тестовой и особенно в промышленной эксплуатации только Linux.  Основные команды для docker идентичны командам для podman, однако есть и приятные доработки (например, ключ --all для команд start, stop, rm, rmi). Формат образов также совместим благодаря спецификации OCI. Ниже приведены примеры наиболее распространенных команд: При запуске контейнер получает доступ на чтение ко всем слоям образа, а также создает свой исполняемый слой с возможностью создавать, обновлять и удалять файлы. Все эти изменения не будут видны для файловой системы хоста и других контейнеров, даже если они используют тот же базовый образ. При удалении контейнера все измененные данные также будут удалены. В большинстве случаев это предпочтительное поведение, однако иногда данные необходимо расшарить между несколькими контейнерами или просто сохранить.  Рассмотрим два способа хранения данных контейнеров: named volumes – именованные тома хранения данныхПозволяет сохранять данные в именованный том, который располагается в каталоге в /var/lib/containers/storage/volumes или ~/.local/share/containers/storage/volumes и не удаляется при удалении контейнера. Том может быть подключен к нескольким контейнерам bind mount – монтирование каталога с хостаПозволяет монтировать файл или каталог с хоста в контейнер. На практике используется для проброса конфигурационных файлов или каталога БД внутрь контейнера Ниже приведены примеры использования named volume и bind mount: Создание и распространение образов – одна из основных задач Podman. Рассмотрим три способа создания образа: commit изменений из контейнераНеобходимо запустить контейнер из базового образа в интерактивном режиме, внести изменения и сохранить результат в образ с помощью команды commit. На практике способ удобен для небольших быстрых доработок декларативное описание через ContainerfileНеобходимо создать файл Containerfile с декларативным описанием в формате yaml через текстовый редактор и запустить сборку образа командой build. Containerfile и Dockerfile полностью идентичны и взаимозаменяемы сборка через утилиту buildahЧитать подробнее: Introduction Tutorial Ниже приведены примеры использования commit и build: Обязательно пробуйте команды на практике, при необходимости прибегая к help или руководствам в Интернете. Читать подробнее: Изучаем Docker, часть 3: файлы Dockerfile (Dockerfile = Containerfile) Podman Compose – это инструмент для декларативного описания и запуска приложений, состоящих из нескольких контейнеров. Фактически Podman Compose есть ни что иное, как реализация Docker Compose для Podman с учетом его особенностей (например, возможности работать с контейнерами без прав доступа root). Он использует yaml файл для настройки сервисов приложения и выполняет процесс создания и запуска всех контейнеров с помощью одной команды.  Читать подробнее: Руководство по Docker Compose для начинающих (Docker Compose = Podman Compose) Podman Pod – это группа из одного или нескольких контейнеров с общим хранилищем и сетевыми ресурсами, а также спецификацией для запуска контейнеров. Концепция подов появилась и реализуется в Kubernetes.  Читать подробнее: Podman: Managing pods and containers in a local container runtime На практике при работе с контейнерами могут быть полезны следующие советы: Для администрирования приложений в контейнерах следует использовать функционал systemd unitУправлять приложениями в контейнерах как обычными сервисами Linux очень удобно – настройка, запуск, остановка, восстановление при сбоях и др. действия становятся простыми и прозрачнымиЧитать подробнее: Как запустить Docker / Podman контейнеры в качестве службы Systemd Docker или Podman?Как определить, что лучше использовать – Docker или Podman? Критериев много, однозначного ответа нет, да и разница на сегодняшний день не так велика. Однако автор рекомендует использовать Podman во всех дистрибутивах, где приложила руку RedHat. Ubuntu, Debian и др. – Docker, RHEL, Fedora – Podman За помощь в подготовке статьи автор выражает искреннюю благодарность  @novikov0805, @Eviil и @KoPashka Все статьи серии: Основы Linux (обзор с практическим уклоном) Основы виртуализации (обзор) Основы контейнеризации (обзор Docker и Podman) Основы мониторинга (обзор Prometheus и Grafana)