Files
KindClustersDashboard/docs/k8s_runbook.md
T
2026-04-04 05:15:54 +03:00

120 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` — только ручной вызов).