# Варианты решения ingress replicas + TCP stall

**Дата:** 2026-07-13  
**Источники:** Sonnet analysis, Opus analysis  
**Связано:** [KUBEVIP-EXTERNAL-TRAFFIC-POLICY.md](KUBEVIP-EXTERNAL-TRAFFIC-POLICY.md)

---

## Ключевой вывод (оба согласны)

Проблемы **две независимые**, решать по отдельности:

1. **TCP stall 51с** — PMTU Blackhole на Geneve-туннеле, а не свойство `Cluster`-политики. `Local` лечит как побочный эффект (убирает cross-node).
2. **Сброс реплик** — `kubectl scale` обходит Helm, поэтому реконсиляция откатывает.

> ⚠️ **Важно (Opus):** `kubectl scale` вы обходите Helm, поэтому и сбрасывается. Любое итоговое решение должно быть в **values чарта**, а не через `kubectl`.

---

## Варианты

### 1. DaemonSet (рекомендуется)

Одно изменение в Helm values:

```yaml
controller:
  kind: DaemonSet
  # replicaCount игнорируется
```

- ✅ Автоматически 1 под/ноду
- ✅ Helm не ломает
- ✅ Стандарт для bare-metal / kube-vip
- ✅ Не требует менять сетевую конфигурацию

### 2. Починить MTU (убрать stall в корне)

51с — это классический Path MTU Discovery Blackhole. Geneve: MTU 1500 → ~1450.

**Диагностика:**
```bash
ip link show | grep -E "cilium|genev"
iptables -t mangle -L FORWARD -n -v | grep TCPMSS
```

**Исправление:**
```bash
iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
```

Если stall устранён → можно вернуть `externalTrafficPolicy: Cluster` и replicaCount 2.

### 3. Cilium DSR (Direct Server Return)

Ответный трафик идёт напрямую от пода к клиенту, без cross-node SNAT.

```yaml
# cilium-config (требует kube-proxy replacement = strict)
loadBalancer:
  mode: dsr
kubeProxyReplacement: strict
```

- ❌ Долго, меняет всю сетевую конфигурацию

### 4. replicaCount: 4 + podAntiAffinity

Если Штурвал не даёт сменить `kind` на DaemonSet:

```yaml
controller:
  replicaCount: 4
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchLabels:
              app.kubernetes.io/name: ingress-nginx
          topologyKey: kubernetes.io/hostname
```

- ✅ Гарантирует 1 под/ноду
- ✅ Helm values зафиксированы, не сбрасывается

---

## Рекомендуемый путь (Sonnet + Opus)

| Приоритет | Действие | Эффект |
|-----------|----------|--------|
| 🟢 Сразу | `controller.kind: DaemonSet` в Helm values | Убирает костыль с репликами навсегда |
| 🟡 Параллельно | Диагностика MTU + MSS clamping | Устраняет stall в корне |
| 🔵 Потом | Если MTU починен — вернуть `Cluster` | Можно уйти от `Local` и требования под/ноду |

## Как передать девопсу (Opus)

Для чарта `shturval-ingress-controller-2.12.1`:

```bash
# Вариант A: DaemonSet
helm upgrade -n ingress shturval-ingress-controller <чарт> \
  --set controller.kind=DaemonSet

# Вариант B: replicaCount + antiAffinity (если DaemonSet недоступен)
helm upgrade -n ingress shturval-ingress-controller <чарт> \
  --set controller.replicaCount=4 \
  --set controller.affinity.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].labelSelector.matchLabels.app\.kubernetes\.io/name=ingress-nginx \
  --set controller.affinity.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey=kubernetes.io/hostname
```

Либо через Штурвал UI — изменить `replicaCount: 4` в настройках чарта.

## Диагностика MTU (Opus)

```bash
# реальный MTU туннеля
ip link show | grep -E "cilium|genev"

# есть ли MSS clamping
iptables -t mangle -L FORWARD -n -v | grep MSS
```

Варианты фикса:
- MSS clamping: `iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu`
- Cilium DSR: `loadBalancer.mode: dsr` + `kubeProxyReplacement: strict`
- Выставить MTU в Cilium под физическую сеть (Geneve overhead ~50 байт)

---

## Текущий статус

`externalTrafficPolicy: Local` — не сбрасывается.  
`replicas: 4` — ручной scale, периодически сбрасывается Helm на 2.
