7.4 KiB
ПЛАН: перенос паттерна «ВМ-буфер + pull» в drhider
Дата: 2026-08-21. Основа: contracts/loadtest/History/2026-08-21-pattern-vm-buffer-pull.md.
Статус: РЕАЛИЗОВАНО 2026-08-21 (v0.0.58), см. 2026-08-21-vm-upload-implemented.md.
Цель
drhider (Managed Flask, Штурвал, drhider.pythonk8s.dev.nubes.ru) получает файлы через
POST /api/upload (multipart). Входной шлюз managed-кластера обрывает тела >~64КБ
(HTTP 000 ~10с). Решение — загружать файл на ВМ напрямую, Flask тянет сам (egress).
Директива (обязательно)
ВСЕ файловые операции загрузки в Flask (из браузера или вообще снаружи) — ТОЛЬКО через ВМ.
Никаких прямых POST /api/upload с телом файла во Flask.
Среда (уточнено 2026-08-21)
iot-naeel— СОБСТВЕННЫЙ кластер, параметры/аннотации правились через kubectl. Пока тестируем/смотрим поведение на нём (лимит 64КБ там может не воспроизводиться).- В дальнейшем деплой — на ОБЫЧНЫЙ managed-кластер (лимит 64КБ будет реальным).
- ВМ — ОДНА (
5.172.178.213,contracts.kube5s.ru), та же, что для loadtest. Location/drhider-upload/добавляется в существующий nginx рядом с/lt-serve/. - Вывод: VM-путь строим СЕЙЧАС, независимо от поведения iot-naeel — прод всё равно на обычном.
Текущий поток
uploadFiles() (index.html ~413): по одному файлу в цикле → FormData → POST /api/upload.
Бэк: MAX_CONTENT_LENGTH=200МБ, сессия 500МБ. HTTP-клиент — httpx (не requests).
Целевой поток
Браузер ──(PUT, файл целиком)──▶ ВМ nginx (без лимита 64КБ)
│ └─ получил URL + токен
│
└──▶ Flask POST /api/upload_refs (JSON: [{name,size,url}], <64КБ)
│
▼
Flask egress GET с ВМ ──▶ читает файл ──▶ add_file() ──▶ дальше как сейчас
Изменения (4 места)
1. ВМ 5.172.178.213, nginx-contracts.conf — приём загрузки
Новый location (рядом с /lt-serve/), приём через PUT + статическая отдача:
location /drhider-upload/ {
alias /var/www/drhider-upload/;
dav_methods PUT;
create_full_put_path on;
client_max_body_size 1024m;
# CORS: браузер с drhider.pythonk8s.dev.nubes.ru шлёт PUT (не simple → preflight)
add_header Access-Control-Allow-Origin https://drhider.pythonk8s.dev.nubes.ru always;
add_header Access-Control-Allow-Methods 'PUT, GET, OPTIONS, DELETE' always;
add_header Access-Control-Allow-Headers 'Content-Type' always;
if ($request_method = OPTIONS) { return 204; }
}
- Каталог
/var/www/drhider-upload/(владелецwww-data), НЕ/home/naeel(403). - Порядок: backup → правка →
nginx -t→systemctl reload nginx.
2. Flask — новый endpoint pull (site/routes/api_bp.py)
@api_bp.route("/upload_refs", methods=["POST"])
def upload_refs():
data = request.get_json(silent=True) or {}
sid = data.get("session") or create_session()
refs = data.get("files") or []
if not refs:
return jsonify({"ok": False, "error": "No files"}), 400
added = 0
with httpx.Client(timeout=120, follow_redirects=True) as client:
for ref in refs:
name, url = ref.get("name"), ref.get("url")
if not name or not url:
continue
content = b"".join(client.stream("GET", url).iter_bytes()) # egress, по частям
if not add_file(sid, name, content):
return jsonify({"ok": False, "error": "Session not found"}), 404
added += 1
return jsonify({"ok": True, "session": sid, "count": file_count(sid)})
- Импорт
httpx(уже в requirements.txt) иfile_countиз session. stream(...).iter_bytes()— НЕcontentцеликом (OOM при 100МБ+).- Обработка ошибок egress (таймаут/403/404) → вернуть 502 с именем файла.
3. Фронт (site/templates/index.html, uploadFiles())
Разбить «Фазу 1» на два шага:
- Шаг 1a: каждый файл →
fetch(<ВМ>/drhider-upload/<token>/<name>, {method:'PUT', body:file}), собирать[{name, size, url}]. Прогресс —xhr.upload.onprogressзаменить наfetch+ReadableStream(или оставить XHR, но на PUT к ВМ). - Шаг 1b: один
POST /api/upload_refsс JSON{session, files:[...]}(<64КБ). - Токен:
crypto.randomUUID(). URL ВМ — константа/data-атрибут. - Показывать прогресс «загрузка на ВМ» отдельно от «загрузка в Flask».
4. requirements.txt — без изменений (httpx уже есть).
Безопасность и жизненный цикл
- Токен в пути URL — только
crypto.randomUUID(), не переиспользуется. - Flask после
add_fileделаетDELETEна URL ВМ (или ВМ чистит по TTL — cron/systemd-timerfind /var/www/drhider-upload -mmin +30 -delete). - ВМ отдаёт файл только по валидному URL с токеном (нет открытого листинга:
autoindex off).
Грабли (из pattern-дока + drhider)
- CORS: PUT — не simple-метод → браузер шлёт OPTIONS preflight; nginx обязан отвечать 204.
site/конфликтует со stdlibsite.py(для loadtest; у drhider — Штурвал, без gunicorn).- egress через
httpxсstream, timeout 120; большие ОТВЕТЫ проходят (проверено 50МБ). - OOM: не читать файл в память целиком; сессия 500МБ уже ограничивает.
- Локальные curl на Krupski идут через прокси
172.17.192.1:10808→ для теста ВМ--noproxy '*'.
Порядок работ + проверка
- ВМ nginx (location + каталог + CORS) → проверить
curl --noproxy '*' -X PUT. - Flask
/api/upload_refs→ проверитьpython3 -c "import py_compile; py_compile.compile(...)". - Фронт
uploadFiles()→node -cнет (это EJS/HTML+JS внутри шаблона) — ручная проверка. - Сквозной тест: файл 10МБ → ВМ → Flask → обфускация → ZIP.
- Commit + push (правило: после правки + bump версии
VERSIONвsite/app.py).
Решения (закрыты)
- Кластер: сейчас
iot-naeel(тест), прод — обычный managed. VM-путь обязателен. - ВМ: одна,
contracts.kube5s.ru(5.172.178.213), общая с loadtest — добавляем location.