fix: debug=False, force read body, bump 1.0.6→1.0.7
Deploy loadtest / validate (push) Waiting to run

This commit is contained in:
2026-07-10 22:59:56 +04:00
parent 6ce7bd9720
commit 91d717585a
8 changed files with 349 additions and 2 deletions
+38
View File
@@ -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?
@@ -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-`
+19
View File
@@ -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.
+46
View File
@@ -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?
+51
View File
@@ -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% без них
+127
View File
@@ -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?
+35
View File
@@ -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-флаппинга?
+4 -2
View File
@@ -4,7 +4,7 @@ from flask import Flask, render_template, request
app = Flask(__name__) app = Flask(__name__)
VERSION = '1.0.6' VERSION = '1.0.7'
@app.route('/') @app.route('/')
@@ -21,6 +21,8 @@ def health():
def upload(): def upload():
t0 = time.time() t0 = time.time()
total = 0 total = 0
# Принудительно читаем тело до парсинга формы
request.get_data()
# base64-режим: клиент отправляет поле "data" # base64-режим: клиент отправляет поле "data"
data = request.form.get('data', '') data = request.form.get('data', '')
if data: if data:
@@ -48,4 +50,4 @@ def upload():
if __name__ == '__main__': 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)