Образ и контейнер веб-приложения
У веб-проекта обычно есть исходники, среда выполнения, зависимости и данные. Пока разработчик работает один, всё это кажется одним приложением: открыли редактор, запустили сервер, получили страницу. На другом компьютере обнаруживается другая версия Node.js, занятый порт или отсутствующий файл. В этом уроке разберём, какую часть такой задачи решает Docker, а затем зафиксируем модель нашего учебного проекта.
В серии создадим professorweb-container-lab: небольшой API каталога учебных серий и отдельный клиент, который показывает полученный список. API будет читать четыре записи из JSON. Клиент сначала заработает в режиме разработки, а затем превратится в статические файлы для веб-сервера. Такая задача достаточно мала для первого знакомства с контейнерами, но уже содержит сеть, два процесса и данные, которые нельзя потерять при обновлении.
Образ описывает подготовленную среду
Образ, или image, содержит слои файловой системы и настройки запуска. Например, в него можно включить Linux-окружение, Node.js, собственный сервер и начальный JSON каталога. Это подготовленный набор материалов, из которого создают экземпляры приложения. Образ сам не отвечает на HTTP-запросы: для этого нужен запущенный процесс в контейнере.
Нам полезно различать исходный каталог проекта и результат сборки образа. Изменение файла на ноутбуке не меняет уже собранный image. Если в образ скопирована старая версия сервера, новый контейнер из этого образа тоже получит старую версию. Позже увидим второй вариант: подключённые файлы могут поступать извне через mount. Тогда результат зависит и от образа, и от внешнего подключения.
Часто образ сравнивают с шаблоном. Такое сравнение объясняет создание экземпляров, но скрывает важную деталь: метка образа может быть переназначена. Имя с тегом позволяет найти образ, однако не гарантирует вечное совпадение его содержимого. Неизменяемый идентификатор содержимого обсуждается отдельно; в начале курса достаточно помнить, что версия программы и название тега — разные сведения.
Контейнер создаёт конкретный экземпляр
Контейнер добавляет к образу изменяемый слой и настройки выполнения. У него есть собственное имя, состояние, сетевое окружение и выбранный основной процесс. Один image позволяет создать два контейнера API с одинаковым кодом, но разными именами и подключёнными каталогами. Для пользователя это два экземпляра, а не два независимо написанных приложения.
На Linux контейнерные процессы используют ядро Linux хоста с механизмами изоляции. Docker Desktop на macOS и Windows запускает Linux-контейнеры через Linux-окружение; архитектура рабочего компьютера от этого не перестаёт иметь значение. Контейнер не приносит своё независимое Linux-ядро в каждый экземпляр. Именно поэтому фраза «внутри находится целый отдельный компьютер» мешает понимать ограничения платформы. Базовая модель описана в документации Docker о контейнерах.
Вместе с тем одинаковый образ не делает любую среду совершенно одинаковой. Сохраняются различия процессорной архитектуры, доступной памяти, сетевых правил, подключённых файлов и переменных окружения. Сборка для одной архитектуры может не подходить другой; внешний сервис может быть недоступен. Docker помогает явно описать часть условий, а оставшиеся условия всё равно нужно записывать.
Четыре части нашего проекта
Для каталога заранее разделим подготовленное содержимое, экземпляр, настройки запуска и сохранённые данные. Это позволит объяснять ошибки по месту возникновения. Если браузер не видит каталог, сначала выясним, запущен ли процесс и опубликован ли его порт. Если заголовок курса исчез после обновления, посмотрим, где именно лежал изменённый JSON.
| Часть | Что относится к ней | Что произойдёт при новом контейнере |
|---|---|---|
| Образ | Node.js, код API, исходный каталог | Будет взято содержимое выбранного image |
| Контейнер | Экземпляр процесса, изменяемый слой | Новый экземпляр получит новый слой |
| Настройки запуска | Порт, environment, mounts | Должны быть заданы заново или описаны в Compose |
| Постоянные данные | Отдельный volume с каталогом | Сохраняются, пока существует это хранилище |
До подключения отдельного хранилища запись внутри контейнера относится к его изменяемому слою. Остановка обычно не удаляет этот слой. Удаление контейнера удаляет его собственный слой. Поэтому остановить, удалить и пересоздать — разные действия. Мы ещё не записываем каталог автоматически, но эта граница нужна до первой операции с данными.
Зафиксируем модель в файле
Создайте папку professorweb-container-lab и файл model.json. Это полный редакционный файл первого состояния. Он не запускает Docker и не выдаёт сведения о реально установленном окружении.
{
"project": "professorweb-container-lab",
"image": "template",
"container": "instance",
"runtime_configuration": "ports/env/mounts",
"persistent_data": "outside mutable container layer",
"execution": "not_run"
}
При чтении файла нужно уметь связать каждое поле с вопросом. Image отвечает на вопрос о подготовленном содержимом. Container — о конкретном экземпляре. Runtime configuration — о том, как экземпляр соединён с окружением. Persistent data — о том, что должно пережить его удаление. Здесь нет текущего ID образа, IP-адреса или измеренного размера: без выполнения программы такие значения нельзя считать установленными фактами.
Какой результат будем считать понятным
Представим два экземпляра нашего API. Первый опубликован на одном порту компьютера, второй на другом; у них один image и разные volumes. Изменение каталога первого экземпляра не должно автоматически менять второй. При одинаковых исходных файлах они могут показывать разные данные, поскольку источник данных входит в настройки выполнения. Это ожидаемая возможность, а не нарушение обещания контейнеризации.
Теперь представим обновление кода. Разработчик собирает новый образ, но запускает старое имя, которое всё ещё указывает на предыдущую сборку. Отсутствие новой функции в ответе API объясняется выбором image. Перезапуск старого контейнера также не заменяет его образ. Для обновления потребуется создать экземпляр из нужного image и осознанно подключить прежние данные.
Эти два мысленных сценария полезнее попытки запомнить сразу десятки команд. Они заставляют различать код и содержимое хранилища. В следующих уроках каждый наблюдаемый результат будет сопоставляться с конкретной частью модели: подготовкой образа, запуском процесса, публикацией порта либо подключением данных.
Границы учебного примера
Наш API будет публичным читающим сервисом. Он не принимает пароли, не предоставляет личный кабинет и не заменяет базу данных. Позже отдельная утилита изменит заголовок учебного курса в JSON; это упражнение одного писателя, без обещания транзакций и многопользовательской записи. Docker также не добавляет HTTPS или резервное копирование автоматически.
В материалах серии есть полные исходные состояния и ожидаемые наблюдения. При подготовке уроков контейнеры, команды, браузер и сборки не запускались; отметка not_run сохраняется в STATE. Перед повторением читатель выберет собственное окружение и запишет его версии. Начнём с этой подготовки, чтобы дальнейшие команды относились к понятному Docker Engine, а не к случайно выбранному удалённому серверу.