15 KiB
История 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 redirectPOST /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)
- статус подключения к БД (или текст ошибки)
- список таблиц (или сообщение об ошибке)
- версию Node,
- Каждая попытка работы с БД обёрнута в 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включает запись.
Логика переноса:
- Приложение подключено к прежней БД — читать данные через
/api/sql(SELECT). - Сменить креды в jsonEnv на новую ПГ (+
WRITE_SQL=1). - Передеплой — теперь
/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 отсутствуют):
DELETE FROM companies WHERE client_id IN ('WZ01112','WZ03709')— убрать с неверных id- Вставить заново под правильные id: id=1→WZ01325, id=2→WZ01112, id=23→WZ03709
- Восстановить
custom_limitи даты для этих 3 строк из дампа (первая вставка ставилаNOW())
После этого маппинг id компаний новой БД полностью совпал со старой.
8.4 Вставка данных через /api/sql
Так как /api/sql выполняет один statement за вызов, INSERT отправлены по одному
стейтменту (клиент на Python):
companies— 9 вставок (с явными id,ON CONFLICT (id) DO NOTHING)whitelist_entries— 20 вставок (id, company_id, value_cidr, comment, created_by, created_at, updated_by, updated_at, deleted_by, deleted_at)audit_log— 39 вставок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).