doc: разбор применимости обхода drhider к SQS (прокси на ВМ, pull вместо push)

- почему схему 1:1 скопировать нельзя (клиенты на AWS SDK, синхронный протокол) ->
  аналог: прозрачный прокси на ВМ;
- условия: сохранение Host для SigV4 (auth_middleware.go:315-317), внешние QueueUrl
  (сейчас http://region.host:port из goaws.yaml, tenant_helpers.go:29-37),
  TLS, доступ ВМ->кластер, отключение буферизации;
- 4 варианта с оценкой (прокси / настройки ingress / MTU / ретраи) и таблица
  'что какой лечит';
- обязательные замеры до решения (раздел 5) и открытые вопросы.
This commit is contained in:
Repinoid
2026-10-08 16:51:45 +03:00
parent a1d72b61d4
commit d8bdea75f3
@@ -0,0 +1,138 @@
# Применимость обхода из drhider к shared-sqs (разбор)
**Дата:** 2026-10-08
**Основание:** `nubes-large-body-workaround-findings-2026-10-08.md` (находки из `~/nubes/drhider`).
**Статус:** анализ. **Ничего ещё не внедрено** — сначала замеры (см. раздел 5).
---
## 1. Почему нельзя скопировать схему 1:1
В drhider обе стороны протокола — свои (браузер + своё приложение на Flask),
поэтому туда встроили двухшаговый протокол: `PUT` файла на ВМ-буфер → `POST`
ссылки в кластер → исходящий `GET` (pull).
В shared-sqs **обе стороны чужие по протоколу**: клиенты — сторонние приложения
с AWS SDK (подпись SigV4), протокол SQS синхронный (запрос → ответ). Вставить
«положите тело на буфер, пришлите ссылку» нельзя без изменения клиентов и потери
совместимости с AWS SDK.
→ Прямой аналог буфера в SQS — **прозрачный обратный прокси на ВМ**, а не буфер
и не хранилище. Данные идут потоком, протокол не меняется.
## 2. Что именно болит у shared-sqs (наши факты)
| Явление | Причина по нашим документам | Источник |
|---|---|---|
| A. Большие тела (>~1.4 КБ) виснут ~51 с | MSS, анонсируемый шлюзом = 1448; MTU пода 1400, underlay 1450 → сегменты 1488 дропаются при DF; PMTUD мёртв (ICMP не доходит) | `nubes-ticket.md:11-13` |
| B. Малые тела (512 Б) — `ReadTimeout` 30 с с периодом 31–33 с | таймер на ВНЕШНЕМ шлюзе; доказано tcpdump: запросы не доходят до пода, nginx чист, Cilium 0 дропов | `nubes-ticket.md` (явление B), HANDOFF |
Что уже проверено ранее:
- **внутри кластера** — проблем нет; **с ВМ (5.172.178.213) p50 = 6–8 мс, таймаутов нет**;
- **MTU пода уже 1400** — то есть тот фикс, который помог drhider (1500→1400), у нас
**уже применён**, а явление A осталось: причина в MSS-анонсе шлюза, а это вне
нашего контроля (`nubes-ticket.md:16-17`);
- лимит SQS — тело ≤ 262144 Б (256 КБ), т.е. большие тела легальны и попадают
ровно в проблемную зону.
## 3. Варианты, применимые к SQS
### Вариант 1 (основной) — публичная точка входа на ВМ-прокси
Схема: `клиент (AWS SDK) → HTTPS на ВМ → nginx (ВМ) → под shared-sqs в кластере`.
Убираем из пути внешний шлюз платформы. Основания:
- путь «интернет → ВМ» в drhider держал тела 63 КБ и 5 МБ **успешно**
(`PROBLEM-AND-SOLUTION.md`), то есть у ВМ нет той патологии, что у шлюза кластера;
- путь «ВМ → кластер» у нас измерен: 6–8 мс, таймаутов нет.
**Технические условия (важные, иначе не заработает):**
1. **SigV4 подписывает заголовок `Host`.** nginx обязан передавать внешний хост:
`proxy_set_header Host $host;`. Сервис берёт хост именно из запроса —
`auth_middleware.go:315-317` (`canonicalHeaderValue`: `name == "host"` → `r.Host`).
Подменять Host на внутренний (например, на служебное имя сервиса) **нельзя** —
подпись перестанет сходиться, клиент получит `SignatureDoesNotMatch`.
2. **URL очередей сейчас жёстко внутренние и без TLS.** `tenantQueueURL`
(`tenant_helpers.go:29-37`) формирует `http://{region}.{host}:{port}/{tenantID}/{queue}`.
Хост и порт читаются **только из `goaws.yaml`** (`conf/config.go:87-90`; в
`applyEnvironmentDefaults` env-переменных для Host нет). Следствия:
- клиенты, которые сами задают `endpoint_url` (все наши тесты), берут из
`QueueUrl` только путь — они не пострадают;
- клиенты **без** `endpoint_url` пойдут на хост из `QueueUrl` → обязаны получить
внешний адрес. Значит: либо (а) подставляем внешний хост/схему в конфиг, либо
(б) nginx переписывает `QueueUrl` в ответах (хрупко), либо (в) требуем от
клиентов явный `endpoint_url`.
3. **Транспорт.** Схема `http://` в URL — при переносе на ВМ либо оставляем
`http` (нежелательно для интернета), либо переводим на `https` и правим
формирование URL. Для TLS нужен сертификат на ВМ-домен.
4. **Доступ ВМ → кластер.** ВМ вне кластера; понадобится NodePort/VIP (или
опубликованный внутренний адрес). Путь ВМ→кластер нужно замерить (см. раздел 5).
5. **Проксирование тел** — без буферизации на диск: `proxy_request_buffering off`,
`client_max_body_size` ≥ 256 КБ (+ запас).
### Вариант 2 — настройки ingress через `ShturvalServiceConfig`
В drhider этим вылечили RST (`keep-alive: 10` → `75`), подняли
`proxy-body-size: 1024m`, выключили HTTP/2, подняли `client-body-timeout: 60`.
Правки делаются **только через `ShturvalServiceConfig`** — kubectl откатывает ArgoCD
(`PROBLEM-AND-SOLUTION.md`).
Оценка для нас: **скорее всего не поможет**. Наш таймер (явление B) доказанно
находится **вне кластера** (tcpdump: запросы не доходят до пода; nginx чист), а
MSS-анонс делает **внешний шлюз**. Но правка дешёвая и безопасная → имеет смысл
проверить замером «до/после», прежде чем строить прокси.
### Вариант 3 — MTU-фикс
**Уже сделан** (MTU пода 1400). Явление A не убрал — так как причина в шлюзе.
Для SQS этот путь закрыт.
### Вариант 4 — клиентские ретраи (+ идемпотентность)
Маскирует явление B, **не решает** явление A (51 с ожидания при большом теле).
Для standard-очередей ретрай без идемпотентности даёт дубли (FIFO-дедуп по
`MessageDeduplicationId` у нас есть, для standard — нет). Годится как временная
мера, не как решение.
## 4. Сводка: что какой вариант лечит
| Вариант | Явление A (большие тела) | Явление B (таймер) | Стоимость/риск |
|---|---|---|---|
| 1. Прокси на ВМ | да (по аналогии с drhider) | да (шлюз вне пути) | TLS, Host, внешние URL, доступ ВМ→кластер |
| 2. Настройки ingress | вероятно нет | вероятно нет | низкая; нужен доступ к платформенному конфигу |
| 3. MTU | нет (уже 1400) | нет | — |
| 4. Ретраи клиентов | нет | частично | дубли для standard |
## 5. Что обязательно замерить ДО решения
Правило проекта: рекомендация без замера — догадка. Замеры на том же хосте и тем
же инструментом:
1. **С ВМ → внешний endpoint кластера**: тела 1 КБ / 10 КБ / 100 КБ / 256 КБ —
есть ли ~51 с? (Малые тела с ВМ проверены: 6–8 мс, таймаутов нет; большие, судя
по всему, отдельно не мерили.)
2. **ВМ → кластер (NodePort/VIP)**: те же размеры — сколько занимает.
3. **Явление B с ВМ**: сохраняется ли период 31–33 с.
4. **После варианта 2** (если включаем): те же замеры, сравнение «до/после».
Инструменты уже есть в репозитории: `tests/tcp_mtu_probe.py`,
`tests/long_compare_local.py`, `tests/compare_ymq_vs_shared.py`,
`tests/platform_probe.py`, `tests/reboot_probe.py`.
## 6. Вывод
- Тикет в поддержку — **не решение** (болезнь платформы), оставляем только как
историю; в drhider её тоже не «починили», а обошли.
- Архитектурный аналог решения drhider для SQS — **прозрачный прокси на ВМ**
(вариант 1). Ключевые камни: сохранение `Host` для SigV4, внешние `QueueUrl`
(сейчас `http://{region}.{host}:{port}` из `goaws.yaml`), TLS, доступ ВМ→кластер.
- Перед стройкой — замеры из раздела 5; возможно, вариант 2 закроет часть проблемы
за 10 минут вместо новой инфраструктуры.
## 7. Открытые вопросы (нужны твои решения)
1. Внешний домен и сертификат для ВМ-прокси — какой брать?
2. Клиенты без явного `endpoint_url` — есть ли такие (иначе вопрос №3 в разделе 3
отпадает)?
3. Делать ли сначала замеры (раздел 5) — я готов.