Перейти к содержанию

Образ и контейнер веб-приложения

У веб-проекта обычно есть исходники, среда выполнения, зависимости и данные. Пока разработчик работает один, всё это кажется одним приложением: открыли редактор, запустили сервер, получили страницу. На другом компьютере обнаруживается другая версия 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, а не к случайно выбранному удалённому серверу.

Оглавление серии