Представьте: понедельник утро, standup через 15 минут, а локальное окружение ещё не поднялось. Десять микросервисов, у каждого свой PostgreSQL, Redis, Kafka — и всё это стартовало последовательно, без health checks. Я засекал: 10 минут 23 секунды от docker-compose up до первого ответа от API Gateway. Сегодня то же самое — 28 секунд. Рассказываю, что мы изменили.
Исходная ситуация
Наш docker-compose.yml выглядел примерно так: 15 сервисов без depends_on, без health checks, все тома привязаны к bind mounts всего проекта. Gradle собирал каждый сервис из исходников, скачивая зависимости каждый раз заново. Kafka стартовала раньше Zookeeper, микросервисы пытались подключиться к PostgreSQL до того, как он принимал соединения — и падали с connection refused.
Каждый второй день у кого-то что-то не поднималось. Разработчики тратили по 15-20 минут на «починить локалку» вместо написания кода.
Шаг 1: Health checks и правильный порядок старта
Первая и самая impactful-оптимизация — добавили health checks на все dependency-сервисы и настроили depends_on с condition: service_healthy.
version: "3.9"
services:
postgres-order:
image: postgres:16-alpine
environment:
POSTGRES_DB: orders_db
POSTGRES_USER: orders
POSTGRES_PASSWORD: orders_pass
healthcheck:
test: ["CMD-SHELL", "pg_isready -U orders -d orders_db"]
interval: 2s
timeout: 3s
retries: 10
volumes:
- pg_order_data:/var/lib/postgresql/data
ports:
- "5432:5432"
redis-cache:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 1s
timeout: 2s
retries: 5
order-service:
build:
context: ./services/order-service
dockerfile: Dockerfile.dev
depends_on:
postgres-order:
condition: service_healthy
redis-cache:
condition: service_healthy
kafka:
condition: service_healthy
environment:
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres-order:5432/orders_db
SPRING_REDIS_HOST: redis-cache
volumes:
- ./services/order-service/src:/app/src
volumes:
pg_order_data:
Результат: микросервисы больше не падают при старте. Они ждут, пока все зависимости станут доступны. Но время старта не изменилось — просто теперь оно надёжное.
Шаг 2: Multi-stage Dockerfile для быстрой сборки
Оригинальный Dockerfile копировал весь проект, запускал gradle build и потом java -jar. Каждый restart — полная пересборка. Мы переписали на multi-stage с кэшированием зависимостей:
# Dockerfile.dev
FROM eclipse-temurin:21-jdk-jammy AS builder
WORKDIR /app
# Кэшируем зависимости — этот слой меняется редко
COPY gradle.gradlele.kts build.gradle.kts settings.gradle.kts ./
COPY gradle/ gradle/
RUN gradle dependencies --no-daemon || true
# Исходники меняются часто — этот слой пересобирается
COPY src/ src/
RUN gradle bootJar --no-daemon -x test
FROM eclipse-temurin:21-jre-jammy
WORKDIR /app
COPY --from=builder /app/build/libs/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005", "-jar", "app.jar"]
Ключевой приём: копируем файлы Gradle отдельно от исходников. При изменении кода Docker использует кэш слоя с зависимостями — и пересборка занимает 15-20 секунд вместо 2-3 минут.
Шаг 3: Hot reload через bind mounts
Мы примонтировали ./services/order-service/src:/app/src и включили Spring Boot DevTools. При изменении Kotlin-файла приложение перезагружается за 3-5 секунд — не нужно пересобирать Docker-образ. Для этого в build.gradle.kts добавили:
dependencies {
developmentOnly("org.springframework.boot:spring-boot-devtools")
}
tasks.bootJar {
// DevTools нужен только при запуске через gradle, не в jar
// В dev-режиме запускаем через gradle bootRun в контейнере
}
Для dev-режима мы изменили entrypoint в compose.override.yml на gradle bootRun вместо java -jar. Это позволило DevTools работать корректно с live-reload.
Шаг 4: compose.override.yml для персональных настроек
Файл docker-compose.override.yml gitignore'd — каждый разработчик может подменить порты, переменные окружения или включить профилировщик без влияния на общую конфигурацию. У меня, например, там:
services:
order-service:
environment:
SPRING_PROFILES_ACTIVE: "dev,debug"
JAVA_TOOL_OPTIONS: "-Xmx256m -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"
ports:
- "5005:5005" # Debugger
- "8081:8080" # Избегаем конфликтов портов
Шаг 5: Параллельный старт
Docker Compose по умолчанию стартует сервисы параллельно, но наши микросервисы ждали друг друга из-за Kafka. Мы выделили инфраструктурные сервисы (PostgreSQL, Redis, Zookeeper, Kafka) в отдельный profile и стартовали их первым:
# Стартуем инфраструктуру (один раз)
docker-compose --profile infra up -d
# Ждём, пока всё станет здоровым
docker-compose --profile infra ps
# Поднимаем микросервисы
docker-compose up -d
Итог: до и после
До: 10 мин 23 сек, рандомные падения, 2-3 перезапуска в среднем. После: 28 секунд до готовности всех сервисов, ноль падений при старте, hot reload за 3-5 секунд. Команда сэкономила в среднем 40 минут в день на каждого разработчика. Если умножить на 6 человек и 22 рабочих дня — это почти 90 часов в месяц, которые теперь идут на разработку, а не на ожидание локалки.
Пока нет комментариев
Будьте первым, кто оставит комментарий к этой статье