Контейнеры и оркестрация: как один раз собрать приложение и запускать его где угодно

Одиннадцатая, завершающая статья серии «Архитектура и производительность». Мы прошли длинный путь: решили, делить ли приложение на части (Монолит против микросервисов), научили части разговаривать (Проектирование API) и не терять тяжёлые задачи (Очереди сообщений), ускорили ответы (Кеширование), раскидали запросы по серверам (Балансировка нагрузки), навели порядок в метриках, логах, базе и крае сети, а в Нагрузочном тестировании узнали, сколько всё это выдерживает. Остался вопрос, без которого любой ответ из предыдущих статей приходится на каждом сервере искать заново: как сделать так, чтобы одна и та же сборка одинаково работала у разработчика, на тесте и в проде — и кто вообще должен запускать, перезапускать и масштабировать десятки процессов.

Ответы: контейнеры и оркестратор. Звучит как лексика больших компаний, но на деле это один текстовый рецепт сборки и один текстовый файл описания запуска. Разберём по-простому: что такое контейнер и чем он не является, как из рецепта получается образ, как одним файлом поднять приложение с базой и кэшем на ноутбуке, что именно делает оркестратор в продакшене и — честно — когда он вам не нужен вовсе.


Часть I. Зачем контейнеры: конец эпохи «а у меня работало»

1. Почему фраза «а у меня работало» стоит денег

Типичный рабочий день: разработчик дописал обработчик, проверил на ноутбуке — всё зелёное. Выкатили на сервер — падает. Причина оказывается одной из десятка:

  • на сервере версия языка старше, чем локально;
  • не хватает расширения — например, для сжатия изображений или для подключения к базе;
  • локально путь к соку базы прописан в конфиге, а на сервере база живёт по другому адресу;
  • разные версии системных библиотек, с которыми код говорит напрямую;
  • локально переменная окружения задана в оболочке, а на сервере её никто не выставлял.

Каждая такая история стоит часы: кто-то идёт выяснять, чем машина разработчика отличается от сервера. В монолите на одном VPS отличия ещё терпимы, но когда у вас пять сервисов на разных языках и два тестовых контура, число комбинаций «что с чем стоит» растёт лавинно. Поэтому в части про микросервисы главным плюсом разделения назвали независимый деплой: независимость имеет смысл только тогда, когда каждая часть гарантированно запускается так же, как была проверена.

2. Идея упаковки: процесс вместе с его окружением

Контейнер решает проблему грубо и эффективно: вместе с кодом кладётся всё, что нужно коду для жизни. Не только файлы проекта, но и операционная система в урезанном виде — библиотеки, интерпретатор, утилиты, настройки по умолчанию. Получается один архив, из которого в любой момент можно запустить процесс.

Машину разработчика, тестовый стенд и продакшен объединяет одно: они запускают тот же самый архив, а не «похожую установку». Разница между «у меня стоит PHP 8.3 с расширением intl» и «там стоит что-то близкое» исчезает, потому что спрашивать про сервер бессмысленно — окружение приехало в составе образа.

Важное следствие: окружение перестаёт быть результатом ручной настройки и становится кодом. Набор библиотек описывается текстовым файлом, лежит в системе контроля версий и проходит те же проверки, что и исходники. Ошибка исправляется правкой файла, а не танцами на проде. Тот же принцип, что и в Нагрузочном тестировании: окружение воспроизводится по описанию, а не «так сложилось».

3. Чем контейнер отличается от виртуальной машины

Контейнеры часто путают с виртуальными машинами, а разница принципиальная.

Виртуальная машинаКонтейнер
Что изолируетсявся машина вместе со своей ядром системытолько пространство процесса: файлы, сеть, идентификаторы
Ядро операционной системысвоё, гостевоеобщее с машиной-хостом
Запускдесятки секунд — минутыдесятки — сотни миллисекунд
Размер носителягигабайтымегабайты
Сколько живёт на сервере3–10десятки

Контейнер — не маленькая операционная система, а изолированный процесс на том же ядре, что и остальные. Изоляция строится на двух механизмах ядра: пространство имён (процесс видит только свою файловую ветку, свою сеть, свой список процессов) и контрольные группы (процессу ограничивают память и процессорное время). Отсюда и цена: контейнер нельзя запустить на ядре принципиально другой системы. Отсюда же — его скорость и лёгкость.

