В этой статье покажу, как поднять в Docker-контейнере приложение, состоящее из трех частей:
- Spring Boot REST API — бекэнд на встроенном Tomcat-сервере
- База данных PostgreSQL
- Фронтэнд на JavaScript (JQuery) на Nginx-сервере
Приложение можно скачать тут.
Оно состоит из одного контроллера и одного html и js-файла, позволяющего выводить и добавлять животных. Животные хранятся в базе.
Установка Docker
Чтобы развернуть приложение в Docker, для начала нужно установить Docker на компьютер. В Window 7 это проблематично, в Windows 10 и других ОС вполне возможно.
Чтобы убедиться, что Docker установлен, выполните из командной строки:
docker --version
Будет выведена версия Docker.
Теперь можно выполнять различные команды, которые билдят образы, запускают контейнеры и удаляют их все.
PostreSQL в контейнере
Одна из причин, почему Java-разработчику полезен Docker, это то, что можно не устанавливать различное дополнительное ПО. Например, базы данных различных версий. Ведь потом их придется удалять, остаются артефакты. Все это нежелательно, да и долго. С Docker проще.Например, чтобы испытать работу приложения с базой данных PostgreSQL 12 версии, можно не устанавливать ее на компьютер, а поднять в Docker-контейнере.
Для этого из командной строки запустим контейнер с базой:
docker run --name some-postgres --volume db-data:/var/lib/postgresql/data -e POSTGRES_PASSWORD=qqq -e POSTGRES_DB=vv -p 5434:5432 postgres:12-alpine
Теперь с базой можно соединиться по такому url:
jdbc:postgresql://localhost:5434/vv
Что и удается:
Разберем строку выше. Она поднимает контейнер из образа PostgreSQL:
docker run ...параметры ....postgres:12-alpine
Параметры команды
postgres:12-alpine — это имя образа. Готовые образы лежат на Docker Hub, там есть разные версии базы. При запуске контейнера образ сначала загружается оттуда, если его нет на компьютере локально.
—name some-postgres — задает имя контейнера. Все контейнеры, которые есть локально, можно просмотреть с помощью команды docker ps —all.
-e POSTGRES_PASSWORD=qqq -e POSTGRES_DB=vv — переменные среды, задаются параметром -e. Эти параметры задают название базы, которая будет создана по умолчанию (vv) и пароль доступа для пользователя postgres (это имя тоже можно переписать с помощью переменной среды POSTGRES_USER, но мы не будем).
db-data — название volume, то есть того места на нашем диске, где реально будет храниться база из контейнера. Если не задавать volume, то при каждом новом запуске контейнера будет создаваться новый volume, и данные в базе для нас окажутся как бы не сохраненные. А с volume они будут сохраняться. Через двоеточие указан путь внутри контейнера, которому соответствует volume.
-p 5434:5432 — указан порт на локальной машине 5434, которому соответствует порт внутри контейнера 5432. То есть выставляем наружу порт контейнера 5432, чтобы присоединяться к базе снаружи через порт нашего локального компьютера 5434 (выбран другой порт (не 5432) чтобы не было конфликтов с локально установленной базой, если она есть).
Таким образом, мы соединились с базой со вкладки Databases редактора Intellij Idea. Так же можно соединяться и из приложения, не устанавливая базу на компьютер, а запуская любую ее версию в контейнере.
Файл docker-compose.yml
Обычно приложение состоит из нескольких компонентов, которые зависят друг от друга, но могут быть запущены изолированно на разных машинах. Например, Spring Boot приложение, предоставляющее REST API, PostgreSQL и фронтэнд на сервере Nginx. Наш пример именно такой. Для такой связки удобно применить docker-compose — это и команда, и файл.
Файл docker-compose.yml представлен ниже:
version: '3.8' services: postgres: build: context: services/postgres dockerfile: Dockerfile.development ports: - "5433:5432" environment: - POSTGRES_USER=postgres - POSTGRES_DB=vv - POSTGRES_PASSWORD=qqq volumes: - "db-data:/var/lib/postgresql/data" app: build: context: services/app dockerfile: Dockerfile.development environment: - PORT=8091 - SPRING_DATASOURCE_URL=jdbc:postgresql://postgres/vv - JS_URL=http://localhost image: 'my-java-application' ports: - 8091:8091 depends_on: - postgres js: build: context: services/js dockerfile: Dockerfile.development image: 'my-js-app' ports: - 80:80 volumes: db-data:
services — ключевое слово, под ним перечислены папки, в которых лежат три части нашего проекта: app, js, postgres.
docker-compose.yml лежит в корне иерархии рядом с папкой services, в которой перечислены сервисы — то есть компоненты, из который состоит наш проект.
Начнем с postgres.
Сервис Postgres: база данных PostgreSQL
Мы уже запускали postgres-контейнер из командной строки. Теперь все параметры вынесены из командной строки в файл docker-compose.yml — переменные среды, volume, порты. Копирую фрагмент этого файла:
postgres: build: context: services/postgres dockerfile: Dockerfile.development ports: - "5433:5432" environment: - POSTGRES_USER=postgres - POSTGRES_DB=vv - POSTGRES_PASSWORD=qqq volumes: - "db-data:/var/lib/postgresql/data"
Кстати, volume с именем db-data создается отдельной строкой в конце файла:
volumes: db-data:
Обратите внимание, что каждого сервиса указан Dockerfile (файл для сборки образа):
dockerfile: Dockerfile.development
для Postgres он лежит в папке services/postgres (см. скриншот выше). Его содержимое предельно кратко:
FROM postgres:12-alpine
Строка выше означает, что мы просто берем готовый образ postgres:12-alpine и ничего к нему не добавляем.
Кстати, порты, указанные в docker-compose.yml:
ports: - "5433:5432"
можно и не выставлять, ведь к базе будем подключаться не мы с localhost, а Spring Boot из соседнего контейнера. А запись выше именно для доступа с локальной машины.
Другие образы будут чуть сложнее.
Сервис App: Spring Boot приложение
Скопирую фрагмент из docker-compose.yml, касающийся приложения Spring Boot:
app: build: context: services/app dockerfile: Dockerfile.development command: java -jar ./app.jar environment: - PORT=8091 - SPRING_DATASOURCE_URL=jdbc:postgresql://postgres/vv - JS_URL=http://localhost image: 'my-java-app' ports: - 8091:8091 depends_on: - postgres
Рассмотрим, из чего он состоит.
Порты
ports: - 8091:8091
Порты указаны так же, как в сервисе postgres, только теперь на указанном порту запущен не сервер базы данных, а http-сервер. Порту 8091 контейнера соответствует порт 8091 нашего компьютера.
Зависимость
depends_on: - postgres
Тут сказано, что наш сервис зависит от сервиса postgres — это означает, что сначала запускается сервис postgres, а потом сервис app.
Имя образа
image: ‘my-java-app’ дает название образу.
Переменные среды
В environment перечислены переменные среды, к которым наше Spring Boot приложение имеет доступ. Мы их прописываем в application.yml приложения таким образом (через двоеточие стоит значение по умолчанию, которое используется в случае, если переменной окружения нет; у меня это просто значения для локального запуска без контейнера):
server: port: ${PORT:8091} spring: datasource: url: ${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost/mydb}
Доступ из одного контейнера в другой
Обратите внимание, что доступ из одного контейнера к другому происходит по имени сервиса. То есть к базе данных мы обращаемся не по localhost, а по postgres:
jdbc:postgresql://postgres/vv
Это значение мы указывали выше в docker-compose.yml в переменной среды SPRING_DATASOURCE_URL.
Dockerfile.development
Сервис app использует такой докер-файл:
FROM bellsoft/liberica-openjdk-alpine-musl:11.0.3 WORKDIR /usr/local/app ADD docker-demo-0.0.1-SNAPSHOT.jar app.jar CMD java -jar ./app.jar
Тут мы собираем образ на основе готового образа openjdk, задаем рабочую папку и копируем в нее наше приложение docker-demo-0.0.1-SNAPSHOT.jar (оно лежит рядом docker-файлом см. структуру папок выше — надо его сюда скопировать) под именем app.jar. А затем запускаем приложение с помощью команды:
java -jar ./app.jar
Наконец, рассмотрим последний компонент.
Сервис JS: JavaScript приложение
Тут все аналогично, только заданы другие порты и другое имя образа:
js: build: context: services/js dockerfile: Dockerfile.development image: 'my-js-app' ports: - 80:80
Dockerfile.development
Докер-файл у сервиса js такой:
FROM nginx:alpine WORKDIR /usr/share/nginx/html COPY dist/ .
То есть мы собираем образ на основе готового образа nginx:alpine. Затем копируем в папку сервера Nginx /usr/share/nginx/html файлы из папки dist (лежит рядом с докер-файлом, содержит html и js-файлы).
Как всё запустить: команда docker-compose
Наконец, из папки с файлом docker-compose.yml надо выполнить команду:
docker-compose up
Команда выше и построит образы, и запустит на их основе контейнеры.
Теперь в браузере по адресу
http://localhost
будет доступно наше приложение.
Чтобы остановить и удалить контейнеры, выполним команду:
docker-compose down
Если нужно удалить и volume с данными, делаем так:
docker-compose down --volume
Если вы сохраняли каких-то животных в приложении, то после этой команды при следующем запуске приложения их не будет.
Спасибо, то что надо!
Очень доступно и без ерунды.
Делал все по статье.
Вот с чем столкнулся (возможно у других будет иначе):
1) Ошибка
ERROR: Version in «./docker-compose.yml» is unsupported. You might be seeing this error because you’re using the wrong Compose file version. Either specify a supported version (e.g «2.2» or «3.3») and place your service….
Решение: для моей Docker version 19.03.12, build 48a66213fe подошел docker-compose версии 3. Исправил, ок.
2) при выполнении команды docker-compose up на 3-ем из 4-х шагов получаю ошибку:
ERROR: Service ‘app’ failed to build: ADD failed: stat /var/lib/docker/tmp/docker-builder897352477/docker-demo-0.0.1-SNAPSHOT.jar: no such file or directory.
Решение пока не нашел.
UPD: Решено. Нужно предварительно сбилдить проект и получить jar файл, который появится в папке target. И далее как указано в статье положить в docker\services\app (см. картинку docker-compose.yml со структурой).