JavaRush /Курсы /Docker for Spring /Активные профили и комбинации режимов

Активные профили и комбинации режимов

Docker for Spring
12 уровень , 1 лекция
Открыта

1. Профиль в Spring Boot: переключатель режима

Когда разработчик впервые слышит “профили”, он часто подсознательно думает так: «О, значит у меня будет несколько приложений: одно для standalone, одно для Postgres…». Это нормальная реакция мозга, который хочет упростить жизнь, разделив всё по папкам и файлам. Но в Spring Boot профиль — это не отдельное приложение, а скорее “набор очков”, через которые оно смотрит на мир. Код остаётся тем же, образ Docker остаётся тем же, меняется только набор активных настроек и, как следствие, включаются/выключаются некоторые бины и конфигурации.

В нашем курсовом проекте Container-Ready Catalog Service профили — это способ аккуратно описать разные “режимы окружения” без копирования Dockerfile и без пересборки образа. Вы можете представить это так: у вас один актёр (один jar, один image), но разные костюмы — standalone, postgres, cache, messaging. И нет, актёр не становится другим человеком от того, что надел шляпу. Хотя иногда кажется, что становится (особенно когда включили messaging, а оно не завелось).

Чтобы увидеть, как профиль влияет на загрузку конфигурации и контекст, полезно держать в голове простую схему:

