Введение в Docker и Java
Современная разработка Java-приложений немыслима без контейнеризации. Docker — это платформа для упаковки, доставки и запуска приложений в изолированных средах (контейнерах). Для Java-разработчиков Docker решает вечную проблему: «На моей машине всё работает!». Контейнер гарантирует, что ваше приложение запустится одинаково на локальной машине, сервере разработки и в production.
В этой статье мы разберем, как контейнеризировать Java-приложения с помощью Docker: от простого Dockerfile до оптимизации образов для микросервисной архитектуры. Все примеры написаны на Java и готовы к использованию.
Основы Docker для Java-приложений
Что такое Docker-образ и контейнер?
Docker-образ — это шаблон (read-only), содержащий всё необходимое для запуска приложения: JVM, библиотеки, код и настройки. Контейнер — запущенный экземпляр образа. Для Java это означает, что вам не нужно вручную устанавливать JDK на сервер — JVM уже будет внутри контейнера.
Создание простого Dockerfile для Java
Рассмотрим базовый пример. У нас есть простое Java-приложение (например, HelloWorld.java).
// HelloWorld.java
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello from Docker and Java!");
}
}
Создадим Dockerfile:
# Используем официальный образ OpenJDK
FROM openjdk:11-jre-slim
# Устанавливаем рабочую директорию
WORKDIR /app
# Копируем скомпилированный jar или class файлы
COPY HelloWorld.class .
# Команда запуска
CMD ["java", "HelloWorld"]
Сборка и запуск:
# Сборка образа
docker build -t hello-java .
# Запуск контейнера
docker run hello-java
Вывод: Hello from Docker and Java!
Контейнеризация Spring Boot приложений
Для реальных проектов (например, на Spring Boot) процесс усложняется. Рассмотрим типичный микросервис.
Многоступенчатая сборка (Multi-stage build)
Это лучшая практика для Java: сначала собираем приложение с полным JDK, затем копируем результат в легковесный образ JRE.
# Этап 1: Сборка
FROM maven:3.8.5-openjdk-11 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn clean package -DskipTests
# Этап 2: Запуск
FROM openjdk:11-jre-slim
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
Объяснение:
- build — использует Maven и полный JDK для компиляции.
- Финальный образ — содержит только JRE и готовый jar-файл. Размер образа уменьшается в 2-3 раза.
Оптимизация кэширования слоёв
Docker кэширует каждый слой (инструкцию). Чтобы ускорить сборку, размещайте редко изменяемые зависимости в начале:
FROM maven:3.8.5-openjdk-11 AS build
WORKDIR /app
# Кэшируем зависимости
COPY pom.xml .
RUN mvn dependency:go-offline
# Теперь копируем код
COPY src ./src
RUN mvn clean package
Теперь, если вы меняете только код, Docker пересоберет только последние слои, а зависимости возьмет из кэша.
Работа с переменными окружения и профилями
Передача параметров в Java-приложение
Docker позволяет гибко настраивать приложение через переменные окружения. Для Spring Boot это особенно удобно.
docker run -e SPRING_PROFILES_ACTIVE=prod -e DB_URL=jdbc:postgresql://db:5432/mydb my-app
В Dockerfile можно задать значения по умолчанию:
ENV SPRING_PROFILES_ACTIVE=dev
ENV DB_URL=jdbc:h2:mem:testdb
Пример docker-compose для Java + БД
Для микросервисов удобно использовать docker-compose.yml:
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- DB_URL=jdbc:postgresql://db:5432/mydb
depends_on:
- db
db:
image: postgres:13
environment:
POSTGRES_DB: mydb
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
Запуск одной командой: docker-compose up
Лучшие практики и устранение проблем
1. Используйте .dockerignore
Аналог .gitignore. Исключите ненужные файлы из контекста сборки:
target/
*.log
.idea/
.git/
*.iml
2. Не запускайте контейнер от root
Создайте непривилегированного пользователя внутри образа:
FROM openjdk:11-jre-slim
RUN groupadd -r appuser && useradd -r -g appuser appuser
USER appuser
COPY --from=build /app/target/*.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
3. Мониторинг памяти JVM в контейнере
Java в контейнере может неверно определять доступную память. Используйте флаги:
ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]
Это заставит JVM использовать не более 75% памяти контейнера.
4. Логирование
Пишите логи в stdout/stderr — Docker собирает их автоматически:
// В logback.xml или application.properties
logging.file.path=/dev/null # Отключаем файловое логирование
logging.pattern.console=%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
Заключение
Docker стал стандартом де-факто для развертывания Java-приложений. Мы рассмотрели:
- Создание