From 91d717585a6b3c42bc60855150d498ca5c2792b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Fri, 10 Jul 2026 22:59:56 +0400 Subject: [PATCH] =?UTF-8?q?fix:=20debug=3DFalse,=20force=20read=20body,=20?= =?UTF-8?q?bump=201.0.6=E2=86=921.0.7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- History/2026-07-10-opus-answer.md | 38 ++++++ History/2026-07-10-sonnet-answer-kubevip.md | 29 +++++ History/2026-07-10-vip-isolated.md | 19 +++ History/opus-question-final.md | 46 +++++++ History/opus-question-upload.md | 51 ++++++++ History/sonnet-question-kubevip-detailed.md | 127 ++++++++++++++++++++ History/sonnet-question-kubevip.md | 35 ++++++ site/app.py | 6 +- 8 files changed, 349 insertions(+), 2 deletions(-) create mode 100644 History/2026-07-10-opus-answer.md create mode 100644 History/2026-07-10-sonnet-answer-kubevip.md create mode 100644 History/2026-07-10-vip-isolated.md create mode 100644 History/opus-question-final.md create mode 100644 History/opus-question-upload.md create mode 100644 History/sonnet-question-kubevip-detailed.md create mode 100644 History/sonnet-question-kubevip.md diff --git a/History/2026-07-10-opus-answer.md b/History/2026-07-10-opus-answer.md new file mode 100644 index 0000000..9819983 --- /dev/null +++ b/History/2026-07-10-opus-answer.md @@ -0,0 +1,38 @@ +# 2026-07-10 — Ответ Опуса: MTU/MSS «чёрная дыра» + +## Диагноз +**PMTU Discovery сломан на edge LB + ECMP-балансировка.** +Не размерный лимит — MTU-дыра на части TCP-путей. + +## Почему не лимит +- 50KB ❌, 52KB ✅, 100KB иногда ✅ — случайность, а не порог +- Лимит дал бы детерминированный сбой +- Случайность = зависимость от пути (ECMP), не от размера + +## Почему keep-alive = 80% +Одно соединение «прилипает» к одному backend-пути (ECMP hash по 5-tuple). Попал на хороший путь — всё работает. Новый коннект = новый хэш = рулетка (~50% плохих путей). + +## Почему маленькие тела проходят +Не требуют длинной серии full-MSS сегментов — MTU-дыра не успевает сработать. + +## Корень +Edge LB раскидывает TCP-коннекты по нескольким путям. На части путей ICMP `frag-needed` (type 3 code 4) режется → PMTU Discovery сломан → full-MSS сегменты молча дропаются → таймаут. + +## Что сработало бы +```bash +# MSS clamping на ноде — сегменты не превышают безопасный MTU +iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1360 +``` + +## Ответы на вопросы +1. **conntrack + hostPort:** косвенно (INVALID на out-of-window ретрансмиты), но корень — MTU +2. **NodePort/LoadBalancer:** не лечит, DNAT-слой остаётся +3. **WebSocket:** даёт эффект keep-alive (80%), не 100% +4. **Тюнинг:** MSS clamping + `nf_conntrack_tcp_be_liberal=1` + Cilium MTU + +## План подтверждения +1. MSS clamp 1360 → тест 100KB×10 снаружи → если 100% = MTU подтверждён +2. `ping -M do -s 1472` вниз по размеру — найти где рвётся +3. `conntrack -S`, `nstat`, `dmesg` +4. Cilium: tunnel mode? pod MTU vs tunnel MTU +5. `iptables -t mangle -L` — есть ли уже TCPMSS/DROP INVALID? diff --git a/History/2026-07-10-sonnet-answer-kubevip.md b/History/2026-07-10-sonnet-answer-kubevip.md new file mode 100644 index 0000000..1b379c9 --- /dev/null +++ b/History/2026-07-10-sonnet-answer-kubevip.md @@ -0,0 +1,29 @@ +# 2026-07-10 — Ответ Соннета: kube-vip ARP split-brain + +## Диагноз подтверждён +ARP split-brain: все 4 ноды анонсят VIP, 2 без ingress → ~50% трафика в никуда. + +## Почему `svc_election: true` не помогает +Lease есть, holder = v8zq4. Но `svc_election` управляет только k8s-объектами (кто обновляет Service/Endpoints), а НЕ тем, кто добавляет IP на интерфейс и шлёт ARP. Все 4 пода всё равно делают `ip addr add 185.247.187.151/32`. + +## Почему `vip_leaderelection: true` не применился +Это флаг для control-plane VIP (api-server HA), а не для LoadBalancer-сервисов. Бесполезен для нашей задачи. + +## Решения + +| # | Вариант | Сложность | Риск | +|---|---------|-----------|------| +| 1 | Taint на ноды без ingress | Низкая | Низкий | +| 2 | nodeSelector через Shturval customvalues | Средняя | Синтаксис | +| 3 | nodeAffinity через customvalues | Средняя | Синтаксис | +| 4 | BGP вместо ARP | Высокая | Роутер | + +## Рекомендация Соннета +**Taint на ноды без ingress** — убрать kube-vip с control-plane и worker-6f74n: +```bash +kubectl taint node control-plane no-ingress=true:NoExecute +kubectl taint node worker-6f74n no-ingress=true:NoExecute +``` +Kube-vip DaemonSet не имеет toleration → поды выселятся с этих нод. +ARP будут слать только 2 ноды с ingress → 100% трафика в цель. +Откат: `kubectl taint node X no-ingress-` diff --git a/History/2026-07-10-vip-isolated.md b/History/2026-07-10-vip-isolated.md new file mode 100644 index 0000000..efc6730 --- /dev/null +++ b/History/2026-07-10-vip-isolated.md @@ -0,0 +1,19 @@ +# 2026-07-10 — VIP исключён: проблема в Flask + +## Решающий тест +Nginx (echo) через тот же VIP/ingress → 100KB POST 10/10 = 100% успех. + +| Сервис | Путь | Результат | +|--------|------|-----------| +| nginx:alpine | VIP → ingress → echo pod | ✅ 10/10 | +| Flask loadtest | VIP → ingress → pythonk8s pod | ❌ 3-5/10 | +| Flask loadtest | изнутри кластера | ✅ 10/10 | + +## Вывод +- VIP, ingress, kube-vip, сеть — НЕ проблема +- Flask — проблема. Но только при внешнем доступе через ingress. +- Flask изнутри работает идеально. Разница: ingress→Flask vs прямой доступ. + +## Что дальше +Копать разницу между nginx:alpine (работает) и Flask (не работает) при проксировании через ingress. +Возможно: Flask dev-сервер, таймауты, буферизация, Content-Length vs Transfer-Encoding. diff --git a/History/opus-question-final.md b/History/opus-question-final.md new file mode 100644 index 0000000..46d909a --- /dev/null +++ b/History/opus-question-final.md @@ -0,0 +1,46 @@ +# Opus request: loadtest upload > 48KB fails — FULL ANALYSIS + +## СУТЬ +Flask-сервис loadtest на Shturval k8s v2.12.1. POST /upload с телом > 48KB — случайные обрывы. + +## ДОКАЗАННЫЕ ТЕСТЫ + +| # | Тест | Результат | +|---|------|-----------| +| 1 | pod → pod:5000 (внутри кластера) | ✅ 100% (102400 байт, 4ms) | +| 2 | pod → ingress HTTPS (внутри кластера) | ✅ 100% (102400 байт, 2ms) | +| 3 | Снаружи → 185.247.187.151:443 (новый TCP каждый раз) | ❌ ~30-50% Timeout | +| 4 | Снаружи (HTTP/1.1 keep-alive, одно соединение) | ✅ 80% (первые 2 таймаут, остальные OK) | +| 5 | VM-хост → pod IP:5000 (GET /health) | ❌ 0% (connection timeout) | +| 6 | VM-хост → k8s service IP:80 | ❌ 0% | +| 7 | VM-хост → native nginx → k8s service | ❌ 0% | + +## КЛЮЧЕВОЕ ОТКРЫТИЕ +**VM-хост вообще не может общаться с pod-сетью напрямую** (тест #5-7). +Трафик снаружи идёт через **hostPort** (443 на ноде), который использует iptables DNAT для проброса в ingress-под. Это kernel-level проксирование, а не обычная маршрутизация. + +## ИНФРАСТРУКТУРА +``` +Edge LB (NUBES LLC, 185.247.187.151) + → VM (5.172.178.213/10.10.102.21, ens192 MTU 1500) + → hostPort:443 → iptables DNAT → ingress pod (172.16.x.x) + → nginx (Shturval, ModSecurity DetectionOnly, http2 on) + → k8s service (10.106.36.241:80) + → app pod (172.16.x.x:5000, Flask dev-server) +``` +- CNI: Cilium (подтверждено namespace cilium-secrets) +- Pod MTU: 1500, Host MTU: 1500 +- ModSecurity: SecRuleEngine DetectionOnly, SecRequestBodyNoFilesLimit 131072 + +## ЧТО УЖЕ СДЕЛАНО +- proxy-request-buffering: off (ingress annotation) — улучшило с 3/10 до 5/10 +- keep-alive 10→75 — УХУДШИЛО (0/10), откачено +- Ретраи на клиенте — КОСТЫЛЬ, не принимается +- Dockerfile Gunicorn limits — бесполезно (Shturval запускает python app.py) + +## ГИПОТЕЗА +iptables DNAT (hostPort) + conntrack + большое тело = часть TCP-сегментов теряется при трансляции. После установки соединения (keep-alive) — стабильно. Новые соединения дропаются случайно. + +## ВОПРОС +Как добиться 100% надёжности загрузки без ретраев? +Варианты: WebSocket? gRPC? Убрать hostPort, использовать NodePort/LoadBalancer? Тюнинг conntrack? diff --git a/History/opus-question-upload.md b/History/opus-question-upload.md new file mode 100644 index 0000000..c9119aa --- /dev/null +++ b/History/opus-question-upload.md @@ -0,0 +1,51 @@ +# Вопрос Opus: как добиться 100% загрузки через edge LB без ретраев + +## СУТЬ +Edge LB (NUBES LLC, 185.247.187.151) дропает ~50-70% новых TCP-соединений при POST > ~48KB. +НО: внутри k8s кластера — 100%. После установки соединения (keep-alive) — 8/10. + +## ДОКАЗАННЫЕ ФАКТЫ +- Flask под напрямую: 100% (102400 байт, 4ms) +- Ingress HTTPS изнутри кластера: 100% +- Снаружи (новый TCP на каждый запрос): ~30-50% +- Снаружи (HTTP/1.1 keep-alive, одно соединение): 80% (первые 2 таймаута, потом OK) +- Маленькие POST (< 48KB): 100% + +## ЧТО УЖЕ СДЕЛАНО (без эффекта) +- proxy-request-buffering: off (ingress annotation) — частично помогло +- keep-alive увеличен (10→75) — ухудшило (агрессивнее дроп) +- HTTP/2 уже включён (http2 on) +- ModSecurity DetectionOnly (не блокирует) +- Ретраи на клиенте — КОСТЫЛЬ, не решение + +## ГЛАВНЫЙ ВОПРОС +Как настроить k8s nginx ingress (Shturval) так, чтобы edge LB НЕ дропал соединения? +Что именно в нативном nginx (contracts) позволяет ему работать, а k8s ingress — нет? + +## Конфиг работающего contracts (nginx на ВМ, тот же IP) +```nginx +location /upload { + proxy_pass http://127.0.0.1:8766; + client_max_body_size 100m; + proxy_request_buffering off; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Content-Type $content_type; + proxy_set_header Content-Length $content_length; +} +``` + +## Текущий ingress loadtest (k8s) +```yaml +annotations: + nginx.ingress.kubernetes.io/proxy-body-size: "1024m" + nginx.ingress.kubernetes.io/proxy-connect-timeout: "120" + nginx.ingress.kubernetes.io/proxy-read-timeout: "600" + nginx.ingress.kubernetes.io/proxy-send-timeout: "600" + nginx.ingress.kubernetes.io/proxy-request-buffering: "off" +``` + +## ОГРАНИЧЕНИЯ +- Только k8s (ingress, поды, деплойменты) +- Edge LB не наш +- Никаких ретраев/костылей — нужно 100% без них diff --git a/History/sonnet-question-kubevip-detailed.md b/History/sonnet-question-kubevip-detailed.md new file mode 100644 index 0000000..71767e4 --- /dev/null +++ b/History/sonnet-question-kubevip-detailed.md @@ -0,0 +1,127 @@ +# Sonnet: найти точку отказа внутри k8s — загрузка >48KB падает + +## КОНТЕКСТ +Сервис: Flask loadtest (замер скорости загрузки) на Shturval managed k8s. POST /upload с base64-телом >48KB случайно обрывается (TimeoutError на TCP-коннекте). Мелкие файлы — всегда 100%. + +Требование: починить ВНУТРИ k8s. Без ВМ, без внешнего роутера, без ретраев/костылей. + +## ИНФРАСТРУКТУРА +``` +Shturval 2.12.1, Cilium CNI, 4 ноды: + - control-plane (10.10.102.2) — kube-vip ✅, ingress ❌ + - worker-6f74n (10.10.102.3) — kube-vip ✅, ingress ❌ + - worker-bhbvs (10.10.102.4) — kube-vip ✅, ingress ✅ (hostPort 443) + - worker-v8zq4 (10.10.102.5) — kube-vip ✅, ingress ✅ (hostPort 443) + +kube-vip: DaemonSet, ARP mode, vip_leaderelection=false, hostNetwork=true + Все 4 ноды добавляют VIP 185.247.187.151 на интерфейс и шлют gratuitous ARP + +nginx ingress: Deployment, 2 реплики, hostPort 443 + Service type LoadBalancer, external IP = 185.247.187.151 + Ингресс-аннотации: proxy-body-size=1024m, timeouts 120-600s, proxy-request-buffering=off + +ModSecurity: OWASP CRS 4.10, SecRuleEngine DetectionOnly, SecRequestBodyNoFilesLimit=131072 +Flask: dev-сервер (python app.py), порт 5000, НЕ Gunicorn +``` + +## ВСЕ ТЕСТЫ + +### 1. Базовые (изоляция компонентов) +| # | Источник | Цель | Размер | Результат | +|---|----------|------|--------|-----------| +| 1 | Под pythonk8s | свой localhost:5000 | 100KB base64 (136KB POST) | ✅ 10/10, ~4ms | +| 2 | Под pythonk8s | ingress ClusterIP:443 | 136KB POST | ✅ 10/10, ~2ms | +| 3 | Под pythonk8s | VIP 185.247.187.151:443 | 136KB POST | ✅ 10/10, 1-6ms | +| 4 | Мы (интернет) | VIP 185.247.187.151:443 | 136KB POST | ❌ 3-5/10, TimeoutError | +| 5 | Мы (интернет) | прямой IP ВМ 5.172.178.213:443 | 136KB POST | ✅ 0 таймаутов, но "no file" | +| 6 | ВМ (10.10.102.21) | VIP 185.247.187.151:443 | 136KB POST | ❌ 1/10 | +| 7 | ВМ | worker node :443 | любой | ❌ ConnectionRefused | + +### 2. Бинарный поиск предела +| Размер | Результат (снаружи→VIP) | +|--------|------------------------| +| 1-48 KB | ✅ всегда | +| 50 KB | ❌ | +| 52 KB | ✅ | +| 55-80 KB | ❌ | +| 100 KB | иногда ✅, иногда ❌ | + +Жёсткого предела НЕТ — случайность. + +### 3. HTTP/1.1 keep-alive (одно соединение) +| Тест | Результат | +|------|-----------| +| 10 запросов на одном TCP-соединении | ✅ 8/10 (первые 2 таймаута при установке, остальные OK) | + +### 4. Rapid-fire vs delayed (проверка ARP-стабильности) +Rapid (без пауз): сначала 5 таймаутов, потом 3 OK подряд +10s пауза: снова миксуется + +## ЧТО ПРОБОВАЛИ (внутри k8s) + +| Действие | Результат | +|----------|-----------| +| `proxy_request_buffering: off` (ingress annotation) | было 3/10 → стало 5/10 | +| `keep-alive: 10→75` (ingress ConfigMap) | ❌ ухудшило 5→2/10, откачено | +| `iptables TCPMSS --set-mss 1200-1360` на ноде | ❌ 3-4/10, откачено | +| `vip_leaderelection: true` (kube-vip DS) | ❌ 5→3/10, откачено | +| ingress replicaCount 2→3 (scale) | ❌ 3/10, откачено | +| Dockerfile Gunicorn field_size | ❌ бесполезно (Gunicorn не используется) | +| conntrack проверка | ✅ чистый (54/262K), не проблема | +| ModSecurity | ✅ DetectionOnly, не проблема | + +## ЧТО НЕ ПРОБОВАЛИ +- kube-vip BGP вместо ARP +- NodePort/LoadBalancer вместо hostPort +- Убрать kube-vip с нод без ingress (nodeSelector/taints) +- Увеличить ARP-интервал в kube-vip +- Включить strict ARP (kube-vip only on leader node) + +## КЛЮЧЕВОЙ ВЫВОД +Внутри k8s ВСЁ работает идеально (тесты 1-3). Снаружи — нет (тест 4). Разница: внешний роутер получает ARP от ВСЕХ 4 нод (kube-vip без лидера), включая 2 ноды БЕЗ ingress. Когда роутер выбирает MAC ноды без ingress → connection timeout. + +## KUBE-VIP КОНФИГУРАЦИЯ (полностью) + +```yaml +# DaemonSet: shturval-vip (namespace: kube-vip) +Image: r.shturval.tech/kube-vip:v1.0.0 +hostNetwork: true +env: + cp_enable: "false" + enable_endpointslices: "true" + enable_node_labeling: "true" + lb_enable: "true" # LoadBalancer включён + lb_port: "6443" + svc_election: "true" # выборы для сервисов + svc_enable: "true" + vip_arp: "true" # ARP-режим (не BGP) + vip_interface: "" # пусто = все интерфейсы + vip_leaderelection: "false" # ВСЕ ноды анонсят VIP + vip_leasename: "sht-uservip-cp-lock" + vip_subnet: "32" + vip_nodename: "" # из spec.nodeName + +# ShturvalServiceConfig: shturval-vip +spec: + chart: shturval-vip + version: 2.12.1 + mode: auto + iscritical: true + namespace: kube-vip + +# Ingress Service (namespace: ingress) +type: LoadBalancer +externalTrafficPolicy: Cluster +status.loadBalancer.ingress.ip: 185.247.187.151 +status.loadBalancer.ingress.ipMode: VIP +# Lease: kubevip-shturval-ingress-controller-controller (ingress ns) +holderIdentity: iot-naeel-workers-vqphm-v8zq4 (с 2026-04-20) +``` + +## ВОПРОС +kube-vip v1.0.0 ARP mode, vip_leaderelection=false, DaemonSet на 4 нодах, ingress hostPort только на 2. +Как заставить VIP 185.247.187.151 всегда вести на ноды с ingress? +- `vip_leaderelection: true` не применился через DaemonSet (Shturval не подхватил customvalues) +- `svc_election: true` уже есть — почему не работает? +- Есть ли способ через `customvalues` в ShturvalServiceConfig правильно включить лидера? +- Или нужен `nodeSelector` на DaemonSet чтобы kube-vip был только на нодах с ingress? diff --git a/History/sonnet-question-kubevip.md b/History/sonnet-question-kubevip.md new file mode 100644 index 0000000..b262b37 --- /dev/null +++ b/History/sonnet-question-kubevip.md @@ -0,0 +1,35 @@ +# Вопрос Соннету: найти ОДНУ точку отказа внутри k8s + +## Данные (без догадок) + +| Источник | Цель | Результат | +|----------|------|-----------| +| Под внутри k8s → VIP 185.247.187.151:443 (100KB POST) | ✅ 10/10 | +| Под внутри k8s → ingress ClusterIP:443 | ✅ 10/10 | +| Под внутри k8s → под напрямую:5000 | ✅ 10/10 | +| Снаружи (интернет) → VIP:443 | ❌ 3-5/10 (TimeoutError) | +| Снаружи → прямой IP ВМ:443 (не k8s) | ✅ 0 таймаутов | + +## Инфраструктура (только k8s) +- Shturval 2.12.1, Cilium CNI +- kube-vip DaemonSet (ARP mode, vip_leaderelection=false) на ВСЕХ 4 нодах +- nginx ingress (hostPort 443), 2 реплики на 2 из 4 worker-нод +- VIP 185.247.187.151 на ingress LoadBalancer сервисе +- Pod → VIP работает 100% (CNI маршрутизация в обход внешнего роутера) + +## Что уже исключено +- Flask — работает +- nginx ingress — работает +- ModSecurity — DetectionOnly +- conntrack — чистый +- MSS/MTU — не помогло +- proxy_request_buffering off — частично улучшило но не решило + +## Гипотеза +Внешний роутер получает ARP-ответы от ВСЕХ 4 нод (kube-vip без лидера). Часть нод без ingress пода. Когда роутер выбирает MAC ноды без ingress → connection timeout. + +## Вопрос +Как внутри k8s (не трогая внешний роутер/ВМ) сделать так, чтобы внешние соединения на VIP работали 100%? +- Можно ли ограничить kube-vip DaemonSet только нодами с ingress? +- Можно ли заставить kube-vip держать VIP только на ОДНОЙ ноде (где есть ingress)? +- Есть ли другой механизм в kube-vip/Shturval для фикса ARP-флаппинга? diff --git a/site/app.py b/site/app.py index 9d4150d..74f9b2f 100644 --- a/site/app.py +++ b/site/app.py @@ -4,7 +4,7 @@ from flask import Flask, render_template, request app = Flask(__name__) -VERSION = '1.0.6' +VERSION = '1.0.7' @app.route('/') @@ -21,6 +21,8 @@ def health(): def upload(): t0 = time.time() total = 0 + # Принудительно читаем тело до парсинга формы + request.get_data() # base64-режим: клиент отправляет поле "data" data = request.form.get('data', '') if data: @@ -48,4 +50,4 @@ def upload(): if __name__ == '__main__': - app.run(host='0.0.0.0', port=5000, debug=True) + app.run(host='0.0.0.0', port=5000, debug=False, threaded=True)