Files
getpgdata/history/2026-08-18.md
T

15 KiB
Raw Blame History

История getpgdata — 2026-08-18

Суть проекта

getpgdata — отдельное приложение для доступа к PostgreSQL (managed k8s, internal-only). Разворачивается на платформе Nubes как NodeJS managed service (nodejsk8s).

Репозиторий: https://gitea.services.ngcloud.ru/Nail/getpgdata.git (ветка master). Локальный путь: /home/naeel/ipwhitelist-app/getpgdata.

1. Создание (Flask-версия, ошибочная)

Первоначально приложение было собрано как Flask (Python) по howto ~/nubes/howto-flask-nubes.md (структура site/app.py + requirements.txt). Запушено коммитом 5c5e11c.

Функции Flask-версии: главная со списком таблиц, просмотр таблицы, SQL-консоль (read-only), /healthz.

2. Ошибка пользователя и замена на Node.js

Пользователь указал, что перепутал стек — нужно Node.js, а не Flask.

Решение: Flask-файлы (site/, requirements.txt) удалены, создана Node.js структура по образцу рабочего ipwhitelist-app:

getpgdata/
├── package.json            # version 1.0.x; main server.js; start: node server.js
├── server.js               # точка входа: Express + pg Pool
├── public/style.css
└── views/
    ├── index.ejs           # главная: подключение + список таблиц
    ├── table.ejs           # просмотр таблицы с пагинацией
    ├── query.ejs           # SQL-консоль (read-only, только SELECT)
    └── error.ejs

Коммит замены: b061dcd.

Проверено: node -c server.js, npm install (90 пакетов), реальный запуск сервера:

  • GET / → 200 (рендер страницы)
  • GET /healthz при недоступной БД → 503 (degraded, приложение не падает)
  • GET /table без имени → 302 redirect
  • POST /query не-SELECT → блокируется (read-only)
  • статика /public/style.css → 200

3. Настройки подключения к БД

  • Хост: postgresqlk8s-master.60bdf3e3-5087-41ff-b760-fe6ea544a80e.svc.cluster.local
  • Порт: 5432
  • БД: ipwhitelist (коммит 102689d — дефолт изменён с postgres на ipwhitelist)
  • Пользователи: super (полный доступ) и contracts — оба с паролями из jsonEnv
  • DB_SSLMODE: disable

Пароли в коде НЕ хранятся (читаются через process.env.DB_PASS). Готовые jsonEnv-блоки на обоих пользователей добавлены в README.md (коммит a890bb9).

4. Проблема деплоя: под nodejsk8s не стартует

Симптом (Nubes UI):

Приложение не запустилось. Производится полный откат установки. Error: jlib.k8s [correctReplicaActive] | ERROR | Под(ы) не работают: 'nodejsk8s' (Deployment).

Диагноз (сравнение с работающим whitelist):

Параметр whitelist (работает) getpgdata (падал)
Точка входа server.js, main в package.json server.js
npm start node server.js node server.js
Дефолтный порт 3000 5000
Liveness /healthz /healthz
Старт без доступной БД стартует стартует ✓

Гипотеза: дефолтный порт 5000 (перенесён из Flask-версии) не совпадает с портом 3000, который ожидает платформа Nubes для NodeJS managed service. Health-проба не находила сервис → под не Ready → «не работает nodejsk8s» → откат.

Исправление: дефолтный PORT в server.js изменён с 5000 на 3000; в README.md jsonEnv-блоки обновлены (PORT: 3000); package.json version → 1.0.1.

Проверено: сервер запущен без env PORT → слушает 0.0.0.0:3000, / → 200, /healthz → 503 (degraded).

⚠️ НЕ проверено: доступ к кластеру Nubes с этой машины отсутствует (iot-naeel и naeel-test-3 требуют OIDC-аутентификацию, managed-сервисы в них не видны). Поэтому точную причину «почему под упал» по логам пода подтвердить нельзя — требуется лог пода из Nubes UI. Если деплой после фикса порта снова не поднимется — смотреть логи пода (nodejsk8s Deployment) в UI, а не только полагаться на гипотезу о порте.

