Docker: образы и контейнеры [352.3, часть 1]

Рейтинг: 65.7% · 17 голосов
Специализация LPIC-3 305 (v3.0): KVM/QEMU и libvirt, Xen, образы дисков, контейнеры (namespaces/cgroups, LXC/LXD, Docker), оркестрация (Compose, Swarm, Kubernetes, Helm), Vagrant/Packer/cloud-init.
Ответить
Аватара пользователя
Sergey_Sysadmin
Сообщения: 134
Зарегистрирован: 11 май 2026, 05:31

Docker: образы и контейнеры [352.3, часть 1]

Сообщение Sergey_Sysadmin »

Оглавление курса (16)
  1. Введение в LPIC-3 305: виртуализация и контейнеризация
  2. Концепции и теория виртуализации [351.1]
  3. Xen [351.2]
  4. QEMU и KVM [351.3]
  5. Управление ВМ через libvirt [351.4, часть 1]
  6. libvirt: виртуальные сети и пулы хранилищ [351.4, часть 2]
  7. Управление образами дисков ВМ [351.5]
  8. Концепции контейнеризации [352.1]
  9. LXC и LXD [352.2]
  10. Docker: образы и контейнеры [352.3, часть 1] (вы здесь)
  11. Docker: тома, сети, логирование, ресурсы [352.3, часть 2]
  12. Оркестрация контейнеров
  13. Облачные инструменты: OpenStack и Terraform [353.1]
  14. Packer [353.2]
  15. cloud-init [353.3]
  16. Vagrant [353.4]
Урок 9. Docker: образы и контейнеры [352.3, часть 1]

Контейнер - это не маленькая виртуалка, а обычный процесс хоста, которому ядро Linux подсунуло урезанный взгляд на систему через namespaces и cgroups. Docker сам ничего из этого не изобретает, он лишь удобная обёртка над ядром плюс формат упаковки файловой системы (образ). В этом уроке разберём, как устроена пара демон-клиент, как из Dockerfile собирается образ слой за слоем, почему кэш сборки то спасает минуты, то портит вам жизнь, как теги и реестры превращают локальный образ в артефакт для деплоя, и чем podman отличается от docker, не требуя демона вообще.

Изображение

Как это работает

Классический Docker состоит из двух частей. Демон dockerd работает как root-процесс и держит сокет /var/run/docker.sock, а тонкий клиент docker через этот сокет отдаёт ему команды по REST API. Всё реальное (запуск контейнеров, тяги образов) делает демон, поэтому доступ к сокету равен правам root - это первая мысль про безопасность. В версии 29 демон по умолчанию хранит образы уже не в своём legacy-хранилище, а в containerd, что даёт честную поддержку нескольких архитектур в одном теге.

Образ - это стопка слоёв, доступных только на чтение. Каждый слой это разница в файловой системе относительно предыдущего. Когда вы запускаете контейнер, сверху кладётся тонкий слой для записи, и все изменения уходят туда (механизм copy-on-write, обычно через overlayfs). Удалили контейнер - этот верхний слой исчез, образ остался нетронутым. Отсюда правило: данные, которые должны пережить контейнер, живут в томах, а не в слое записи.

Dockerfile - это рецепт сборки. Каждая инструкция, меняющая файловую систему (RUN, COPY, ADD), порождает новый слой и кэшируется. При повторной сборке демон идёт сверху вниз и переиспользует слой, пока совпадают и инструкция, и её входные данные. Первое же изменение ломает кэш для этой строки и всех нижних. Поэтому редко меняющееся (установку пакетов) ставят выше, а часто меняющееся (свой код) - ниже. С 2018 года движок сборки по умолчанию BuildKit: он строит граф зависимостей, тянет независимые шаги параллельно и не пересобирает то, что не нужно.

Реестр - это сервер хранения образов (Docker Hub, GitHub Container Registry, Harbor, ваш приватный). Полное имя образа складывается из реестра, репозитория и тега: registry.example.com/team/app:1.4. Нет реестра в имени - подставляется Docker Hub, нет тега - подставляется latest. push отправляет слои в реестр, pull тянет их обратно, причём оба переносят только недостающие слои.

Команды и примеры

Базовый жизненный цикл контейнера:

Код: Выделить всё

docker run -d --name web -p 8080:80 nginx:1.27   # запустить в фоне, проброс порта
docker ps                                         # живые контейнеры
docker ps -a                                      # включая остановленные
docker exec -it web sh                            # войти внутрь работающего
docker logs -f web                                # смотреть stdout/stderr
docker stop web && docker rm web                  # корректно погасить и удалить
Ключи: -d отвязывает от терминала, -it даёт интерактивный псевдотерминал, -p хост:контейнер пробрасывает порт. stop шлёт SIGTERM и через 10 секунд SIGKILL, в отличие от грубого kill.

Минимальный Dockerfile для приложения:

Код: Выделить всё

FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
ENTRYPOINT ["gunicorn"]
CMD ["app:app", "-b", "0.0.0.0:8000"]
Здесь COPY requirements.txt отдельной строкой - тот самый трюк с кэшем: пока зависимости не менялись, тяжёлый RUN pip install берётся из кэша даже после правок кода. ENTRYPOINT задаёт неизменяемую часть команды, CMD - аргументы по умолчанию, которые легко переопределить при docker run. Используйте exec-форму (JSON-массив), а не shell-форму: тогда процесс получит PID 1 напрямую и увидит сигналы остановки.

Сборка, теги и реестр:

Код: Выделить всё

