концепт AI ассистента
This commit is contained in:
1578
AI_ASSISTANT_IMPLEMENTATION_PLAN.md
Normal file
1578
AI_ASSISTANT_IMPLEMENTATION_PLAN.md
Normal file
File diff suppressed because it is too large
Load Diff
292
DEVOPS.md
292
DEVOPS.md
@@ -1,292 +0,0 @@
|
||||
# РАЗРАБОТКА И ВНЕДРЕНИЕ ОТКАЗОУСТОЙЧИВОЙ МУЛЬТИТЕНАНТНОЙ ИНФРАСТРУКТУРЫ ДЛЯ СИСТЕМЫ УПРАВЛЕНИЯ УНИВЕРСИТЕТСКИМ РАСПИСАНИЕМ «МАГИСТР»
|
||||
|
||||
**Диссертационное исследование и отчет о проделанной инженерной работе в качестве DevOps-архитектора проекта**
|
||||
|
||||
---
|
||||
|
||||
## ВВЕДЕНИЕ
|
||||
|
||||
Современные информационные системы для образовательных учреждений требуют строгого соблюдения требований к безопасности, изоляции данных различных организаций (университетов), гибкости масштабирования и высокой наблюдаемости (observability). В рамках разработки проекта **«Магистр»** — системы управления расписанием учебных занятий — передо мной встала задача проектирования и построения инфраструктуры, способной обслуживать десятки университетов в рамках единого прикладного контура при условии жесткой изоляции их баз данных.
|
||||
|
||||
В качестве DevOps-инженера проекта я спроектировал и реализовал архитектурное решение, объединяющее технологии аппаратной виртуализации, оркестрации контейнеров, автоматизации конфигурации, распределенного мониторинга и непрерывной интеграции. В данном документе подробно описаны теоретические предпосылки, практическая реализация и архитектурные решения, внедренные мной в продакшн-окружение проекта.
|
||||
|
||||
---
|
||||
|
||||
## РАЗДЕЛ 1. СИСТЕМНАЯ АРХИТЕКТУРА И КОНЦЕПЦИЯ МУЛЬТИТЕНАНТНОСТИ
|
||||
|
||||
### 1.1 Выбор паттерна изоляции данных
|
||||
При проектировании мультитенантных (multi-tenant) систем классически выделяют три подхода к организации баз данных:
|
||||
1. **Shared Database & Shared Schema**: Все клиенты используют общие таблицы, записи разделяются по полю `tenant_id`. Подход дешев в обслуживании, но несет колоссальные риски утечки данных из-за ошибок в SQL-запросах приложения и не позволяет разграничивать физический доступ к базам.
|
||||
2. **Shared Database & Separate Schemas**: Одна СУБД, но разные логические схемы для каждого клиента. Обеспечивает умеренную изоляцию, но сохраняет единую точку отказа и общие аппаратные ресурсы СУБД.
|
||||
3. **Database-per-Tenant (Выбранный подход)**: Каждый клиент (университет) владеет собственной физически изолированной базой данных, расположенной на выделенном сервере или виртуальной машине.
|
||||
|
||||
Для проекта «Магистр» мной был выбран и реализован паттерн **Database-per-Tenant**. Это обусловлено спецификой клиентов (высшие учебные заведения), которые согласно законодательству (например, ФЗ-152 «О персональных данных») обязаны хранить свои данные локально или требовать полной изоляции конфиденциальной информации. Кроме того, данный подход позволяет:
|
||||
* Исключить влияние высокой нагрузки одного университета (например, в период составления сессий) на доступность системы для других вузов («синдром шумного соседа»).
|
||||
* Выполнять индивидуальное резервное копирование и обслуживание БД каждого университета без остановки всей платформы.
|
||||
* Переносить базы данных вузов на их собственные физические серверы в локальных сетях (on-premise), сохраняя при этом работу приложения в центральном облаке.
|
||||
|
||||
### 1.2 Архитектурная схема движения трафика
|
||||
Ниже представлена разработанная мной схема прохождения сетевых запросов и агрегации телеметрии в системе:
|
||||
|
||||
```mermaid
|
||||
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; версия и manifest digest закреплены в Dockerfile.
|
||||
* Для балансировки и внутреннего доступа настроен `Service` типа ClusterIP, слушающий порт 80.
|
||||
|
||||
* **Backend (`backend.yaml`)**:
|
||||
* `Deployment` с 1-2 репликами (детали балансировки конфигурации описаны ниже).
|
||||
* Для сборки образов используется multi-stage сборка Maven (JDK 17); build/runtime-образы одновременно закреплены точным tag и manifest digest.
|
||||
* Интегрирован Java-агент OpenTelemetry для автоматического инструментирования трассировки и логов.
|
||||
* Для связи с Ingress настроен ClusterIP-сервис на порту 8080.
|
||||
|
||||
* **Ingress (`ingress.yaml`)**:
|
||||
* В роли Ingress-контроллера выступает встроенный **Traefik** (`kube-system/traefik`).
|
||||
* Он доступен извне через Klipper Service LoadBalancer (DaemonSet), который прокидывает порты 80/443 (hostPort) на внешние IP адреса нод кластера (`192.168.1.104`, `192.168.1.105`, `192.168.1.106`).
|
||||
* Мной настроены строгие правила маршрутизации по доменным именам (например, `magistr.zuev.company` и `n8n.zuev.company`). Запросы к API (`/api`) проксируются на бэкенд, а к корню (`/`) — на фронтенд.
|
||||
|
||||
### 3.2 Реализация динамической мультитенантности через K8s API
|
||||
Одним из наиболее сложных этапов проектирования стала организация бесшовного добавления новых университетов без перезапуска бэкенда и изменения исходного кода приложения.
|
||||
|
||||
1. **Хранение конфигурации**:
|
||||
Список подключений к базам данных университетов хранится в JSON-формате (`tenants.json`) внутри Kubernetes `ConfigMap` с именем `tenants-config`.
|
||||
|
||||
2. **Проблема Read-Only монтирования**:
|
||||
По умолчанию Kubernetes монтирует ConfigMap как файловую систему в режиме Read-Only. Это исключало возможность для Java-приложения перезаписывать `tenants.json` при запросах от администратора на добавление нового тенанта.
|
||||
|
||||
3. **Решение с использованием `initContainer` и `emptyDir`**:
|
||||
Я спроектировал схему с временным перезаписываемым томом (`emptyDir`):
|
||||
* В спецификацию пода backend добавлен `initContainer` на базе образа `busybox`.
|
||||
* При старте пода `initContainer` монтирует ConfigMap `tenants-config` и копирует файл `tenants.json` во временный раздел `emptyDir` (путь `/config`).
|
||||
* Основной контейнер `backend` монтирует этот же `emptyDir` в режиме Read-Write.
|
||||
|
||||
4. **K8s RBAC и динамическое сохранение**:
|
||||
Чтобы предоставить бэкенду возможность сохранять изменения конфигурации обратно в кластер, мной были написаны манифесты авторизации (`rbac.yaml`):
|
||||
* Создан `ServiceAccount` `backend-sa` и назначен поду бэкенда.
|
||||
* Описана Kubernetes `Role` `backend-configmap-role`, дающая права только на операции `get` и `patch` для ресурса `configmaps` с конкретным именем `tenants-config` (принцип наименьших привилегий).
|
||||
* Создан `RoleBinding` `backend-configmap-binding`, связывающий сервис-аккаунт с этой ролью.
|
||||
|
||||
В коде бэкенда класс `ConfigMapUpdater` через Kubernetes Java Client отправляет PATCH-запрос к API-серверу при добавлении нового тенанта, обновляя ConfigMap в самом кластере.
|
||||
|
||||
5. **Репликация и синхронизация подов**:
|
||||
Для обеспечения консистентности данных в условиях работы нескольких реплик бэкенда:
|
||||
* Класс `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`):
|
||||
|
||||
1. **`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`).
|
||||
|
||||
2. **`docker` (Среда контейнеризации)**:
|
||||
* Автоматическое добавление официальных GPG-ключей и репозиториев Docker CE.
|
||||
* Установка Docker Engine, CLI и Compose плагина.
|
||||
* Включение системной службы `docker` и добавление пользователя `deploy` в группу `docker` для беспарольного запуска контейнеров.
|
||||
|
||||
3. **`firewall` (Сетевая безопасность)**:
|
||||
* Настройка межсетевого экрана UFW (для Debian-based систем) или firewalld (для RedHat и ALT Linux).
|
||||
* Полная блокировка входящего трафика по умолчанию.
|
||||
* Разрешение входящих пакетов только на порт SSH (`2222`) и порт PostgreSQL (`5432`). Реализована поддержка ограничения доступа к порту `5432` только для IP-адресов нод Kubernetes-кластера (`firewall_postgresql_allowed_ips`).
|
||||
|
||||
4. **`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`).
|
||||
|
||||
5. **`backup` (Резервное копирование на уровне БД)**:
|
||||
* Установка на хост автоматического bash-скрипта бэкапа.
|
||||
* Настройка задачи `cron` (запуск ежедневно в 03:00).
|
||||
* Скрипт осуществляет горячий дамп базы через `docker exec pg_dump`, архивирует его с помощью `gzip` и сохраняет в локальный каталог `/opt/magistr/backups/`.
|
||||
* Реализован алгоритм автоматической ротации: удаление архивных файлов старше заданного количества дней (по умолчанию 14 дней).
|
||||
|
||||
6. **`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` запускается конвейер:
|
||||
1. **Checks**: Backend/frontend-тесты, Compose validation, тест immutable rollback и статическая проверка закрепления артефактов.
|
||||
2. **Build & Push**: Параллельная сборка и публикация backend/frontend с SHA-tag или release tag; mutable `latest` не создаётся.
|
||||
3. **Attestations**: BuildKit добавляет к обоим образам SBOM и максимальную provenance-attestation.
|
||||
4. **Security scan**: Закреплённый digest Trivy блокирует доставку при исправимых уязвимостях `HIGH`/`CRITICAL`.
|
||||
5. **Deploy**: Только прошедшие gates registry digests передаются в production rollout. Все сторонние Actions закреплены полными commit SHA.
|
||||
|
||||
### 5.2 Доставка в кластер (CD)
|
||||
Для авторизации нод K3s в приватном реестре Gitea мной был создан секрет `gitea-registry` типа `docker-registry` в пространстве имен `magistr`. Этот секрет ассоциирован со спецификациями деплоев через директиву `imagePullSecrets`.
|
||||
|
||||
Непосредственно Gitea Actions Runner работает как демон `act_runner` внутри изолированного LXC контейнера (CTID 107). В пайплайне шаг развертывания (`deploy-to-k8s`) динамически генерирует `kubeconfig` из секрета, проверяет checksum закреплённой версии `kubectl` и выполняет атомарное обновление обоих образов без использования тяжеловесных GitOps операторов:
|
||||
|
||||
```bash
|
||||
bash scripts/deploy-images.sh \
|
||||
gitea.zuev.company/zuev/magistr-backend@sha256:<digest> \
|
||||
gitea.zuev.company/zuev/magistr-frontend@sha256:<digest>
|
||||
```
|
||||
|
||||
Скрипт принимает только полные `image@sha256:...`, ожидает готовность обоих Deployment и при
|
||||
ошибке возвращает предыдущую пару digest. Поэтому содержимое релиза не зависит от повторного
|
||||
разрешения mutable tag.
|
||||
|
||||
---
|
||||
|
||||
## РАЗДЕЛ 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-сервер развернут в изолированном LXC контейнере (CTID 101) и обрабатывает запросы ко всем поддоменам проекта. Он перенаправляет их на ноды кластера K3s, выполняя роль внешнего балансировщика, а также проксирует телеметрию с фронтенда напрямую в сборщик OTel:
|
||||
|
||||
```caddyfile
|
||||
(otel_proxy) {
|
||||
handle_path /otel/* { reverse_proxy 192.168.1.100:4318 }
|
||||
}
|
||||
|
||||
(k8s_nodes) {
|
||||
handle {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
magistr.zuev.company { import otel_proxy; import k8s_nodes }
|
||||
n8n.zuev.company { import otel_proxy; import k8s_nodes }
|
||||
```
|
||||
Сетевые запросы к приложениям распределяются по 3 физическим нодам K3s. Благодаря сниппетам, добавление нового тенанта в инфраструктуре Caddy требует всего двух строчек конфигурации.
|
||||
|
||||
---
|
||||
|
||||
## РАЗДЕЛ 7. СКВОЗНОЙ МОНИТОРИНГ И ОБСЕРВАБИЛИТИ (SigNoz + OpenTelemetry)
|
||||
|
||||
Для контроля здоровья системы и оперативного выявления аномалий мной была спроектирована и внедрена централизованная система мониторинга на базе APM-платформы **SigNoz** (с хранилищем **ClickHouse**) и стандартов **OpenTelemetry (OTel)**.
|
||||
|
||||
Платформа SigNoz вынесена в отдельный LXC контейнер (CTID 100) по адресу `192.168.1.100` и развернута через `docker-compose`. Данные ClickHouse и SQLite метаданные персистентно хранятся в Docker volumes хоста для обеспечения сохранности при рестартах.
|
||||
|
||||
### 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 Мониторинг баз данных на хостах
|
||||
Для сбора детальной статистики с изолированных серверов баз данных:
|
||||
1. На хостах БД силами Ansible разворачивается OpenTelemetry Collector.
|
||||
2. В конфигурации коллектора (`otel-collector-config.yml.j2`) описывается ресивер `postgresql`, который подключается к локальной БД и считывает системные метрики (размер таблиц, cache hit ratio, количество транзакций, активные сессии, блокировки).
|
||||
3. К собираемым метрикам жестко прикрепляются ресурсные атрибуты хоста: `service.name=magistr-db-<tenant_domain>` и `university.name=<University Name>`.
|
||||
4. Метрики экспортируются в центральный коллектор SigNoz. В результате в интерфейсе SigNoz доступны кастомные дашборды JVM, PostgreSQL и HTTP, позволяющие оперативно локализовать проблемы с производительностью баз данных конкретных вузов.
|
||||
|
||||
---
|
||||
|
||||
## ЗАКЛЮЧЕНИЕ
|
||||
|
||||
Разработанная и внедренная мной DevOps-архитектура для проекта «Магистр» успешно решила задачи изоляции данных, отказоустойчивости и автоматизации администрирования.
|
||||
|
||||
### Ключевые результаты проделанной работы:
|
||||
1. **Изоляция данных**: Реализована физическая изоляция БД университетов на выделенных серверах (VM в Proxmox VE), соответствующая требованиям безопасности и законодательства.
|
||||
2. **Динамическое масштабирование**: Внедрен механизм динамического добавления тенантов без простоя системы (zero-downtime) за счет связки Spring Boot, K8s RBAC и ConfigMapWatcher.
|
||||
3. **Отказоустойчивость**: Минимизированы риски каскадных сбоев бэкенда за счет применения in-memory fallback баз данных и оптимизации параметров подключения HikariCP.
|
||||
4. **Автоматизация**: Создан универсальный Ansible-плейбук для быстрого ввода в эксплуатацию новых серверов БД (время развертывания «под ключ» сокращено до нескольких минут).
|
||||
5. **Наблюдаемость**: Реализован сквозной мониторинг логов, метрик и трассировок с детализацией по тенантам на базе стека OpenTelemetry + SigNoz.
|
||||
|
||||
Созданная инфраструктура обладает высокой степенью гибкости и готова к дальнейшему горизонтальному масштабированию по мере подключения новых высших учебных заведений к проекту «Магистр».
|
||||
162
STARTUP_GUIDE.md
162
STARTUP_GUIDE.md
@@ -1,162 +0,0 @@
|
||||
# Инструкция запуска Magistr
|
||||
|
||||
Ниже — минимальный порядок действий. Подробности по инфраструктуре и секретам находятся в
|
||||
[`docs/INFRASTRUCTURE.md`](docs/INFRASTRUCTURE.md) и
|
||||
[`docs/SECURITY_RUNBOOK.md`](docs/SECURITY_RUNBOOK.md).
|
||||
|
||||
## 1. Локальный запуск
|
||||
|
||||
Нужны Git, Docker Engine, Docker Compose и запущенный Caddy из `../сaddy-proxy/`.
|
||||
|
||||
1. В корне проекта создайте локальный файл настроек:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
openssl rand -base64 36 # значение POSTGRES_PASSWORD
|
||||
openssl rand -base64 48 # значение JWT_SECRET
|
||||
```
|
||||
|
||||
Вставьте полученные значения в `.env`. Файл `.env` не коммитьте.
|
||||
|
||||
2. Один раз создайте общую Docker-сеть, затем запустите Caddy и Magistr:
|
||||
|
||||
```bash
|
||||
docker network inspect proxy >/dev/null 2>&1 || docker network create proxy
|
||||
docker compose -f ../сaddy-proxy/compose.yaml up -d
|
||||
docker compose config --quiet
|
||||
docker compose up -d --build
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
3. Откройте `https://localhost` (`http://localhost` перенаправит на HTTPS). Проверьте backend
|
||||
через Caddy:
|
||||
|
||||
```bash
|
||||
curl -kfsS https://localhost/actuator/health/readiness
|
||||
```
|
||||
|
||||
Если браузер не доверяет локальному сертификату Caddy, на CachyOS/Arch выполните:
|
||||
|
||||
```bash
|
||||
docker cp caddy:/data/caddy/pki/authorities/local/root.crt /tmp/caddy-local-root.crt
|
||||
sudo trust anchor --store /tmp/caddy-local-root.crt
|
||||
```
|
||||
|
||||
До установки проверьте SHA-256 отпечаток командой
|
||||
`openssl x509 -in /tmp/caddy-local-root.crt -noout -fingerprint -sha256`, затем полностью
|
||||
перезапустите браузер.
|
||||
|
||||
Для первого локального входа: `admin` / `admin`. Эти данные предназначены только для
|
||||
разработки.
|
||||
|
||||
4. Если запуск не удался:
|
||||
|
||||
```bash
|
||||
docker compose logs --tail=200 backend db frontend
|
||||
```
|
||||
|
||||
Полный локальный сброс БД выполняется командой `docker compose down -v`, но она удалит
|
||||
все локальные данные. Обычная остановка без удаления данных: `docker compose down`.
|
||||
|
||||
## 2. Обновление существующего production
|
||||
|
||||
Push в `main` автоматически выполняет проверки, собирает и сканирует образы, а затем
|
||||
обновляет backend/frontend в Kubernetes по проверенным immutable digest. Новые файлы
|
||||
`../k8s` CI не применяет: первый rollout и изменения самих манифестов выполняются
|
||||
оператором через `../k8s/deploy.sh`. Gitea 1.25 игнорирует `environment` и `concurrency`,
|
||||
поэтому они не используются как production-защита.
|
||||
|
||||
1. Подготовьте секреты по [`docs/SECURITY_RUNBOOK.md`](docs/SECURITY_RUNBOOK.md):
|
||||
|
||||
- `app-secret`: новый случайный `JWT_SECRET` не короче 32 байт, `POSTGRES_USER`,
|
||||
`POSTGRES_PASSWORD`;
|
||||
- `tenants-secret`: новый `tenants.json` с актуальными JDBC URL и ротированными паролями;
|
||||
- `otel-postgres-secret`: восемь обязательных параметров подключений Collector;
|
||||
- `gitea-registry`: доступ K3s к registry.
|
||||
|
||||
Для `magistr.zuev.company` нужен tenant с `"domain": "magistr"`, для
|
||||
`n8n.zuev.company` — `"domain": "n8n"`. Секреты не сохраняйте в Git или CI-логах.
|
||||
|
||||
2. Выполните локальные проверки, затем отправьте изменения в `main`:
|
||||
|
||||
```bash
|
||||
K8S_DIR=../k8s bash scripts/check-production-secrets.sh
|
||||
K8S_DIR=../k8s bash scripts/check-artifact-pinning.sh
|
||||
bash scripts/test-deploy-images.sh
|
||||
kubectl kustomize ../k8s >/dev/null
|
||||
```
|
||||
|
||||
Дождитесь успешных checks, сборки и Trivy scan. Скопируйте точные backend/frontend
|
||||
ссылки вида `image@sha256:...` из CI/registry.
|
||||
|
||||
3. Объявите техническое окно и временно закройте публичный доступ во внешнем Caddy. Затем
|
||||
остановите backend:
|
||||
|
||||
```bash
|
||||
kubectl scale deployment/backend -n magistr --replicas=0
|
||||
```
|
||||
|
||||
Полностью пересоздайте **каждую** PostgreSQL tenant-БД, указанную в новом
|
||||
`tenants.json`. Недостаточно удалить `flyway_schema_history` или отдельные таблицы:
|
||||
миграции V2–V7 объединены в новую `V1__init.sql`, поэтому старая БД получит checksum
|
||||
error, а непустая БД без истории может быть ошибочно принята Flyway за baseline.
|
||||
Пользователь БД должен уметь создавать схему, функции, триггеры и расширения
|
||||
`pgcrypto`/`btree_gist`.
|
||||
|
||||
4. С рабочей станции с актуальным каталогом `../k8s` выполните первый rollout вручную:
|
||||
|
||||
```bash
|
||||
export BACKEND_IMAGE_REF='gitea.zuev.company/zuev/magistr-backend@sha256:<digest>'
|
||||
export FRONTEND_IMAGE_REF='gitea.zuev.company/zuev/magistr-frontend@sha256:<digest>'
|
||||
bash ../k8s/deploy.sh apply
|
||||
```
|
||||
|
||||
Скрипт применит ConfigMap, RBAC, Secret mount, probes, OTel и Ingress, поднимет две
|
||||
реплики backend и дождётся Flyway/readiness. После этого следующие image-only
|
||||
обновления автоматически разворачиваются успешным workflow после push в `main`.
|
||||
`workflow_dispatch` остаётся дополнительным способом повторного запуска, но не
|
||||
требуется для обычной поставки.
|
||||
|
||||
5. Перед открытием публичного доступа проверьте:
|
||||
|
||||
```bash
|
||||
kubectl rollout status deployment/backend -n magistr --timeout=300s
|
||||
kubectl rollout status deployment/frontend -n magistr --timeout=120s
|
||||
kubectl get pods -n magistr
|
||||
kubectl logs -n magistr -l app=backend --tail=200
|
||||
curl -fsSI https://magistr.zuev.company/
|
||||
```
|
||||
|
||||
Проверьте в браузере login → reload → refresh → logout для каждого tenant-домена.
|
||||
Refresh-cookie должна иметь `HttpOnly`, `Secure`, `SameSite=Lax`, path `/api/auth`;
|
||||
access JWT не должен находиться в Local/Session Storage. До снятия технического окна
|
||||
смените или отключите все демонстрационные учётные записи из `V1__init.sql`.
|
||||
|
||||
6. После проверки удалите старый `ConfigMap/tenants-config`, отзовите старые DB/OTel
|
||||
пароли и верните обычный доступ через Caddy. В дальнейшем image-only изменения
|
||||
автоматически разворачиваются после push в `main`, но любые изменения `../k8s`
|
||||
по-прежнему нужно применять отдельно через `../k8s/deploy.sh`.
|
||||
|
||||
### Важные ограничения rollback
|
||||
|
||||
- Смена `JWT_SECRET` и сброс БД завершат все старые пользовательские сессии.
|
||||
- Readiness требует доступности и успешной миграции всех tenant-БД; одна старая или
|
||||
недоступная БД остановит rollout.
|
||||
- Автоматический rollback возвращает только два image reference. Он не откатывает БД,
|
||||
Secret, ConfigMap или RBAC; после применения новой V1 безопаснее исправлять проблему
|
||||
вперёд либо снова пересоздавать БД под старую версию.
|
||||
- CI не запускает Testcontainers integration tests: полный PostgreSQL-прогон нужно
|
||||
повторить вручную, если после зафиксированных 300 успешных тестов код ещё менялся.
|
||||
- `TRUSTED_PROXY_CIDRS=10.42.0.0/16` соответствует текущей pod-сети K3s. При смене
|
||||
cluster CIDR его нужно обновить, иначе rate limit увидит неверные клиентские IP.
|
||||
|
||||
## 3. Что могу выполнить я
|
||||
|
||||
По вашей команде я могу подготовить `.env`, запустить и диагностировать локальный Compose,
|
||||
проверить/исправить код и манифесты, прогнать тесты и dry-run Kubernetes, а при доступном
|
||||
`kubectl` — проверить состояние кластера.
|
||||
|
||||
Без вас я не могу получить реальные пароли и токены, настроить DNS/сертификаты у внешнего
|
||||
провайдера или войти в Gitea/Kubernetes, если доступы не предоставлены. Production deploy,
|
||||
ротацию Secret и удаление БД я не выполняю без отдельного явного разрешения: это меняет
|
||||
внешнюю систему и может прервать работу либо уничтожить данные.
|
||||
Reference in New Issue
Block a user