Привет, Хабр!
К 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), который регламентирует и стандартизирует развитие контейнерных технологий по сей день.
Читать подробнее: Недостающее введение в контейнеризацию
Что такое контейнеры?
Контейнеризация (виртуализация на уровне ОС) – технология, которая позволяет запускать программное обеспечение в изолированных на уровне операционной системы пространствах. Контейнеры являются наиболее распространенной формой виртуализации на уровне ОС. С помощью контейнеров можно запустить несколько приложений на одном сервере (хостовой машине), изолируя их друг от друга.
![](https://habrastorage.org/getpro/habr/upload_files/86e/44e/ea9/86e44eea9b21d43a456cbfa64f99a7df.png)
Процесс, запущенный в контейнере, выполняется внутри операционной системы хостовой машины, но при этом он изолирован от остальных процессов. Для самого процесса это вылядит так, будто он единственный работает в системе.
Механизмы изоляции контейнеров
Изоляция процессов в контейнерах осуществляется благодаря двум механизмам ядра Linux – пространствам имен (namespaces) и контрольным группам (cgroups).
Пространства имен гарантируют, что процесс будет работать с собственным представлением системы. Существует несколько типов пространств имен:
файловая система (mount, mnt) – изолирует файловую систему
UTS (UNIX Time-Sharing, uts) – изолирует хостнейм и доменное имя
идентификатор процессов (process identifier, pid) – изолирует процессы
сеть (network, net) – изолирует сетевые интерфейсы
межпроцессное взаимодействие (ipc) – изолирует конкурирующее взаимодействие процессами
пользовательские идентификаторы (user) – изолирует ID пользователей и групп
Процесс принадлежит не одному пространству имен, а одному пространству имен каждого типа.
Контрольные группы гарантируют, что процесс не будет конкурировать за ресурсы, зарезервированные за другими процессами. Они ограничивают (контролируют) объем ресурсов, который процесс может потреблять – ЦПУ, ОЗУ, пропускную способность сети и др.
Читать подробнее:
Основные понятия
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).
![](https://habrastorage.org/getpro/habr/upload_files/662/9a0/b1b/6629a0b1b313ea996550a0f7a0e2ae1f.png)
Читать подробнее:
Подсказки перед практикой
На практике при работе с контейнерами могут быть полезны следующие советы:
Простейший сценарий – скачать образ, создать контейнер и запустить его (выполнить команду внутри)
Документацию по запуску контейнера (путь к образу и необходимые команды с ключами) как правило можно найти в реестре образов (например, у Docker Hub есть очень удобный поисковик) или в ReadMe репозитория с исходным кодом проекта. Создать образ и сохранить его в публичный реестр может практически каждый, поэтому старайтесь пользоваться только официальной документацией и проверенными образами! Примеры: Docker Hub/nginx, Docker Hub/debian, GitHub Readme/prometheus
Для скачивания образов используется команда pull, однако в целом она необязательна – при выполнении большинства команд (create, run и др.) образ скачается автоматически, если не будет обнаружен локально
При выполнении команд pull, create, run и др. следует указывать репозиторий и тег образа. Если этого не делать, то будут использоваться значения по умолчанию – репозиторий как правило docker.io, а тег latest
При запуске контейнера выполняется команда по умолчанию (точка входа), однако можно выполнить и другую команду
Работа с Docker
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.
Основные команды
Ниже приведены примеры наиболее распространенных команд:
# справочная информация
docker --help # список доступных команд
docker <command> --help # информация по команде
docker --version # версия Docker
docker info # общая информация о системе
# работа с образами
docker search debian # поиск образов по ключевому слову debian
docker pull ubuntu # скачивание последней версии (тег по умолчанию latest) официального образа ubuntu (издатель не указывается) из репозитория по умолчанию docker.io/library
docker pull prom/prometheus # скачивание последней версии (latest) образа prometheus от издателя prom из репозитория docker.io/prom
docker pull docker.io/library/ubuntu:18.04 # скачивание из репозитория docker.io официального образа ubuntu с тегом 18.04
docker images # просмотр локальных образов
docker rmi <image_name>:<tag> # удаление образа. Вместо <image_name>:<tag> можно указать <image_id>. Для удаления образа все контейнеры на его основе должны быть как минимум остановлены
docker rmi $(docker images -aq) # удаление всех образов
# работа с контейнерами
docker run hello-world # Hello, world! в мире контейнеров
docker run -it ubuntu bash # запуск контейнера ubuntu и выполнение команды bash в интерактивном режиме
docker run --name docker-getting-started --publish 8080:80 docker/getting-started # запуск контейнера gettind-started с отображением (маппингом) порта 8080 хоста на порт 80 внутрь контейнера
docker run --detach --name mongodb docker.io/library/mongo:4.4.10 # запуск контейнера mongodb с именем mongodb в фоновом режиме. Данные будут удалены при удалении контейнера!
docker ps # просмотр запущенных контейнеров
docker ps -a # просмотр всех контейнеров (в том числе остановленных)
docker stats --no-stream # просмотр статистики
docker start alpine # создание контейнера из образа alpine
docker start <container_name> # запуск созданного контейнера. Вместо <container_name> можно указать <container_id>
docker start $(docker ps -a -q) # запуск всех созданных контейнеров
docker stop <container_name> # остановка контейнера. Вместо <container_name> можно указать <container_id>
docker stop $(docker ps -a -q) # остановка всех контейнеров
docker rm <container_name> # удаление контейнера. Вместо <container_name> можно указать <container_id>
docker rm $(docker ps -a -q) # удаление всех контейнеров
# система
docker system info # общая информация о системе (соответствует docker info)
docker system df # занятое место на диске
docker system prune -af # удаление неиспользуемых данных и очистка диска
Обязательно пробуйте команды на практике, при необходимости прибегая к help или руководствам в Интернете.
Хранение данных
При запуске контейнер получает доступ на чтение ко всем слоям образа, а также создает свой исполняемый слой с возможностью создавать, обновлять и удалять файлы. Все эти изменения не будут видны для файловой системы хоста и других контейнеров, даже если они используют тот же базовый образ. При удалении контейнера все измененные данные также будут удалены. В большинстве случаев это предпочтительное поведение, однако иногда данные необходимо расшарить между несколькими контейнерами или просто сохранить.
Рассмотрим два способа хранения данных контейнеров:
named volumes – именованные тома хранения данных
Позволяет сохранять данные в именованный том, который располагается в каталоге в /var/lib/docker/volumes и не удаляется при удалении контейнера. Том может быть подключен к нескольким контейнерамbind mount – монтирование каталога с хоста
Позволяет монтировать файл или каталог с хоста в контейнер. На практике используется для проброса конфигурационных файлов или каталога БД внутрь контейнера
Ниже приведены примеры использования named volume и bind mount:
# справочная информация
docker <command> --help
# named volume
docker run --detach --name jenkins --publish 80:8080 --volume=jenkins_home:/var/jenkins_home/ jenkins/jenkins:lts-jdk11 # запуск контейнера jenkins с подключением каталога /var/jenkins_home как тома jenkins_home
docker volume ls # просмотр томов
docker volume prune # удаление неиспользуемых томов и очистка диска. Для удаления тома все контейнеры, в которых он подключен, должны быть остановлены и удалены
# bind mount
# запуск контейнера node-exporter с монтированием каталогов внутрь контейнера в режиме read only: /proc хоста прокидывается в /host/proc:ro внутрь контейнера, /sys - в /host/sys:ro, а / - в /rootfs:ro
docker run \
-p 9100:9100 \
-v "/proc:/host/proc:ro" \
-v "/sys:/host/sys:ro" \
-v "/:/rootfs:ro" \
--name node-exporter prom/node-exporter:v1.1.2
Обязательно пробуйте команды на практике, при необходимости прибегая к help или руководствам в Интернете.
Читать подробнее: Хранение данных в Docker
Создание образа (Dockerfile)
Создание и распространение образов – одна из основных задач Docker. Рассмотрим два способа создания образа:
commit изменений из контейнера
Необходимо запустить контейнер из базового образа в интерактивном режиме, внести изменения и сохранить результат в образ с помощью команды commit. На практике способ удобен для небольших быстрых доработокдекларативное описание через Dockerfile
Основной способ создания образов. Необходимо создать файл Dockerfile с декларативным описанием в формате yaml через текстовый редактор и запустить сборку образа командой build
Ниже приведены примеры использования commit и build:
# справочная информация
docker <command> --help
# commit
# запуск контейнера из образа ubuntu в интерактивном режиме, установка утилиты ping и коммит образа под именем ubuntu-ping:20.04
docker run -it --name ubuntu-ping ubuntu:20.04 bash
apt update && apt install -y iputils-ping
exit
docker commit ubuntu-ping ubuntu-ping:20.04
docker images
# Dockerfile
# создание файла Dockerfile декларативного описания
FROM ubuntu:20.04
RUN apt-get update && apt-get install -y iputils-ping
# запуск команды build из каталога с Dockerfile для создания образа simust/ubuntu-ping:20.04
docker build -t ubuntu-ping:20.04 .
docker images
# tag, login, push
docker tag ubuntu-ping:20.04 simust/ubuntu-ping:20.04 # создание из локального образа ubuntu-ping:20.04 тега с репозиторием для издателя simust
docker images
# вход в репозиторий docker.io под пользователем simust и отправка образа
docker login -u simust docker.io
docker push simust/ubuntu-ping:20.04
Обязательно пробуйте команды на практике, при необходимости прибегая к help или руководствам в Интернете.
Читать подробнее: Изучаем Docker, часть 3: файлы Dockerfile.
Мультиконтейнерные приложения (Docker Compose)
Docker Compose – это инструмент для декларативного описания и запуска приложений, состоящих из нескольких контейнеров. Он использует yaml файл для настройки сервисов приложения и выполняет процесс создания и запуска всех контейнеров с помощью одной команды. Утилита docker-compose позволяет выполнять команды на нескольких контейнерах одновременно – создавать образы, масштабировать контейнеры, запускать остановленные контейнеры и др.
Читать подробнее: Руководство по Docker Compose для начинающих
Работа с Podman
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.
Ниже приведены примеры наиболее распространенных команд:
# справочная информация
podman --help # список доступных команд
podman <command> --help # информация по команде
# работа с образами
podman search nginx # поиск образов по ключевому слову nginx
podman pull ubuntu # скачивание последней версии (тег по умолчанию latest) официального образа ubuntu (издатель не указывается) из репозитория по умолчанию docker.io/library
podman pull quay.io/bitnami/nginx:latest # скачивание последней версии образа nginx от издателя bitnami из репозитория quay.io/bitnami
podman pull docker.io/library/ubuntu:18.04 # скачивание из репозитория docker.io официального образа ubuntu с тегом 18.04
podman images # просмотр локальных образов
podman rmi <image_name>:<tag> # удаление образа. Вместо <image_name>:<tag> можно указать <image_id>. Для удаления образа все контейнеры на его основе должны быть как минимум остановлены
podman rmi --all # удаление всех образов
# работа с контейнерами
podman run hello-world # Hello, world! в мире контейнеров
podman run -it ubuntu bash # запуск контейнера ubuntu и выполнение команды bash в интерактивном режиме
podman run --detach --name nginx --publish 9090:8080 quay.io/bitnami/nginx:1.20.2 # запуск контейнера nginx с отображением (маппингом) порта 9090 хоста на порт 8080 внутрь контейнера
podman run --detach --name mongodb docker.io/library/mongo:4.4.10 # запуск контейнера mongodb с именем mongodb в фоновом режиме. Данные будут удалены при удалении контейнера!
podman ps # просмотр запущенных контейнеров
podman ps -a # просмотр всех контейнеров (в том числе остановленных)
podman stats --no-stream # просмотр статистики. Если у пользователя нет прав доступа root, то необходимо переключиться на cgroups v2
podman create alpine # создание контейнера из образа alpine
podman start <container_name> # запуск созданного контейнера. Вместо <container_name> можно указать <container_id>
podman start --all # запуск всех созданных контейнеров
podman stop <container_name> # остановка контейнера. Вместо <container_name> можно указать <container_id>
podman stop --all # остановка всех контейнеров
podman rm <container_name> # удаление контейнера. Вместо <container_name> можно указать <container_id>
podman rm --all # удаление всех контейнеров
# система
podman system info # общая информация о системе
podman system df # занятое место на диске
podman system prune -af # удаление неиспользуемых данных и очистка диска
Хранение данных
При запуске контейнер получает доступ на чтение ко всем слоям образа, а также создает свой исполняемый слой с возможностью создавать, обновлять и удалять файлы. Все эти изменения не будут видны для файловой системы хоста и других контейнеров, даже если они используют тот же базовый образ. При удалении контейнера все измененные данные также будут удалены. В большинстве случаев это предпочтительное поведение, однако иногда данные необходимо расшарить между несколькими контейнерами или просто сохранить.
Рассмотрим два способа хранения данных контейнеров:
named volumes – именованные тома хранения данных
Позволяет сохранять данные в именованный том, который располагается в каталоге в /var/lib/containers/storage/volumes или ~/.local/share/containers/storage/volumes и не удаляется при удалении контейнера. Том может быть подключен к нескольким контейнерамbind mount – монтирование каталога с хоста
Позволяет монтировать файл или каталог с хоста в контейнер. На практике используется для проброса конфигурационных файлов или каталога БД внутрь контейнера
Ниже приведены примеры использования named volume и bind mount:
# справочная информация
podman <command> --help
# named volume
podman run --detach --name jenkins --publish 8080:8080 --volume=jenkins_home:/var/jenkins_home/ docker.io/jenkins/jenkins:lts-jdk11 # запуск контейнера jenkins с подключением каталога /var/jenkins_home как тома jenkins_home
podman volume ls # просмотр томов
podman volume prune # удаление неиспользуемых томов и очистка диска. Для удаления тома все контейнеры, в которых он подключен, должны быть остановлены и удалены
# bind mount
# запуск контейнера node-exporter с монтированием каталогов внутрь контейнера в режиме ro (read only)
podman run \
-p 9100:9100 \
-v "/proc:/host/proc:ro" \
-v "/sys:/host/sys:ro" \
-v "/:/rootfs:ro" \
--name node-exporter docker.io/prom/node-exporter:v1.1.2
Создание образов (Containerfile)
Создание и распространение образов – одна из основных задач Podman. Рассмотрим три способа создания образа:
commit изменений из контейнера
Необходимо запустить контейнер из базового образа в интерактивном режиме, внести изменения и сохранить результат в образ с помощью команды commit. На практике способ удобен для небольших быстрых доработокдекларативное описание через Containerfile
Необходимо создать файл Containerfile с декларативным описанием в формате yaml через текстовый редактор и запустить сборку образа командой build. Containerfile и Dockerfile полностью идентичны и взаимозаменяемысборка через утилиту buildah
Читать подробнее: Introduction Tutorial
Ниже приведены примеры использования commit и build:
# справочная информация
podman <command> --help
# commit
# запуск контейнера из образа ubuntu в интерактивном режиме, установка утилиты ping и коммит образа под именем simust/ubuntu-ping:20.04
podman run -it --name ubuntu-ping ubuntu:20.04 bash
apt update && apt install -y iputils-ping
exit
podman commit ubuntu-ping simust/ubuntu-ping:20.04
podman tag ubuntu-ping:20.04 ubuntu-ping:20.04
# Containerfile
# создание файла Containerfile декларативного описания
FROM ubuntu:20.04
RUN apt-get update && apt-get install -y iputils-ping
# запуск команды build из каталога с Containerfile для создания образа ubuntu-ping:20.04
podman build -t ubuntu-ping:20.04 .
# tag, login, push
podman tag ubuntu-ping:20.04 quay.io/simust/ubuntu-ping:20.04 # создание из локального образа ubuntu-ping:20.04 тега с репозиторием для издателя simust
# вход в репозиторий quay.io под пользователем simust и отправка образа
podman login -u simust quay.io
podman push quay.io/simust/ubuntu-ping:20.04
Обязательно пробуйте команды на практике, при необходимости прибегая к help или руководствам в Интернете.
Читать подробнее: Изучаем Docker, часть 3: файлы Dockerfile (Dockerfile = Containerfile)
Мультиконтейнерные приложения (Podman Compose и Podman Pod)
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 контейнеры в качестве службы SystemdDocker или Podman?
Как определить, что лучше использовать – Docker или Podman? Критериев много, однозначного ответа нет, да и разница на сегодняшний день не так велика. Однако автор рекомендует использовать Podman во всех дистрибутивах, где приложила руку RedHat. Ubuntu, Debian и др. – Docker, RHEL, Fedora – Podman
За помощь в подготовке статьи автор выражает искреннюю благодарность @novikov0805, @Eviil и @KoPashka
Все статьи серии: