Привет, Хабр!

К 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 пользователей и групп

Процесс принадлежит не одному пространству имен, а одному пространству имен каждого типа.

Контрольные группы гарантируют, что процесс не будет конкурировать за ресурсы, зарезервированные за другими процессами. Они ограничивают (контролируют) объем ресурсов, который процесс может потреблять – ЦПУ, ОЗУ, пропускную способность сети и др.

Читать подробнее:

Основные понятия

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). Примеры: DockerPodman.

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).

Читать подробнее: 

Подсказки перед практикой

На практике при работе с контейнерами могут быть полезны следующие советы:

  • Простейший сценарий – скачать образ, создать контейнер и запустить его (выполнить команду внутри)

  • Документацию по запуску контейнера (путь к образу и необходимые команды с ключами) как правило можно найти в реестре образов (например, у 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 контейнеры в качестве службы Systemd

  • Docker или Podman?
    Как определить, что лучше использовать – Docker или Podman? Критериев много, однозначного ответа нет, да и разница на сегодняшний день не так велика. Однако автор рекомендует использовать Podman во всех дистрибутивах, где приложила руку RedHat. Ubuntu, Debian и др. – Docker, RHEL, Fedora – Podman


За помощь в подготовке статьи автор выражает искреннюю благодарность @novikov0805, @Eviil и @KoPashka

Комментарии (21)


  1. Flowka
    04.04.2022 21:42
    +2

    Спасибо за статью.

    Не могли бы Вы добавить пару слов о безопасности в докере (attack surface)? Тема важная в наши дни, и обычно про нее забывают. Ну и пару слов о том, как докер работает с сетями и какие они бывают (хотя конечно это тема для отдельной статьи).


    1. simust Автор
      04.04.2022 21:53

      На мой взгляд обе эти темы однозначно заслуживают отдельных статей, особенно безопасность :) К счастью кое-что уже есть:


  1. Himura
    04.04.2022 23:08
    +3

    Все круто, но идея управять контейнерами через systemd кажется странной. Можно ведь поставить `restart: unless-stopped`, зачем для этого systemd? Или в Podman это не сработает?


    1. simust Автор
      05.04.2022 21:39

      Для меня главное практическое преимущество – возможность не думать лишний раз. На каждом из десятков и сотен серверов есть удобный механизм – systemd юниты, который управляет зависимостями, заботится о восстановлении при сбоях, позволяет смотреть конфигурации и управлять всем необходимым ПО единообразно. В целом, думаю, можно обойтись и без systemd, особенно в небольших средах и проектах – дело вкуса


    1. thunderspb
      05.04.2022 23:02

      Простой пример. Есть сайт на пхп, использует мускуль и мемкеш. И все это живет в своей сети. У меня юниты системд имеют зависимости. Все юниты требуют наличия сети и триггерят её создание. Сам пхпфпм имеет в зависимостях юниты контейнеров мускуля и мемкеша. Т.е. я могу просто запустить пхпфпм и он автоматически запустит юниты контейнеров мускуля и мемкеша, которые в свою очередь запустят создание сети, если она не создана. Ну и плюс логи каждого контейнера пишутся в journald. бОльшую часть задач может решить podman/docker compose... Но для автоматического запуска всего, при возможном ребуте системы, его все равно нужно будет допиливать напильником. Да и какой же сайт без крон задач? Тут вообще на помощь прийдёт systemd и timers :)

      Для локальной разработки,да, использую podman/docker compose.


      1. Himura
        06.04.2022 07:29
        +1

        Для автоматического запуска всего при старте системы в нужном порядке и с созданием виртуальной сети можно использовать docker-compose. И контейнеры могут являться единым способом управления ворклоадом, при чем, удобно то что они не смешиваются с системными делами, и всегда можно посмотреть чистый список запущенного. Зашёл на сервер, позвал "docker ps" и все узнал про него. Логироввние ликера можно настроить так чтобы оно использовало journald, но это не обязательно, так как всегда есть "docker logs". Мне до сих пор не понятно зачем использовать возможности хост-системы (которых может и не быть в systemd-less системах) для управления контейнерами, если контейнеры и сами отлично справляются. Если что-то на холсте работает без докера, то конечно systemd-units это единственный адекватный вариант управления жизненными циклом, но docker совершенно точно обладает достаточным функционалом чтобы его заменить.

        А для десятков серверов с десятками контейнеров этот подход с локальным докером в принципе не походит, тут уже оркестратор нужен. Это же три независимых подхода: systemd (+Ansible?) -> docker/podman (+ docker-compose) -> K8s/Nomad... Каждый из них полностью спмостоятельно позволяет управлять жизненным циклом ворклоада.


        1. thunderspb
          06.04.2022 14:58
          +1

          Так я и не говорю, что системд панацея. Я для себя так сделал и привел пример, что можно вот так. опять же, подман редхатовский, что говорит о том, что там будет системд. Для запуска разных контейнеров, не связанных между собой, системд тоже вполне себе подойдет. для более серьезных архитектур уже можно смотреть и в сторону кубера, но там свои нюансы и цены.


  1. LostAlly
    04.04.2022 23:21
    +2

    Спасибо за статью, но никак не могу решиться изучать докер или нет, вроде бы нужно но и непонятно справится ли он с моей задачей лучше чем справляется проксмокс?

    Есть сервер с несколькими контейнерами на проксмосе, веб сервер, сервер бд. На вебсервере несколько сайтов крутятся на nginx+php-fpm, есть желание разделить это все на несколько контейнеров, каждый сайт в своем контейнере с php-fpm, мускул и постгрес на отдельных контейнерах, в отдельном контейнере nginx. Ну и контейнеры отдельные для разработки. Вот стоит у меня вопрос, что будет эффективней, разделить это все в проксмосе, или изучить докер и реализовать на нем.

    Может я вообще неправильно все понял. Прошу совета.


    1. khajiit
      05.04.2022 09:12
      +2

      It may vary.
      Если у вас кластер проксов и поднято HA, и вы не знаете как правильно сделать HA средствами самой БД — то не стоит.
      Если же просто один хост, то нет принципиальной разницы, как все контейнеризировать. В докере слои, в проксе — container templates. В докере все изменения умирают, если не озаботиться их сохранением, lxc в проксе гораздо ближе к классике. Докер сам управляет файрволлом (через директивы в Dockerfile), прокс надо заставлять что-то делать через WebUI/API. Прокс умеет в снепшоты.
      Еще можно поднять докер в контейнере прокса, выставив keyctl=1 и nesting=1, да посмотреть, как оно будет.


    1. SlavikF
      05.04.2022 09:28
      +2

      Я именно так и делаю.

      Есть у меня несколько персональных проектов, небольшие заказы сайтов, пару форумов и т.д. Всего штук 20 сайтов. Некоторые PHP, некоторые Python.

      Вот все эти сайты у меня и работают — каждый в своём контейнере.
      В каждом контейнере можно ставить нужную версию PHP, набор модулей и т.д.
      Load Balancer для всего этого — Traefik.
      Логи собираю FileBeat и отсылаю на Elastic.
      База данных — mysql, одна на всех, установленная на хосте через apt install. Мне показалось, что базу надёжней будет поставить так, а не в докере, и по ресурсам лучше одну на всех. DB умеет нормально изолировать пользователей друг от друга.

      Из минусов только то, что обновлять всё это хозяйство тяжелей.
      Раньше было apt update; apt upgrade.
      А теперь надо смотреть за контейнерами, некоторые из которых своей сборки.


      1. NAI
        05.04.2022 10:02
        +3

        Обновление хорошо решается через ci-cd процессы того же gitlab\github. Правда придется немного изучить Ansible (ну или что-нибудь альтернативное, по вкусу).

        Тогда все обновление сведется к MRу в ветку prod.


      1. danvop
        05.04.2022 16:19

        Здравствуйте, можете подсказать по вопросам:

        1. Веб-сервера в отдельных контейнерах?

        2. Проброс портов как решается?

        3. Используется что-то типа nginx-proxy или всё средствами Traefik?

        4. Используется ли ssl?


        1. SlavikF
          05.04.2022 19:12
          +1

          1. Да, каждый контейнер некоторый набор, например
          — Nginx + php-fpm
          — Apache + mod_php
          — или просто уже готовые наборы, например «phpmyadmin/phpmyadmin»

          Кто-то скажет, что это не докер-вэй — пихать в один контейнер несколько сервисов, и Nginx должен быть в отдельном контейнере, а php-fpm — в другом и т.д. Может и так, но я делаю так, как мне удобней.

          2, 3, 4. Это всё делается через Traefik.
          Traefik торчит наружу портами 80, 443
          Traefik управляем ssl (автоматически)
          nginx-proxy не нужен, потому что всё делает Traefik. Раньше я пользовался nginx-proxy — тоже неплохое решение, но Traefik показался мне удобней.
          У всех других контейнеров порты не пробрасываются. Traefik цепляется к ним по докеровской сети.


    1. simust Автор
      05.04.2022 21:52
      +1

      Как понимаю, речь идет о переходе с Linux Containers на Docker или Podman. По-моему это как раз тот случай, когда лучше изучить, чем не изучать – цинично, но в будущее технологий под эгидой OCI я верю больше :)


    1. ryanl
      06.04.2022 09:41
      +1

      никак не могу решиться изучать докер или нет

      Докер несомненно самый популярный сейчас движок контейнеризации. Более того, Docker Inc. недавно привлек много мильонов долларов дополнительных инвестиций, так что будьте уверены - все ваши усилия, потраченные на изучение, точно окупятся сторицей.


  1. Tirarex
    05.04.2022 18:45
    +1

    Статья хорошая для начинающего, я бы добавил сноску про Portainer (docker gui), управлять из гуи проще, визуальное отображение volume и образов приложений, быстрая настройка всех параметров включая авто запуск контейнеров и окружения, а compose вместо возни с файлами превращается в удобный "stacks" где yaml файл можно писать прямо в браузере, как и быстро + удобно настроить переменные окружения.


    1. simust Автор
      05.04.2022 21:56

      Спасибо за дополнение! Честно говоря не пользовался этим GUI на практике. Что-то похожее видел в Docker Desktop, когда устанавливал его при подготовке статьи. А так в основном только CLI под рукой


      1. thunderspb
        05.04.2022 23:10
        +1

        Portainer немного другое, чем docker desktop, рекомендую посмотреть. Это больше про шареную вебморду с пользователями, доступами и всякое. Я это прикручивал к своему сварму с внутренним регистри.


  1. KoPashka
    05.04.2022 22:07
    +2

    Если прочитали статью и всё равно ничего не понятно, установите себе виртуалку с Убунту и поднимите там контейнер. Попробуйте всё команды из статьи, очень помогает в понимании работы контейнеров.

    PS: не рекомендуется тыкать команды на рабочем сервере в ознакомительных целях????


  1. Joey_Nim
    06.04.2022 09:42

    Что-то не пойму. А разве управление докер контейнерами при отсутствии рута не решается простым добавлением пользователя в группу docker? Исходя из этого по содержанию статьи разницы между подманом и докером выходит нет.


    1. simust Автор
      06.04.2022 11:15
      +1

      Несколько различий все же есть:

      1. Запуск контейнеров под пользователями без прав root и управление контейнерами пользователями без прав root – существенно разные вещи

      2. Docker требуется сервис, Podman работает без него (минус единая точка отказа)

      3. Docker использует Container Runtime runc, Podman использует crun

      Однако совершенно верно, что с точки зрения рядового пользователя разницы особой нет. Podman изначально разрабатывали так, чтобы внешне он ничем (практически) не отличался

      P.S. Я не топлю за Podman, но вижу его применение оправданным в дистрибутивах с сильным влиянием RedHat :)