flowchart TD
  %% Профили добавляют отличия в конфигурации и бинах, но не превращают сервис в «другое приложение».
  A["Старт приложения"] --> B["Определяем активные профили
(env / -D / --args)"] B --> C["Загружаем application.yml (base)"] C --> D["Подмешиваем application-{profile}.yml
для каждого активного профиля"] D --> E["Если один ключ задан несколько раз
работает правило last-wins"] E --> F["Строим ApplicationContext:
часть бинов может быть @Profile(...)"]

Здесь важна именно логика: профиль включает добавочные отличия, а не создаёт параллельную вселенную. Если вы поймаете себя на мысли «сделаю отдельный образ на postgres и отдельный образ на cache», значит вы снова незаметно скатываетесь в “прошивку настроек в артефакт” — ровно туда, откуда Docker пытается вас вытащить.

2. spring.profiles.default и spring.profiles.active: два руля

Очень хочется думать, что в Spring Boot есть просто “активный профиль”, и всё. Но на практике полезно различать два руля, которые похожи названием, но решают разные задачи. spring.profiles.active — это “включи вот это прямо сейчас, я знаю, что делаю”. А spring.profiles.default — это “если ничего не сказали, веди себя вот так”. В контейнерном мире это особенно важно, потому что контейнеры часто стартуют в разных местах: у вас локально, у коллеги, в CI, завтра в Compose, послезавтра ещё где-нибудь.

Самая здоровая стратегия для учебного проекта (и для многих рабочих сервисов) — иметь понятный default, который даёт быстрый запуск без инфраструктуры. В нашем случае таким “мягким” режимом является standalone: приложение стартует без PostgreSQL/Redis/RabbitMQ и использует in-memory хранение. Это не потому что мы не любим базы данных. Это потому что мы любим, когда сервис вообще стартует, а затем уже усложняется контролируемо.

Тут нам нужен не весь application.yml, а только кусок, где живёт fallback-профиль:

# src/main/resources/application.yml
spring:
  profiles:
    # Фолбэк-профиль: включится только если активные профили не заданы явно
    default: standalone

Остальные общие ключи в этом же файле сейчас не важны; здесь нас интересует именно роль fallback-настройки.

Обратите внимание на тонкую, но важную идею: default срабатывает только если активные профили не заданы явно. То есть default — не “жёсткое правило”, а “подушка безопасности”.

Теперь про active. Его можно задать по-разному, но смысл один: “включи эти профили именно для этого запуска”. Например:

--spring.profiles.active=postgres

Важное правило для жизни (и для сохранения нервных клеток): spring.profiles.active и spring.profiles.default не кладут в application-{profile}.yml. Почему? Потому что профильный файл читается только когда профиль уже активен. Это как пытаться написать на дверце холодильника «чтобы открыть холодильник, сначала открой холодильник». Логика круговая, холодильник смеётся, вы плачете.

Если нужно, чтобы на машине в конкретном окружении всегда включался postgres, это решается извне: env var, аргументом запуска, system property, внешним конфигом. Внутри application-postgres.yml вы описываете отличия профиля, а не то, как этот профиль включается.

3. Способы задания активных профилей

В Spring Boot есть несколько способов активировать профили, и это похоже на выбор инструмента в гараже: молоток, отвёртка и шуруповёрт делают похожие вещи, но удобны в разных местах. В контейнерах чаще всего побеждает вариант с переменными окружения, потому что Docker и Compose “из коробки” умеют их прокидывать. Но иногда удобнее передать --spring.profiles.active=... прямо в командной строке — например, для разового запуска или для демонстрации.

Самое простое: активировать профиль через env var. Благодаря relaxed binding свойство spring.profiles.active превращается в SPRING_PROFILES_ACTIVE. Например, для запуска контейнера с режимом Postgres (пример именно про профили, без подробностей про БД):

# Запуск контейнера с активным профилем postgres
docker run --rm \
  -e SPRING_PROFILES_ACTIVE=postgres \
  -p 8080:8080 \
  docker-java-catalog-service

Если нужно включить несколько профилей, вы перечисляете их через запятую:

# Запуск контейнера с несколькими профилями: базовый режим + надстройка
docker run --rm \
  -e SPRING_PROFILES_ACTIVE=postgres,cache \
  -p 8080:8080 \
  docker-java-catalog-service

Тот же смысл, но через аргумент приложения (это удобно, когда вы запускаете java прямо или хотите явно видеть параметры старта):

# Аргументы после jar — это параметры Spring Boot приложения (формат --key=value)
java -jar build/libs/docker-java-catalog-service.jar \
  --spring.profiles.active=postgres,cache

И есть старый добрый JVM-способ через -D (system property). Он часто встречается в “старых” скриптах и в некоторых командах, где любят всё через -D:

# Параметры вида -D... — это system properties JVM (они читаются Spring как свойства окружения)
java -Dspring.profiles.active=postgres \
  -jar build/libs/docker-java-catalog-service.jar

Важно не запутаться: -D... — это параметр JVM, --... — это аргумент Spring Boot приложения. Оба способа валидны, но если вы новичок, советую внутренне “раскрасить” их разными цветами в голове. JVM-аргументы — про среду JVM, Spring Boot args — про приложение. Иначе в какой-то момент вы добавите -Dserver.port=... и будете удивляться, почему оно не работает, а оно работает, но не там… В общем, типичный квест.

Чтобы свести всё в одну картинку, удобна таблица:

Где задаём Как выглядит Когда удобно
Env var SPRING_PROFILES_ACTIVE=postgres,cache Docker/Compose, runtime-конфиг без лишних символов
App args --spring.profiles.active=postgres,cache Явный запуск, легко читать в командах
JVM system property -Dspring.profiles.active=postgres,cache Когда уже есть JVM-скрипты и вы хотите единый стиль

4. Комбинации профилей: как не устроить “зоопарк” и поддерживать набор

Профили очень легко превратить в бесконечную вселенную вариантов: local, docker, docker-local, docker-prod, prod2, staging2-real-final… И это тот самый момент, где проект начинает “пахнуть” не инженерией, а отчаянием. Наша цель в курсе другая: сделать конфигурацию читаемой и предсказуемой, чтобы запуск выглядел как осмысленная комбинация режимов, а не как угадайка.

Для этого мы придерживаемся простого принципа: один профиль — одна ось отличия. В нашем проекте базовая ось — “как храним данные”: standalone (in-memory) или postgres (внешняя БД). А профили cache и messaging — это дополнительные возможности, которые “прикручиваются” к базовому режиму, а не заменяют его целиком.

Поэтому мы фиксируем поддерживаемые комбинации и не делаем вид, что “всё можно со всем”. В документах проекта и курса канонический набор такой:

Комбинация (spring.profiles.active) Смысл режима (человечески) Инфраструктурные ожидания (на уровне курса)
standalone Быстрый старт без внешних зависимостей Ничего внешнего не нужно
postgres Данные в PostgreSQL Должна быть доступна PostgreSQL
postgres,cache PostgreSQL + кэширование чтения PostgreSQL + Redis (как зависимость окружения)
postgres,cache,messaging Полный учебный режим PostgreSQL + Redis + RabbitMQ

Обратите внимание: мы намеренно не поддерживаем, например, cache без postgres. Это не потому что технически нельзя, а потому что это “портит историю курса”. Нам важно, чтобы cache и messaging были надстройками над взрослым базовым режимом, а не самостоятельными мирами. Иначе вы получите конфигурационную матрицу из десятков файлов и начнёте писать “документацию” больше, чем код.

Ещё один нюанс: иногда новичок пытается назвать профиль docker, потому что “ну мы же в Docker”. Это звучит логично, но методически вредно. Docker — это способ запуска, а профиль — это смысл отличия в поведении приложения. Если вы назовёте профиль docker, то через неделю вы не сможете честно ответить на вопрос: “А что конкретно меняется?” Порт? База? Логи? Экспорт? Всё сразу? В итоге docker превращается в мешок случайностей. Нам так не надо.

5. Правило last-wins и порядок профилей

Когда активен один профиль, всё обычно понятно. Но как только вы включаете несколько профилей (postgres,cache), у Spring Boot появляется ситуация: один и тот же ключ может быть определён в нескольких местах. Тогда вступает в силу простое правило “последний победил” — last-wins. И вот тут начинаются очень человеческие баги: «Я точно задал значение в application-postgres.yml, почему оно не применяется?» — а потому что вы потом случайно задали тот же ключ в application-cache.yml.

Важно уловить, что “last-wins” — это не злой умысел Spring Boot. Это логичная инженерная модель: вы перечислили профили в определённом порядке, значит вы явно разрешаете более позднему профилю перекрыть более ранний. Проблема обычно не в механике, а в том, что мы не заметили дубликат ключа.

Давайте посмотрим на маленьком искусственном примере. Представим, что мы добавили “чисто учебное” свойство app.mode-label, чтобы видеть, какой профиль “говорит последним”:

# application-postgres.yml
app:
  mode-label: "postgres"
# application-cache.yml
app:
  mode-label: "cache"

Если мы стартуем с профилями в таком порядке:

java -jar app.jar --spring.profiles.active=postgres,cache

то итоговое значение app.mode-label будет "cache", потому что профиль cache указан последним. Если поменять порядок:

java -jar app.jar --spring.profiles.active=cache,postgres

то итоговым станет "postgres".

На практике из этого следуют два важных вывода. Во-первых, порядок профилей — часть конфигурации, а не косметика. Во-вторых, хороший дизайн профилей старается избегать ситуации, когда один и тот же ключ определён в трёх местах. Если у вас spring.datasource.url внезапно появился в application.yml, application-postgres.yml и ещё где-то “на всякий случай”, то last-wins превращается в игру “угадай, откуда взялось значение”.

В учебном проекте мы стараемся держать профили короткими и “узкими”: postgres отвечает за datasource и persistence-настройки, cache — только за кэш-связанные параметры, messaging — только за messaging-связанные. Тогда пересечений почти нет, а если они есть — это осознанное перекрытие, а не случайный дубликат.

6. Проверка активных профилей

Профили коварны тем, что ошибку легко не заметить. Приложение может стартовать, отвечать на /actuator/health, и вы будете уверены, что включили postgres, хотя на деле оно в standalone и просто держит данные в памяти. Поэтому полезно уметь явно проверять, какие профили активны.

Самый простой способ — смотреть стартовые логи Spring Boot: обычно он пишет что-то вроде “The following profiles are active: ...”. Но чтобы не зависеть от того, как именно оформлены логи, можно добавить маленький “операционный” кусочек кода в проект. Например, в пакет ops (он у нас выделен под такие вещи) можно добавить ApplicationRunner, который печатает активные профили:

package com.example.catalog.ops;

import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

@Configuration // Конфигурационный класс: Spring подхватит его при сканировании компонентов
public class ActiveProfilesPrinterConfig {

    @Bean // Регистрируем runner как бин, чтобы он выполнился на старте приложения
    ApplicationRunner printActiveProfiles(Environment env) {
        return args -> {
            // env.getActiveProfiles() возвращает массив строк с реально активными профилями
            // (именно активными, не default, если он был перекрыт явно)
            String profiles = String.join(",", env.getActiveProfiles());

            // Учебный «фонарик»: печатаем в stdout, чтобы это было видно в логах контейнера
            System.out.println("Active profiles: " + profiles);
        };
    }
}

Если вы запустите приложение без явного spring.profiles.active, то при spring.profiles.default: standalone вы увидите примерно:

Active profiles: standalone

Если запустить с env var:

SPRING_PROFILES_ACTIVE=postgres java -jar app.jar

то вывод станет:

Active profiles: postgres

Да, это System.out.println, и да, в реальном проекте вы бы логировали нормально (и мы к этому подойдём в нужный момент курса). Но как учебный “фонарик” эта штука хороша: она позволяет проверить профили даже в самой простой среде. А ещё она отлично напоминает о принципе: контейнер живёт в мире stdout/stderr, так что “видимый сигнал” нам реально важен.

Ещё один практический момент: spring.profiles.default не “магически добавляется” к active. Если вы поставили default: standalone, а потом явно передали active=postgres, то default не будет участвовать. Это нормально: явная конфигурация важнее fallback.

7. Типичные ошибки при работе с профилями

В профилях чаще всего ломается не “сложная магия”, а обычная человеческая логика: мы путаем роли, складываем всё в одну кучу и надеемся, что Spring сам догадается. Ниже — несколько ошибок, которые встречаются постоянно, и почти всегда их можно “вылечить” одним и тем же лекарством: вернуть ясность в модель профилей.

Ошибка №1: профиль называется docker, dev, mode2 и ничего не объясняет.
Когда профиль назван по способу запуска (docker) или по эмоциональному состоянию автора (dev), вы теряете смысл. Профиль должен отвечать на вопрос: “что именно меняется?” Для нашего проекта это standalone (встроенное хранение) и postgres (внешняя БД), плюс cache и messaging как добавочные инфраструктурные расширения. С такими именами вам не нужно гадать — их смысл уже в названии.

Ошибка №2: попытка сделать профиль “всем обо всём” и складывать туда случайные настройки.
Очень быстро появляется файл application-docker.yml, в котором и порт, и datasource, и логирование, и экспорт, и ещё десять пунктов “чтобы работало”. В итоге вы не можете комбинировать режимы и не понимаете, какая настройка к чему относится. Лечится это тем, что один профиль отражает одну ось отличия, а общие настройки живут в application.yml.

Ошибка №3: запись spring.profiles.active в application-postgres.yml.
Это классика. Профильный файл читается только если профиль уже активен, поэтому задавать активный профиль внутри профильного файла бессмысленно. Активный профиль задаётся извне: env var, -D... или --.... Внутри application-postgres.yml живут отличия режима postgres, а не команда “включи postgres”.

Ошибка №4: разрешили любые комбинации профилей и получили profile explosion.
Если вы не фиксируете поддерживаемые комбинации, команда начинает включать профили “как получится”, а потом удивляться странным конфликтам. В курсе мы держим маленький набор: standalone, postgres, postgres,cache, postgres,cache,messaging. Это дисциплина, которая резко снижает хаос и делает запуск предсказуемым.

Ошибка №5: дублирование одних и тех же ключей в нескольких профилях без явной причины.
Из-за правила last-wins один и тот же ключ, раскиданный по нескольким application-*.yml, превращает запуск в лотерею. Если вы видите, что, например, один и тот же app.export-dir встречается в трёх файлах, это почти всегда сигнал: конфигурация “разрезана” неправильно. Лучше выбрать один “дом” для ключа и переопределять его только там, где это действительно нужно.

1
Задача
Docker for Spring, 12 уровень, 1 лекция
Недоступна
Профиль по умолчанию и явный активный профиль
Профиль по умолчанию и явный активный профиль
1
Задача
Docker for Spring, 12 уровень, 1 лекция
Недоступна
Правило last-wins для двух активных профилей
Правило last-wins для двух активных профилей
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