Практический вывод для PHP-проекта: два контейнера с разными ядрами не получится, зато десять контейнеров с одной базовой системой, разными версиями языка и разными наборами библиотек — обычное дело.


Часть II. Образ против контейнера: чертёж и изделие

4. Определения, которые путают все новички

Образ (image) — собранный по рецепту неизменяемый набор слоёв: базовая система, библиотеки, файлы приложения, настройки запуска. Он похож на чертёж или установочный архив: сам ничего не делает, но по нему в любой момент получают работающий процесс. Образ имеет имя и метку (tag) — например shop-php:1.4 — и весит от десятков мегабайт до сотен.

Контейнер (container) — запущенный образ: процесс или несколько процессов, у которых появилась своя временная файловая ветка, свой адрес, свои переменные. Контейнер живой: его можно остановить, удалить, посмотреть его логи, залезть внутрь. Удалите контейнер — от него ничего не останется, кроме данных, явно сохранённых снаружи (об этом — в Части IV).

Простейшая аналогия: образ — это класс в коде, контейнер — объект. Класс один, объектов сколько угодно, и каждый живёт своей жизнью.

5. Слои: почему пересборка занимает секунды

Образ собирается из Dockerfile, где каждая строчка рецепта превращается в слой — кусок файловой системы, наложенный на предыдущие. Каждый слой имеет контрольную сумму, и сборщик их кэширует: если строчки не менялись и входные файлы те же, слой берётся готовым, а не пересобирается.

Отсюда правило, которое экономит часы ожидания: сначала кладите то, что меняется редко, и только потом то, что меняется часто. Если первым шагом копировать весь исходник, а вторым ставить зависимости, любая правка одной строки обнулит кэш всех последующих слоёв — и полная установка библиотек будет повторяться на каждой сборке.

Слой 1  базовая система         ~80 МБ   почти никогда не пересобирается
Слой 2  системные библиотеки    ~120 МБ  меняются раз в месяц
Слой 3  зависимости проекта      ~40 МБ  меняются при обновлении пакета
Слой 4  исходники приложения      ~5 МБ  меняются в каждом коммите

При запуске используется общая база: десять контейнеров из одного образа занимают не десять раз по двести мегабайт — они разделяют нижние слои, а каждый пишет только в свой тонкий рабочий слой. Это и объясняет, почему на сервере живут десятки контейнеров вместо трёх виртуальных машин.

6. Реестр образов: как чертёж попадает на сервер

Собранный образ нужно куда-то класть, чтобы его могли забрать тест и продакшен. Для этого есть реестр (registry) — хранилище образов по имени и метке, вроде registry.example.com/shop/catalog:1.4.2. Работает как репозиторий для бинарников: сборка выкладывает образ (push), сервер его скачивает (pull).

Три практических правила:

  • Метка сборки уникальна. Вечно переиспользовать метку latest — распространённая ошибка: непонятно, что сейчас запущено и куда откатываться. Хорошая метка — версия плюс хэш коммита: 1.4.2-a81c9f.
  • Продакшен скачивает образ из реестра, а не собирается сам. Собирает конвейер, разворачивает продакшен. Иначе на живой машине появляются компиляторы, ключи доступа и случайные изменения.
  • Один и тот же образ, а не один и тот же рецепт. Настоящая гарантия «как на тесте, так и в проде» — идентичные байты образа, а не идентичный Dockerfile. Прогон тестов и раскатка указывают на одну метку; именно эту цепочку мы описывали в CI/CD-части нагрузочного тестирования.

Часть III. Dockerfile: рецепт сборки и приём multi-stage

7. Читаем рецепт построчно

Пример Dockerfile:

FROM php:8.3-fpm

RUN apt-get update && apt-get install -y \
        libzip-dev \
        libpng-dev \
    && docker-php-ext-install pdo_mysql zip gd opcache

WORKDIR /var/www/html

COPY . /var/www/html

RUN chown -R www-data:www-data /var/www/html

EXPOSE 9000

CMD ["php-fpm", "-F"]

Что происходит:

  • FROM — базовый образ, от которого отталкиваемся: официальный образ PHP-FPM уже содержит интерпретатор и мастер-процесс.
  • RUN — команда, выполняемая во время сборки; её результат закрепляется в новом слое. Здесь ставят системные библиотеки и включают расширения.
  • WORKDIR — рабочая папка внутри образа, от неё отсчитываются последующие пути.
  • COPY — положить файлы проекта в образ.
  • EXPOSE — подсказка о порте, который процесс будет слушать; сама по себе строка порт не открывает.
  • CMD — что запускать при старте контейнера.