5. Прочее

  • package-lock.json закоммичен (зависимости: express, ejs, pg).
  • node_modules/ в .gitignore.
  • Версия в package.json повышается при каждой правке кода (правило проекта).

6. Минимальный вывод на экран (фикс падения пода)

Несмотря на фикс порта (п.4), деплой снова упал — под nodejsk8s не поднялся.

Требование пользователя: «сделай код минимальным. Сам код не удаляй — сделай вывод на экран какой-то информации и всё».

Решение (коммит c8a776e, версия 1.0.2):

  • Роут GET / переписан так, чтобы не зависеть от EJS-шаблонов: отдаёт простой HTML напрямую (res.send), выводя:
    • версию Node, APP_ENV, PID
    • конфиг БД (host, port, database, user)
    • статус подключения к БД (или текст ошибки)
    • список таблиц (или сообщение об ошибке)
  • Каждая попытка работы с БД обёрнута в try/catch, поэтому страница всегда отдаёт 200 с информацией, даже если БД недоступна.
  • Весь остальной код (роуты /table, /query, /healthz, dbConfig, pool) не удалён — только главная страница упрощена.

Проверено: синтаксис OK; запуск без env PORT → слушает 0.0.0.0:3000; GET / → 200 (HTML с конфигом и статусом БД), GET /healthz при недоступной БД → 503 degraded.

⚠️ Точная причина падения пода по-прежнему не подтверждена (нет доступа к логам Nubes). Если под не поднимется и после этого — смотреть логи nodejsk8s Deployment в Nubes UI: replica не активна (CrashLoopBackOff, invalidImage, порт/health, зависший старт).

7. JSON API POST /api/sql (перенос данных в новую ПГ)

Контекст: нужно перенести белые списки (IP, кто создал, когда) из прежней БД в новую PostgreSQL (тот же realm k8s). Доступ к новой БД — через Keycloak, автоматизировать нельзя. Решение: код в репе getpgdata + передеплой с кредами новой ПГ.

Что добавлено (версия 1.0.3):

  • Эндпоинт POST /api/sql — JSON API для произвольных SQL-команд. Body: { "sql": "...", "params": [...] } (params → $1..$n).
    • SELECT/WITH{ ok:true, fields, rows, rowCount } (всегда доступен)
    • INSERT/UPDATE/DELETE/DDL → { ok:true, rowCount } только при WRITE_SQL=1
    • иначе → HTTP 403 «Write-SQL отключён»
  • Защита: выполняется только первый statement (split по ;) — блокирует мульти-команды вроде SELECT 1; DROP TABLE ....
  • Флаг в env: WRITE_SQL=1 включает запись.

Логика переноса:

  1. Приложение подключено к прежней БД — читать данные через /api/sql (SELECT).
  2. Сменить креды в jsonEnv на новую ПГ (+ WRITE_SQL=1).
  3. Передеплой — теперь /api/sql выполняет запросы уже в новой БД; выполнить INSERT данных.

Проверено: node -c server.js OK; локально: пустой SQL → 400; SELECT → идёт в БД; INSERT без WRITE_SQL → 403 (до попытки подключения); WRITE_SQL=1 + мульти-statement → берётся только первый; процесс проверки остановлен.

8. Перенос данных из прежней БД в новую (60bdf3e3 → a5e9d24f)

Цель: перенести белые списки (IP, кто создал, когда) и связанные данные в новую PostgreSQL (инстанс a5e9d24f), тот же realm k8s. Доступ к новой БД — через Keycloak, автоматизировать нельзя → перенос выполнен через getpgdata + POST /api/sql.

8.1 Выгрузка данных из прежней БД

