128 lines
6.3 KiB
Markdown
128 lines
6.3 KiB
Markdown
# 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?
|