280 lines
19 KiB
Markdown
280 lines
19 KiB
Markdown
|
|
|||
|
# Лабораторная работа 3.
|
|||
|
|
|||
|
### Задание
|
|||
|
**Цель**: изучение современных технологий контейнеризации.
|
|||
|
|
|||
|
**Задачи**:
|
|||
|
|
|||
|
- Установить средство контейнеризации docker.
|
|||
|
- Изучить применение и принципы docker.
|
|||
|
- Изучить утилиту docker-compose и структуру файла docker-compose.yml.
|
|||
|
- Развернуть не менее 3х различных сервисов при помощи docker-compose.
|
|||
|
- Оформить отчёт в формате Markdown и создать Pull Request в git-репозитории.
|
|||
|
|
|||
|
### Как запустить лабораторную работу
|
|||
|
В директории с файлом характеристик docker-compose.yaml выполнить команду:
|
|||
|
```
|
|||
|
docker-compose -f docker-compose.yaml up
|
|||
|
```
|
|||
|
### Описание лабораторной работы
|
|||
|
#### Создание базы данных
|
|||
|
Каждый сервис реализует CRUD-операции, поэтому были выбраны следующие сущности, соответствующие теме диплома: упражнение и тренировка. Эти сущности связаны отношением один ко многим. Созданные таблицы базы данных:
|
|||
|
|
|||
|
```sql
|
|||
|
-- Создание таблицы тренировок
|
|||
|
CREATE TABLE t_training (
|
|||
|
id SERIAL PRIMARY KEY,
|
|||
|
name VARCHAR(255) NOT NULL,
|
|||
|
description VARCHAR(255) NOT NULL
|
|||
|
);
|
|||
|
-- Создание таблицы упражнений
|
|||
|
CREATE TABLE t_exercise (
|
|||
|
id SERIAL PRIMARY KEY,
|
|||
|
name VARCHAR(255) NOT NULL,
|
|||
|
description VARCHAR(255) NOT NULL,
|
|||
|
id_training INTEGER,
|
|||
|
FOREIGN KEY (id_training) REFERENCES t_training(id)
|
|||
|
);
|
|||
|
```
|
|||
|
|
|||
|
Также в файле `docker-compose.yaml` создадим соответствующий сервис:
|
|||
|
```dockerfile
|
|||
|
#database
|
|||
|
postgresql:
|
|||
|
#configuration
|
|||
|
image: postgres:latest
|
|||
|
ports:
|
|||
|
- 5432:5432
|
|||
|
environment:
|
|||
|
POSTGRES_PASSWORD: admin
|
|||
|
POSTGRES_USER: admin
|
|||
|
POSTGRES_DB: traininarium
|
|||
|
volumes:
|
|||
|
- ./database.sql:/docker-entrypoint-initdb.d/database.sql
|
|||
|
restart: always
|
|||
|
networks:
|
|||
|
- mynetwork
|
|||
|
```
|
|||
|
|
|||
|
- `image: postgres:latest` указывает, что мы хотим использовать последнюю версию образа `PostgreSQL`.
|
|||
|
- `ports: - 5432:5432` пробрасывает порт 5432 контейнера (стандартный порт PostgreSQL) на порт 5432 хоста, чтобы можно было подключаться к базе данных с внешнего устройства.
|
|||
|
- `environment` определяет переменные окружения, которые будут использоваться контейнером. В данном случае, устанавливаются значения для переменных `POSTGRES_PASSWORD`, `POSTGRES_USER` и `POSTGRES_DB`.
|
|||
|
- `volumes` указывает путь к файлу `database.sql` в текущей директории, который будет использоваться для инициализации базы данных при запуске контейнера.
|
|||
|
- `restart: always` гарантирует, что контейнер будет перезапущен автоматически, если он остановится или перезагрузится.
|
|||
|
- `networks` определяет сеть, к которой будет присоединен контейнер. В данном случае, контейнер будет присоединен к сети с именем `mynetwork`.
|
|||
|
|
|||
|
В итоге будет создан контейнер с базой данных `PostgreSQL`, настроенной с указанными переменными окружения, проброшенным портом и файлом `database.sql` для инициализации базы данных.
|
|||
|
|
|||
|
#### Создание микросервиса *Упражнения*
|
|||
|
После реализации необходимых элементов микросервиса, таких как: controller, model, modelDTO, repository и service, создадим конфигурационный файл Dockerfile, в котором пропишем следующее:
|
|||
|
```dockerfile
|
|||
|
FROM openjdk:17-jdk
|
|||
|
|
|||
|
WORKDIR /app
|
|||
|
COPY ./exercise-app/build/libs/exercise-app-0.0.1-SNAPSHOT.jar /app/exercise-app-0.0.1-SNAPSHOT.jar
|
|||
|
EXPOSE 8081
|
|||
|
|
|||
|
CMD ["java", "-jar", "exercise-app-0.0.1-SNAPSHOT.jar"]
|
|||
|
```
|
|||
|
В данном файле мы указываем базовый образ, который будет использован для создания контейнера (*OpenJDK* версии 17 с установленным *JDK*); устанавливаем рабочую директорию внутри контейнера, где будут размещены файлы приложения; указываем, что приложение внутри контейнера будет слушать на порту 8081 и определяем команду, которая будет выполнена при запуске контейнера, а именно запуск приложения *Java* из файла `exercise-app-0.0.1-SNAPSHOT.jar` внутри контейнера.
|
|||
|
|
|||
|
Также в файле `docker-compose.yaml` создадим соответствующий сервис:
|
|||
|
```dockerfile
|
|||
|
exercise-service:
|
|||
|
build:
|
|||
|
context: .
|
|||
|
dockerfile: ./exercise-app/Dockerfile
|
|||
|
ports:
|
|||
|
- 8081:8081
|
|||
|
environment:
|
|||
|
DATASOURCE_URL: jdbc:postgresql://postgresql:5432/traininarium
|
|||
|
DATASOURCE_USERNAME: admin
|
|||
|
DATASOURCE_PASSWORD: admin
|
|||
|
restart: always
|
|||
|
#wait build database
|
|||
|
depends_on:
|
|||
|
- postgresql
|
|||
|
networks:
|
|||
|
- mynetwork
|
|||
|
```
|
|||
|
- `exercise-service` является именем сервиса, который будет создан и запущен в контейнере.
|
|||
|
- `context: .` указывает, что контекстом для сборки Docker-образа является текущая директория.
|
|||
|
- `dockerfile: ./exercise-app/Dockerfile` указывает путь к `Dockerfile`, который будет использоваться для сборки образа.
|
|||
|
- `ports` определяет проброс портов между хостом и контейнером.
|
|||
|
`8081:8081` указывает, что порт 8081 на хосте будет проброшен на порт 8081 внутри контейнера.
|
|||
|
- `environment` определяет переменные окружения, которые будут доступны внутри контейнера.
|
|||
|
`DATASOURCE_URL: jdbc:postgresql://postgresql:5432/traininarium` определяет URL для подключения к базе данных `PostgreSQL`.
|
|||
|
`DATASOURCE_USERNAME: admin` определяет имя пользователя для подключения к базе данных.
|
|||
|
`DATASOURCE_PASSWORD: admin` определяет пароль для подключения к базе данных.
|
|||
|
- `restart: always` указывает, что контейнер будет автоматически перезапущен в случае его остановки.
|
|||
|
depends_on определяет зависимость данного сервиса от другого сервиса.
|
|||
|
- `postgresql` указывает, что данный сервис зависит от сервиса с именем `postgresql`.
|
|||
|
- `mynetwork` указывает, что контейнер будет присоединен к сети с именем `mynetwork`.
|
|||
|
|
|||
|
#### Создание микросервиса *Тренировки*
|
|||
|
Аналогично реализуем controller, model, modelDTO, repository и service для микросервиса *Тренировки*, но дополняя кодом отправки запроса к сервису *Упражнений*. Пример запроса к сервису *Упражнений*:
|
|||
|
|
|||
|
```java
|
|||
|
public List<TrainingDto> findAllTraining() throws Exception {
|
|||
|
RestTemplate restTemplate = new RestTemplate();
|
|||
|
HttpEntity<String> entity = new HttpEntity<>(new HttpHeaders());
|
|||
|
List<TrainingDto> trainingDtos = new ArrayList<>();
|
|||
|
for (Training training : trainingRepository.findAll()) {
|
|||
|
TrainingDto trainingDto = new TrainingDto();
|
|||
|
trainingDto.setId(training.getId());
|
|||
|
trainingDto.setName(training.getName());
|
|||
|
trainingDto.setDescription(training.getDescription());
|
|||
|
|
|||
|
ResponseEntity<String> response = restTemplate.exchange(
|
|||
|
"http://" + exercise_service_host + "/exercise/training/" + trainingDto.getId(), HttpMethod.GET, entity, String.class);
|
|||
|
|
|||
|
if (response.getStatusCode().is2xxSuccessful()) {
|
|||
|
String responseBody = response.getBody();
|
|||
|
ObjectMapper objectMapper = new ObjectMapper();
|
|||
|
List<ExerciseDto> exerciseDtos;
|
|||
|
try {
|
|||
|
exerciseDtos = objectMapper.readValue(responseBody, ArrayList.class);
|
|||
|
trainingDto.setExercises(exerciseDtos);
|
|||
|
} catch (JsonProcessingException e) {
|
|||
|
throw new Exception("Не удалось десериализовать тело запроса в объект");
|
|||
|
}
|
|||
|
} else {
|
|||
|
throw new Exception("Ошибка получения объекта");
|
|||
|
}
|
|||
|
trainingDtos.add(trainingDto);
|
|||
|
}
|
|||
|
return trainingDtos;
|
|||
|
}
|
|||
|
```
|
|||
|
Теперь создадим конфигурационный файл Dockerfile, в котором пропишем следующее:
|
|||
|
```dockerfile
|
|||
|
FROM openjdk:17-jdk
|
|||
|
|
|||
|
WORKDIR /app
|
|||
|
COPY ./training-app/build/libs/training-app-0.0.1-SNAPSHOT.jar /app/training-app-0.0.1-SNAPSHOT.jar
|
|||
|
EXPOSE 8082
|
|||
|
|
|||
|
CMD ["java", "-jar", "training-app-0.0.1-SNAPSHOT.jar"]
|
|||
|
```
|
|||
|
В данном файле мы указываем базовый образ, который будет использован для создания контейнера (*OpenJDK* версии 17 с установленным *JDK*); устанавливаем рабочую директорию внутри контейнера, где будут размещены файлы приложения; указываем, что приложение внутри контейнера будет слушать на порту 8082; копируем файл `training-app-0.0.1-SNAPSHOT.jar` из локальной директории `./training-app/build/libs/` внутрь контейнера в папку `/app` и определяем команду, которая будет выполнена при запуске контейнера, а именно запуск приложения Java из файла `training-app-0.0.1-SNAPSHOT.jar` внутри контейнера.
|
|||
|
|
|||
|
Также в файле `docker-compose.yaml` создадим соответствующий сервис:
|
|||
|
```dockerfile
|
|||
|
training-service:
|
|||
|
build:
|
|||
|
context: .
|
|||
|
dockerfile: ./training-app/Dockerfile
|
|||
|
ports:
|
|||
|
- 8082:8082
|
|||
|
environment:
|
|||
|
EXERCISE_SERVICE_HOST: exercise-service:8081
|
|||
|
DATASOURCE_URL: jdbc:postgresql://postgresql:5432/traininarium
|
|||
|
DATASOURCE_USERNAME: admin
|
|||
|
DATASOURCE_PASSWORD: admin
|
|||
|
restart: always
|
|||
|
#wait build database
|
|||
|
depends_on:
|
|||
|
- postgresql
|
|||
|
networks:
|
|||
|
- mynetwork
|
|||
|
```
|
|||
|
|
|||
|
- `training-service` является именем сервиса, который будет создан и запущен в контейнере.
|
|||
|
- `context: .` указывает, что контекстом для сборки Docker-образа является текущая директория.
|
|||
|
- `dockerfile: ./training-app/Dockerfile` указывает путь к `Dockerfile`, который будет использоваться для сборки образа.
|
|||
|
- `8082:8082` указывает, что порт 8082 на хосте будет проброшен на порт 8082 внутри контейнера.
|
|||
|
- `environment` определяет переменные окружения, которые будут доступны внутри контейнера.
|
|||
|
`EXERCISE_SERVICE_HOST: exercise-service:8081` определяет хост и порт для подключения к сервису `exercise-service`.
|
|||
|
`DATASOURCE_URL: jdbc:postgresql://postgresql:5432/traininarium` определяет URL для подключения к базе данных `PostgreSQL`.
|
|||
|
`DATASOURCE_USERNAME: admin` определяет имя пользователя для подключения к базе данных.
|
|||
|
`DATASOURCE_PASSWORD: admin` определяет пароль для подключения к базе данных.
|
|||
|
- `restart: always` указывает, что контейнер будет автоматически перезапущен в случае его остановки.
|
|||
|
- `depends_on` определяет зависимость данного сервиса от другого сервиса, данный сервис зависит от сервиса с именем `postgresql`.
|
|||
|
- `mynetwork` указывает, что контейнер будет присоединен к сети с именем `mynetwork`.
|
|||
|
|
|||
|
#### Реализация gateway при помощи *nginx*
|
|||
|
|
|||
|
Для того, чтобы использовать *Nginx* для обработки HTTP-запросов и маршрутизации их к соответствующим сервисам, создадим файл конфигурации *Nginx*:
|
|||
|
|
|||
|
```
|
|||
|
events {
|
|||
|
worker_connections 1024;
|
|||
|
}
|
|||
|
|
|||
|
http {
|
|||
|
|
|||
|
upstream exercise-service {
|
|||
|
server exercise-service:8081;
|
|||
|
}
|
|||
|
upstream training-service {
|
|||
|
server training-service:8082;
|
|||
|
}
|
|||
|
|
|||
|
server {
|
|||
|
|
|||
|
listen 80;
|
|||
|
listen [::]:80;
|
|||
|
server_name localhost;
|
|||
|
|
|||
|
location /exercise-service/ {
|
|||
|
proxy_pass http://exercise-service/;
|
|||
|
}
|
|||
|
|
|||
|
location /training-service/ {
|
|||
|
proxy_pass http://training-service/;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
- `events` определяет настройки событий для сервера *Nginx*.
|
|||
|
- `worker_connections 1024` указывает максимальное количество одновременных соединений, которые могут быть обработаны сервером.
|
|||
|
- `http` определяет настройки HTTP-сервера *Nginx*.
|
|||
|
- `upstream` определяет группу серверов, которые могут обрабатывать запросы.
|
|||
|
`exercise-service` определяет группу серверов с именем `exercise-service`, в которой находится только один сервер `exercise-service:8081`.
|
|||
|
`training-service` определяет группу серверов с именем `training-service`, в которой находится только один сервер `training-service:8082`.
|
|||
|
- `server` определяет настройки для конкретного виртуального сервера.
|
|||
|
- `listen 80` указывает на порт, на котором сервер будет слушать входящие HTTP-запросы.
|
|||
|
- `listen [::]:80` указывает на IPv6-адрес и порт, на котором сервер будет слушать входящие HTTP-запросы.
|
|||
|
- `server_name localhost` указывает имя сервера.
|
|||
|
- `location /exercise-service/` определяет местоположение для обработки запросов, которые начинаются с `/exercise-service/`.
|
|||
|
`proxy_pass http://exercise-service/` указывает, что все запросы, начинающиеся с `/exercise-service/`, должны быть перенаправлены на группу серверов `exercise-service`.
|
|||
|
- `location /training-service/` определяет местоположение для обработки запросов, которые начинаются с `/training-service/`.
|
|||
|
`proxy_pass http://training-service/` указывает, что все запросы, начинающиеся с `/training-service/`, должны быть перенаправлены на группу серверов `training-service`.
|
|||
|
|
|||
|
|
|||
|
Таким образом, при запуске сервера *Nginx* с использованием этого конфигурационного файла, сервер будет слушать входящие HTTP-запросы на порту 80 и маршрутизировать запросы, начинающиеся с `/exercise-service/`, на группу серверов `exercise-service`, а запросы, начинающиеся с `/training-service/`, на группу серверов `training-service`.
|
|||
|
|
|||
|
Далее в файле `docker-compose.yaml` создадим соответствующий сервис:
|
|||
|
|
|||
|
```dockerfile
|
|||
|
nginx:
|
|||
|
#configuration
|
|||
|
image: nginx:latest
|
|||
|
ports:
|
|||
|
- 80:80
|
|||
|
volumes:
|
|||
|
- ./nginx.conf:/etc/nginx/nginx.conf
|
|||
|
restart: always
|
|||
|
depends_on:
|
|||
|
- training-service
|
|||
|
- exercise-service
|
|||
|
networks:
|
|||
|
- mynetwork
|
|||
|
```
|
|||
|
|
|||
|
- `nginx` - это название сервиса, который будет запущен в контейнере.
|
|||
|
- `image: nginx:latest` указывает на использование последней версии образа *Nginx* из *Docker Hub*.
|
|||
|
- `80:80` пробрасывает порт 80 контейнера на порт 80 хостовой машины.
|
|||
|
- `volumes` определяет привязку тома между контейнером и хостовой машиной.
|
|||
|
- `./nginx.conf:/etc/nginx/nginx.conf` привязывает файл `nginx.conf` из текущего рабочего каталога хостовой машины к файлу `nginx.conf` внутри контейнера *Nginx*. Это позволяет настроить *Nginx* с помощью внешнего файла конфигурации.
|
|||
|
- `restart: always` указывает, что контейнер должен быть автоматически перезапущен при его остановке или падении.
|
|||
|
- `depends_on` указывает на зависимость этого сервиса от других сервисов.
|
|||
|
- `training-service` указывает, что контейнер Nginx должен быть запущен после контейнера `training-service`.
|
|||
|
- `exercise-service` указывает, что контейнер Nginx должен быть запущен после контейнера `exercise-service`.
|
|||
|
- `networks` определяет сети, к которым будет присоединен контейнер.
|
|||
|
- `mynetwork` добавляет контейнер в сеть с именем `mynetwork`.
|
|||
|
|
|||
|
### Видео
|
|||
|
|
|||
|
https://disk.yandex.ru/i/OYPw_Tzl0QrzIw
|