Задача Егора
This commit is contained in:
@@ -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