Процесс, контейнер, образ и хост
Чтение и практика ~15 мин
Ситуация
Коллега говорит: «Запусти приложение в Docker — оно в контейнере, значит, изолировано и безопасно, пусть пользуется кто угодно». Похоже на виртуальную машину: свои файлы, свой root, свои процессы. Так ли это?
Главная мысль
Контейнер — это обычный процесс на хосте, которому ядро показывает ограниченный мир: свои файлы (из образа), свой список процессов, своё имя машины, свою сеть. Ядро у всех контейнеров общее — ядро хоста. Поэтому изоляция контейнера — это настройки одного ядра, а не отдельная машина.
Новые слова
- Хост — машина (настоящая или виртуальная), на которой работает Docker и его ядро Linux.
- Образ (image) — неизменяемый набор файлов и настроек запуска: «что лежит на диске и какую команду выполнить».
Пример:
python:3.12-slim— минимальный Linux с Python 3.12. - Контейнер (container) — запущенный (или остановленный) экземпляр образа: процесс плюс собственный слой для изменений файлов. Из одного образа можно запустить много контейнеров.
- Docker Engine — служба на хосте (
dockerd), которая скачивает образы и запускает контейнеры; командаdocker— её пульт управления. - Реестр (registry) — хранилище образов, например Docker Hub.
Сравнение с курсом Git: образ похож на коммит — снимок, который не меняется; контейнер — на рабочую папку, развёрнутую из снимка, где можно что-то менять.
Разбор: смотрим на контейнер с двух сторон
Команды выполняются на учебной машине с Docker от root (или пользователя в группе docker). Папка не важна.
Одна команда в контейнере
docker run --rm python:3.12-slim python3 -c "print('Привет из контейнера')"
Привет из контейнера
Docker взял образ python:3.12-slim (если его нет локально — сначала скачает), создал контейнер, выполнил в нём
python3 -c … и, благодаря --rm, удалил контейнер после завершения. Python на хосте при этом не нужен.
При первом запуске перед выводом появятся строки загрузки образа (Unable to find image … locally, Pull complete).
Долгий процесс — изнутри и снаружи
Запустим контейнер, который просто спит пять минут. -d — в фоне, --name — понятное имя с учебным префиксом:
docker run -d --name rpd-sleep python:3.12-slim sleep 300
Вывод — длинный идентификатор контейнера. Спросим изнутри, какая программа работает под номером 1:
docker exec rpd-sleep cat /proc/1/comm
sleep
docker exec выполняет ещё одну команду в работающем контейнере. Внутри sleep — процесс номер 1: первый
и главный процесс контейнера. А теперь посмотрим с хоста:
docker top rpd-sleep
UID PID PPID C STIME TTY TIME CMD
root 5740 5716 14 14:50 ? 00:00:00 sleep 300
Тот же процесс, но с номером 5740: хост видит его как обычный процесс среди остальных, и ps -ef на хосте
тоже его покажет. Ядро одно — просто внутри контейнера список процессов «обрезан». Номера у вас будут другими;
столбцы в выводе docker top для телефона сжаты.
Имя машины внутри — свой, по умолчанию начало идентификатора контейнера:
docker exec rpd-sleep cat /etc/hostname
77708b97ea32
Убираем учебный контейнер:
docker rm -f rpd-sleep
-f останавливает и удаляет контейнер. Удаляем только свой, по имени.
Образы на машине
docker images python
Строка с python и тегом 3.12-slim — образ остался после удаления контейнеров. Образ не меняется
от того, что с ним делали контейнеры.
Почему root внутри — не безопасность
id внутри покажет uid=0(root). Это тот же номер пользователя 0, что у root хоста, и процесс работает на том же
ядре. От остального хоста его отделяют настройки ядра — отдельные пространства имён (namespaces), ограничения
ресурсов (cgroups) и урезанные права (capabilities). Они хорошо работают для обычных программ, но:
- достаточно дать контейнеру лишнее — флаг
--privileged, доступ к сокету Docker/var/run/docker.sockили папку хоста через-v, — и root внутри получает root снаружи; - уязвимость в ядре затрагивает все контейнеры сразу: ядро у них общее;
- право запускать
dockerравно праву root на машине: кто может выполнитьdocker run, может смонтировать в контейнер любую папку хоста.
Поэтому контейнер с root нельзя считать песочницей для посторонних. Учебные песочницы RootPoligon устроены сложнее (отдельная среда выполнения, лимиты, отдельная машина), и даже их платформа открывает только после отдельной проверки. В курсе Docker работает на вашей учебной машине, и доступ к ней вы ни с кем не делите.
Практика: два контейнера из одного образа
- Предскажите: если запустить два контейнера
rpd-aиrpd-bизpython:3.12-slimи вrpd-aсоздать файл/tmp/hello.txt, увидит ли егоrpd-b? - Проверьте. Создать файл:
docker exec rpd-a sh -c 'echo привет > /tmp/hello.txt'; посмотреть:docker exec rpd-b ls /tmp. - Уберите оба контейнера.
Признак успеха: вы видите ответ на вопрос 1 в выводе и можете объяснить его словами «образ» и «контейнер»;
docker ps -a --filter name=rpd- в конце пуст.
Подсказка 1
Контейнеры нужно запустить так, чтобы они жили, пока вы с ними работаете. Какая команда в разборе держала контейнер живым пять минут?
Подсказка 2
docker run -d --name rpd-a python:3.12-slim sleep 300 и так же для rpd-b.
Подсказка 3
Удалить: docker rm -f rpd-a rpd-b. Проверить, что учебных контейнеров не осталось:
docker ps -a --filter name=rpd-.
Решение — в answers/01-containers-and-images.md.
Если результат другой
permission denied while trying to connect to the Docker daemon socket. Ваш пользователь не в группеdocker. На учебной машине работайте от root. Добавлять в группуdockerпосторонних нельзя — см. раздел выше.Cannot connect to the Docker daemon … Is the docker daemon running?. Служба Docker не запущена:systemctl status docker.Conflict. The container name "/rpd-a" is already in use. Контейнер с таким именем остался с прошлой попытки. Удалите его:docker rm -f rpd-a.
Проверка понимания
- Чем образ отличается от контейнера? Ответьте одной фразой со словом «снимок».
- Почему фраза «в контейнере root, но это же контейнер — пусть пользуется кто угодно» опасна?
Ответы — в answers/01-containers-and-images.md.
Дальше: 2. Жизненный цикл контейнера.
Проверка автора и источники
- Воспроизведено: все команды разбора и практики — Docker Engine 29.8.2, Ubuntu 24.04. Вместо
python:3.12-slimиспользовался локальный образ-заменитель с Python 3.12 (Docker Hub из окружения проверки недоступен), поэтому строки загрузки образа и размер образа вdocker imagesне проверены. В разборе намеренно используются толькоcat,idиsh; процессы смотрим черезdocker topс хоста. Codex на настоящемpython:3.12-slimподтвердил:sh,cat,id,lsесть,psиcurlнет — GIT_DOCKER_REVIEW.md. - Источники: Docker overview,
docker run,
Docker Engine security (атакуемая поверхность демона, группа
docker), namespaces(7).
Разбор и ответы — откройте после своей попытки
Практика
docker run -d --name rpd-a python:3.12-slim sleep 300
docker run -d --name rpd-b python:3.12-slim sleep 300
docker exec rpd-a sh -c 'echo привет > /tmp/hello.txt'
docker exec rpd-a ls /tmp
hello.txt
docker exec rpd-b ls /tmp
Вывод пустой: в rpd-b файла нет. Каждый контейнер получает свой слой для изменений поверх одного и того же
неизменяемого образа. Запись в rpd-a попала только в его слой; образ и rpd-b её не видят.
docker rm -f rpd-a rpd-b
docker ps -a --filter name=rpd-
rpd-a
rpd-b
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Таблица пустая — учебных контейнеров не осталось. Файл hello.txt исчез вместе с rpd-a: слой контейнера
удаляется с контейнером (подробнее — глава 5).
Воспроизведено: Docker Engine 29.8.2, образ-заменитель python:3.12-slim (см. проверку автора в главе).
Проверка понимания
- Образ — неизменяемый снимок файлов и настроек запуска; контейнер — процесс, запущенный из этого снимка, со своим слоем для изменений.
- Процесс в контейнере работает на общем ядре хоста под тем же uid 0. Одна лишняя настройка (
--privileged, папка хоста, сокет Docker) или уязвимость ядра — и посторонний получает root на машине. А тот, кому дали запускатьdocker, уже фактически root.
Прочитали и разобрались? Отметьте урок — прогресс сохранится в этом браузере.