Обратите внимание на && внутри одного RUN: установки склеены в одну команду, чтобы получился один слой вместо трёх. Итоговый образ меньше, а кэш сборки предсказуемее.

8. Multi-stage: несколько стадий сборки в одном файле

Multi-stage — приём, когда в одном Dockerfile несколько секций FROM, то есть несколько независимых стадий, а финальная забирает у предыдущих только нужное. По-русски: многостадийная сборка, несколько стадий сборки в одном файле.

Зачем. Для сборки PHP-проекта нужен менеджер зависимостей, часто компиляторы, иногда Node для фронтенда. Во время работы приложения всё это не нужно: оно только раздувает образ и добавляет лишние уязвимые места. Multi-stage позволяет собрать в одной стадии, а жить в другой — чистой и лёгкой.

Пример Dockerfile с двумя стадиями:

# ---------- Стадия 1: сборка ----------
FROM composer:2 AS builder
WORKDIR /build
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-autoloader --prefer-dist
COPY . .
RUN composer dump-autoload --optimize

# ---------- Стадия 2: то, что поедет на сервер ----------
FROM php:8.3-fpm AS runtime

RUN apt-get update && apt-get install -y --no-install-recommends libzip-dev \
    && docker-php-ext-install pdo_mysql zip opcache \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /var/www/html

# забираем из стадии builder только готовый код с зависимостями
COPY --from=builder /build /var/www/html

RUN chown -R www-data:www-data /var/www/html

EXPOSE 9000
CMD ["php-fpm", "-F"]

Ключевые места: AS builder даёт стадии имя, а COPY --from=builder копирует из неё в финальный образ. Итог — образ без менеджера зависимостей, без компиляторов, с чистой установкой расширений. Размер уменьшается в разы, а список того, что внутри, становится понятным.

Для фронтенда схема та же: первая стадия на образе Node собирает статику, вторая — лёгкий веб-сервер, куда копируются только готовые файлы. Node в финальном образе не остаётся.

9. Что класть в образ нельзя

  • Секреты. Пароли к базе, ключи, приватные ключи — не через COPY и не аргументами сборки: они остаются в слоях навсегда и видны любому, кто скачает образ. Источник — переменные окружения из внешнего хранилища либо файл, смонтированный только на время работы.
  • Тестовые данные и дампы. Локальная копия базы на восемь гигабайт делает невозможными и раскатку, и проверку под нагрузкой.
  • Инструменты сборки и отладки в финальной стадии. Как раз для них и нужен multi-stage.

Часть IV. docker-compose: весь проект одним файлом

10. Что такое compose и когда он уместен

docker-compose — инструмент, который описывает группу контейнеров в одном YAML-файле и запускает их вместе одной командой. Вместо десяти ручных шагов и пятидесяти флагов — декларация: вот приложение, вот веб-сервер, вот база, вот Redis, вот общий диск и сеть между ними.

Это ровно тот случай, который обещала шестая часть статьи про монолит: начать с контейнеров, не разводя микросервисы. Один монолитный WordPress или одно PHP-приложение в compose — обычное дело, и это уже огромный шаг вперёд против настройки сервера руками.

Пример docker-compose.yml (приложение, веб-сервер, база и кэш):

YAML
services:
  web:
    image: nginx:1.27-alpine
    ports:
      - "8080:80"
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - app

  app:
    build: .
    volumes:
      - ./:/var/www/html
    environment:
      DB_HOST: db
      DB_NAME: shop
      REDIS_HOST: cache
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started

  db:
    image: mysql:8.4
    environment:
      MYSQL_DATABASE: shop
      MYSQL_USER: shop
      MYSQL_PASSWORD: devpass
      MYSQL_ROOT_PASSWORD: devroot
    volumes:
      - dbdata:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5

  cache:
    image: redis:7-alpine
    command: ["redis-server", "--maxmemory", "256mb", "--maxmemory-policy", "allkeys-lru"]

volumes:
  dbdata:

