docs: finalize 64kb ingress investigation

This commit is contained in:
Naeel
2026-04-12 07:59:04 +03:00
parent eba01c9580
commit a1ff9c4e52
5 changed files with 643 additions and 1 deletions
+239
View File
@@ -0,0 +1,239 @@
# Thinking Log — 2026-04-12
# Agent: GitHub Copilot (GPT-5.4)
---
## Задача: Довести расследование проблемы 64KB+ payload до окончательного технического вывода
### Контекст
На момент начала этой сессии уже было подтверждено следующее:
- сервер shared-sqs после перехода на Redis schema v2 и per-message persistence работает быстро на малых и средних сообщениях;
- проблема проявляется именно на payload около 64KB и выше;
- через port-forward тот же запрос проходит быстро, значит Go-сервис и Redis не являются первичным узким местом;
- через ingress проблема воспроизводится у boto3 и AWS CLI, но не воспроизводится у curl, http.client и низкоуровневого urllib3.
Главный незакрытый вопрос был таким: это баг нашего сервиса или поведение платформенного ingress controller штурвала?
### Рабочая гипотеза в начале сессии
Если объект ingress у shared-sqs настроен корректно, а итоговый nginx.conf внутри ingress controller не отражает часть аннотаций, то причина находится в платформенном ingress controller, а не в приложении.
### Почему выбрал именно эту гипотезу
Потому что она была самой дешёвой для проверки и лучше всего объясняла противоречие:
- в YAML ingress аннотация `nginx.ingress.kubernetes.io/proxy-request-buffering: "off"` есть;
- в фактическом nginx.conf для host `qu.kube5s.ru` всё равно остаётся `proxy_request_buffering on;`.
Если это подтверждается, дальнейший поиск в коде shared-sqs теряет смысл.
---
## Ход расследования
### 1. Проверка, чей это ingress вообще
Сначала была цель не гадать, а проверить ownership в кластере.
Что было подтверждено:
- ingress для `qu.kube5s.ru` обслуживается IngressClass `nginx`;
- этот класс ведёт на deployment `shturval-ingress-controller-controller`;
- контроллер живёт в namespace `ingress`;
- используется образ `r.shturval.tech/ingress-nginx/controller:v1.12.6`;
- это не ingress, встроенный в shared-sqs, а платформенный ingress controller кластера.
Вывод: проблема находится в общей ingress-инфраструктуре штурвала.
### 2. Проверка, нет ли конфликта нескольких ingress-ресурсов
Следующая гипотеза была локальная и простая: возможно, для одного host существует несколько Ingress-объектов, и location-блок в nginx собирается из другого ресурса, не из того YAML, который мы смотрим.
Проверка показала:
- в кластере для host `qu.kube5s.ru` существует только один ingress: `shared-sqs/shared-sqs-ingress`.
Вывод: это не конфликт нескольких ingress-объектов на один host.
### 3. Сверка объекта ingress с фактическим nginx.conf
Дальше был ключевой шаг: сравнить декларацию и факт.
В самом ingress-объекте у shared-sqs присутствуют:
- `nginx.ingress.kubernetes.io/proxy-body-size: "10m"`
- `nginx.ingress.kubernetes.io/client-body-buffer-size: "512k"`
- `nginx.ingress.kubernetes.io/proxy-request-buffering: "off"`
- `nginx.ingress.kubernetes.io/server-snippet: client_body_timeout 120s;`
- proxy timeouts.
В сгенерированном nginx.conf для `qu.kube5s.ru` было найдено:
- `client_max_body_size 10m;`
- `client_body_buffer_size 512k;`
- `proxy_send_timeout 30s;`
- `proxy_read_timeout 30s;`
- `proxy_buffering off;`
- `proxy_request_buffering on;`
Это важнейшая развилка расследования.
Что это означает:
- ingress controller видит ingress-ресурс;
- часть аннотаций применяет корректно;
- но конкретно `proxy-request-buffering` не доходит до итоговой конфигурации;
- следовательно проблема не в том, что ingress целиком игнорируется;
- проблема в selective handling конкретных директив/аннотаций.
### 4. Проверка версии и документации ingress-nginx
Дальше нужно было отсечь ещё одну ложную ветку: а вдруг upstream ingress-nginx вообще не поддерживает `proxy-request-buffering` в нашей версии?
Проверка документации и исходников upstream ingress-nginx показала:
- аннотация `nginx.ingress.kubernetes.io/proxy-request-buffering` официально поддерживается;
- в коде есть parser для этого поля;
- в upstream есть e2e-тест на сценарий `should turn off proxy-request-buffering`;
- в шаблоне nginx используется переменная `location.Proxy.RequestBuffering`.
Вывод: upstream ingress-nginx такую аннотацию умеет. Значит поведение штурвала отличается не потому, что аннотация “не существует”, а потому что либо:
- в платформенной сборке/конфигурации происходит баг;
- либо значение не прокидывается на этапе построения location model;
- либо контроллер живёт в состоянии, где дефолт `on` побеждает annotation override.
### 5. Проверка самого шаблона в pod ingress controller
Чтобы не строить догадки про кастомный шаблон, была проверка прямо внутри pod.
Что найдено:
- в `/etc/nginx/template/nginx.tmpl` директива не захардкожена;
- там стоит шаблонная подстановка `{{ $location.Proxy.RequestBuffering }}`;
- в итоговом `/etc/nginx/nginx.conf` для конкретного host всё равно стоит `on`.
Это сузило диагноз ещё сильнее:
- шаблон не виноват;
- parser и upstream поддержка есть;
- значит проблема в данных, которыми шаблон кормят, либо в платформенном runtime поведении контроллера.
Именно здесь стало окончательно понятно, что дальше копать shared-sqs бессмысленно.
### 6. Почему `server-snippet` тоже не дал ожидаемого эффекта
Параллельно был вопрос: если `proxy-request-buffering` не работает, можно ли продавить workaround через snippet.
Проверка документации ingress-nginx показала:
- snippet-аннотации по умолчанию контролируются флагом `allow-snippet-annotations`;
- его дефолтное значение — `false`.
В ConfigMap ingress controller у штурвала этот флаг не был включён.
Вывод:
- рассчитывать на `server-snippet` и `configuration-snippet` без изменения platform ConfigMap нельзя;
- даже если ingress-объект принимает такую аннотацию, итоговая конфигурация может её не внедрить по политике безопасности.
### 7. Наблюдение про ConfigMap drift
Ещё один важный операционный вывод дал повторный просмотр ConfigMap ingress controller.
Ранее вручную поднимался `client-body-timeout`, но позже в ConfigMap снова был виден `client-body-timeout: "10"`.
Это сильный индикатор того, что:
- ручные правки штурвального ingress controller могут откатываться;
- platform layer, вероятно, управляется Helm/GitOps/reconcile-процессом;
- даже если бы ручной patch помог, он мог бы быть временным.
Вывод: править такие настройки нужно не как разовую операцию в живом кластере, а в источнике правды платформы.
---
## Итоговые технические выводы
### Что подтверждено надёжно
1. **Go-сервис shared-sqs не является первичной причиной зависания 64KB+ сообщений.**
Это доказано быстрым прохождением запросов через port-forward.
2. **Проблемный слой находится на ingress path.**
Конкретно — в поведении платформенного ingress controller штурвала.
3. **Ingress YAML приложения сам по себе не является ошибочным.**
Нужные аннотации на объекте есть.
4. **Контроллер применяет аннотации выборочно.**
`proxy-body-size` и `client-body-buffer-size` доходят до nginx.conf, а `proxy-request-buffering` — нет.
5. **Upstream ingress-nginx поддерживает `proxy-request-buffering`.**
Следовательно, это не “неподдерживаемая фича”, а platform-specific проблема/баг/ограничение.
6. **Snippet-аннотации в текущем штурвальном контроллере по факту недоступны как безопасный пользовательский workaround** без отдельного platform-level разрешения.
7. **Ручные правки ConfigMap контроллера выглядят нестабильными и могут откатываться.**
### Что больше НЕ считаю разумным делать
1. Продолжать искать root cause в Go-коде shared-sqs.
2. Тратить время на новые попытки “починить” только ingress приложения без изменения platform controller.
3. Внедрять большой рефакторинг сервиса ради проблемы, лежащей за пределами сервиса.
4. Форсить hard-limit в коде без острой продуктовой необходимости.
### Финальное продуктово-техническое решение
После всех проверок наиболее прагматичный вывод такой:
- протокольный лимит SQS остаётся 256KB;
- **в коде shared-sqs hard-limit 32KB не вводим**;
- **операционно считаем 32KB безопасным практическим размером** для клиентов AWS CLI/botocore в текущей инфраструктуре;
- проблему 64KB+ классифицируем как ограничение платформенного ingress path, а не баг shared-sqs business logic.
---
## Мысли и оценка инженерного качества решения
### Почему не стоит вводить hard-limit 32KB в коде
Изначально идея казалась хорошей: жёстко ограничить размер сообщения и снять проблему.
Но по мере расследования стало ясно, что это слишком грубое лечение чужой инфраструктурной болезни. Если мы режем размер в коде, мы:
- маскируем platform issue под якобы ограничение сервиса;
- вводим продуктовое ограничение, которого нет в протоколе SQS;
- создаём технический долг: потом придётся объяснять, почему сервис “совместим с SQS”, но режет на 32KB.
То есть hard-limit удобен как короткий workaround, но архитектурно это неправильное место для фикса.
### Почему 32KB всё-таки остаётся хорошей практической рекомендацией
Потому что 32KB:
- заметно ниже порога деградации;
- стабильно проходит через AWS CLI/botocore в текущем ingress path;
- покрывает подавляющее большинство типовых сообщений SQS;
- даёт пользователю рабочее эксплуатационное правило без вранья про реальные причины.
### Почему идея “переписать сервис с нуля” не выглядит рациональной
Этот вопрос возник естественно на фоне раздражения из-за 64KB+ проблемы.
Но расследование показало обратное:
- core shared-sqs работает хорошо;
- сервер не является бутылочным горлышком в текущем кейсе;
- переписывание сервиса не уберёт поведение ingress controller штурвала;
- значит ROI у полного переписывания низкий.
Гораздо разумнее развивать текущий код и отдельно эскалировать platform ingress issue.
---
## Что считать окончательным статусом инцидента
### Статус
**Исследование завершено на уровне, достаточном для инженерного решения.**
### Причина остановки дальнейшего копания
Не потому, что “не нашли”, а потому что нашли достаточно:
- место проблемы локализовано;
- границы ответственности определены;
- прикладное решение выбрано;
- дальнейшее время будет тратиться уже с плохим ROI.
### Если когда-нибудь возвращаться к теме
Возвращаться стоит только в двух случаях:
- если появится доступ к source-of-truth штурвального ingress controller;
- если 64KB+ payload станет реально важным use-case для пользователей.
Иначе правильнее оставить это как известное platform limitation.
---
## Короткий финальный вывод одним абзацем
Проблема 64KB+ payload у shared-sqs оказалась не багом Go-сервиса и не проблемой Redis/persistence, а ограничением ingress path в кластере штурвала: объект ingress у приложения настроен корректно, часть аннотаций применяется, но именно `proxy-request-buffering` platform controller в итоговый nginx.conf не прокидывает, при этом upstream ingress-nginx такую аннотацию поддерживает. Поэтому вводить hard-limit 32KB в коде я считаю неправильным; правильное практическое решение на текущий момент — оставить сервис без искусственного code-level ограничения, а 32KB считать безопасным эксплуатационным размером и документировать это как известную особенность инфраструктуры.