Задача Егора
This commit is contained in:
116
docs/API.md
116
docs/API.md
@@ -1,6 +1,7 @@
|
||||
# 🔌 REST API
|
||||
|
||||
Все эндпоинты имеют префикс `/api/`. Ответы возвращаются в формате JSON.
|
||||
Все прикладные эндпоинты имеют префикс `/api/`. Служебные проверки Kubernetes доступны
|
||||
под `/actuator/health/`. Ответы возвращаются в формате JSON.
|
||||
|
||||
Необработанные ошибки проходят через единый `GlobalExceptionHandler`. Для `400`, `404` и `500` используется общий JSON-формат:
|
||||
|
||||
@@ -18,6 +19,53 @@
|
||||
|
||||
---
|
||||
|
||||
## Служебные проверки состояния
|
||||
|
||||
Эти endpoints предназначены для Kubernetes kubelet, не требуют bearer-токен и не зависят
|
||||
от tenant-домена в заголовке `Host`. Из Actuator наружу опубликован только `health`, а
|
||||
состав компонентов, домены, JDBC URL и credentials в ответах скрыты.
|
||||
|
||||
### `GET /actuator/health/liveness`
|
||||
|
||||
Проверяет только жизнеспособность процесса. Недоступность tenant-БД не меняет liveness и
|
||||
не должна создавать цикл перезапусков pod.
|
||||
|
||||
**Ответ работающего процесса (200):**
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "UP"
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /actuator/health/readiness`
|
||||
|
||||
Разрешает направлять трафик в pod только после успешных миграций и свежей успешной
|
||||
проверки соединения со всеми обязательными tenant-БД. Пустая конфигурация, H2-заглушка,
|
||||
ошибка чтения tenant-конфигурации, незавершённая или неуспешная миграция, недоступное либо
|
||||
просроченное соединение делают pod неготовым.
|
||||
|
||||
**Готов (200):**
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "UP"
|
||||
}
|
||||
```
|
||||
|
||||
**Не готов (503):**
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "DOWN"
|
||||
}
|
||||
```
|
||||
|
||||
`UP` и `DOWN` — стандартные машинные значения протокола Spring Boot Actuator, а не
|
||||
пользовательские сообщения интерфейса.
|
||||
|
||||
---
|
||||
|
||||
## Аутентификация
|
||||
|
||||
### `POST /api/auth/login`
|
||||
@@ -1090,6 +1138,8 @@ GET /api/workload/teachers?departmentId=1&startDate=2026-05-20&endDate=2026-06-0
|
||||
|
||||
## Управление тенантами (Базы данных)
|
||||
|
||||
Все endpoints раздела доступны только пользователю с ролью `ADMIN`.
|
||||
|
||||
### `GET /api/database/status`
|
||||
|
||||
Статус текущего подключения (определяется по домену запроса).
|
||||
@@ -1107,11 +1157,23 @@ GET /api/workload/teachers?departmentId=1&startDate=2026-05-20&endDate=2026-06-0
|
||||
|
||||
### `GET /api/database/tenants`
|
||||
|
||||
Список всех тенантов.
|
||||
Список всех тенантов. Пароль в ответ не включается.
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "СВФУ",
|
||||
"domain": "swsu",
|
||||
"url": "jdbc:postgresql://db-host:5432/swsu_db",
|
||||
"username": "dbuser",
|
||||
"connected": true
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### `POST /api/database/tenants`
|
||||
|
||||
Добавление нового тенанта.
|
||||
Создание нового или обновление существующего тенанта по `domain`.
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -1123,19 +1185,56 @@ GET /api/workload/teachers?departmentId=1&startDate=2026-05-20&endDate=2026-06-0
|
||||
}
|
||||
```
|
||||
|
||||
`domain` приводится к нижнему регистру и должен быть одной DNS-меткой длиной от 1 до
|
||||
63 символов. `url` обязателен и должен начинаться с `jdbc:`. Если `name` пуст, вместо
|
||||
него используется нормализованный `domain`.
|
||||
|
||||
**Логика:**
|
||||
1. Создаёт HikariCP пул для нового тенанта
|
||||
2. Запускает Flyway миграции на его БД
|
||||
3. Обновляет внешний Kubernetes Secret `tenants-secret`
|
||||
1. Создаёт временный HikariCP pool, ещё не доступный маршрутизатору.
|
||||
2. Открывает соединение и явно проверяет его готовность.
|
||||
3. Выполняет Flyway-валидацию и миграции tenant-БД.
|
||||
4. Читает актуальный `tenants-secret`, применяет только upsert запрошенного `domain` и
|
||||
выполняет условный `PUT` с прочитанным Kubernetes `resourceVersion`.
|
||||
5. При конфликте повторно читает Secret и заново применяет свою мутацию с ограниченным
|
||||
retry/backoff; неизменившаяся конфигурация не записывается повторно.
|
||||
6. Одной атомарной публикацией заменяет связку `TenantConfig + DataSource`.
|
||||
7. Передаёт прежний pool на отложенное закрытие после завершения активных запросов
|
||||
либо по истечении защитного таймаута.
|
||||
|
||||
Backend соединяется с Kubernetes API только через проверенный service-account CA и
|
||||
hostname verification. Если безопасно сохранить tenant-конфигурацию не удалось, операция
|
||||
не возвращается как успешная. Значения credentials никогда не включаются в ответ или лог
|
||||
Kubernetes updater.
|
||||
|
||||
При ошибке credentials, проверки соединения, Flyway или сохранения Secret временный pool
|
||||
закрывается, а прежнее подключение продолжает обслуживать запросы. Если Kubernetes
|
||||
подтвердил запись, но последующая локальная активация завершилась ошибкой, backend
|
||||
восстанавливает прежний снимок только пока Secret сохраняет `resourceVersion` этой записи.
|
||||
Более новое изменение другого pod не перезаписывается. Неопределённый сетевой результат
|
||||
сначала сверяется повторным чтением и не создаёт основания для небезопасной компенсации.
|
||||
Ошибка lifecycle возвращается как безопасный русский ответ без JDBC/Flyway details:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "Не удалось выполнить миграции базы данных тенанта"
|
||||
}
|
||||
```
|
||||
|
||||
HTTP-статусы: `400` для некорректного payload и `503` для ошибки подключения, миграции,
|
||||
персистенции или активации.
|
||||
|
||||
Вне Kubernetes обновление Secret пропускается, поэтому добавленный через API tenant живёт
|
||||
только до перезапуска процесса. Для постоянной локальной конфигурации используется
|
||||
неотслеживаемый файл `backend/tenants.json`.
|
||||
|
||||
### `DELETE /api/database/tenants/{domain}`
|
||||
|
||||
Удаление тенанта.
|
||||
Удаление тенанта. Backend читает актуальный Secret, удаляет только запрошенный `domain` и
|
||||
выполняет условный `PUT` по `resourceVersion`, затем атомарно исключает tenant из локальной
|
||||
маршрутизации и передаёт pool на отложенное закрытие. При отказе локального удаления
|
||||
компенсация также допускается только для подтверждённой версии и не затирает более новое
|
||||
изменение другого pod. Неизвестный `domain` возвращает `404`, lifecycle-ошибка — `503`.
|
||||
|
||||
### `POST /api/database/test`
|
||||
|
||||
@@ -1166,5 +1265,8 @@ Kubernetes updater.
|
||||
| `200` | Успех |
|
||||
| `400` | Ошибка валидации или некорректные параметры запроса |
|
||||
| `401` | Неверные учётные данные |
|
||||
| `403` | Недостаточно прав для операции |
|
||||
| `404` | Ресурс / тенант не найден |
|
||||
| `409` | Конфликт бизнес-инвариантов или конкурентного изменения |
|
||||
| `500` | Внутренняя ошибка сервера |
|
||||
| `503` | Внешняя БД или обязательная инфраструктура временно недоступна |
|
||||
|
||||
@@ -78,11 +78,18 @@ sequenceDiagram
|
||||
|-------|-----------|
|
||||
| `TenantInterceptor` | Извлекает поддомен из заголовка `Host` и определяет тенант |
|
||||
| `TenantContext` | `ThreadLocal`-хранилище имени текущего тенанта |
|
||||
| `TenantRoutingDataSource` | Наследует `AbstractRoutingDataSource`, маршрутизирует запросы к нужной БД |
|
||||
| `TenantRoutingDataSource` | Маршрутизирует запросы и атомарно публикует неизменяемый снимок `TenantConfig + DataSource` |
|
||||
| `TenantDataSourceConfig` | Загружает конфигурацию тенантов из JSON-файла, создаёт HikariCP пулы |
|
||||
| `TenantWebMvcConfig` | Регистрирует `TenantInterceptor` и `AuthorizationInterceptor` в MVC-слое |
|
||||
| `TenantConfigWatcher` | Периодически (каждые 30 сек) перечитывает `tenants.json`, синхронизирует тенантов |
|
||||
| `KubernetesTenantSecretUpdater` | Обновляет Kubernetes Secret с tenant-конфигурацией через API, проверяя TLS по service-account CA |
|
||||
| `TenantConfigStore` | Задаёт явные атомарные операции upsert/remove и условную компенсацию persisted-конфигурации |
|
||||
| `KubernetesTenantSecretUpdater` | Реализует `TenantConfigStore` через GET и условный PUT Kubernetes Secret по `resourceVersion`, проверяя TLS по service-account CA |
|
||||
| `TenantLifecycleService` | Сериализует prepare/validate/migrate/persist/swap/remove и выполняет компенсацию при отказе |
|
||||
| `TenantDatabaseMigrationService` | Запускает Flyway для подготовленного, ещё не опубликованного pool |
|
||||
| `RetiredTenantPoolService` | После swap дожидается завершения активных подключений старого pool и закрывает его |
|
||||
| `TenantReadinessRegistry` | Хранит атомарный снимок обязательных tenant, миграций и свежести проверок соединения |
|
||||
| `TenantDatabaseHealthMonitor` | Ограниченно-параллельно проверяет соединения с обязательными tenant-БД в фоне |
|
||||
| `TenantReadinessHealthIndicator` | Включает агрегированную готовность tenant-БД в Actuator readiness group |
|
||||
| `TenantConfig` | POJO с параметрами тенанта: `name`, `domain`, `url`, `username`, `password` |
|
||||
|
||||
### Определение тенанта
|
||||
@@ -121,16 +128,84 @@ sequenceDiagram
|
||||
|
||||
### Жизненный цикл тенанта
|
||||
|
||||
1. **Добавление через API:** `POST /api/database/tenants` → создаёт HikariCP пул → запускает Flyway миграции → обновляет `tenants-secret`
|
||||
2. **Синхронизация подов:** `TenantConfigWatcher` каждые 30 сек проверяет смонтированный `tenants.json` → добавляет новые / удаляет отсутствующие тенанты
|
||||
3. **Удаление:** `DELETE /api/database/tenants/{domain}` → закрывает пул → обновляет `tenants-secret`
|
||||
1. **Добавление или обновление через API:** `TenantLifecycleService` нормализует конфигурацию,
|
||||
создаёт непубликуемый HikariCP candidate, проверяет реальное соединение и выполняет
|
||||
Flyway. Затем `TenantConfigStore` читает актуальный Secret и применяет к нему только
|
||||
upsert запрошенного домена.
|
||||
2. **Межподовая запись:** `KubernetesTenantSecretUpdater` выполняет `GET` текущего
|
||||
`tenants-secret`, нормализует и сортирует список, после чего отправляет полный объект
|
||||
условным `PUT` с прочитанным `metadata.resourceVersion`. При `409 Conflict` актуальное
|
||||
состояние читается заново, а собственная мутация повторно применяется к нему. Число
|
||||
попыток ограничено тремя, задержка между попытками возрастает; идемпотентная операция
|
||||
не отправляет лишний `PUT`.
|
||||
3. **Атомарная локальная публикация:** только после успешной или подтверждённой повторным
|
||||
чтением персистенции одной публикацией заменяется связка `TenantConfig + DataSource`.
|
||||
4. **Безопасный отказ и компенсация:** ошибка credentials, соединения, Flyway или Secret
|
||||
закрывает candidate, не меняя действующий route. Компенсация прежним снимком разрешена
|
||||
только для подтверждённой записи и только пока текущий Secret сохраняет выданный ей
|
||||
`resourceVersion`. Если другой pod уже записал более новую версию, автоматический откат
|
||||
пропускается. Неопределённый сетевой результат сначала сверяется повторным `GET` и не
|
||||
создаёт квитанцию для небезопасной компенсации. HTTP-граница получает русское безопасное
|
||||
сообщение без JDBC/Flyway details.
|
||||
5. **Drain старого pool:** после успешного swap новые запросы сразу используют candidate,
|
||||
а прежний Hikari pool закрывается после завершения активных подключений либо по истечении
|
||||
настраиваемого grace timeout.
|
||||
6. **Синхронизация подов:** `TenantConfigWatcher` каждые 30 секунд проверяет смонтированный
|
||||
`tenants.json` и применяет добавление/удаление под тем же локальным lifecycle-monitor.
|
||||
7. **Удаление:** `DELETE /api/database/tenants/{domain}` применяет к актуальному Secret только
|
||||
удаление указанного домена через тот же условный `PUT`, затем атомарно исключает tenant
|
||||
из маршрутизации и передаёт pool на drain. Для отказа локального удаления действуют те же
|
||||
ограничения компенсации по `resourceVersion`.
|
||||
|
||||
Сериализация lifecycle и атомарный снимок защищают запросы внутри одного backend-процесса.
|
||||
Между pod потерянное обновление предотвращает optimistic locking Kubernetes: каждый конфликт
|
||||
заставляет заново прочитать Secret и повторно применить только свою доменную мутацию. Локальный
|
||||
monitor по-прежнему не считается межподовой блокировкой.
|
||||
|
||||
Вне Kubernetes `KubernetesTenantSecretUpdater` работает как runtime-only адаптер: Secret и
|
||||
локальный `tenants.json` не изменяются. Постоянная локальная конфигурация задаётся файлом до
|
||||
запуска приложения.
|
||||
|
||||
Для обращения к Kubernetes API backend загружает
|
||||
`/var/run/secrets/kubernetes.io/serviceaccount/ca.crt`, использует стандартную PKIX-проверку
|
||||
цепочки и обязательную проверку hostname. Trust-all fallback отсутствует: ошибка CA или TLS
|
||||
завершает персистенцию безопасным отказом.
|
||||
|
||||
`TenantConfigWatcher` сравнивает содержимое `tenants.json` по SHA-256, а не по `String.hashCode()`, чтобы изменение ConfigMap не пропускалось из-за 32-битной коллизии.
|
||||
`TenantConfigWatcher` сравнивает содержимое смонтированного из Secret `tenants.json` по
|
||||
SHA-256, а не по `String.hashCode()`, чтобы изменение файла не пропускалось из-за
|
||||
32-битной коллизии. На текущем этапе watcher активирует новые домены и удаляет отсутствующие,
|
||||
но ещё не заменяет подключение существующего домена при изменении его URL или credentials.
|
||||
Полная синхронизация такого изменения относится к проблеме №13; до её выполнения новое
|
||||
значение сбрасывает readiness этого tenant в состояние ожидания, а pod не должен считаться
|
||||
готовым по старому подключению.
|
||||
|
||||
### Liveness и readiness
|
||||
|
||||
Spring Boot Actuator публикует две независимые служебные группы:
|
||||
|
||||
- `GET /actuator/health/liveness` включает только `livenessState` приложения. Отказ любой
|
||||
tenant-БД не переводит процесс в liveness failure и не создаёт restart storm;
|
||||
- `GET /actuator/health/readiness` включает `readinessState` и `tenantReadiness`. Ответ имеет
|
||||
статус `UP` только если задан непустой набор обязательных tenant, миграция каждого успешно
|
||||
завершена и для каждого есть свежая успешная проверка соединения.
|
||||
|
||||
`TenantDataSourceConfig` регистрирует обязательный набор до startup-активации и сохраняет
|
||||
неуспешную миграцию как readiness failure. H2-заглушка позволяет инициализировать Spring JPA,
|
||||
но никогда не делает приложение готовым к production-трафику. Ошибка чтения обязательного
|
||||
`tenants.json` также фиксируется отдельно от состояния процесса.
|
||||
|
||||
`TenantDatabaseHealthMonitor` по умолчанию запускается через секунду после старта, затем
|
||||
проверяет соединения каждые 10 секунд пакетами до четырёх параллельных задач. Таймаут пакета
|
||||
составляет 6 секунд: по его истечении отменяются только незавершённые задачи, а уже полученные
|
||||
результаты сохраняются с фактическим временем завершения проверки. Результат старше 30 секунд
|
||||
считается просроченным. Запоздалый результат привязан к HMAC-fingerprint конкретной конфигурации
|
||||
и не может отметить новую конфигурацию готовой; исходные credentials в fingerprint и
|
||||
health-ответ не попадают. Планировщик использует два потока, поэтому ожидающий health-pass не
|
||||
задерживает периодическое чтение tenant Secret.
|
||||
|
||||
Tenant interceptor исключает `/actuator/**`, а authorization interceptor применяется только
|
||||
к `/api/**`. При этом наружу опубликован только endpoint `health`, его details и components
|
||||
скрыты: kubelet получает минимальный `UP`/`DOWN` без доменов и реквизитов tenant-БД.
|
||||
|
||||
### Fallback при отсутствии тенантов
|
||||
|
||||
@@ -138,6 +213,9 @@ sequenceDiagram
|
||||
1. Проверяется наличие `spring.datasource.url` → создаётся тенант `default`
|
||||
2. Если datasource тоже нет → создаётся H2 in-memory заглушка для инициализации Spring JPA
|
||||
|
||||
Для production обязательность смонтированной конфигурации задаётся
|
||||
`TENANTS_CONFIG_REQUIRED=true`; отсутствие или ошибка чтения файла снимают readiness.
|
||||
|
||||
`TenantContext` хранит имя текущего тенанта в `ThreadLocal` и очищается интерцептором после завершения запроса. Виртуальные потоки в приложении не включены; если их включать в будущем, нужно отдельно проверить перенос и очистку tenant/auth context на асинхронных участках.
|
||||
|
||||
---
|
||||
|
||||
@@ -59,7 +59,9 @@ RUN chown -R www-data:www-data /usr/local/apache2/htdocs/
|
||||
|
||||
### Расположение конфигурации
|
||||
|
||||
Файлы Kubernetes манифестов: `../k8s/`
|
||||
Ожидаемое расположение production-манифестов: `../k8s/`. В текущей рабочей копии этот
|
||||
внешний каталог недоступен, поэтому приведённые ниже изменения являются обязательным
|
||||
runbook для оператора, а не утверждением о применённом или проверенном состоянии кластера.
|
||||
|
||||
### Ключевые ресурсы
|
||||
|
||||
@@ -92,21 +94,146 @@ RUN chown -R www-data:www-data /usr/local/apache2/htdocs/
|
||||
| `otel-postgres-secret` | Credentials PostgreSQL receivers для OTel Collector |
|
||||
| `gitea-registry` | Доступ Kubernetes к registry |
|
||||
|
||||
Secret `tenants-secret` монтируется в pod backend по пути `/config/tenants.json`.
|
||||
Backend ожидает ключ `tenants.json` из Secret `tenants-secret` по пути
|
||||
`/config/tenants.json`. Чтобы kubelet доставлял последующие обновления Secret, необходимо
|
||||
монтировать каталог `/config`, а не отдельный файл через `subPath`:
|
||||
|
||||
```yaml
|
||||
volumeMounts:
|
||||
- name: tenants-config
|
||||
mountPath: /config
|
||||
readOnly: true
|
||||
|
||||
volumes:
|
||||
- name: tenants-config
|
||||
secret:
|
||||
secretName: tenants-secret
|
||||
optional: false
|
||||
items:
|
||||
- key: tenants.json
|
||||
path: tenants.json
|
||||
```
|
||||
|
||||
В production для backend также требуется:
|
||||
|
||||
```yaml
|
||||
env:
|
||||
- name: TENANTS_CONFIG_REQUIRED
|
||||
value: "true"
|
||||
```
|
||||
|
||||
При обязательной конфигурации отсутствие, пустой список или ошибка разбора `tenants.json`
|
||||
снимают readiness; резервная H2-БД не считается готовой tenant-БД.
|
||||
|
||||
При добавлении тенанта через API:
|
||||
1. `DatabaseController` обновляет in-memory DataSource
|
||||
2. `KubernetesTenantSecretUpdater` обновляет Secret через Kubernetes API
|
||||
3. `TenantConfigWatcher` на остальных pod подхватывает смонтированное изменение (каждые 30 сек)
|
||||
1. `TenantLifecycleService` создаёт отдельный candidate pool, проверяет соединение и Flyway
|
||||
2. `KubernetesTenantSecretUpdater` выполняет `GET` актуального Secret и применяет к списку
|
||||
только upsert запрошенного домена
|
||||
3. полный объект Secret отправляется условным `PUT` с текущим `metadata.resourceVersion`;
|
||||
при `409 Conflict` выполняются повторное чтение и повторное применение своей мутации
|
||||
4. `TenantRoutingDataSource` атомарно публикует candidate только после успешной либо
|
||||
подтверждённой повторным чтением персистенции
|
||||
5. старый pool перестаёт принимать новые запросы и закрывается после drain/grace timeout
|
||||
6. `TenantConfigWatcher` на остальных pod видит обновлённый файл и каждые 30 секунд
|
||||
применяет добавление или удаление домена
|
||||
|
||||
Удаление использует тот же persistence-first порядок и применяет к актуальному Secret только
|
||||
remove указанного домена. Число попыток записи ограничено тремя, задержка между повторами
|
||||
возрастает, а идемпотентное состояние не записывается повторно.
|
||||
|
||||
Если после подтверждённой записи Secret локальная активация или удаление завершаются ошибкой,
|
||||
backend восстанавливает прежний снимок только пока Secret сохраняет `resourceVersion` этой
|
||||
записи. Более новое межподовое изменение останавливает компенсацию и не перезаписывается.
|
||||
Неопределённый сетевой результат сначала сверяется повторным `GET`; совпавшее целевое
|
||||
состояние принимается без квитанции, допускающей небезопасный автоматический откат.
|
||||
|
||||
Watcher пока не заменяет подключение существующего домена при изменении URL или credentials.
|
||||
Такое изменение сбрасывает readiness, но полное применение на другом pod относится к
|
||||
проблеме №13. До её исправления нельзя считать watcher механизмом полной синхронизации всех
|
||||
полей `TenantConfig`.
|
||||
|
||||
Kubernetes-клиент доверяет только service-account CA и проверяет hostname API server.
|
||||
RBAC ограничен объектом `tenants-secret`. Скрипт `../k8s/deploy.sh` проверяет наличие
|
||||
объектов и ключей до rollout, не читая и не печатая их значения.
|
||||
Поскольку реализация использует HTTP `GET` и `PUT`, минимальная Role должна разрешать только
|
||||
Kubernetes verbs `get` и `update` для одного существующего Secret:
|
||||
|
||||
```yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: backend-tenant-secret
|
||||
namespace: magistr
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
resourceNames: ["tenants-secret"]
|
||||
verbs: ["get", "update"]
|
||||
```
|
||||
|
||||
`patch`, `list`, `watch`, `create` и `delete` текущему Kubernetes-клиенту не нужны. RoleBinding
|
||||
должен связывать эту Role с фактическим `spec.template.spec.serviceAccountName` backend в
|
||||
namespace `magistr`. Secret должен существовать заранее и не иметь `immutable: true`.
|
||||
|
||||
Создание, ротация, отзыв скомпрометированных значений и очистка истории описаны в
|
||||
[`SECURITY_RUNBOOK.md`](SECURITY_RUNBOOK.md). Эти действия требуют полномочий оператора и
|
||||
не выполняются автоматически.
|
||||
|
||||
### Liveness и readiness probes backend
|
||||
|
||||
TCP probe подтверждает только открытый порт и не отражает готовность обязательных tenant-БД.
|
||||
В production Deployment необходимо использовать раздельные HTTP probes:
|
||||
|
||||
```yaml
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /actuator/health/liveness
|
||||
port: 8080
|
||||
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /actuator/health/readiness
|
||||
port: 8080
|
||||
```
|
||||
|
||||
Liveness зависит только от состояния процесса. Readiness возвращает `200 UP` после успешных
|
||||
миграций и свежей успешной проверки всех обязательных tenant-БД, иначе — `503 DOWN` и pod
|
||||
исключается из приёма трафика без перезапуска. Actuator не публикует components, домены,
|
||||
JDBC URL или credentials.
|
||||
|
||||
Параметры `initialDelaySeconds`, `periodSeconds`, `timeoutSeconds` и `failureThreshold` нужно
|
||||
согласовать с текущим Deployment; замена механизма probe не подтверждает автоматически
|
||||
корректность этих порогов.
|
||||
|
||||
### Внешние действия для production Kubernetes
|
||||
|
||||
Каталог `../k8s/` не изменялся и не проверялся в рамках текущей рабочей копии. Оператору после
|
||||
получения доступа к нему необходимо:
|
||||
|
||||
1. обновить backend Deployment: directory mount без `subPath`,
|
||||
`TENANTS_CONFIG_REQUIRED=true` и две HTTP probes;
|
||||
2. добавить либо сузить Role до `get`/`update` одного `tenants-secret` и проверить RoleBinding
|
||||
фактического ServiceAccount;
|
||||
3. убедиться, что внешний `tenants-secret` существует, содержит непустой ключ `tenants.json`
|
||||
и допускает обновление;
|
||||
4. отрендерить манифесты и выполнить серверную проверку без применения:
|
||||
|
||||
```bash
|
||||
kubectl kustomize ../k8s >/dev/null
|
||||
kubectl apply --dry-run=server -k ../k8s
|
||||
```
|
||||
|
||||
5. отдельно проверить полномочия фактического ServiceAccount:
|
||||
|
||||
```bash
|
||||
BACKEND_SERVICE_ACCOUNT='укажите-фактический-service-account'
|
||||
kubectl auth can-i get secret/tenants-secret \
|
||||
--as="system:serviceaccount:magistr:${BACKEND_SERVICE_ACCOUNT}" -n magistr
|
||||
kubectl auth can-i update secret/tenants-secret \
|
||||
--as="system:serviceaccount:magistr:${BACKEND_SERVICE_ACCOUNT}" -n magistr
|
||||
```
|
||||
|
||||
Применение манифестов, rollout и проверка реальных pod являются отдельными внешними
|
||||
операциями и без разрешения автоматически не выполняются.
|
||||
|
||||
### Обновление backend
|
||||
|
||||
```bash
|
||||
|
||||
Reference in New Issue
Block a user