Что здесь важно понять:

  • build: . — приложение собирается из Dockerfile в текущей папке; база и кэш берутся готовыми из реестра.
  • ports: "8080:80" — снаружи порт 8080, внутри контейнера 80. У базы и кэша портов наружу нет: они видны только внутри проекта, а не всей сети.
  • depends_on с условием service_healthy — не просто «запустить раньше», а дождаться, пока проверка живости базы ответит успешно. Иначе приложение стартует в момент, когда база ещё инициализирует таблицы. Тот же механизм подробного разобран в части про проверки живости.
  • dbdata — именованный том: данные базы живут вне контейнера, удаление контейнера их не трогает.
  • allkeys-lru в команде Redis — политика вытеснения из части про алгоритмы очистки.

11. Имя сервиса — это адрес: сеть внутри проекта

Compose создаёт внутреннюю сеть, и каждый сервис доступен в ней по своему имени как по домену: приложение пишет DB_HOST: db и REDIS_HOST: cache, никаких адресов. Один и тот же конфиг работает и на ноутбуке, и на сервере, потому что внутри проекта имена стабильны.

Отсюда один из самых частых «непонятных» багов при переезде в контейнер: в конфиге остался хост localhost. Внутри контейнера localhost — это сам контейнер, а не база на машине-хосте.

12. Команды ежедневного обихода

bash-пример:

# один раз: собрать образ приложения по Dockerfile
docker build -t shop-php:1.4.2-a81c9f .

# поднять весь проект в фоне, пересобрав образ при необходимости
docker compose up -d --build

# что запущено и в каком состоянии
docker compose ps

# логи приложения, следуя за новыми строками
docker compose logs -f app

# залезть внутрь работающего контейнера
docker compose exec app sh

# выполнить команду в базе: импорт дампа
docker compose exec -T db mysql -ushop -pdevpass shop < dump.sql

# остановить проект, сохранив данные
docker compose down

# остановить проект ВМЕСТЕ с томами данных — быть внимательным
docker compose down -v

Полезно помнить: down -v удаляет тома, то есть стирает базу. В скриптах сборки это штатный шаг «чистое окружение», на живой машине — потеря данных.


Часть V. Что делает оркестратор в проде

13. Пробел, который оркестратор закрывает

Compose хорош, когда всё живёт на одной машине. Но когда у вас пять серверов и сорок контейнеров, появляются вопросы, на которые YAML-файл не отвечает:

  • на какой машине что запускать;
  • что делать, если контейнер умер, а машин, где его можно поднять, три;
  • как выкатить новую версию, не останавливая обслуживание;
  • как добавить мощности, когда нагрузка выросла втрое;
  • как сервисам находить друг друга, если адреса меняются от запуска к запуску.

Оркестратор — система, которая получает описание желаемого состояния («хочу три копии сервиса каталог версии 1.4.2») и сама добивается его: выбирает узлы, запускает контейнеры, следит за здоровьем, заменяет мёртвые, перекладывает при нехватке места. Kubernetes — самый распространённый такой механизм. Он не выполняет ваш код, он управляет запуском.

Три вещи, которые он делает вместо вас и ради которых его обычно и заводят: само-восстановление, раскатка без простоя, масштабирование.

14. Само-восстановление: упавший сервис поднимается сам

Оркестратор постоянно опрашивает контейнеры. Если процесс завершился с ошибкой, завис или перестал отвечать на проверке — система запускает новый вместо него, при необходимости на другом узле. Человек не участвует.

Это прямое продолжение части про проверки живости: там живой узел определялся по эндпоинту готовности, и мёртвый исключался из пула. В оркестрированной системе тот же эндпоинт работает дважды: балансировка не шлёт запросы на нездоровую копию, а оркестратор эту копию заменяет новой. Отсюда требование к вашему коду — честная точка готовности: эндпоинт отвечает «ок», только когда сервис реально может работать (подключение к базе есть, миграции применены, кэш доступен).

15. Раскатка без простоя

Обновление без остановки называется раскаткой (rolling update): оркестратор запускает копии новой версии, проверяет их готовность, переводит трафик и только после этого выключает старые копии. При трёх репликах и правиле «по одной копии» в любой момент обслуживают минимум две.

Здесь и нужна уникальная метка образа из Части II. Если раскатывать latest, система не увидит разницы между «что было» и «что надо» и ничего не заменит; откат превратится в гадание. Уникальная метка делает раскатку обычным делом, а откат — возвратом к предыдущей метке. Та же механика даёт канареечный выпуск: несколько минут часть трафика идёт на новую версию. После этого раскатка встраивается в конвейер сборки и в проверки под нагрузкой, о которых речь шла в статье №10.

