first commit

This commit is contained in:
2026-04-04 05:15:54 +03:00
commit c5219ec4d6
25 changed files with 2022 additions and 0 deletions
+119
View File
@@ -0,0 +1,119 @@
# Runbook эксплуатации Kubernetes модуля
Документ относится к приложению **AppsTemplate** (модуль Kubernetes в веб-платформе). Репозиторий **kind-k8s-develop** хранит этот runbook рядом с утилитами локального kind.
Автор: Сергей Антропов
Сайт: https://devops.org.ru
## 1. Кластер недоступен
- Симптомы: `GET /api/v1/k8s/clusters/{id}/health` возвращает `unreachable`, preflight показывает ошибку connectivity.
- Проверки:
- Валиден ли kubeconfig кластера в записи `k8s_clusters`.
- Доступен ли API endpoint кластера из контейнера приложения.
- Не истекли ли сертификаты/токены в kubeconfig.
- Действия:
- Обновить kubeconfig кластера через `PATCH /api/v1/k8s/clusters/{id}`.
- Повторно запустить health-check с `force=1`.
- При необходимости временно отложить массовую выдачу доступов.
### 1.1. Docker Desktop: kubeconfig с `127.0.0.1`, приложение в контейнере
- **Симптом:** health-check и любые вызовы API кластера из приложения дают `unreachable`, хотя `kubectl` с хоста работает.
- **Причина:** в kubeconfig `server: https://127.0.0.1:…` означает «loopback того процесса, который подключается». Внутри контейнера `127.0.0.1` — это не хост с Docker Desktop.
- **Что сделать:** в записи кластера заменить адрес API на тот, который виден **из контейнера приложения**:
- **macOS / Windows, Docker Desktop:** обычно `https://host.docker.internal:ПОРТ` (тот же порт, что был у `127.0.0.1`, часто `6443`).
- **Linux:** при необходимости добавить в `docker-compose` для сервиса приложения `extra_hosts: ["host.docker.internal:host-gateway"]` и использовать `host.docker.internal`, либо указать IP шлюза к хосту / LAN-IP.
- **Имя вида `Something.local` (Bonjour / mDNS):** с Mac такое имя обычно резолвится на хосте, но **внутри контейнера приложения часто не резолвится** — health-check снова будет `unreachable` при том же kubeconfig. Для Docker Desktop надёжнее **`https://host.docker.internal:6443`** (или фиксированный LAN-IP Mac), а не `*.local`.
- **Только IPv6 у `host.docker.internal`:** если в контейнере `getent hosts host.docker.internal` показывает один адрес `fdc4:...` (IPv6), а API слушает IPv4, соединение может не установиться. В репозитории **AppsTemplate** для сервиса `app` в `docker-compose.yml` задано `extra_hosts: ["host.docker.internal:host-gateway"]` — после `docker compose up -d` перепроверьте `getent` (должен появиться маршрут через IPv4-шлюз хоста). Kubeconfig: `server: https://host.docker.internal:6443`.
- **TLS / hostname mismatch:** при `server: https://host.docker.internal:6443` сертификат API чаще всего выписан **не** на это имя → ошибка вида `CERTIFICATE_VERIFY_FAILED` / `Hostname mismatch`. **`GET /clusters/{id}/health`** сначала подключается с kubeconfig из БД; при типичной ошибке TLS выполняется **вторая попытка** с временной копией, где для cluster из `current-context` включён `insecure-skip-tls-verify` (в БД ничего не пишется). В ответе может быть `tls_insecure_fallback_used: true`. Для **apply RBAC / выдачи доступов** по-прежнему нужен рабочий TLS в сохранённом kubeconfig или явный `insecure-skip-tls-verify` в YAML.
- **Явный insecure в kubeconfig (рекомендуется для dev, если нужны не только health):** в записи **того же** `cluster` добавьте **`insecure-skip-tls-verify: true`**. Пример фрагмента:
```yaml
clusters:
- name: docker-desktop
cluster:
server: https://host.docker.internal:6443
insecure-skip-tls-verify: true
# certificate-authority-data: ... # при insecure можно убрать, чтобы не путаться
```
В **production** так не делают: там `server` и SAN в сертификате должны совпадать, проверка TLS включена.
- Обновление kubeconfig: UI **«Редактировать кластер»** (`/k8s/clusters/{id}/edit`) или `PATCH /api/v1/k8s/clusters/{id}`.
## 2. Частично примененные манифесты RBAC
- Симптомы: часть пользователей получила доступ, часть — ошибки в `POST /api/v1/k8s/access/bulk`.
- Проверки:
- Использовать preflight (`/access/preflight`, `/access/bulk/preflight`) перед повторной операцией.
- Проверить существующие активные записи в `k8s_user_configs`.
- Проверить аудит `k8s_access_audit`.
- Действия:
- Повторить операцию с `idempotency_key` только после устранения причины.
- Для конфликтных пользователей выполнить точечный revoke/restore или delete/create.
- Если обнаружены дубликаты, очистить лишние записи и заново выдать доступ.
## 3. Восстановление после ошибок миграций
- Симптомы: ошибки `UndefinedTableError` при старте приложения.
- Проверки:
- Убедиться, что применены миграции модуля из `app/db/migrations/modules/k8s/` (репозиторий AppsTemplate).
- Проверить наличие таблиц `k8s_*` в БД.
- Действия:
- Применить восстановительную миграцию `app/db/migrations/102_k8s_repair_missing_tables.sql`.
- Перезапустить приложение.
- Проверить работу API `GET /api/v1/k8s/health`.
## 4. Ротация ключа шифрования kubeconfig
- **Основной источник ключа:** настройка в БД `k8s.kubeconfig_encryption_key` (страница **Настройки → Модули → Kubernetes**). При непустом значении в БД оно имеет приоритет над переменной окружения.
- **Резерв:** `K8S_KUBECONFIG_ENCRYPTION_KEY` в `.env` — используется только если в БД ключ пустой (удобно для первого запуска и CI; см. `env.example` в AppsTemplate).
- Рекомендуемый порядок смены ключа:
1. Перевести сервис в окно обслуживания.
2. Считать и дешифровать существующие значения из `k8s_clusters.kubeconfig_encrypted` и `k8s_user_configs.kubeconfig_encrypted` **текущим** ключом (из БД или ENV — в том же порядке приоритета, что у приложения).
3. Перешифровать новым ключом и сохранить обратно в таблицы.
4. Сохранить новый секрет в настройках модуля (или обновить ENV, если используете только резерв).
5. Перезапустить приложение при необходимости и проверить health-check и скачивание kubeconfig.
- Важно: не удалять старый ключ до завершения полной перешифровки.
## 5. Мониторинг и безопасность фоновых задач
- Используйте страницу `/k8s/jobs` и API `GET /api/v1/k8s/jobs` (пагинация `skip`/`limit`, фильтр `status`) и `GET /api/v1/k8s/jobs/{job_id}` для полного текста ошибки и результата.
- Детальные статусы:
- `GET /api/v1/k8s/access/jobs/{job_id}`
- `GET /api/v1/k8s/access/bulk/jobs/{job_id}`
- `GET /api/v1/k8s/kubeconfig/merge/jobs/{job_id}`
- Все job-status ответы проходят маскировку чувствительных полей (`kubeconfig`, `token`, `secret`, `password`, `private_key`, `certificate`).
- Для merge-задач поле `result.rendered` не возвращается в статусе и доступно только через download endpoint.
- Рекомендуется искать инциденты по `correlation_id` в логах приложения и в записи задачи в `k8s_jobs`.
- Утилита наблюдаемости: `GET /api/v1/k8s/observability/jobs/{job_id}` для задач с `job_type=k8s.observability`; UI — `/k8s/observability`.
## 6. Утилита стека наблюдаемости (Metrics Server / Prometheus)
- Настройки: `k8s.observability_metrics_server_manifest_url` (по умолчанию официальный `components.yaml` metrics-server), `k8s.observability_metrics_server_kubelet_insecure_tls`, `k8s.observability_prometheus_manifest_bundle_url` (опционально), таймауты и лимит кластеров за операцию.
- **Metrics Server:** при типичных kubeadm/kind кластерах без корректных kubelet-сертификатов включите `--kubelet-insecure-tls` (чекбокс в UI или настройка/тело запроса install).
- **Prometheus stack:** полный `kube-prometheus-stack` обычно ставят **Helm** вне приложения; через API имеет смысл подключать **сокращённый** multi-doc YAML, где все объекты имеют поддерживаемые `kind`. Иначе часть документов попадёт в `skipped` в ответе задачи.
- **Откат:** `POST .../uninstall/jobs` удаляет ресурсы в обратном порядке того же бандла; при ручных правках в кластере возможны остаточные объекты — добейте `kubectl delete` / повторным uninstall.
### 6.1. Локальный кластер kind для разработки
Этот репозиторий (**kind-k8s-develop**): образ **kind-k8s-tools**, **`make create`** (Docker и make на хосте) поднимает kind; артефакты — **`clusters/<имя>/kubeconfig`**. Импорт kubeconfig в **AppsTemplate** см. **§1.1** (адрес API должен быть достижим **из контейнера приложения**, не обязательно `127.0.0.1`). Краткая инструкция по командам — в **`README.md`** в корне этого репозитория.
## 7. Срок действия доступа (`access_expires_at`)
- В `POST /access`, `POST /access/bulk` и `PATCH /access/{id}` можно задать дату окончания; пустое значение в PATCH сбрасывает срок.
- Фоновый цикл использует `k8s.access_expiry_check_interval_hours`; отзыв выполняется так же, как ручной revoke (RBAC в кластере, уведомление владельцу; в аудите `reason: access_expired`).
- Уведомление при автоотзыве: в тексте указано «система (автоматически)».
## 8. Отправка kubeconfig в Telegram
- В настройках модуля задать `k8s.telegram_bot_token`. У владельца доступа в профиле — поле **`telegram_chat_id`** (числовой id; миграция `105_profile_field_telegram_chat_id.sql` добавляет описание в реестр полей).
- Ссылка `https://t.me/...` **не подставляет** chat_id: пользователь должен написать боту и узнать id (например через @userinfobot), затем сохранить `telegram_chat_id`.
- `POST /api/v1/k8s/access/{config_id}/send-telegram`: владелец или лид с `modules.k8s:update`. Длинный YAML режется по `k8s.telegram_kubeconfig_max_chars` — тогда в чат уходит короткое сообщение со ссылкой на `/k8s/my-configs`.
## 9. Дрейф RBAC и сверка с кластером (reconcile)
- **Симптом:** в UI на карточке кластера колонка «Сверка RBAC» показывает `drift_detected` или `sync_error`, либо администратор вручную удалил Role/Binding в кластере.
- **Проверки:**
- `POST /api/v1/k8s/access/{config_id}/reconcile-check` (или кнопка «Сверка» на вкладке «Доступы») — пересчитать статус по сохранённым `rbac_manifests`.
- Убедиться, что у записи доступа непустые `rbac_manifests` (иначе сначала `apply-manifests` или PATCH с пересборкой).
- **Действия при дрейфе:** `POST /api/v1/k8s/access/{config_id}/apply-manifests` — повторное применение; после успешного apply статус сверки обновляется вместе с записью.
- **Настройки:** `k8s.reconcile_check_timeout_seconds`, опционально `k8s.reconcile_background_interval_hours` (часы между фоновыми прогонами для активных доступов; `0` — только ручной вызов).