Пока приложение было подключено к прежней БД (60bdf3e3), все таблицы выгружены через /api/sql (SELECT * ORDER BY id) и сохранены в репо в data/*.json:

Файл Таблица Строк Статус
data/companies.json companies 9 полный, валиден
data/whitelist_entries.json whitelist_entries 20 полный, валиден
data/audit_log.json audit_log 39 полный, валиден
data/_migrations.json _migrations 2 полный, валиден
session session 1147 исключён (битый дамп, не нужен для переноса)

Сессии намеренно НЕ переносятся: это временные пользовательские сессии входа (connect-pg-simple), в новой БД они создаются заново.

Коммиты: данные ~, генератор data/import.sql (коммит 0b2d7ac).

⚠️ Урок (зафиксирован в памяти): нужно было выгрузить ВСЕ таблицы до смены кредов. Первая выгрузка сохранила только whitelist_entries; companies/audit_log пришлось догонять после временного возврата кредов на прежнюю БД.

8.2 Переключение на новую БД

В jsonEnv Nubes (modify) заданы креды новой ПГ + WRITE_SQL=1:

{
  "DB_HOST": "postgresqlk8s-master.a5e9d24f-0b5d-4196-8903-7ed60f6e6557.svc.cluster.local",
  "DB_PORT": "5432",
  "DB_NAME": "ipwhitelist",
  "DB_USER": "super",
  "DB_PASS": "<пароль новой ПГ>",
  "DB_SSLMODE": "disable",
  "WRITE_SQL": "1",
  "PORT": "3000"
}

После modify проверено: GET / показывает host новой ПГ; CREATE TEMP TABLE → 200 (запись разрешена).

8.3 Проблема: несоответствие id компаний

В новой БД изначально уже были компании под другими id (типовая схема), поэтому при прямой вставке по старым id возникли конфликты:

  • Вставка с явным id: WZ03709(id=23) упал — duplicate key по client_id (в новой БД WZ03709 уже был под id=2)
  • WZ01112 и WZ03709 оказались на неверных id (1 и 2 вместо 2 и 23)
  • WZ01325 (должен быть id=1) — вообще отсутствовал

Причина: уникальный индекс companies_client_id_key (client_id), id в новой БД не совпадали со старой.

Решение (безопасно, т.к. записи/аудит в новой БД были пусты и FK на companies отсутствуют):

  1. DELETE FROM companies WHERE client_id IN ('WZ01112','WZ03709') — убрать с неверных id
  2. Вставить заново под правильные id: id=1→WZ01325, id=2→WZ01112, id=23→WZ03709
  3. Восстановить custom_limit и даты для этих 3 строк из дампа (первая вставка ставила NOW())

После этого маппинг id компаний новой БД полностью совпал со старой.

8.4 Вставка данных через /api/sql

Так как /api/sql выполняет один statement за вызов, INSERT отправлены по одному стейтменту (клиент на Python):

  1. companies — 9 вставок (с явными id, ON CONFLICT (id) DO NOTHING)
  2. whitelist_entries — 20 вставок (id, company_id, value_cidr, comment, created_by, created_at, updated_by, updated_at, deleted_by, deleted_at)
  3. audit_log — 39 вставок
  4. SELECT setval(...) — сброс sequence для трёх таблиц на MAX(id)

8.5 Проверка целостности и соответствия

После переноса выполнена посимвольная сверка новой БД против дампа старой:

Таблица Строк old/new missing extra diffs Результат
whitelist_entries 20/20 0 0 0 MATCH
audit_log 39/39 0 0 0 MATCH
companies 9/9 0 0 0 (после фикса) MATCH
  • orphan (записи без компании): 0 — все связи верны
  • sequences: companies→368, whitelist_entries→20, audit_log→39

8.6 Итоговое состояние новой БД

  • companies = 9
  • whitelist_entries = 20 (из них активных 12, удалённых 8 — перенесены с deleted_at)
  • audit_log = 39
  • Данные идентичны прежней БД.

Активные белые списки (12): WZ02425 (2), WZ02727 (6), WZ03656 (2), WZ01112 (8.8.8.8).