16. Реплики под нагрузкой и автоматическое масштабирование

Реплика — это просто ещё одна копия того же сервиса. Три реплики — три одинаковых контейнера, обслуживающих один и тот же набор запросов. Условие: состояние вынесено вовне, в базу и кэш (см. часть про липкие сессии), иначе копии перестают быть взаимозаменяемыми.

Оркестратор умеет менять число реплик сам: следит за потреблением процессора и памяти (или за длиной очереди) и добавляет копии, когда текущим тяжело, убирает, когда нагрузка спала. Это автоматическое масштабирование — «автоматический регулятор числа копий по горизонтали».

Связка всей серии получается такой: нагрузочное тестирование измеряет, сколько выдерживает одна копия; автоматическое масштабирование превращает это число в правило; кеширование и оптимизация запросов снижают саму потребность в копиях; CDN снимает часть запросов до того, как они дойдут до оркестратора. И наоборот: если масштабировать только приложение, а база остаётся одна, вы упрётесь в неё — об этом прямо говорилось в статье про оптимизацию баз данных.


Часть VI. Kubernetes в минимуме: реплики и проверка живости

17. Развёртывание: желаемое состояние одним файлом

Основная сущность в Kubernetes — развёртывание (deployment): сколько копий нужно, каким образом, какой образ и как проверять здоровье. Минимальный пример.

Пример Kubernetes Deployment (YAML):

YAML
apiVersion: apps/v1
kind: Deployment
metadata:
  name: catalog
spec:
  replicas: 3                     # держим три копии сервиса
  selector:
    matchLabels:
      app: catalog
  strategy:
    type: RollingUpdate           # раскатка без простоя
    rollingUpdate:
      maxUnavailable: 0           # ни одной рабочей копии не убираем заранее
      maxSurge: 1                 # сверху добавляем максимум одну
  template:                       # этот шаблон и есть «что запускать»
    metadata:
      labels:
        app: catalog
    spec:
      containers:
        - name: catalog
          image: registry.example.com/shop/catalog:1.4.2-a81c9f
          ports:
            - containerPort: 9000
          readinessProbe:         # готов ли принимать трафик
            httpGet:
              path: /healthz
              port: 9000
            initialDelaySeconds: 5
            periodSeconds: 5
          livenessProbe:          # жив ли процесс: иначе перезапустить
            httpGet:
              path: /healthz
              port: 9000
            initialDelaySeconds: 20
            periodSeconds: 10
            failureThreshold: 3
          resources:
            requests:
              cpu: 100m           # заявленная потребность
              memory: 128Mi
            limits:
              memory: 256Mi       # жёсткий потолок по памяти

Слово replicas переводится как копии: параметр задаёт, сколько одинаковых контейнеров сервиса должно жить одновременно. Именно это число оркестратор меняет под нагрузкой.

18. Проверка живости и проверка готовности: две пробы, два разных решения

Эти два поля путают чаще всего, а различие принципиальное.

Liveness probe (буквально «проба живости», по-русски — проверка живости) отвечает на вопрос: «процесс ещё вообще имеет смысл?» Если ответы не приходят нужное число раз подряд, оркестратор убивает контейнер и запускает новый. Это лечит зависание: процесс жив, но работать не может — например, умер пул соединений и все потоки сидят в ожидании.

Readiness probe (проверка готовности) отвечает на другой вопрос: «можно ли ему сейчас слать запросы?» Неготовую копию не убивают — её исключают из раздачи трафика и возвращают, когда она снова ответит успешно. Это нужно при долгом старте, плановом обслуживании, отключении зависимой очереди.

Вывод для PHP-приложения: обе проверки могут смотреть на один и тот же эндпоинт, но пороги разные. Проверка живости должна быть с запасом и без обращения к внешним системам — иначе временная проблема базы перезапустит все копии подряд и получится каскадный отказ. Проверка готовности как раз обязана зависимости видеть. Про то, как писать такие эндпоинты и снимать с них метрики, — статья про метрики и мониторинг и статья про логирование и трассировку.

19. Команды ежедневного обихода

bash-пример:

Bash
# применить описание
kubectl apply -f deployment-catalog.yaml

# посмотреть, сколько копий реально живёт
kubectl get pods -l app=catalog

# раскатка новой версии образа
kubectl set image deployment/catalog \
  catalog=registry.example.com/shop/catalog:1.4.3-b21f07

