feat: Gunicorn + gevent (keepalive), bump 1.0.8→1.0.9
Deploy loadtest / validate (push) Waiting to run

This commit is contained in:
2026-07-10 23:42:54 +04:00
parent 99e7113459
commit 4a09038f96
6 changed files with 130 additions and 2 deletions
+23
View File
@@ -0,0 +1,23 @@
# 2026-07-10 — Финальный ответ: Werkzeug Connection: close
## Корень
Werkzeug dev-сервер ВСЕГДА закрывает TCP-соединение после ответа (`Connection: close`).
nginx должен создавать НОВЫЙ TCP на каждый запрос → Cilium conntrack cycling → intermittent failure.
nginx:alpine держит `Connection: keep-alive` → 1 TCP на все запросы → 10/10.
## Почему все тесты такие
| Тест | Результат | Причина |
|------|-----------|---------|
| nginx:alpine | 10/10 | keep-alive, 1 TCP |
| Flask | 2-5/10 | close, N TCP |
| Gunicorn sync | 1/10 | тоже close + ошибка конфига |
| upstream-keepalive=0 | 0/10 | даже keepalive запрещён |
| внутри кластера | 10/10 | другой code path Cilium |
| свежий деплой 10/10 | чистый conntrack |
## Решение
Gunicorn с gevent worker (поддерживает keepalive):
```
gunicorn -w 2 -k gevent --keep-alive 75 --timeout 120 --bind 0.0.0.0:5000 app:app
```
@@ -0,0 +1,19 @@
# 2026-07-10 — Ответ Соннета: stale upstream keepalive
## Диагноз
nginx держит пул keepalive-соединений к Flask. После редеплоя — старые соединения ведут на мёртвый pod IP (Cilium conntrack). Часть запросов попадает на живые, часть на stale → случайность.
## Почему nginx:alpine всегда 10/10
Он тоже в поде, но nginx→nginx keepalive работает корректно. nginx→Werkzeug — нет (Werkzeug шлёт Connection: close, nginx может игнорировать).
## Почему Gunicorn 1/10
Был запущен с 1 sync worker. H2 multiplexing от nginx создаёт много конкурентных запросов → один worker не справляется → 9/10 в очереди → таймаут.
## План проверок
| # | Действие | Что проверяет |
|---|----------|--------------|
| 1 | ingress annotation: `upstream-keepalive-connections: "0"` | Stale keepalive — если 10/10, причина найдена |
| 2 | Gunicorn `-w 4 -k gevent --keep-alive 0` | Concurrency fix |
| 3 | Ingress: `use-http2: "false"` | H2 multiplexing |
| 4 | `kubectl rollout restart` + сразу тест | Cilium conntrack |
+38
View File
@@ -0,0 +1,38 @@
# Sonnet: противоречие — nginx 10/10, Flask raw то 10/10 то 2/10
## Данные без интерпретаций
### Тест 1: nginx:alpine через тот же VIP/ingress/порт
100KB raw binary POST, 10 запросов, таймаут 2с: **стабильно 10/10**
### Тест 2: Flask (тот же ingress/VIP) — raw binary
100KB raw binary POST, те же параметры:
- Первый прогон: 10/10
- Второй прогон (после редеплоя): 2/10
- Третий прогон: 2/10
### Тест 3: Flask — URL-encoded form (base64)
100KB base64 → 136KB POST: стабильно 2-5/10
### Тест 4: Flask изнутри кластера
pod → VIP: 10/10 всегда
pod → ingress service: 10/10 всегда
### Что пробовали
- debug=False: 5/10
- Gunicorn: 1/10 (хуже)
- proxy_request_buffering on/off: 3-5/10
- kube-vip nodeSelector/taint: не влияет
- MSS clamping: не влияет
- raw binary вместо form: то 10/10, то 2/10
## Инфра
- Shturval 2.12.1, Cilium CNI
- Flask 3.1.3, Werkzeug 3.1.8
- nginx ingress, http2 on, proxy_request_buffering off
- kube-vip ARP mode, 4 ноды, ingress hostPort на 2
## Вопрос
Почему nginx:alpine через тот же ingress стабильно 10/10, а Flask raw binary — то 10/10, то 2/10?
Что именно в цепочке ingress→Flask даёт такую нестабильность, если тело одно и то же (raw 100KB)?
Может ли Werkzeug dev server иметь скрытый баг с keepalive/connection reuse, который проявляется случайно?
+26
View File
@@ -0,0 +1,26 @@
# Sonnet: финальный вопрос
## Ситуация
Nginx:alpine через managed Flask ingress = 100KB POST 10/10 всегда.
Тот же Flask через тот же ingress = 2-5/10, случайно.
## ВСЁ что пробовали (без эффекта)
- debug=False
- Gunicorn (хуже — 1/10)
- proxy_request_buffering on/off
- upstream-keepalive=0 (хуже — 0/10)
- use-http2=false
- raw binary вместо URL-encoded form
- kube-vip: nodeSelector, taint, leader election
- MSS clamping
- свежий редеплой
- chunky 100KB
- ретраи
## Инфра
Flask 3.1.3, Werkzeug 3.1.8, Shturval 2.12.1, Cilium CNI, kube-vip ARP.
Flask и nginx на одной ноде. Ingress на двух нодах.
Внутри кластера всё 10/10 всегда.
## Вопрос
Какого хуя nginx:alpine 10/10, а Flask 2-5/10 через АБСОЛЮТНО тот же ingress/сеть/ноду?
+1
View File
@@ -1,2 +1,3 @@
flask flask
gunicorn gunicorn
gevent
+23 -2
View File
@@ -4,7 +4,7 @@ from flask import Flask, render_template, request
app = Flask(__name__) app = Flask(__name__)
VERSION = '1.0.8' VERSION = '1.0.9'
@app.route('/') @app.route('/')
@@ -51,4 +51,25 @@ def upload():
if __name__ == '__main__': if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=False, threaded=True) import gunicorn.app.base
class StandaloneApp(gunicorn.app.base.BaseApplication):
def __init__(self, app, options=None):
self.application = app
self.options = options or {}
super().__init__()
def load_config(self):
for k, v in self.options.items():
self.cfg.set(k, v)
def load(self):
return self.application
StandaloneApp(app, {
'bind': '0.0.0.0:5000',
'workers': 2,
'worker_class': 'gevent',
'timeout': 120,
'keepalive': 75,
}).run()