Образ и контейнер веб-сервера
В предыдущем уроке «Контур» получил требования к Terraform и Docker-провайдеру. Теперь добавим первый настоящий предмет управления: образ Nginx и контейнер веб-сервера. Конфигурация будет полной, но создание ресурсов в этой редакционной работе не выполняется. Ожидаемый результат относится к дальнейшему запуску читателем в своей отдельной локальной лаборатории.
В Docker образ и контейнер выполняют разные роли. Образ содержит файловую систему и параметры запуска; контейнер является экземпляром с собственным идентификатором и состоянием процесса. Из одного образа можно создать несколько контейнеров. Если смешать эти понятия, становится непонятно, почему замена контейнера может не требовать нового скачивания образа, а удаление контейнера не обязательно удаляет сам образ.
Связь ресурса образа с контейнером
В images.tf объявлен docker_image.web. Локальное имя web входит в адрес Terraform, но не переименовывает образ в Docker. Настоящее имя задаётся аргументом name. Мы используем явный учебный тег nginx:1.28.0-alpine, чтобы не получить неопределённое значение latest. Тег всё же не является неизменяемым хешем содержимого: владелец реестра может обновить его. Точное закрепление digest будет обсуждаться вместе со стратегией обновления.
Поле keep_locally = true сохраняет скачанный образ, когда соответствующий ресурс перестаёт управляться Terraform. Это осознанное решение для лаборатории, где образ может пригодиться другим локальным контейнерам. Оно не сохраняет контейнер и не защищает его данные. Разбор плана удаления должен различать эти последствия: «ресурс образа больше не в состоянии» и «байты образа физически удалены с диска» здесь не одно действие.
В main.tf контейнер получает ссылку на docker_image.web.image_id. Значение image_id сообщает идентификатор реального образа. У ресурса есть и поле id, но оно обозначает идентичность самого объекта в модели провайдера и не должно подставляться вместо идентификатора Docker-образа. Использование правильной ссылки одновременно передаёт значение и показывает зависимость: контейнер нуждается в готовом образе.
Полный файл images.tf для этого состояния:
resource "docker_image" "web" {
name = "nginx:1.28.0-alpine"
keep_locally = true
}
Полный файл main.tf для этого состояния:
resource "docker_container" "web" {
name = "kontur-web"
image = docker_image.web.image_id
ports {
internal = 80
external = 8088
ip = "127.0.0.1"
}
}
Два порта и один адрес привязки
Внутри контейнера Nginx принимает HTTP на порту 80. На хосте мы выбираем 8088, чтобы не требовать привилегированного порта и уменьшить вероятность столкновения с другим сервером. Браузер обращается к адресу http://127.0.0.1:8088, а Docker перенаправляет соединение к внутреннему порту. Порт браузера не меняет настройку слушателя внутри Nginx. Отсюда следует, почему замена external и замена internal имеют разный смысл.
Обратите внимание на ip = "127.0.0.1". Без явной привязки порт может быть опубликован на всех интерфейсах хоста. Для нашей учебной страницы это лишняя доступность. Loopback означает, что вход предназначен для процессов на этом же компьютере, но не представляет полноценную модель авторизации приложения. Позднее другие контейнеры смогут общаться внутри своей сети; их внутренний доступ не описывается адресом браузера на хосте.
Имя kontur-web удобно для просмотра Docker-инвентаря. В Docker оно должно быть уникальным в пределах daemon. Адрес docker_container.web уникален в пределах состояния Terraform. Эти пространства имён не совпадают. Две независимые папки могут содержать одинаковый адрес ресурса, но попытка использовать одинаковое Docker-имя на одном daemon приведёт к конфликту. Решение заключается в разном префиксе окружения, а не в случайном удалении мешающего объекта.
Как читать будущий результат
После самостоятельной инициализации и рассмотрения плана читатель должен увидеть предложение создать два управляемых ресурса. Не воспринимайте эту ожидаемую цифру как журнал выполненной работы: провайдер может учитывать уже доступный образ, состояние может отличаться, а endpoint может быть недоступен. Важно понять саму структуру: один ресурс отвечает за образ, второй за контейнер. Число процессов на компьютере из неё напрямую не выводится.
Когда приложение будет отдельно создано, запрос корневого пути должен возвращать стандартную страницу Nginx. Мы пока не загружаем собственный HTML, поэтому название «Контур» существует только в конфигурационных именах. Наличие HTTP-ответа проверяет путь от браузера к веб-серверу, но не доказывает правильность будущих маршрутов, сертификатов или восстановления после отказа. Учебный сервер не предназначен для публичного доступа.
Рассмотрим ошибочный вариант: заменить ссылку на строку nginx:1.28.0-alpine, оставив ресурс образа отдельно. Конфигурация может выглядеть почти одинаково, но связь между двумя ресурсами станет менее явной. Terraform уже не получит зависимость из выражения контейнера. Возможность Docker самостоятельно разрешить имя не является педагогической заменой зависимости в нашей модели. Предпочтём ссылку, когда объект действительно объявлен в том же проекте.
Вторая ошибка — решить конфликт порта сменой внутреннего номера на 8089. Nginx всё равно продолжит слушать 80, а перенаправление будет указывать туда, где ничего не отвечает. Если занят хостовый 8088, изменять надо внешний порт. Сначала разберитесь, какой существующий процесс использует адрес; наш пример не предполагает остановку чужих сервисов. В дальнейшем переменная с проверкой позволит изменять внешний порт без копирования всего ресурса.
Образ, контейнер и опубликованный порт теперь описаны отдельно и связаны через значения. Этот небольшой граф станет основой следующих уроков. В нём ещё нет собственной страницы, именованной сети, нескольких экземпляров или автоматической гарантии непрерывной работы. Добавлять всё сразу неудобно: при первой ошибке читатель не поймёт, проблема находится в образе, маршруте подключения или шаблоне HTML. Следующий шаг посвящён тому, как Terraform показывает изменение такого графа до применения.
Когда ресурс уже выглядит знакомым
Пусть Docker-инвентарь показывает контейнер kontur-web, созданный вручную вчера. Совпадение имени не передаёт его автоматически в новое состояние Terraform. План из пустой папки может предложить создание, после чего имя окажется занято. Нужно решить, является ли объект прежней лабораторией, которую следует принять под управление, либо чужим сервисом, которому нужен другой префикс. В курсе импорт появится позже как отдельный механизм.
Также нельзя заменить адрес браузера случайным внутренним IP, увиденным в Docker. Такой адрес относится к сетевой среде контейнера и может быть недоступен напрямую с хоста. Публикация порта уже задаёт подходящий маршрут для нашего пользователя. Когда вы объясняете проблему подключения, сначала назовите место источника запроса и место слушателя, затем проследите перенаправление. Такая схема предотвращает случайную правку трёх разных адресов одновременно.
Оглавление серии, общая подборка и полный архив исходников. Для этого урока выберите lesson-02.
Первичные источники: Версии Terraform, Учебник Terraform и Docker, Схема Docker container 3.6.2, Схема Docker image 3.6.2, Адреса и moved, Локальный backend, Import, Refresh-only plan.
Редакционный статус: локальный черновик. Учебные команды, приложение и проверки не запускались; описанные результаты ожидаемые. Профиль версий закреплён, разрешённый lockfile не создавался.