# следить, как идёт раскатка
kubectl rollout status deployment/catalog

# откат на предыдущую версию
kubectl rollout undo deployment/catalog

# вручную задать число копий, пока автоматическое масштабирование выключено
kubectl scale deployment/catalog --replicas=5

# заглянуть в логи конкретной копии
kubectl logs -l app=catalog --tail=200

Дальше к этому добавляются сервис (внутренняя балансировка между копиями — то, что алгоритмы из части про балансировку делали руками Nginx и HAProxy), точка входа снаружи, конфигурационные карты и секреты. Но минимальный рабочий круг — это ровно команды выше.


Часть VII. Честно про цену: когда оркестратор избыточен

20. Что стоит дорого на самом деле

Оркестратор — не бесплатная функция, а отдельная система со своей сложностью. Цена складывается из пяти позиций:

  1. Минимум узлов. Нормальный Kubernetes — три и больше узла: два рабочих и один управляющий. Один VPS — не конфигурация для оркестратора.
  2. Компетенции. Нужен человек, который понимает сети, хранение, обновления самой платформы и не боется читать логи планировщика. Без него кластер — источник сюрпризов, а не надёжности.
  3. Сетевой слой. Внутри всё решается именами сервисов; снаружи нужны вход, сертификаты, правила распределения и разбор того, почему запрос не доходит.
  4. Наблюдаемость. Без метрик, логов и трассировки кластер с сорока копиями — чёрный ящик. Обязательные соседи — Метрики и мониторинг и Логирование и трассировка.
  5. Хранение и базы. Состояние остаётся там же, где было. Kubernetes не делает MySQL распределённым; он только перезапускает контейнер с ним.

21. Таблица выбора: что взять под свою ситуацию

СитуацияКонтейнерыcomposeОркестратор
Один сайт WordPress на одном VPSполезенне нуженне нужен
Монолит + база + Redis + очередь на одном сервередаоптимальноне нужен
Два сервера, хочется переживать падение одногодаплюс внешняя балансировкауже можно посмотреть
Пять–десять сервисов, команда с инженером по эксплуатациидатолько для локальной разработкиоправдан
Нужны раскатки без простоя и автоматические копии по метрике нагрузкидане умеетоправдан
Регулярные пики трафика в разы выше среднего уровнядане справитсяоправдан при готовой базе и кэше

Вывод, который стоит повторить: контейнеры почти всегда, оркестратор — только по размеру задачи. Для одного VPS с WordPress полноценный Kubernetes — избыточное усложнение: вы получите тридцать строк YAML вместо одной настройки веб-сервера, а выигрыша не увидите, потому что масштабировать нечего: узел один.

22. Что взять вместо оркестратора, если серверов немного

Порядок шагов для малого проекта:

  1. Один Dockerfile с multi-stage для приложения и один compose-файл с приложением, веб-сервером, базой, Redis и очередью.
  2. Именованные тома для данных базы и вынос состояния из процесса — тогда копии действительно взаимозаменяемы.
  3. Сборка образа вне сервера, раскатка по метке; откат — смена метки обратно.
  4. Внешний балансировщик или два узла с плавающим адресом — см. часть про единую точку отказа.
  5. Проверка живости по эндпоинту и автоматический перезапуск на уровне веб-сервера или системного менеджера службы — вместо оркестратора.
  6. Тест под нагрузкой до выбора архитектуры, а не после: статья №10 описывает, как измерить ёмкость одной копии.

Когда эти пункты перестают решать задачу — сервисов много, узла несколько, простой дорог, — появляется Kubernetes. Компромисс посередине — управляемый сервис запуска контейнеров: облако само держит заданное число копий, а вы эту платформу не администрируете.

23. Типичные ошибки при заходе в контейнеры

  • Данные внутри контейнера. Слой контейнера удаляется вместе с ним; база, лежащая не на томе, теряется при каждом остановочном шаге.
  • Состояние в памяти процесса. Сессии держатся в копии — и копию нельзя заменить; решение — наружу, в Redis (часть про Redis).
  • Образ-комбайн. Один Dockerfile на разработку и прод: в проде живёт компилятор и тестовые дампы.
  • latest как метод выпуска. Невозможно понять, что запущено, невозможно откатить, невозможно воспроизвести проблему.
  • Нет ограничения по памяти. Контейнер съедает всю машину и утаскивает соседей; заявленная потребность и жёсткий потолок обязательны.
  • Проверка живости обращается к базе. Временный сбой базы перезапускает все копии, а после восстановления они стартуют одновременно — холодный старт всего сервиса разом.
  • Секреты в слоях. Самое дорогое, повторим отдельно.

