Контейнер. Что это?
Docker контейнер - это стандартизированная, легковесная и автономная единица программного обеспечения, которая включает в себя все, что необходимо для запуска приложения, включая код, среду выполнения, библиотеки и зависимости. Контейнеризация позволяет упаковать приложение и его зависимости в изолированный контейнер, который может быть запущен на любом совместимом хосте.
Структура Docker контейнера основана на слоях файловой системы и дополнительных метаданных, которые описывают контейнер и его настройки. Вот основные компоненты структуры контейнера:
- Образ контейнера: Образ контейнера - это основа контейнера, которая состоит из нескольких файловых слоев (layers). Каждый слой содержит файлы, директории, библиотеки, исполняемые файлы и другие ресурсы, необходимые для работы приложения. Эти слои хранятся в образе в виде дифференциальных изменений относительно предыдущих слоев, что позволяет экономить место на диске.
- Метаданные: Метаданные контейнера хранят информацию о контейнере, такую как имя контейнера, используемые порты, переменные окружения, команды для выполнения и т. д. Эти метаданные хранятся в JSON-файле и используются для конфигурирования и запуска контейнера.
- Файловая система: Каждый слой образа добавляет или изменяет файлы и директории в файловой системе контейнера. Когда контейнер запускается, все слои объединяются в единую файловую систему, и приложение работает в этой изолированной среде.
- Пространство имен: Контейнеры изолированы друг от друга с помощью пространств имен. Это означает, что каждый контейнер имеет свое уникальное пространство имен для процессов, сети, файловых систем и других ресурсов.
- Процессы: Каждый контейнер запускает свои собственные процессы в изолированной среде. Это позволяет контейнерам быть независимыми и изолированными, даже если они работают на одном хосте.
- Сеть: Контейнеры могут иметь свои собственные сетевые интерфейсы и IP-адреса, что обеспечивает изоляцию и позволяет контейнерам общаться друг с другом и с внешним миром.
Слои контейнера
Слои контейнера - это основные строительные блоки, из которых состоит Docker образ. Когда вы создаете образ, Docker использует концепцию слоев для оптимизации хранения, передачи и обновления образов. Каждый слой представляет собой набор изменений относительно предыдущего слоя, что позволяет сэкономить место на диске и ускорить процесс создания и обновления образов.
Вот как это работает:
- Базовый образ: Каждый Docker образ начинается с базового образа. Этот базовый образ может быть операционной системой, такой как Ubuntu, Alpine Linux или другой. Базовый образ представляет собой первый слой контейнера.
- Изменения слоев: После базового слоя создаются последующие слои, каждый из которых представляет собой изменения в файловой системе, добавленные к предыдущему слою. Это могут быть новые файлы, измененные файлы или даже удаленные файлы.
- Слои на чтение и запись: Важно понимать, что каждый слой на чтение остается неизменным и не может быть изменен после создания. Когда вы создаете контейнер из образа, Docker создает новый слой на запись поверх образа для изменений, внесенных во время работы контейнера. Это обеспечивает изоляцию и возможность создания множества контейнеров на основе одного образа.
- Инкрементальность: Поскольку каждый слой представляет собой набор изменений, образы могут делить общие слои между собой. Это означает, что если несколько образов используют один и тот же слой, он будет сохранен только один раз на диске, что сэкономит место.
- Кэширование: Docker также использует кэширование слоев. Если слой был создан ранее и файлы не изменились, Docker просто повторно использует этот слой из кэша, вместо того чтобы создавать его заново. Это ускоряет процесс создания образов.
Использование слоев позволяет Docker обеспечивать эффективное хранение и передачу образов, а также быстрое создание и обновление контейнеров, что делает работу с Docker более эффективной и масштабируемой.
Главное отличие docker-image от контейнера - это доступный для записи верхний слой контейнера. Все, что контейнер будет писать и менять, происходит в этом верхнем слое, и никак не влияет на нижние слои docker-образа, на основы которого создан контейнер.
Процесс в контейнере
Один контейнер - один процесс! Это классическая и рекомендуемая схема применения контейнеров. Приложение, запушенное в контейнере, стартует с помощью ENRYPOINT и/или CMD, дальше этот процесс "единолично" работает в контейнере, он может "форкнуться" при необходимости, но изначально это один главный процесс.
Если необходимо запустить в своем контейнере несколько процессов
Ограничение доступных ресурсов
По умолчанию, контейнер никак не ограничен в использовании ресурсов хостового компьютера. Но в Докере есть механизм который позволяет задавать лимиты на используюмую память и CPU для отдельно взятого контейнера.
Memory limits
- Жесткие ограничения, не дают контейнеру использовать больше, чем указано в ограничении
- Мягкие ограничения, позволяют процессу выходить за лимиты, пока на хосте не сработают ред-флаги, например: ядро обнаружет, что осталось мало памяти или за нее началась конкуренция.
CPU limits
Конфигурация лимитов CPU осуществляется через CFS scheduler
Метрики в реальном времени
Самый простой способ снять метрики с контейнеров - команда docker stats, в режиме реального времени показывает сколько каких ресурсов потребляет docker.
docker stats <container1> container2> # позволяет указывать метрики каких контейнеров выводить
docker stats --no-stream # отменяет потоковый вывод метрик
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
00a1fdecdca6 cadvisor 10.71% 91.57MiB / 3.82GiB 2.34% 1.8GB / 114GB 86.4MB / 0B 19
f09289fd17bb prometheus 0.00% 166.4MiB / 3.82GiB 4.26% 119GB / 2.13GB 2.65GB / 34.5GB 9
e58a8379b9f4 alertmanager 0.23% 19.29MiB / 3.82GiB 0.49% 1.38MB / 454kB 158MB / 696kB 9
68aa594554ae grafana 0.12% 80.17MiB / 3.82GiB 2.05% 123MB / 473MB 794MB / 493MB 15
997ba88d455f node-exporter 0.00% 12.45MiB / 3.82GiB 0.32% 312MB / 5.56GB 43.2MB / 0B 9
Размер контейнера
Что бы узнать сколько места занимает запущенный контейнер используется команда docker ps -s
docker ps -s --format "table {{.Names}}\t{{.ID}}\t{{.Size}}"
# -size: это все, что контейнер записал в верхний слой
# -virtual size: все место которое занимает контейнер в ФС, включая ro-слои образа
Важно понимать, что два разных контейнера, созданных из одного и того же образа будут использовать read-only слои совместно, что существенно экономит место на хосте.