239 lines
17 KiB
Markdown
239 lines
17 KiB
Markdown
# 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 считать безопасным эксплуатационным размером и документировать это как известную особенность инфраструктуры. |