35 KiB
РАЗРАБОТКА И ВНЕДРЕНИЕ ОТКАЗОУСТОЙЧИВОЙ МУЛЬТИТЕНАНТНОЙ ИНФРАСТРУКТУРЫ ДЛЯ СИСТЕМЫ УПРАВЛЕНИЯ УНИВЕРСИТЕТСКИМ РАСПИСАНИЕМ «МАГИСТР»
Диссертационное исследование и отчет о проделанной инженерной работе в качестве DevOps-архитектора проекта
ВВЕДЕНИЕ
Современные информационные системы для образовательных учреждений требуют строгого соблюдения требований к безопасности, изоляции данных различных организаций (университетов), гибкости масштабирования и высокой наблюдаемости (observability). В рамках разработки проекта «Магистр» — системы управления расписанием учебных занятий — передо мной встала задача проектирования и построения инфраструктуры, способной обслуживать десятки университетов в рамках единого прикладного контура при условии жесткой изоляции их баз данных.
В качестве DevOps-инженера проекта я спроектировал и реализовал архитектурное решение, объединяющее технологии аппаратной виртуализации, оркестрации контейнеров, автоматизации конфигурации, распределенного мониторинга и непрерывной интеграции. В данном документе подробно описаны теоретические предпосылки, практическая реализация и архитектурные решения, внедренные мной в продакшн-окружение проекта.
РАЗДЕЛ 1. СИСТЕМНАЯ АРХИТЕКТУРА И КОНЦЕПЦИЯ МУЛЬТИТЕНАНТНОСТИ
1.1 Выбор паттерна изоляции данных
При проектировании мультитенантных (multi-tenant) систем классически выделяют три подхода к организации баз данных:
- Shared Database & Shared Schema: Все клиенты используют общие таблицы, записи разделяются по полю
tenant_id. Подход дешев в обслуживании, но несет колоссальные риски утечки данных из-за ошибок в SQL-запросах приложения и не позволяет разграничивать физический доступ к базам. - Shared Database & Separate Schemas: Одна СУБД, но разные логические схемы для каждого клиента. Обеспечивает умеренную изоляцию, но сохраняет единую точку отказа и общие аппаратные ресурсы СУБД.
- Database-per-Tenant (Выбранный подход): Каждый клиент (университет) владеет собственной физически изолированной базой данных, расположенной на выделенном сервере или виртуальной машине.
Для проекта «Магистр» мной был выбран и реализован паттерн Database-per-Tenant. Это обусловлено спецификой клиентов (высшие учебные заведения), которые согласно законодательству (например, ФЗ-152 «О персональных данных») обязаны хранить свои данные локально или требовать полной изоляции конфиденциальной информации. Кроме того, данный подход позволяет:
- Исключить влияние высокой нагрузки одного университета (например, в период составления сессий) на доступность системы для других вузов («синдром шумного соседа»).
- Выполнять индивидуальное резервное копирование и обслуживание БД каждого университета без остановки всей платформы.
- Переносить базы данных вузов на их собственные физические серверы в локальных сетях (on-premise), сохраняя при этом работу приложения в центральном облаке.
1.2 Архитектурная схема движения трафика
Ниже представлена разработанная мной схема прохождения сетевых запросов и агрегации телеметрии в системе:
graph TD
Client[Пользовательский браузер] -->|HTTPS: tenant.zuev.company| Caddy[Caddy Reverse Proxy]
Caddy -->|HTTP Round-Robin| Ingress[Traefik Ingress Controller]
subgraph K3s Cluster
Ingress -->|Route /*| Frontend[Frontend Pods x2 Apache]
Ingress -->|Route /api/*| Backend[Backend Pods x1-x2 Spring Boot]
Backend -->|K8s API: Get/Patch CM| K8sAPI[Kubernetes API Server]
BackendConfig[ConfigMap: tenants-config] -.->|Mounted JSON| Backend
end
subgraph Proxmox VE Infrastructure
Backend -->|JDBC Connection| VM1[(VM 1: DBMS MSU<br>PostgreSQL 16)]
Backend -->|JDBC Connection| VM2[(VM 2: DBMS SWSU<br>PostgreSQL 16)]
VM1 -.->|Metrics OTLP/gRPC| Collector1[OTel Collector VM1]
VM2 -.->|Metrics OTLP/gRPC| Collector2[OTel Collector VM2]
end
subgraph Observability Hub
Backend -->|Logs & Traces OTLP| SigNoz[SigNoz APM Server]
Collector1 -->|Postgres Metrics| SigNoz
Collector2 -->|Postgres Metrics| SigNoz
end
classDef cluster fill:#e1f5fe,stroke:#01579b,stroke-width:2px;
classDef vm fill:#efebe9,stroke:#4e342e,stroke-width:2px;
classDef monitor fill:#efe8ff,stroke:#512da8,stroke-width:2px;
class K3s Cluster cluster;
class Proxmox VE Infrastructure vm;
class Observability Hub monitor;
РАЗДЕЛ 2. АППАРАТНАЯ ВИРТУАЛИЗАЦИЯ НА БАЗЕ PROMVOX VE
Для размещения баз данных клиентов и вспомогательных инфраструктурных служб мной был развернут гипервизор Proxmox Virtual Environment (PVE) на выделенных серверных мощностях.
2.1 Конфигурация виртуализации и изоляции ресурсов
Каждая база данных университета разворачивается в выделенной виртуальной машине (KVM), что гарантирует жесткую изоляцию на уровне ядра ОС и гипервизора.
- Сетевой уровень: В Proxmox настроена виртуальная коммутация (Linux Bridge
vmbr0). Сетевые интерфейсы виртуальных машин баз данных вынесены в приватную подсеть, изолированную от внешней сети. Доступ к ним имеет только подсеть кластера Kubernetes и управляющая машина администратора. - Дисковая подсистема: Использовано локальное хранилище на базе ZFS с зеркалированием (RAID-10 на SSD-накопителях). Это обеспечивает:
- Высокую скорость операций ввода-вывода (IOPS), критически важную для СУБД при транзакционных нагрузках.
- Механизм мгновенных снимков (snapshots) для безопасного проведения обновлений.
- Аппаратное сжатие данных (LZ4) без потери производительности, снижающее износ накопителей.
- Резервное копирование: Интегрирован Proxmox Backup Server (PBS). Каждую ночь выполняются инкрементальные бэкапы виртуальных машин с дедупликацией на уровне блоков данных, что минимизирует нагрузку на сеть и диски, позволяя восстановить любую БД на любой момент времени за последние 30 дней.
РАЗДЕЛ 3. ОРКЕСТРАЦИЯ КОНТЕЙНЕРНОЙ ИНФРАСТРУКТУРЫ В KUBERNETES (K3s)
В качестве платформы оркестрации приложений был выбран K3s — сертифицированный дистрибутив Kubernetes от Rancher, оптимизированный под средние и малые нагрузки, обладающий минимальными накладными расходами на RAM/CPU для Control Plane, но сохраняющий полную совместимость с upstream-спецификациями Kubernetes API.
3.1 Архитектура манифестов и структура ресурсов
Мной была разработана декларативная структура ресурсов, развернутая в изолированном пространстве имен magistr (манифест namespace.yaml):
-
Frontend (
frontend.yaml):- Реализован в виде
Deploymentс 2 репликами для обеспечения высокой доступности (HA) и возможности бесшовного обновления rolling-update. - В качестве базового образа контейнера применен легковесный веб-сервер Apache HTTPd (
httpd:alpine). - Для балансировки и внутреннего доступа настроен
Serviceтипа ClusterIP, слушающий порт 80.
- Реализован в виде
-
Backend (
backend.yaml):Deploymentс 1-2 репликами (детали балансировки конфигурации описаны ниже).- Для сборки образов используется multi-stage сборка Maven (JDK 17) и запуск под управлением
eclipse-temurin:17-jre-alpine. - Интегрирован Java-агент OpenTelemetry для автоматического инструментирования трассировки и логов.
- Для связи с Ingress настроен ClusterIP-сервис на порту 8080.
-
Ingress (
ingress.yaml):- В роли Ingress-контроллера выступает стандартный для K3s Traefik.
- Мной настроены строгие правила маршрутизации по доменным именам (например,
magistr.zuev.companyдля тестового окружения,n8n.zuev.companyи т.д.). Маршрутизация по путям разделяет трафик: запросы к API (/api) проксируются на бэкенд-сервис, а запросы к статическим ресурсам и страницам (/) — на фронтенд-сервис.
3.2 Реализация динамической мультитенантности через K8s API
Одним из наиболее сложных этапов проектирования стала организация бесшовного добавления новых университетов без перезапуска бэкенда и изменения исходного кода приложения.
-
Хранение конфигурации: Список подключений к базам данных университетов хранится в JSON-формате (
tenants.json) внутри KubernetesConfigMapс именемtenants-config. -
Проблема Read-Only монтирования: По умолчанию Kubernetes монтирует ConfigMap как файловую систему в режиме Read-Only. Это исключало возможность для Java-приложения перезаписывать
tenants.jsonпри запросах от администратора на добавление нового тенанта. -
Решение с использованием
initContainerиemptyDir: Я спроектировал схему с временным перезаписываемым томом (emptyDir):- В спецификацию пода backend добавлен
initContainerна базе образаbusybox. - При старте пода
initContainerмонтирует ConfigMaptenants-configи копирует файлtenants.jsonво временный разделemptyDir(путь/config). - Основной контейнер
backendмонтирует этот жеemptyDirв режиме Read-Write.
- В спецификацию пода backend добавлен
-
K8s RBAC и динамическое сохранение: Чтобы предоставить бэкенду возможность сохранять изменения конфигурации обратно в кластер, мной были написаны манифесты авторизации (
rbac.yaml):- Создан
ServiceAccountbackend-saи назначен поду бэкенда. - Описана Kubernetes
Rolebackend-configmap-role, дающая права только на операцииgetиpatchдля ресурсаconfigmapsс конкретным именемtenants-config(принцип наименьших привилегий). - Создан
RoleBindingbackend-configmap-binding, связывающий сервис-аккаунт с этой ролью.
В коде бэкенда класс
ConfigMapUpdaterчерез Kubernetes Java Client отправляет PATCH-запрос к API-серверу при добавлении нового тенанта, обновляя ConfigMap в самом кластере. - Создан
-
Репликация и синхронизация подов: Для обеспечения консистентности данных в условиях работы нескольких реплик бэкенда:
- Класс
TenantConfigWatcherзапускает фоновую задачу, которая каждые 30 секунд сверяет MD5-хеш локального файлаtenants.jsonс версией из ConfigMap. - При обнаружении расхождения файл перезаписывается, DataSource-инстансы пересоздаются в памяти приложения на лету, исключая рассинхронизацию подов.
- Примечание: В ходе тестирования для минимизации задержек и исключения эффекта "мигания" данных на время отладки количество реплик backend было ограничено до 1, с последующим переходом на полноценный StatefulSet/Shared Storage при росте количества нод.
- Класс
3.3 Повышение отказоустойчивости бэкенда (Resiliency)
При запуске бэкенда в облаке возникала проблема: если база данных одного из университетов становилась недоступной (например, из-за сетевого сбоя или регламентных работ), пул подключений HikariCP падал с ошибкой при инициализации бинов, отправляя под бэкенда в циклическую перезагрузку (CrashLoopBackOff), что делало недоступной систему для всех остальных вузов.
Для предотвращения этого каскадного сбоя я внедрил комплекс защитных механизмов:
- H2 Fallback: В сборку (
pom.xml) добавлена СУБД H2 in-memory. Если при старте ConfigMap пуст, Spring Boot инициализирует пустой JPA-контекст на базе H2, что позволяет приложению успешно пройти фазу запуска. - HikariCP Resiliency: В конфигурации источников данных задано свойство
setInitializationFailTimeout(-1). Это заставляет HikariCP игнорировать отсутствие связи с целевой БД при старте приложения, продолжая попытки подключения в фоновом режиме. - Явное указание диалекта Hibernate: Принудительно задан
org.hibernate.dialect.PostgreSQLDialectвTenantDataSourceConfig.java. Это избавило Hibernate от необходимости выполнять тестовый запрос к БД для автоматического определения диалекта на ранних этапах инициализации контекста. - Автоматическое создание таблиц (
DataInitializer): Для новых БД вузов полностью автоматизирован процесс наката структуры. При обнаружении новой базы Java-код проверяет наличие таблицыusersи, в случае ее отсутствия, самостоятельно применяет SQL-скрипт инициализации (init.sql), включая создание дефолтных справочников и учетной записи суперадминистратора.
РАЗДЕЛ 4. АВТОМАТИЗАЦИЯ КОНФИГУРАЦИИ СУБД С ПОМОЩЬЮ ANSIBLE
Для развертывания и администрирования баз данных университетов на удаленных виртуальных машинах Proxmox мной был спроектирован и реализован комплексный плейбук Ansible с модульной структурой ролей.
4.1 Структура и функционал Ansible-ролей
Вся конфигурация целевого сервера разбита на 6 последовательных этапов (плейбук site.yml):
-
common(Базовая подготовка и Hardening ОС):- Создание системного пользователя
deployс беспарольным доступом кsudo(через валидацию файла/etc/sudoers.d/deployутилитойvisudo). - Установка системных утилит (
curl,git,htop,chronyдля синхронизации времени по NTP). - SSH Hardening: Изменение стандартного SSH-порта на
2222, отключение авторизации по паролям, запрет входа для пользователяroot, ограничение таймаута сессий. - Адаптация под различные дистрибутивы ОС: написаны отдельные сценарии для семейств Debian/Ubuntu/Astra Linux (пакетный менеджер
apt), RedHat/РЕД ОС (dnf) и ALT Linux (кастомная обертка дляapt-get).
- Создание системного пользователя
-
docker(Среда контейнеризации):- Автоматическое добавление официальных GPG-ключей и репозиториев Docker CE.
- Установка Docker Engine, CLI и Compose плагина.
- Включение системной службы
dockerи добавление пользователяdeployв группуdockerдля беспарольного запуска контейнеров.
-
firewall(Сетевая безопасность):- Настройка межсетевого экрана UFW (для Debian-based систем) или firewalld (для RedHat и ALT Linux).
- Полная блокировка входящего трафика по умолчанию.
- Разрешение входящих пакетов только на порт SSH (
2222) и порт PostgreSQL (5432). Реализована поддержка ограничения доступа к порту5432только для IP-адресов нод Kubernetes-кластера (firewall_postgresql_allowed_ips).
-
postgresql(Контейнеризированная СУБД):- Генерация файлов конфигурации
docker-compose.ymlи.envна основе шаблонов Jinja2. - Развертывание PostgreSQL 16 на базе легковесного Alpine-образа.
- Физическое монтирование каталога данных БД с хост-системы (
/opt/magistr/postgres/data), предотвращающее потерю данных при пересоздании контейнера. - Ограничение системных ресурсов контейнера (limits: memory: 512M) и тюнинг параметров PostgreSQL (
shared_buffers=256MB, оптимизация пула соединений, логирование медленных запросовlog_min_duration_statement=1000).
- Генерация файлов конфигурации
-
backup(Резервное копирование на уровне БД):- Установка на хост автоматического bash-скрипта бэкапа.
- Настройка задачи
cron(запуск ежедневно в 03:00). - Скрипт осуществляет горячий дамп базы через
docker exec pg_dump, архивирует его с помощьюgzipи сохраняет в локальный каталог/opt/magistr/backups/. - Реализован алгоритм автоматической ротации: удаление архивных файлов старше заданного количества дней (по умолчанию 14 дней).
-
monitoring(Сбор телеметрии хоста и БД):- Развертывание контейнера OpenTelemetry Collector на целевом сервере БД.
- Конфигурирование коллектора на сбор метрик производительности PostgreSQL и ОС (процессор, память, диски) и их отправку на единый сервер SigNoz по OTLP/HTTP.
4.2 Управление секретами: Ansible Vault
Для исключения попадания паролей администраторов БД в систему контроля версий (Git) все чувствительные переменные (например, vault_postgresql_password) вынесены в файл group_vars/databases/vault.yml и зашифрованы алгоритмом AES-256 с помощью Ansible Vault.
Для бесшовной интеграции в CI/CD пароль расшифрования считывается из локального файла .vault_pass на управляющей машине, доступ к которому ограничен правами chmod 600.
РАЗДЕЛ 5. НЕПРЕРЫВНАЯ ИНТЕГРАЦИЯ И ДОСТАВКА (CI/CD)
В рамках оптимизации внутренних процессов разработки и деплоя мной была развернута и настроена инфраструктура непрерывной интеграции на базе Gitea и Gitea Actions.
5.1 Автоматизация сборки (CI)
В репозитории проекта создан workflow-манифест .gitea/workflows/docker-build.yaml. При каждом пуше изменений в ветку main запускается конвейер:
- Checkout: Загрузка актуального исходного кода проекта на ранер.
- Setup Buildx: Инициализация Docker Buildx для оптимизации кэширования слоев.
- Login to Registry: Аутентификация во встроенном реестре контейнеров Gitea Container Registry (
git.zuev.company) с использованием сервисного токенаZUEV_TOKEN(праваwrite:package). - Build & Push: Параллельная сборка Docker-образов для бэкенда и фронтенда с тегом
latestи отправка их в приватный реестр.
5.2 Доставка в кластер (CD)
Для авторизации нод K3s в приватном реестре Gitea мной был создан секрет типа docker-registry в пространстве имен magistr:
kubectl create secret docker-registry gitea-registry \
--docker-server=gitea.zuev.company \
--docker-username=Zuev \
--docker-password=${ZUEV_TOKEN} \
--namespace=magistr
Этот секрет ассоциирован со спецификациями деплоев в backend.yaml и frontend.yaml через директиву imagePullSecrets. Обновление приложений в кластере после завершения сборки образов выполняется путем контролируемого перезапуска подов:
kubectl rollout restart deployment backend -n magistr
kubectl rollout restart deployment frontend -n magistr
РАЗДЕЛ 6. ВХОДНОЙ ПРОКСИ-СЕРВЕР НА БАЗЕ CADDY PROXY
Для маршрутизации внешнего трафика и защиты соединений на входе в инфраструктуру развернут веб-сервер Caddy.
6.1 Преимущества Caddy и управление SSL/TLS
В отличие от классического Nginx, Caddy был выбран благодаря:
- Встроенной интеграции с удостоверяющими центрами Let's Encrypt и ZeroSSL. При добавлении нового поддомена вуза (например,
swsu.zuev.company) Caddy автоматически запрашивает, валидирует через DNS/HTTP-вызовы и устанавливает TLS-сертификат, а также следит за его продлением. - Лаконичному синтаксису конфигурации (
Caddyfile). - Полноценной поддержке протокола HTTP/3 «из коробки».
6.2 Конфигурация балансировки
Внешний Caddy-сервер принимает запросы ко всем поддоменам *.zuev.company и перенаправляет их на ноды кластера K3s, выполняя роль внешнего балансировщика нагрузки (L4/L7 Load Balancer):
*.zuev.company {
reverse_proxy 192.168.1.104:80 192.168.1.105:80 192.168.1.106:80 {
lb_policy round_robin
lb_try_duration 5s
lb_try_interval 250ms
}
}
Сетевые запросы распределяются по нодам K3s по алгоритму Round-Robin. В случае недоступности одной из нод, Caddy временно исключает ее из пула, обеспечивая отказоустойчивость инфраструктуры на сетевом уровне.
РАЗДЕЛ 7. СКВОЗНОЙ МОНИТОРИНГ И ОБСЕРВАБИЛИТИ (SigNoz + OpenTelemetry)
Для контроля здоровья системы и оперативного выявления аномалий мной была спроектирована и внедрена централизованная система мониторинга на базе APM-платформы SigNoz и стандартов OpenTelemetry (OTel).
7.1 Сбор телеметрии Backend
Java-приложение бэкенда запускается с подключением агента OpenTelemetry (opentelemetry-javaagent.jar). Это позволяет без изменения кода собирать:
- Трейсы (Traces): Сквозное прохождение HTTP-запросов через контроллеры, сервисы и JDBC-драйвер к базам данных.
- Метрики (Metrics): Метрики JVM (утилизация Heap, активность сборщика мусора GC, состояние потоков), метрики HTTP-запросов (интенсивность, латентность, коды ответов).
- Логи (Logs): Системные логи Logback экспортируются по протоколу OTLP напрямую в SigNoz.
7.2 Идентификация тенантов в мониторинге
Для глубокого анализа производительности СУБД конкретных университетов критически важно разделять телеметрию по тенантам. Мной была реализована сквозная маркировка:
- В бэкенде через Java-интерцептор
TenantInterceptorпри каждом запросе идентификатор тенанта помещается в контекст логирования SLF4J MDC (MDC.put("tenant.id", tenant)) и одновременно записывается в атрибуты текущего OpenTelemetry спана (Span.current().setAttribute("tenant.id", tenant)). - Благодаря переменной окружения
OTEL_INSTRUMENTATION_LOGBACK_APPENDER_EXPERIMENTAL_CAPTURE_MDC_ATTRIBUTES=tenant.id, OTel-агент автоматически парсит MDC и прикрепляет его к логам, отправляемым в SigNoz. Это позволяет администраторам фильтровать ошибки и строить графики нагрузки в разрезе каждого университета.
7.3 Мониторинг баз данных на хостах
Для сбора детальной статистики с изолированных серверов баз данных:
- На хостах БД силами Ansible разворачивается OpenTelemetry Collector.
- В конфигурации коллектора (
otel-collector-config.yml.j2) описывается ресиверpostgresql, который подключается к локальной БД и считывает системные метрики (размер таблиц, cache hit ratio, количество транзакций, активные сессии, блокировки). - К собираемым метрикам жестко прикрепляются ресурсные атрибуты хоста:
service.name=magistr-db-<tenant_domain>иuniversity.name=<University Name>. - Метрики экспортируются в центральный коллектор SigNoz. В результате в интерфейсе SigNoz доступны кастомные дашборды JVM, PostgreSQL и HTTP, позволяющие оперативно локализовать проблемы с производительностью баз данных конкретных вузов.
ЗАКЛЮЧЕНИЕ
Разработанная и внедренная мной DevOps-архитектура для проекта «Магистр» успешно решила задачи изоляции данных, отказоустойчивости и автоматизации администрирования.
Ключевые результаты проделанной работы:
- Изоляция данных: Реализована физическая изоляция БД университетов на выделенных серверах (VM в Proxmox VE), соответствующая требованиям безопасности и законодательства.
- Динамическое масштабирование: Внедрен механизм динамического добавления тенантов без простоя системы (zero-downtime) за счет связки Spring Boot, K8s RBAC и ConfigMapWatcher.
- Отказоустойчивость: Минимизированы риски каскадных сбоев бэкенда за счет применения in-memory fallback баз данных и оптимизации параметров подключения HikariCP.
- Автоматизация: Создан универсальный Ansible-плейбук для быстрого ввода в эксплуатацию новых серверов БД (время развертывания «под ключ» сокращено до нескольких минут).
- Наблюдаемость: Реализован сквозной мониторинг логов, метрик и трассировок с детализацией по тенантам на базе стека OpenTelemetry + SigNoz.
Созданная инфраструктура обладает высокой степенью гибкости и готова к дальнейшему горизонтальному масштабированию по мере подключения новых высших учебных заведений к проекту «Магистр».