DAS_2023_1/kochkareva_elizaveta_lab_3/README.md

280 lines
19 KiB
Markdown
Raw Normal View History

2024-01-19 11:38:02 +04:00
# Лабораторная работа 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