Инструмент под задачу

Собрать аккуратный Dockerfile/docker-compose под тот же путь «от монолита к оркестрации» (multi-stage, healthcheck, правильный порядок слоёв COPY ради кэша) удобнее по живому генератору. Попробуйте инструмент прямо здесь, на своих данных:

🐳 ИИ-генератор Dockerfile
Гость
Токены: 0 / 50 000
Гостевой лимит сбрасывается раз в месяц. Зарегистрируйтесь — лимит выше.
Python / Flask Node.js / Express PHP / Laravel Go / Gin Ruby / Rails
0 / 15 000

Результат
Заполните описание и нажмите «Сгенерировать Dockerfile». Пояснение, сам Dockerfile, docker-compose и .dockerignore появятся здесь.
×

Лимит токенов исчерпан

Финал серии. Путь от монолита до оркестрации

24. Одна мысль, объединяющая одиннадцать статей

Если прочитать серию одной фразой: сложность архитектуры должна соответствовать размеру задачи, а всякое усложнение окупается только тогда, когда появилась конкретная боль. Мы начали с монолита и с честного ответа «не надо делить рано» — и закончили оркестратором с таким же честным ответом «не надо его заводить, когда узел один».

25. Что за чем идёт, если складывать в порядок внедрения

ШагСтатьяЧто приобретаете
1. Определиться с формой приложенияМонолит против микросервисовответ: остаётесь вместе или делите по домену
2. Оформить договор между частямиПроектирование APIпредсказуемые контракты и версии
3. Вынести тяжёлое и долгоеОчереди сообщенийфоновые задачи не блокируют ответ
4. Перестать считать одно и то жеКешированиеудар по задержке без переписывания кода
5. Убрать единую точку отказаБалансировка нагрузкирост по горизонтали и живучесть
6. Перестать гадатьМетрики и мониторингчисла о системе и пороги оповещений
7. Научиться видеть прошлоеЛогирование и трассировкапуть запроса через все сервисы
8. Выжать из базы максимумОптимизация баз данныхиндексы, реплики, пул соединений
9. Приблизить ответ к пользователюCDN и edge-вычислениякэш у края сети и защита от пиков
10. Узнать реальную ёмкостьНагрузочное тестированиецифры вместо ощущений
11. Сделать запуск воспроизводимымэта статьяодин образ, один шаг раскатки, дежурный над процессами

Порядок не случаен: без метрик (шаг 6) не понять эффект кеширования (шаг 4), без измеренной ёмкости (шаг 10) честно не настроишь число копий (шаг 11), а возможности шага 11 делают практичным шаг 1 — быструю раскатку и откат того самого решения о делении.

26. Практический чек-лист серии

  • Приложение описано Dockerfile с multi-stage; в финальном образе нет инструментов сборки и секретов.
  • Один compose-файл поднимает проект на машине разработчика с нуля, без ручной настройки.
  • Состояние процесса вынесено в базу и кэш; копии взаимозаменяемы.
  • Данные сервиса лежат на внешних томах, а не в слое контейнера.
  • Есть эндпоинты проверки готовности и проверки живости, и пороги у них разные.
  • Метка образа уникальна для сборки; откат — это выбор предыдущей метки, а не поиск в истории.
  • Тот же образ, что прошёл тесты, раскатывается в прод.
  • Известна ёмкость одной копии по результатам теста под нагрузкой.
  • Решение об оркестраторе принято осознанно: сервисов много, узлов несколько, простой имеет цену.
  • Наблюдаемость настроена до того, как сервисов стало двадцать.

Материал написан простым языком. Примеры рассчитаны на связку Docker, docker-compose, Kubernetes и PHP/WordPress на VPS. Синтаксис инструментов меняется: конкретные поля и флаги стоит сверять с официальной документацией используемых версий. Термины расшифрованы при первом упоминании: image — образ, container — запущенный контейнер, orchestrator — оркестратор, replica — копия сервиса, liveness probe — проверка живости, readiness probe — проверка готовности, multi-stage — многостадийная сборка.

👤 Зарегистрируйтесь, чтобы добавить материал в закладки.