docker build -t myapp:1.4 .                # собрать и пометить
docker tag myapp:1.4 ghcr.io/me/myapp:1.4  # добавить второе имя
docker login ghcr.io
docker push ghcr.io/me/myapp:1.4           # отправить в реестр
docker pull ghcr.io/me/myapp:1.4           # притянуть на другой машине
docker history myapp:1.4                   # посмотреть слои и их размер
Установка движка различается по семействам. Debian/Ubuntu (репозиторий Docker):

Код: Выделить всё

apt-get install ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
apt-get update
apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin
RHEL/Fedora (dnf):

Код: Выделить всё

dnf install dnf-plugins-core
dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo
dnf install docker-ce docker-ce-cli containerd.io docker-buildx-plugin
systemctl enable --now docker
Podman ставится прямо из штатных репозиториев и не нуждается в демоне: apt install podman в Debian/Ubuntu, dnf install podman в Fedora/RHEL. CLI почти один в один: podman run, podman build, podman ps. Главное отличие - podman запускает контейнеры как обычные дочерние процессы текущего пользователя (rootless из коробки), без постоянного root-демона. Для совместимости со скриптами есть alias docker=podman, а podman build читает тот же Dockerfile.

Частые грабли
  • latest - это не самая новая версия, а буквально тег по имени latest. Никто не двигает его автоматически. В продакшене пиннуйте конкретный тег или digest (image@sha256:...), иначе сборка перестанет быть воспроизводимой.
  • COPY . . без файла .dockerignore затаскивает в контекст сборки .git, node_modules и секреты, раздувает образ и ломает кэш на каждом коммите.
  • Каждый RUN это слой. Цепочка из пяти RUN с apt-get оставит мусор и кэш пакетов в промежуточных слоях. Объединяйте через && и чистите в той же строке (rm -rf /var/lib/apt/lists/*).
  • Shell-форма CMD ping host запускает процесс под /bin/sh -c, и ваше приложение становится PID 2. SIGTERM уходит в шелл, а не в приложение, и docker stop вырождается в SIGKILL через 10 секунд. Берите exec-форму.
  • ENTRYPOINT и CMD путают. ENTRYPOINT - что запускать всегда, CMD - аргументы по умолчанию. Если задан только CMD, его легко переопределить; если ENTRYPOINT в shell-форме, он молча проглотит CMD.
  • docker rm не удаляет образ, удаляет контейнер. Для образов docker rmi, для уборки висящего - docker image prune. Болтающиеся <none>:<none> образы это потерявшие тег слои, а не битьё.
  • В rootless podman порты ниже 1024 по умолчанию не пробрасываются обычным пользователем - правьте net.ipv4.ip_unprivileged_port_start или маппьте на высокий порт.
Мини-лаба
  • Создайте каталог проекта, положите туда простой Dockerfile на базе alpine с RUN apk add --no-cache curl и CMD ["sleep", "3600"] в exec-форме.
  • Соберите образ: docker build -t lab:v1 . Запустите docker history lab:v1 и запишите, сколько слоёв получилось и их размеры.
  • Запустите контейнер в фоне, зайдите внутрь через docker exec -it и убедитесь, что curl установлен. Выйдите, не останавливая контейнер.
  • Поменяйте только CMD (например аргумент sleep 60), пересоберите и сравните по docker history, какие слои взялись из кэша, а какие пересобраны.
  • Перетегируйте образ под локальный реестр: запустите docker run -d -p 5000:5000 registry:2, сделайте docker tag и docker push localhost:5000/lab:v1.
  • Удалите локальный образ через docker rmi, затем docker pull localhost:5000/lab:v1 - убедитесь, что он тянется из реестра.
  • Повторите сборку этого же Dockerfile командой podman build и запустите podman run от обычного пользователя без sudo. Сравните, под каким UID на хосте крутится процесс (ps aux), с docker.
Контрольные вопросы
  • Чем слой записи запущенного контейнера отличается от слоёв образа и что с ним происходит при docker rm?
  • Почему COPY requirements.txt отдельной строкой до COPY всего кода ускоряет повторные сборки?
  • В чём разница между ENTRYPOINT и CMD и почему для них рекомендуют exec-форму, а не shell-форму?
  • Из каких частей складывается полное имя образа и что подставляется, если опустить реестр и тег?
  • Что именно переносят по сети docker push и docker pull, и почему повторная тяга обычно быстрее первой?
  • За счёт чего podman работает без постоянного демона и в чём практическое следствие этого для безопасности по сравнению с docker.sock?
👍3 ❤️1 🔥3 😄 🤔1
Аватара пользователя
bunguru
Сообщения: 1
Зарегистрирован: 27 май 2026, 04:35

Re: Docker: образы и контейнеры [352.3, часть 1]

Сообщение bunguru »

Подскажите, а если containerd теперь дефолтное хранилище образов в 29-й версии, старые образы из docker save надо как-то конвертить или оно само подхватывает?
👍 ❤️1 🔥 😄 🤔
Аватара пользователя
linuxguru
Сообщения: 1
Зарегистрирован: 02 июн 2026, 21:22

Re: Docker: образы и контейнеры [352.3, часть 1]

Сообщение linuxguru »

Споткнулся ровно на shell-форме CMD. Контейнер с ноды не гасился по stop, висел все 10 секунд и улетал в kill. Переписал на JSON-массив и сигнал сразу дошёл, спасибо что разжевали про PID 1
👍1 ❤️ 🔥 😄 🤔
Ответить
← Предыдущая глава
LXC и LXD [352.2]
Следующая глава →
Docker: тома, сети, логирование, ресурсы [352.3, часть 2]

Все главы курса «LPIC-3 305: виртуализация и контейнеризация»

Поделиться темой: ✈ Telegram VK

Вернуться в «LPIC-3 305: виртуализация и контейнеры»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость