Перейти до вмісту

Модуль 3.8: Шлях даних мережі кластера

Hands-On Lab Available
K8s Cluster advanced 40 min
Launch Lab ↗

Opens in Killercoda in a new tab

Складність: [MEDIUM] — Ключова тема діагностики

Час на виконання: ~35 хвилин

Передумови: Модуль 3.1 (Сервіси), Модуль 3.6 (Мережеві політики), Модуль 3.7 (CNI)


Що ви зможете робити

Розділ «Що ви зможете робити»

Після цього модуля ви зможете:

  • Простежити пакет від пода A до пода B між вузлами через весь мережевий стек
  • Пояснити, як VXLAN, IP-in-IP та native routing працюють на рівні Linux
  • Дебажити проблеми міжвузлового з’єднання, перевіряючи маршрути, мости та тунельні інтерфейси
  • Порівняти overlay та native routing підходи та їх компроміси щодо продуктивності

Чому цей модуль важливий

Розділ «Чому цей модуль важливий»

Ви можете створювати Сервіси та писати мережеві політики цілий день, але коли щось ламається у продакшені о 3 годині ночі, вам потрібно розуміти, куди насправді йдуть пакети. Цей модуль навчить вас ментальній моделі, яка перетворює мережеві загадки на розвʼязувані задачі.

Бойова історія: Мовчазне скидання через MTU

Платформна команда мігрувала кластер з Flannel із host-gw на Flannel із vxlan-інкапсуляцією. Все виглядало нормально — малі проби перевірки стану проходили, ping між Подами працював, Сервіси розвʼязувалися коректно. Але кожні кілька хвилин критичне пакетне завдання зависало та зрештою завершувалося таймаутом.

Після двох днів безрезультатної діагностики (перезапуск Подів, перевірка DNS, звинувачення застосунку) молодший інженер запустив tcpdump на вузлі та помітив дещо особливе: TCP SYN-пакети проходили між вузлами нормально, але великі навантаження даних мовчазно скидалися. Причина? VXLAN додає 50-байтовий заголовок, зменшуючи ефективний MTU з 1500 до 1450. CNI був налаштований на MTU 1500, тому будь-який пакет, близький до ліміту, був занадто великий для тунелю, і біт Don't Fragment змушував ядро мовчазно скидати його замість фрагментації.

Виправлення було однорядковою зміною конфігурації ("MTU": 1450), але знайти його вимагало розуміння фактичного шляху даних — де пакети входять в ядро, як вони інкапсулюються та де вони виходять. Саме цьому і вчить цей модуль.


Після завершення цього модуля ви зможете:

  • Простежити пакет від клієнтського Поду через правила kube-proxy до Поду-бекенду
  • Розрізняти відповідальність CNI та kube-proxy
  • Пояснити, як працює розвʼязання імен CoreDNS від початку до кінця
  • Використовувати tcpdump, iptables-save, conntrack та nslookup для діагностики реальних мережевих проблем
  • Застосовувати систематичну ментальну модель діагностики мережі кластера

  • kube-proxy насправді нічого не проксіює (попри свою назву). У режимі iptables він просто програмує правила DNAT в ядрі. Фактична пересилка пакетів повністю обробляється мережевим стеком Linux — kube-proxy ніколи не бачить самих пакетів даних.

  • Один Сервіс із 1000 бекендами генерує ~8000 правил iptables у режимі iptables. Ось чому великі кластери (5000+ Сервісів) часто переходять на режим IPVS або рішення на основі eBPF, як Cilium, які можуть обробляти сотні тисяч бекендів без лінійного сканування правил.

  • Kubernetes вимагає пласку мережу: кожен Під повинен мати можливість звʼязатися з кожним іншим Подом без NAT. Це єдине проєктне рішення (задокументоване в мережевій моделі Kubernetes) робить можливою всю абстракцію Сервісів — і саме тому існують плагіни CNI.

  • CoreDNS обробляє приблизно 10 000–50 000 запитів на секунду у типовому продакшен-кластері. Одне неправильно налаштоване значення ndots може помножити це на 5, тому що кожен запит ініціює розширення пошукових доменів (наприклад, api.example.com перетворюється на 5 окремих DNS-запитів, перш ніж останній успішно завершиться).


Частина 1: Потік від Сервісу до Поду — прохід пакету

Розділ «Частина 1: Потік від Сервісу до Поду — прохід пакету»

Розуміння точного шляху пакету — це основа всієї мережевої діагностики. Простежимо запит від Поду A до ClusterIP Сервісу, який маршрутизує до Поду B.

1.1 Прохід пакету ClusterIP

Розділ «1.1 Прохід пакету ClusterIP»
┌─────────────────────────────────────────────────────────────────────────┐
│ Прохід пакету ClusterIP (режим iptables) │
│ │
│ Під A (10.244.1.5) Під B (10.244.2.8) │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ curl 10.96.0.50 │ │ nginx :80 │ │
│ └────────┬────────┘ └────────▲────────┘ │
│ │ │ │
│ ▼ │ │
│ ┌─────────────────┐ ┌────────┴────────┐ │
│ │ 1. пара veth │ │ 7. пара veth │ │
│ │ (під → вузол)│ │ (вузол → під)│ │
│ └────────┬────────┘ └────────▲────────┘ │
│ │ │ │
│ ▼ │ │
│ ┌─────────────────┐ │ │
│ │ 2. iptables │ Ланцюжок PREROUTING │ │
│ │ правило DNAT │ dst: 10.96.0.50:80 │ │
│ │ перезаписує │ → 10.244.2.8:80 │ │
│ │ dst │ │ │
│ └────────┬────────┘ │ │
│ │ │ │
│ ▼ │ │
│ ┌─────────────────┐ │ │
│ │ 3. conntrack │ Записує NAT- │ │
│ │ запис таблиці│ відображення для │ │
│ └────────┬────────┘ зворотного трафіку │ │
│ │ │ │
│ ▼ │ │
│ ┌─────────────────┐ ┌────────┴────────┐ │
│ │ 4. Рішення │ │ 6. Рішення │ │
│ │ маршрутизації│ │ маршрутизації│ │
│ │ (той самий │──── той самий ────────►│ (доставити │ │
│ │ вузол чи │ вузол? │ локально) │ │
│ │ тунель?) │ │ │ │
│ └────────┬────────┘ └─────────────────┘ │
│ │ інший вузол │
│ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 5a. Інкапсуляція│ ═══ VXLAN/Geneve ═══►│ 5b. Декапсуляція│ │
│ │ CNI │ тунель │ CNI │ │
│ │ (якщо │ │ (на вузлі │ │
│ │ оверлей) │ │ призначення)│ │
│ └─────────────────┘ └────────┬────────┘ │
│ Вузол 1 │ Вузол 2 │
│ └──► крок 6 │
└─────────────────────────────────────────────────────────────────────────┘

Ось що відбувається на кожному кроці:

  1. Пара veth: Пакет залишає мережевий простір імен Поду A через віртуальну пару ethernet, яка зʼєднує його з мережевим простором імен хоста (вузла).
  2. iptables DNAT: kube-proxy запрограмував правила iptables. Ланцюжок PREROUTING знаходить збіг з адресою призначення 10.96.0.50 (ClusterIP Сервісу) і перезаписує її на IP Поду-бекенду — скажімо, 10.244.2.8. Якщо існує кілька бекендів, випадковий або циклічний вибір відбувається через правила ймовірності iptables.
  3. conntrack: Модуль відстеження зʼєднань ядра записує це NAT-відображення. Коли Під B відповідає, conntrack автоматично обертає трансляцію, щоб Під A бачив відповідь від IP Сервісу, а не від IP Поду.
  4. Рішення маршрутизації: Ядро маршрутизує пакет на основі перезаписаної адреси призначення. Якщо Під B на тому ж вузлі, пакет іде безпосередньо до пари veth Поду B. Якщо Під B на іншому вузлі, він іде до CNI.
  5. Інкапсуляція CNI: Для оверлейних мереж (VXLAN, Geneve) CNI загортає пакет у зовнішній заголовок для тунелювання до вузла призначення. Для маршрутизованих мереж (BGP, host-gw) ядро пересилає його безпосередньо.
  6. Доставка: На вузлі призначення пакет декапсулюється (якщо потрібно) та маршрутизується до пари veth Поду B.
  7. Під отримує: Під B бачить пакет від IP Поду A, призначений для його власного IP на порті 80.

1.2 Прохід пакету NodePort

Розділ «1.2 Прохід пакету NodePort»

NodePort додає додатковий крок на початку:

┌─────────────────────────────────────────────────────────────────────────┐
│ Прохід пакету NodePort │
│ │
│ Зовнішній клієнт │
│ ┌─────────────────┐ │
│ │ curl │ │
│ │ 192.168.1.10: │ │
│ │ 30080 │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ Вузол 1 (192.168.1.10) │
│ │ 1. eth0 вузла │ │
│ │ приймає на │ │
│ │ порті 30080 │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 2. iptables │ Ланцюжок KUBE-NODEPORTS: │
│ │ DNAT │ dst-port 30080 → 10.96.0.50:80 (ClusterIP) │
│ │ │ → потім обирає бекенд: 10.244.2.8:80 │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 3. SNAT (можливо)│ Якщо externalTrafficPolicy: Cluster (стандартно)│
│ │ │ джерело перезаписується на IP вузла, щоб │
│ │ │ зворотний трафік повертався через цей вузол │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ (далі як потік ClusterIP з кроку 4) │
│ │
└─────────────────────────────────────────────────────────────────────────┘

Ключова деталь: З externalTrafficPolicy: Cluster (за замовчуванням) kube-proxy виконує SNAT вихідної адреси, що означає — Під-бекенд бачить IP вузла, а не реальний IP клієнта. Налаштування externalTrafficPolicy: Local зберігає IP клієнта, але маршрутизує лише до Подів на вузлі-отримувачі — якщо там їх немає, зʼєднання відхиляється.

Коли Під викликає свій власний Сервіс (наприклад, Під за web-svc робить curl до web-svc), пакет може бути маршрутизований назад до нього самого. Це називається hairpin або hairpin NAT:

┌─────────────────────────────────────────────────────────────┐
│ Потік hairpin │
│ │
│ Під A відправляє на IP Сервісу │
│ │ │
│ ▼ │
│ iptables DNAT обирає... сам Під A! │
│ │ │
│ ▼ │
│ Пакет повинен вийти з netns Поду A і знову увійти в нього │
│ (потребує режиму hairpin на мості/veth) │
│ │
│ Якщо режим hairpin ВИМКНЕНО → пакет мовчазно скидається │
│ Якщо режим hairpin УВІМКНЕНО → пакет коректно повертається │
│ │
└─────────────────────────────────────────────────────────────┘

Більшість плагінів CNI вмикають режим hairpin за замовчуванням. Якщо ви бачите переривчасті збої, коли Під іноді не може звʼязатися зі своїм власним Сервісом, hairpin — найімовірніша причина. Перевірте за допомогою:

Terminal window
# Перевірити режим hairpin на інтерфейсі veth
k exec <pod> -- cat /sys/class/net/eth0/brport/hairpin_mode
# Або на вузлі:
cat /sys/devices/virtual/net/<veth-name>/brport/hairpin_mode

Частина 2: Відповідальність CNI проти kube-proxy

Розділ «Частина 2: Відповідальність CNI проти kube-proxy»

Це одне з найбільш неправильно зрозумілих розмежувань у мережі Kubernetes. Помилка призводить до діагностики неправильного компонента.

2.1 Розподіл відповідальності

Розділ «2.1 Розподіл відповідальності»
┌─────────────────────────────────────────────────────────────────────────┐
│ CNI проти kube-proxy: Хто що робить? │
│ │
│ ┌──────────────────────────────┐ ┌──────────────────────────────┐ │
│ │ Плагін CNI │ │ kube-proxy │ │
│ │ │ │ │ │
│ │ ✓ Призначення IP Подам │ │ ✓ DNAT Сервіс → Під │ │
│ │ ✓ Створення пар veth │ │ ✓ Маршрутизація ClusterIP │ │
│ │ ✓ Налаштування маршрутів │ │ ✓ Правила NodePort │ │
│ │ Подів │ │ ✓ Правила LoadBalancer │ │
│ │ ✓ Міжвузлове тунелювання │ │ ✓ Привʼязка сесій │ │
│ │ (VXLAN, Geneve, BGP) │ │ ✓ Вибір Endpoint │ │
│ │ ✓ Застосування мережевих │ │ │ │
│ │ політик (деякі CNI) │ │ ✗ НЕ призначає IP │ │
│ │ ✓ Звʼязок Під-до-Поду │ │ ✗ НЕ створює тунелі │ │
│ │ │ │ ✗ НЕ застосовує політики │ │
│ │ ✗ НЕ обробляє Сервіси │ │ (окрім через правила DNAT) │ │
│ │ (якщо eBPF не замінює │ │ │ │
│ │ kube-proxy, напр. Cilium)│ │ │ │
│ └──────────────────────────────┘ └──────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Швидка діагностика │ │
│ │ │ │
│ │ "Під A не може звʼязатися з Подом B за IP Поду" │ │
│ │ → Проблема в CNI (маршрутизація, інкапсуляція, MTU) │ │
│ │ │ │
│ │ "Під A не може звʼязатися із Сервісом, але МОЖЕ звʼязатися │ │
│ │ з Подом B за IP Поду" │ │
│ │ → Проблема в kube-proxy (правила DNAT, endpoints) │ │
│ │ │ │
│ │ "Під A не може розвʼязати імʼя сервісу" │ │
│ │ → Проблема в CoreDNS (див. Частину 3) │ │
│ │ │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘

2.2 Швидка матриця CNI

Розділ «2.2 Швидка матриця CNI»

Різні плагіни CNI використовують різні підходи. Ось порівняння, актуальне для діагностики:

ФункціяCalicoFlannelCilium
Площина данихiptables або eBPFVXLAN або host-gweBPF
Маршрутизація за замовчуваннямBGP (без інкапсуляції)VXLAN-оверлейeBPF пряма маршрутизація
Мережеві політикиТак (нативні)Ні (потрібен додаток)Так (L3–L7)
Може замінити kube-proxyТак (режим eBPF)НіТак (заміна kube-proxy)
Проблема MTUНемає (без інкапсуляції)Так (−50 байт для VXLAN)Залежить від конфігурації
Інструмент діагностикиcalicoctl node statusПеревірити інтерфейс VXLANcilium status

kube-proxy може працювати в різних режимах. Режим впливає на те, як Сервіси реалізовані в ядрі:

Terminal window
# Перевірити, який режим використовує kube-proxy
k get configmap kube-proxy -n kube-system -o yaml | grep mode
# Або перевірити логи kube-proxy
k logs -n kube-system -l k8s-app=kube-proxy | head -20
РежимЯк працюєПродуктивністьКоли використовувати
iptablesПравила DNAT на кожен Сервіс/EndpointОбчислення правил O(n)За замовчуванням, підходить для < 5000 Сервісів
IPVSВіртуальний сервер з реальними бекендамиПошук O(1) через хеш-таблицюВеликі кластери (5000+ Сервісів)
nftablesНаступне покоління заміни iptablesКраще за iptablesРекомендований шлях для K8s 1.31+

Частина 3: Шлях розвʼязання DNS (CoreDNS)

Розділ «Частина 3: Шлях розвʼязання DNS (CoreDNS)»

DNS — це клей, який робить імена Сервісів функціональними. Коли Під викликає curl web-service, ось що насправді відбувається.

3.1 Повний шлях розвʼязання DNS

Розділ «3.1 Повний шлях розвʼязання DNS»
┌─────────────────────────────────────────────────────────────────────────┐
│ Шлях розвʼязання DNS │
│ │
│ Під (10.244.1.5) │
│ ┌──────────────────────────────┐ │
│ │ curl http://web-svc │ │
│ │ │ │
│ │ 1. glibc читає │ │
│ │ /etc/resolv.conf: │ │
│ │ nameserver 10.96.0.10 │ ◄── ClusterIP CoreDNS │
│ │ search default.svc. │ │
│ │ cluster.local │ │
│ │ svc.cluster.local │ │
│ │ cluster.local │ │
│ │ options ndots:5 │ │
│ └──────────┬───────────────────┘ │
│ │ │
│ │ 2. "web-svc" має 0 крапок, що < ndots (5) │
│ │ Тому спочатку пробуються пошукові домени: │
│ │ │
│ │ Запит 1: web-svc.default.svc.cluster.local ← ЗНАЙДЕНО!│
│ │ (Якщо промах: web-svc.svc.cluster.local) │
│ │ (Якщо промах: web-svc.cluster.local) │
│ │ (Якщо промах: web-svc.) ← абсолютний запит останній │
│ │ │
│ ▼ │
│ ┌──────────────────────────────┐ │
│ │ 3. UDP-пакет до │ │
│ │ 10.96.0.10:53 │ │
│ │ (ClusterIP CoreDNS) │ │
│ └──────────┬───────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────┐ │
│ │ 4. DNAT kube-proxy │ │
│ │ 10.96.0.10 → 10.244.0.3 │ ◄── Фактичний IP Поду CoreDNS │
│ └──────────┬───────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────┐ │
│ │ 5. Під CoreDNS │ │
│ │ - Перевіряє плагін │ │
│ │ kubernetes (записи │ │
│ │ всередині кластера) │ │
│ │ - Повертає A-запис: │ │
│ │ 10.96.45.123 │ ◄── ClusterIP Сервісу │
│ └──────────┬───────────────────┘ │
│ │ │
│ ▼ │
│ Під отримує IP, зʼєднується з 10.96.45.123 │
│ (далі вступає в дію потік Сервіс → Під з Частини 1) │
│ │
└─────────────────────────────────────────────────────────────────────────┘

Стандартне значення ndots:5 у Kubernetes означає, що будь-яке імʼя з менш ніж 5 крапками розглядається як відносне. Це запускає розширення пошукових доменів:

Terminal window
# Запит "api.example.com" (2 крапки, < 5) генерує такі запити:
# 1. api.example.com.default.svc.cluster.local → NXDOMAIN
# 2. api.example.com.svc.cluster.local → NXDOMAIN
# 3. api.example.com.cluster.local → NXDOMAIN
# 4. api.example.com. → SUCCESS
# Це 4 DNS-запити замість 1!

Для Подів, що часто звертаються до зовнішніх доменів, зменште ndots або використовуйте завершальну крапку:

# Варіант 1: Встановити ndots у специфікації Поду
apiVersion: v1
kind: Pod
metadata:
name: optimized-dns
spec:
dnsConfig:
options:
- name: ndots
value: "2"
containers:
- name: app
image: nginx
Terminal window
# Варіант 2: Використовуйте завершальну крапку (абсолютне імʼя, пропускає пошук)
curl http://api.example.com.
# ^ завершальна крапка = абсолютне, без розширення пошуку

3.3 Типові режими збоїв DNS

Розділ «3.3 Типові режими збоїв DNS»
СимптомІмовірна причинаЯк перевірити
Весь DNS не працюєПоди CoreDNS не працюютьk get pods -n kube-system -l k8s-app=kube-dns
Переривчасті таймаути DNSCoreDNS перевантажений або NetworkPolicy блокує UDP/53k top pods -n kube-system, перевірте політики
Зовнішні імена не працюютьCoreDNS не може звʼязатися з upstream DNSПеревірте конфігурацію плагіна forward CoreDNS, DNS вузла
Міжпросторові запити не працюютьНеправильний FQDN або пошуковий доменВикористовуйте повний FQDN: svc.ns.svc.cluster.local
DNS працює, зʼєднання не працюєDNS в порядку, проблема в Сервісі/CNInslookup успішний, але curl не працює = не DNS
Terminal window
# Швидка перевірка здоровʼя DNS з будь-якого Поду
k run dns-check --rm -it --image=busybox:1.36 --restart=Never -- \
nslookup kubernetes.default
# Перевірити логи CoreDNS на помилки
k logs -n kube-system -l k8s-app=kube-dns --tail=50

Частина 4: Ментальна модель діагностики

Розділ «Частина 4: Ментальна модель діагностики»

Коли мережа ламається, вам потрібен систематичний підхід. Не вгадуйте — слідкуйте за пакетом.

4.1 Трирівнева діагностика

Розділ «4.1 Трирівнева діагностика»
┌─────────────────────────────────────────────────────────────────────────┐
│ Дерево рішень діагностики мережі │
│ │
│ "Під A не може звʼязатися із Сервісом X" │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────┐ │
│ │ Рівень 1: DNS │ │
│ │ Чи може Під A розвʼязати імʼя? │ │
│ │ nslookup <service> │ │
│ └──────┬──────────┬───────────────┘ │
│ НІ │ │ ТАК │
│ │ │ │
│ ▼ ▼ │
│ Перевірте CoreDNS │ │
│ Поди, resolv.conf │ │
│ NetworkPolicy │ │
│ на UDP/53 │ │
│ │ │
│ ┌─────────────────▼───────────────┐ │
│ │ Рівень 2: Сервіс (kube-proxy) │ │
│ │ Чи має Сервіс endpoints? │ │
│ │ k get endpoints <service> │ │
│ └──────┬──────────┬───────────────┘ │
│ НІ │ │ ТАК │
│ │ │ │
│ ▼ ▼ │
│ Перевірте збіг │ │
│ селекторів, проби │ │
│ готовності Подів │ │
│ │ │
│ ┌─────────────────▼───────────────┐ │
│ │ Рівень 3: Під-до-Поду (CNI) │ │
│ │ Чи може Під A звʼязатися з IP │ │
│ │ endpoint безпосередньо? │ │
│ │ curl <endpoint-ip>:<port> │ │
│ └──────┬──────────┬───────────────┘ │
│ НІ │ │ ТАК │
│ │ │ │
│ ▼ ▼ │
│ Проблема CNI: Під не слухає │
│ маршрути, на порті або │
│ інкапсуляція, застосунок │
│ MTU, мережеві зламаний │
│ політики │
│ │
└─────────────────────────────────────────────────────────────────────────┘

4.2 Основні команди діагностики

Розділ «4.2 Основні команди діагностики»
Terminal window
# === Рівень 1: DNS ===
# Перевірити DNS зсередини Поду
k run debug --rm -it --image=busybox:1.36 --restart=Never -- nslookup web-svc
# Перевірити resolv.conf всередині Поду
k exec <pod> -- cat /etc/resolv.conf
# === Рівень 2: Сервіс / kube-proxy ===
# Перевірити наявність endpoints
k get endpoints <service-name>
# Перевірити правила iptables для конкретного Сервісу (на вузлі)
iptables-save | grep <service-name>
# Перевірити таблицю conntrack на застарілі записи
conntrack -L -d <service-clusterip>
# === Рівень 3: Під-до-Поду / CNI ===
# Перевірити пряме зʼєднання Під-до-Поду
k run debug --rm -it --image=busybox:1.36 --restart=Never -- \
wget -qO- --timeout=5 http://<pod-ip>:<port>
# Захопити пакети на вузлі (запускайте на вузлі, не в Поді)
tcpdump -i any -nn host <pod-ip> and port <port>
# Перевірити MTU
k exec <pod> -- ip link show eth0
# Шукайте значення "mtu" — має відповідати конфігурації CNI
# Перевірити маршрути всередині Поду
k exec <pod> -- ip route

4.3 Conntrack: Прихований стан

Розділ «4.3 Conntrack: Прихований стан»

Conntrack (відстеження зʼєднань) — це модуль ядра, який забезпечує роботу NAT. Він запамʼятовує, які зʼєднання відповідають яким трансляціям. Застарілі записи conntrack — поширене джерело загадкових збоїв:

Terminal window
# Список усіх записів conntrack для IP Сервісу
conntrack -L -d 10.96.0.50
# Підрахувати записи (великі числа можуть вказувати на витік зʼєднань)
conntrack -C
# Видалити застарілі записи (обережно у продакшені)
conntrack -D -d 10.96.0.50 -p tcp --dport 80

Коли conntrack вас підводить: Якщо Під видалений і створений заново з тим самим IP (рідко, але можливо), conntrack може все ще мати записи, що вказують на старий стан зʼєднання. Симптоми включають зʼєднання, які зависають або скидаються без видимої причини, але лише до конкретних Подів.

4.4 Чеклист скидання пакетів між вузлами

Розділ «4.4 Чеклист скидання пакетів між вузлами»

Коли пакети скидаються між вузлами, пройдіть цей список:

  1. Невідповідність MTU: Чи використовує CNI інкапсуляцію? Якщо так, чи зменшено MTU відповідно?

    Terminal window
    # Перевірити MTU на інтерфейсі Поду проти тунельного інтерфейсу вузла
    ip link show vxlan.calico # або flannel.1, cilium_vxlan
  2. Правила файрвола: Чи дозволяють файрволи вузлів (iptables, firewalld, хмарні групи безпеки) протокол CNI?

    • VXLAN: UDP порт 4789
    • Geneve: UDP порт 6081
    • BGP: TCP порт 179
    • Wireguard: UDP порт 51820
  3. Здоровʼя CNI: Чи працює демон CNI на всіх вузлах?

    Terminal window
    k get pods -n kube-system -l k8s-app=calico-node # або flannel, cilium
  4. Вичерпання IP: Чи закінчилися IP у CIDR Подів на конкретному вузлі?

    Terminal window
    k describe node <node> | grep -A5 "PodCIDR"

ПомилкаПроблемаРішення
Діагностика DNS, коли проблема в kube-proxyВитрачений час на неправильний рівеньДотримуйтесь трирівневої моделі: спочатку DNS, потім Сервіс, потім CNI
Ігнорування MTU після зміни режиму CNIВеликі пакети мовчазно скидаютьсяЗавжди встановлюйте MTU = фізичний MTU мінус накладні витрати інкапсуляції (50 для VXLAN)
Не перевіряти conntrackЗастарілі записи NAT спричиняють переривчасті збоїВикористовуйте conntrack -L для перевірки стану, коли зʼєднання зависають
Забути про externalTrafficPolicyВтрата вихідного IP клієнта або відсутність бекендів на вузліРозумійте Cluster (SNAT, усі бекенди) проти Local (зберігає IP, лише локальні)
Встановлення занадто високого ndotsМноження DNS-запитів, повільні запитиВикористовуйте ndots: 2 для Подів, що звертаються до зовнішніх сервісів, або використовуйте завершальні крапки
Тестування ClusterIP ззовні кластераТаймаут зʼєднанняClusterIP працює лише всередині кластера; використовуйте NodePort/port-forward для зовнішніх тестів
Запуск tcpdump на неправильному інтерфейсіЗахоплення нічого не показуютьВикористовуйте tcpdump -i any для захоплення на всіх інтерфейсах, потім звужуйте
Звинувачення застосунку перед перевіркою мережіГодини витрачені на діагностику коду застосункуЗавжди спочатку перевіряйте мережеве зʼєднання простими інструментами (wget, curl)

1. Під може звʼязатися з іншим Подом за його IP (10.244.2.8), але не може звʼязатися з ним через ClusterIP Сервісу (10.96.0.50). Який компонент найімовірніше несправний?

Відповідь

Найімовірніше несправний kube-proxy. Оскільки зʼєднання Під-до-Поду працює, CNI функціонує коректно. ClusterIP Сервісу обробляється правилами iptables/IPVS/nftables kube-proxy. Перевірте:

  • k get endpoints <service> — чи є endpoints?
  • iptables-save | grep <service-name> — чи присутні правила DNAT?
  • Чи працює kube-proxy? k get pods -n kube-system -l k8s-app=kube-proxy

2. Ви змінюєте CNI з Flannel з host-gw на Flannel з vxlan. Малі запити (перевірки стану, ping) працюють нормально, але великі HTTP-відповіді скидаються. Яка найімовірніша причина та виправлення?

Відповідь

Невідповідність MTU. Інкапсуляція VXLAN додає 50-байтовий заголовок. Якщо MTU Поду все ще 1500 (стандартне для host-gw), пакети близько 1500 байт перевищуватимуть ємність тунелю після інкапсуляції.

Виправлення: Встановіть MTU CNI на 1450 (1500 − 50 для накладних витрат VXLAN). У ConfigMap Flannel:

{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "vxlan",
"MTU": 1450
}
}

Потім перезапустіть Поди Flannel, щоб усі вузли отримали новий MTU.

3. Розробник повідомляє, що curl api.external.com з Поду займає 2 секунди, але лише 50 мс з його ноутбука. DNS є вузьким місцем. Поясніть чому та як виправити.

Відповідь

Стандартне значення ndots:5 у Kubernetes означає, що api.external.com (2 крапки, що менше 5) розглядається як відносне імʼя. Резолвер пробує ці запити по черзі, перш ніж успішно завершитися:

  1. api.external.com.default.svc.cluster.local — NXDOMAIN (~500 мс)
  2. api.external.com.svc.cluster.local — NXDOMAIN (~500 мс)
  3. api.external.com.cluster.local — NXDOMAIN (~500 мс)
  4. api.external.com. — SUCCESS (~50 мс)

Це 3 зайвих запити, що додають ~1,5 секунди затримки.

Виправлення (оберіть одне):

  • Використовуйте завершальну крапку: curl api.external.com.
  • Встановіть dnsConfig.options.ndots: 2 у специфікації Поду
  • Використовуйте FQDN із завершальною крапкою в конфігурації застосунку

4. Ви запускаєте k get endpoints my-service і бачите <none>. Поди працюють і мають правильні мітки. Що ще може спричинити порожні endpoints?

Відповідь

Навіть якщо Поди працюють з правильними мітками, endpoints будуть порожніми, якщо:

  1. Поди не Ready — проба готовності не проходить. Лише Поди, що проходять пробу готовності, додаються до обʼєкта Endpoints. Перевірте: k get pods (шукайте 0/1 READY).
  2. Селектор Сервісу вимагає мітки, яких немає у Подів — перевірте k describe svc my-service та порівняйте з k get pods --show-labels.
  3. Поди знаходяться в іншому просторі імен, ніж Сервіс. Сервіси обирають Поди лише у своєму просторі імен.
  4. Контролер endpoint не працює — вкрай рідко, але перевірте Під kube-controller-manager у kube-system.

Найчастіше відповідь — невдалі проби готовності.

5. Після видалення та повторного створення Поду-бекенду деякі існуючі зʼєднання до Сервісу зависають на 30+ секунд перед відновленням. Нові зʼєднання працюють нормально. Що відбувається?

Відповідь

Застарілі записи conntrack. Таблиця відстеження зʼєднань ядра все ще має записи, що відображають існуючі зʼєднання на IP старого Поду. Оскільки цей IP більше не існує (або належить іншому Поду), пакети відправляються в нікуди.

Записи зрештою закінчаться (таймаут TCP, зазвичай 120 секунд для встановлених зʼєднань), тому проблема зрештою зникає. Нові зʼєднання працюють, бо створюють свіжі записи conntrack, що вказують на дійсні бекенди.

Для негайного виправлення: conntrack -D -d <service-clusterip> -p tcp --dport <port>. Для запобігання: використовуйте graceful-завершення Поду (хуки preStop, зливання зʼєднань), щоб Під видалив себе з endpoints перед завершенням процесу.


Практична вправа: Виклик відстеження пакету

Розділ «Практична вправа: Виклик відстеження пакету»

Мета: Простежити запит від початку до кінця — від клієнтського Поду через DNS, kube-proxy та CNI до Поду-бекенду. Ви використовуватимете tcpdump, nslookup та iptables-save для спостереження за кожним рівнем.

Середовище: кластер kind або minikube (одного вузла достатньо для цієї вправи).

Terminal window
# Створити Deployment бекенду та Сервіс
k create deployment trace-backend --image=nginx --replicas=2
k expose deployment trace-backend --port=80 --name=trace-svc
# Зачекати на готовність Подів
k wait --for=condition=ready pod -l app=trace-backend --timeout=60s
# Створити Під для діагностики, який залишається працювати
k run trace-client --image=nicolaka/netshoot --restart=Never -- sleep 3600
# Зачекати на нього
k wait --for=condition=ready pod/trace-client --timeout=60s

Крок 1: Дослідити рівень DNS

Розділ «Крок 1: Дослідити рівень DNS»
Terminal window
# Перевірити конфігурацію DNS клієнтського Поду
k exec trace-client -- cat /etc/resolv.conf
# Примітка: nameserver має бути ClusterIP CoreDNS
# Розвʼязати імʼя Сервісу
k exec trace-client -- nslookup trace-svc
# Має повернути ClusterIP trace-svc
# Спробувати FQDN
k exec trace-client -- nslookup trace-svc.default.svc.cluster.local
# Порівняти: розвʼязати із завершальною крапкою (пропускає пошукові домени)
k exec trace-client -- nslookup trace-svc.default.svc.cluster.local.

Запишіть: Який IP повернув trace-svc? Це ClusterIP.

Крок 2: Дослідити рівень Сервісу

Розділ «Крок 2: Дослідити рівень Сервісу»
Terminal window
# Отримати деталі Сервісу
k get svc trace-svc -o wide
# Отримати endpoints (IP Подів-бекендів)
k get endpoints trace-svc
# Переглянути правила iptables для цього Сервісу (потрібен доступ до вузла)
# На minikube: minikube ssh
# На kind: docker exec -it <node-container> bash
# Потім запустіть:
iptables-save | grep trace-svc
# Ви побачите ланцюжки KUBE-SERVICES, KUBE-SVC-* та KUBE-SEP-*
# Ланцюжок KUBE-SVC містить правила ймовірності для балансування навантаження
# Ланцюжки KUBE-SEP містять правила DNAT до конкретних IP Подів

Запишіть: Скільки записів KUBE-SEP існує? Має відповідати кількості реплік (2).

Крок 3: Простежити пакет за допомогою tcpdump

Розділ «Крок 3: Простежити пакет за допомогою tcpdump»
Terminal window
# В одному терміналі запустіть tcpdump на вузлі (потрібен доступ до вузла)
# На kind: docker exec -it <node-container> bash
tcpdump -i any -nn port 80 and host $(k get pod trace-client -o jsonpath='{.status.podIP}')
# В іншому терміналі зробіть запит з клієнтського Поду
k exec trace-client -- curl -s http://trace-svc
# Спостерігайте за виводом tcpdump:
# 1. Ви побачите початковий SYN від IP trace-client до ClusterIP
# 2. Потім пакет після DNAT від IP trace-client до IP Поду-бекенду
# 3. Відповідь від IP Поду-бекенду до IP trace-client
# 4. Conntrack обертає NAT, тому trace-client бачить ClusterIP

Крок 4: Дослідити conntrack

Розділ «Крок 4: Дослідити conntrack»
Terminal window
# На вузлі перевірте записи conntrack
conntrack -L -d $(k get svc trace-svc -o jsonpath='{.spec.clusterIP}') 2>/dev/null
# Ви побачите записи, що показують:
# src=<client-pod-ip> dst=<clusterIP> dport=80
# та зворотне відображення:
# src=<backend-pod-ip> dst=<client-pod-ip>

Крок 5: Перевірити вплив мережевої політики

Розділ «Крок 5: Перевірити вплив мережевої політики»
Terminal window
# Застосувати політику, що блокує трафік до бекенду
cat << 'EOF' | k apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-trace-backend
spec:
podSelector:
matchLabels:
app: trace-backend
policyTypes:
- Ingress
ingress: [] # Порожній = заборонити весь ingress
EOF
# Спробувати запит знову (має не спрацювати/таймаут)
k exec trace-client -- curl -s --connect-timeout 5 http://trace-svc
# Очікується: таймаут або відмова зʼєднання (залежить від CNI)
# Перевірити, що DNS все ще працює (так і повинно бути — DNS іде до CoreDNS, не до бекенду)
k exec trace-client -- nslookup trace-svc
# Видалити політику
k delete networkpolicy deny-trace-backend
# Перевірити, що зʼєднання відновлено
k exec trace-client -- curl -s --connect-timeout 5 http://trace-svc
Terminal window
k delete pod trace-client --force
k delete deployment trace-backend
k delete svc trace-svc

Критерії успіху:

  • Вміння визначити ClusterIP через розвʼязання DNS
  • Вміння знайти правила DNAT iptables для Сервісу
  • Вміння спостерігати потік пакету в tcpdump (до DNAT та після DNAT)
  • Вміння переглядати записи conntrack для активних зʼєднань
  • Розуміння, що NetworkPolicy блокує трафік Під-до-Поду, але не DNS
  • Вміння описати трирівневу модель діагностики (DNS, Сервіс, CNI)

  1. Слідкуйте за пакетом, а не за припущеннями. Використовуйте tcpdump, iptables-save та conntrack, щоб побачити, що насправді відбувається, замість вгадування.
  2. Три рівні (DNS, kube-proxy/Сервіс, CNI/Під-до-Поду) незалежні. Ізолюйте, який рівень зламаний, перш ніж заглиблюватися.
  3. MTU має значення. Кожного разу, коли залучена інкапсуляція, ефективний MTU зменшується. Мовчазне скидання великих пакетів — класичний симптом.
  4. conntrack невидимий, але критичний. Він підтримує стан NAT для кожного зʼєднання через Сервіс. Застарілі записи спричиняють одні з найбільш заплутаних переривчастих збоїв.
  5. ndots:5 — це дорого. Для навантажень, що звертаються до зовнішніх сервісів, або зменште ndots, або використовуйте завершальні крапки в іменах доменів.


Модуль 3.3: DNS у Kubernetes — Глибоке занурення в конфігурацію CoreDNS, користувацькі DNS-політики та розширену діагностику.