DAS_2023_1/kochkareva_elizaveta_lab_3
2024-01-19 11:38:02 +04:00
..
exercise-app kochkareva_elizaveta_lab_3 is ready 2024-01-19 11:38:02 +04:00
training-app kochkareva_elizaveta_lab_3 is ready 2024-01-19 11:38:02 +04:00
database.sql kochkareva_elizaveta_lab_3 is ready 2024-01-19 11:38:02 +04:00
docker-compose.yaml kochkareva_elizaveta_lab_3 is ready 2024-01-19 11:38:02 +04:00
kind-windows-amd64.exe kochkareva_elizaveta_lab_3 is ready 2024-01-19 11:38:02 +04:00
nginx.conf kochkareva_elizaveta_lab_3 is ready 2024-01-19 11:38:02 +04:00
README.md kochkareva_elizaveta_lab_3 is ready 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-операции, поэтому были выбраны следующие сущности, соответствующие теме диплома: упражнение и тренировка. Эти сущности связаны отношением один ко многим. Созданные таблицы базы данных:

-- Создание таблицы тренировок
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 создадим соответствующий сервис:

#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, в котором пропишем следующее:

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 создадим соответствующий сервис:

 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 для микросервиса Тренировки, но дополняя кодом отправки запроса к сервису Упражнений. Пример запроса к сервису Упражнений:

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, в котором пропишем следующее:

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 создадим соответствующий сервис:

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 создадим соответствующий сервис:

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