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

Модуль 5.1: Сервіси

Hands-On Lab Available
K8s Cluster intermediate 45-55 min
Launch Lab ↗

Opens in Killercoda in a new tab

Складність: [СЕРЕДНЯ] — базова концепція мережі, кілька типів, які потрібно зрозуміти

Час на проходження: 45-55 хвилин

Передумови: Модуль 1.1 (Поди), Модуль 2.1 (Деплойменти), розуміння основ мережевих технологій


Результати навчання

Розділ «Результати навчання»

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

  • Спроєктувати та створити Сервіси типів ClusterIP, NodePort, LoadBalancer, ExternalName і headless для реалістичних сценаріїв доступу до застосунків
  • Діагностувати з’єднання з Сервісом через перевірку селекторів, ендпоінтів, EndpointSlices, DNS-імен і відображень портів
  • Оцінити та порівняти типи Сервісів для внутрішніх викликів, доступу на рівні Ноди, хмарного балансування навантаження та прямого виявлення Подів
  • Реалізувати багатопортові Сервіси та прив’язку сесій без приховування того, як port, targetPort і nodePort керують потоком трафіку

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

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

Гіпотетичний сценарій: ви щойно випустили Деплоймент API з трьома репліками для процесу оформлення замовлень, і Поди справні, мають стан Ready та обслуговують трафік на своєму контейнерному порту. Інший Деплоймент намагається звернутися до нього за IP-адресою Пода, бо саме так працювало під час швидкої перевірки. Один послідовний перезапуск пізніше — і IP-адреси Подів змінюються, половина викликів зазнає невдачі, а справжня помилка зовсім не в коді застосунку: команда оминула стабільний мережевий контракт, який надають Сервіси Kubernetes.

Сервіси важливі, тому що Kubernetes ставиться до Подів як до взаємозамінних деталей реалізації. Деплоймент може створювати нові Поди під час розгортання, переміщувати їх на іншу Ноду після її витіснення або замінювати їх після невдалої проби готовності. Сервіс дає клієнтам стале ім’я та віртуальну адресу, поки Kubernetes підтримує актуальним набір опорних ендпоінтів — подібно до того, як корпоративний телефонний довідник зберігає сталий внутрішній номер відділу, навіть коли окремі співробітники змінюють робочі місця.

Іспит CKAD очікує, що ви швидко створюватимете Сервіси, але самої лише швидкості недостатньо для роботи з продакшеном. Вам потрібно міркувати від симптомів до механізмів: чи розв’язав DNS ім’я, чи збігся селектор Сервісу з Подами, чи мав Сервіс готові ендпоінти, чи використав клієнт порт Сервісу і чи зміг kube-proxy або площина даних переслати пакет. Цей модуль орієнтований на Kubernetes 1.35 або новіший і зосереджується на рішеннях та діагностичних кроках, які тримають ці рівні окремо у вашій голові.

Типи Сервісів і маршрути трафіку

Розділ «Типи Сервісів і маршрути трафіку»

Сервіс — це невеликий об’єкт API з непропорційно великою роллю: він відокремлює ідентичність, якою користуються клієнти, від Подів, які наразі живі. Без такого розділення клієнту довелося б виявляти кожну IP-адресу Пода, видаляти неактивні, додавати нові й вирішувати, як балансувати запити щоразу, коли робоче навантаження змінюється. Kubernetes централізує цей облік, дозволяючи вам оголосити селектор і набір портів, а потім безперервно виводить поточний набір ендпоінтів із Подів, що збігаються.

Тип Сервісу визначає, де доступна стабільна точка входу. ClusterIP створює внутрішню віртуальну IP-адресу, якою інші Поди можуть користуватися всередині кластера. NodePort будується на цьому внутрішньому Сервісі та відкриває статичний порт на кожній Ноді. LoadBalancer просить середовище — зазвичай хмарного провайдера або локальну реалізацію балансувальника навантаження — виділити зовнішню адресу, яка пересилає трафік до Сервісу. ExternalName відрізняється тим, що створює DNS-псевдонім, а не адресу Сервісу, яка працює як проксі.

Віртуальна IP-адреса — це не Под і не серверний процес, що слухає всередині вашого простору імен. Це адреса, яку розпізнає площина даних кластера й транслює до готових ендпоінтів — зазвичай через правила, запрограмовані kube-proxy або еквівалентною реалізацією, яку надає мережевий стек. Ця відмінність важлива під час діагностики: перезапуск цільових Подів може змінити ендпоінти, але він не відтворює адресу Сервісу, а видалення Сервісу прибирає стабільний контракт, навіть якщо кожен Под залишається справним.

Маршрутизація Сервісу також залежить від готовності, а не лише від існування. Под може збігатися із селектором, але все одно бути виключеним зі звичайного трафіку Сервісу, бо його проба готовності ще не пройшла. Саме тому kubectl get pods і kubectl get endpoints відповідають на різні запитання. Список Подів показує те, що створив контролер робочих навантажень; огляд ендпоінтів показує, куди Сервіс наразі готовий надсилати трафік. У реальних інцидентах ця відмінність часто є різницею між помилкою застосунку і захисним механізмом готовності розгортання, що виконує свою роботу.

ClusterIP є типом за замовчуванням, бо більшість трафіку Kubernetes — це трафік між сервісами. Внутрішнім викликачам не потрібно знати, на якій Ноді розміщено цільовий Под, і їм не потрібен публічний доступ лише для того, щоб дістатися до іншого компонента в тому самому кластері. Сервіс нижче відкриває порт 80 для клієнтів, водночас пересилаючи трафік на контейнерний порт 8080, що поширене, коли вам потрібен звичний порт сервісу, але застосунок слухає на специфічному для фреймворку порту всередині контейнера.

apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: ClusterIP # Default, can be omitted
selector:
app: my-app
ports:
- port: 80 # Service port
targetPort: 8080 # Container port

Імперативна форма корисна під час іспиту CKAD, тому що kubectl expose deployment копіює селектор Деплоймента до Сервісу. Це допомагає уникнути ручного введення міток селектора в умовах браку часу, але результат усе одно слід перевірити, бо скопійовані мітки корисні лише тоді, коли мітки шаблону Пода в Деплойменті вже правильні. Команди curl, наведені тут, призначені для запуску з Пода чи іншого контексту виконання всередині кластера, а не з оболонки вашого ноутбука.

Сервіси ClusterIP також можуть зазнавати впливу вибору сімейства адрес на рівні кластера. У кластерах, налаштованих на двостекову (dual-stack) мережу, Сервіс може мати сімейства IPv4 та IPv6 відповідно до його ipFamilyPolicy та пов’язаних полів. Вам не потрібно опановувати політику двостекової мережі для цього модуля, але слід знати, що API Сервісу володіє цими стабільними адресами і що клієнтам усе одно варто використовувати ім’я Сервісу замість жорсткого закодування того сімейства IP, яке вони побачать першим.

Terminal window
# Create imperatively (--name overrides the default Service name my-app)
kubectl expose deployment my-app --name=my-service --port=80 --target-port=8080
# Access from within cluster
curl http://my-service:80
curl http://my-service.default.svc.cluster.local:80

NodePort — це наступний рівень назовні. Kubernetes усе одно створює Сервіс ClusterIP, але також виділяє або приймає порт із діапазону портів Ноди Сервісу, який за замовчуванням становить 30000-32767. Пакет, надісланий на <node-ip>:30080, досягає Ноди, входить у правила мережі кластера і пересилається до одного з ендпоінтів Сервісу, якщо тільки політика, брандмауер чи конфігурація площини даних не блокує цей шлях.

apiVersion: v1
kind: Service
metadata:
name: my-nodeport
spec:
type: NodePort
selector:
app: my-app
ports:
- port: 80 # Service port (ClusterIP)
targetPort: 8080 # Container port
nodePort: 30080 # Node port (30000-32767)

NodePort часто розуміють неправильно, бо він відкриває кожну Ноду, а не лише ті Ноди, на яких наразі працюють Поди, що збігаються. За політики трафіку за замовчуванням Нода, яка отримує пакет, може переслати його через мережу кластера до Пода на іншій Ноді. Така поведінка зручна для простих лабораторних робіт, але в продакшені це означає, що шлях від зовнішнього клієнта до Пода може містити хмарні брандмауери, групи безпеки Ноди, правила kube-proxy, маршрутизацію CNI та вибір ендпоінтів на основі готовності.

Поведінка NodePort за замовчуванням також впливає на видимість IP-адреси джерела. Коли трафік може перестрибнути з приймальної Ноди до ендпоінта на іншій Ноді, бекенд може бачити транслювану адресу джерела залежно від площини даних і політики трафіку. externalTrafficPolicy: Local іноді використовують, коли збереження оригінальної IP-адреси джерела клієнта важливіше за прийом трафіку на кожній Ноді, але це змінює поведінку при відмовах, бо Ноди без локальних готових ендпоінтів не повинні успішно отримувати трафік. Це експлуатаційний вибір, а не просто декоративний елемент YAML.

Terminal window
# Create imperatively (--node-port pins the port shown in the YAML above)
kubectl expose deployment my-app --name=my-nodeport --type=NodePort --port=80 --target-port=8080 --node-port=30080
# Access from outside cluster
curl http://<node-ip>:30080

Сервіси LoadBalancer зберігають ту саму абстракцію Сервісу, але делегують публічну точку входу інфраструктурі поза межами сервера API Kubernetes. У керованому хмарному кластері менеджер хмарного контролера зазвичай створює балансувальник навантаження провайдера і записує його адресу у статус Сервісу. У локальному кластері вам може знадобитися такий компонент, як MetalLB або вбудована підтримка балансувальника навантаження у дистрибутиві, інакше зовнішня адреса може залишатися у стані pending, навіть якщо об’єкт Сервісу коректний.

Цей стан pending — корисний приклад того, чому статус Сервісу і специфікація Сервісу різняться. Специфікація каже, що ви попросили Kubernetes створити; статус каже, що середовище фактично виділило. Сервіс LoadBalancer із зовнішньою адресою у стані pending все одно може мати ClusterIP та готові ендпоінти, тож клієнти всередині кластера можуть працювати, тоді як зовнішні клієнти не можуть розпочати роботу. Правильне виправлення — перевірити інтеграцію з провайдером, клас балансувальника навантаження, квоти чи підтримку локального балансувальника, а не переписувати Деплоймент застосунку.

apiVersion: v1
kind: Service
metadata:
name: my-loadbalancer
spec:
type: LoadBalancer
selector:
app: my-app
ports:
- port: 80
targetPort: 8080

Команда оманливо схожа на команду NodePort, бо Kubernetes навмисно зберігає узгодженою форму API. Різниця в тому, хто може дістатися до Сервісу і хто володіє життєвим циклом зовнішньої адреси. Сервіс LoadBalancer — це правильний інструмент, коли вам потрібна зовнішня точка входу четвертого рівня, але маршрутизація HTTP за хостом, маршрутизація за шляхом, термінація сертифікатів і спільні віртуальні хости зазвичай належать до рівня Інгресу чи Gateway, який ми розглянемо пізніше.

LoadBalancer також не є гарантією того, що кожен хмарний провайдер поводиться однаково. Деякі середовища виділяють вузлові порти для перевірки справності, деякі підтримують специфічні для провайдера анотації, а деякі вимагають певного класу балансувальника навантаження. Переносна для CKAD частина — це контракт Сервісу: тип, селектор, порти, статус та ендпоінти. Специфічні для провайдера поля можуть бути потрібними у продакшені, але вони не повинні заступати базову модель Kubernetes, яку ви можете перевірити звичайними командами kubectl.

Terminal window
# Create imperatively
kubectl expose deployment my-app --name=my-loadbalancer --type=LoadBalancer --port=80 --target-port=8080
# Get external IP
kubectl get svc my-loadbalancer
# EXTERNAL-IP column shows the LB IP

Зупиніться і спрогнозуйте: у вас є Деплоймент із трьома репліками з міткою app: web, а потім ви створюєте Сервіс із селектором app: webapp. Перш ніж запускати будь-яку команду, спрогнозуйте, скільки ендпоінтів матиме Сервіс і яке єдине поле ви перевірили б першим, щоб довести свою відповідь. Цей прогноз важливий, бо Сервіс без ендпоінтів усе одно може мати ClusterIP, DNS-ім’я та коректний YAML, тож об’єкт може виглядати справним, водночас пересилаючи трафік у нікуди.

ExternalName розв’язує іншу задачу, ніж типи Сервісів, які працюють як проксі. Він дозволяє Подам використовувати ім’я у формі Сервісу Kubernetes, тоді як DNS повертає зовнішнє канонічне ім’я, наприклад ім’я керованої бази даних поза кластером. Тут немає селектора, немає ClusterIP, немає балансування навантаження між ендпоінтами і немає пересилання через kube-proxy, тож ExternalName корисний для узгодженості іменування, але не для керування трафіком, фільтрації за готовністю чи маршрутизації з урахуванням протоколу.

Оскільки ExternalName працює лише з DNS, він може здивувати команди, які очікують, що решта інструментарію Сервісів застосовуватиметься. Ви не можете перевірити ендпоінти Kubernetes для зовнішньої цілі, бо Kubernetes не обирав жодного Пода. Ви не можете виправити справність зовнішньої цілі, змінивши пробу готовності, бо жодна готовність Пода не живить цей псевдонім. Ви все ж можете використовувати його, щоб зберігати конфігурацію застосунку стабільною, поки змінюється зовнішнє DNS-ім’я, але вам слід розглядати його як абстракцію іменування, а не як мережевий проксі.

apiVersion: v1
kind: Service
metadata:
name: external-db
spec:
type: ExternalName
externalName: database.example.com

Практичне рішення — обрати найменшу поверхню доступу, яка задовольняє викликача. Якщо робоче навантаження викликають лише Поди, почніть із ClusterIP. Якщо щось поза кластером потребує швидкого шляху четвертого рівня у лабораторній роботі, NodePort може бути достатнім. Якщо середовище може виділити стабільний зовнішній балансувальник навантаження і застосунку потрібен прямий доступ TCP чи UDP, використовуйте LoadBalancer. Якщо ціль уже є зовнішнім DNS-іменем, а Kubernetes має лише надати внутрішній псевдонім, використовуйте ExternalName.

Виявлення Сервісів, селектори та ендпоінти

Розділ «Виявлення Сервісів, селектори та ендпоінти»

Виявлення Сервісів має дві половини: розв’язання імен та вибір ендпоінтів. DNS перетворює імена на кшталт my-service.default.svc.cluster.local на адресу чи псевдонім Сервісу. Селектори та контролери ендпоінтів вирішують, які IP-адреси Подів можуть отримувати трафік за цим іменем. Діагностика стає значно простішою, коли ви запитуєте, яка половина зазнала збою, замість того щоб трактувати кожен збій з’єднання як загальну мережеву проблему.

Kubernetes створює DNS-записи для Сервісів за передбачуваною ієрархією. Под зазвичай може розв’язати Сервіс у тому самому просторі імен за коротким іменем, але міжпросторові виклики потребують щонайменше форми з кваліфікацією простору імен. Повне доменне ім’я детальніше, проте воно цінне у прикладах, бо робить простір імен і домен svc явними, замість того щоб покладатися на список пошуку розв’язувача імен Пода.

Список пошуку зручний, доки він не приховує простір імен, який ви мали на увазі викликати. Под у orders, який запитує payments, може шукати orders.svc.cluster.local, перш ніж узагалі розгляне щось у billing. Саме тому міжпросторові приклади мають показувати простір імен, навіть коли коротке ім’я спрацювало у тесті в межах того самого простору імен. Під час діагностики збій DNS має привести вас до перевірки простору імен викликача, простору імен Сервісу, імені Сервісу та списку пошуку розв’язувача, перш ніж редагувати порти Сервісу.

<service-name>.<namespace>.svc.cluster.local
DNS-ім’яРозв’язується у
my-serviceТой самий простір імен
my-service.defaultпростір імен default
my-service.default.svcпростір імен default, svc
my-service.default.svc.cluster.localПовне FQDN

Змінні середовища — це старіший механізм виявлення, і вони все ще мають значення, бо ви можете побачити їх під час діагностики. Kubernetes впроваджує пов’язані із Сервісом змінні середовища лише для Сервісів, які існують до запуску Пода, тож вони не є повноцінною системою динамічного виявлення. DNS зазвичай є кращою звичкою, бо він відстежує створення та зміни Сервісів, не змушуючи перезапускати кожен клієнтський Под.

Є ще одна практична причина надавати перевагу DNS в уроках і маніфестах: змінні середовища створюють приховані припущення про порядок запуску. Якщо клієнтський Под запускається до того, як з’явиться Сервіс, цей Под не отримає змінні Сервісу, доки його не буде перестворено. DNS уникає цієї конкретної пастки, бо пошук імені відбувається тоді, коли клієнт розв’язує ім’я, а не тоді, коли Под був допущений. Вам усе ще може знадобитися повторювати спроби у застосунку, але вам не потрібно перезапускати клієнта лише тому, що Сервіс з’явився пізніше.

Terminal window
# Inside a pod
env | grep MY_SERVICE
# MY_SERVICE_SERVICE_HOST=10.96.0.1
# MY_SERVICE_SERVICE_PORT=80

Діаграма нижче — це компактна ментальна модель, яку варто тримати в голові під час іспиту. ClusterIP — це внутрішній базовий рівень, NodePort додає точку входу на рівні Ноди, а LoadBalancer додає керовану інфраструктурою зовнішню адресу перед цим стеком. Порт Сервісу — це те, що використовує клієнт, targetPort — це те, що отримує Под, а nodePort існує лише для тих типів Сервісів, які відкривають порт на рівні Ноди.

┌─────────────────────────────────────────────────────────────┐
│ Service Types │
├─────────────────────────────────────────────────────────────┤
│ │
│ ClusterIP (Internal Only) │
│ ┌─────────────────────────────────────┐ │
│ │ cluster.local:80 ──► Pod:8080 │ │
│ │ ──► Pod:8080 │ │
│ │ ──► Pod:8080 │ │
│ └─────────────────────────────────────┘ │
│ │
│ NodePort (ClusterIP + Node Access) │
│ ┌─────────────────────────────────────┐ │
│ │ <NodeIP>:30080 ──► ClusterIP:80 ──► Pods │
│ └─────────────────────────────────────┘ │
│ │
│ LoadBalancer (NodePort + External LB) │
│ ┌─────────────────────────────────────┐ │
│ │ <ExternalIP>:80 ──► NodePort ──► ClusterIP ──► Pods │
│ └─────────────────────────────────────┘ │
│ │
│ Service Port Flow: │
│ ┌──────────────────────────────────────────────────┐ │
│ │ │ │
│ │ External ──► nodePort ──► port ──► targetPort │ │
│ │ :80 :30080 :80 :8080 │ │
│ │ │ │
│ └──────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

Селектори — це звичайні запити за мітками, а не зв’язки володіння. Сервіс не вказує на Деплоймент за іменем, і йому байдуже, який контролер створив Под. Він запитує Поди, чиї мітки збігаються з усіма ключами та значеннями селектора, а потім контролери ендпоінтів публікують адреси збіжних готових Подів для Сервісу. Така конструкція гнучка, але це також означає, що одна неправильно написана мітка може створити цілком коректний Сервіс без жодних бекендів.

# Service
spec:
selector:
app: my-app
tier: frontend
# Pod (must match ALL labels)
metadata:
labels:
app: my-app
tier: frontend

Ендпойнти — це видимий результат такого збігу селектора. Сучасний Kubernetes також використовує EndpointSlices для масштабованого відстеження ендпоінтів, але kubectl get endpoints залишається корисним у діагностиці у стилі CKAD, бо дає швидку відповідь: чи має Сервіс хоч якісь готові адреси бекендів? Якщо список порожній, не марнуйте час на тестування зовнішніх балансувальників навантаження, перш ніж перевірите мітки, готовність і цільовий порт.

EndpointSlices розділяють дані про ендпоінти на менші об’єкти API, щоб великі Сервіси не залежали від одного об’єкта Endpoints, що постійно зростає. Для повсякденної роботи з CKAD ви можете трактувати їх як масштабоване представлення тієї самої концепції Сервісу: обрані, готові адреси бекендів з інформацією про порти. У діагностиці продакшену EndpointSlices можуть показувати додаткові підказки, як-от тип адреси та умови, що допомагає, коли Сервіс має багато реплік чи двостекові адреси. Принцип залишається тим самим: ендпоінти — це похідний стан, а не список, який ви зазвичай підтримуєте вручну для Сервісів на основі селекторів.

Terminal window
# View endpoints
kubectl get endpoints my-service
# NAME ENDPOINTS AGE
# my-service 10.244.0.5:8080,10.244.0.6:8080 5m
# Describe shows pod IPs
kubectl describe endpoints my-service

Перш ніж це запускати, який вивід ви очікуєте, якщо селектор Сервісу коректний, але кожен Под провалює свою пробу готовності? Уважна відповідь розрізняє Поди у стані Running та готові ендпоінти. Поди можуть існувати і навіть приймати kubectl logs, але Сервіс не повинен надсилати їм звичайний трафік, доки вони не стануть готовими, що захищає викликачів від Подів, які запустилися, але ще не обслуговують коректно.

Коли Сервіс не має збіжних Подів, вигляд ендпоінтів дає найчіткіший сигнал. Тоді ви можете порівняти селектор на Сервісі з фактичними мітками на Подах, а не вгадувати за іменами. Імена — для людей; мітки — це дані, які читає контролер Сервісу. Саме тому Деплоймент із іменем web може обслуговувати Сервіс із іменем frontend, доки збігаються мітки.

Terminal window
kubectl get endpoints my-service
# NAME ENDPOINTS AGE
# my-service <none> 5m

Сценарій вправи: розробник перейменовує Деплоймент із api на orders-api та оновлює образ контейнера, але залишає старіший селектор Сервісу. Клієнти все ще розв’язують DNS-ім’я Сервісу, і kubectl get svc усе ще показує ClusterIP, тож перший симптом — це тайм-аут застосунку, а не очевидна помилка API. Надійна послідовність діагностики — перевірити селектор Сервісу, переглянути Поди з мітками, перевірити ендпоінти, а потім протестувати з Пода всередині кластера за DNS-іменем Сервісу.

Коли набір ендпоінтів існує, але трафік усе одно не проходить, перейдіть на один рівень глибше замість того, щоб ходити по колу навколо міток. Перевірте, чи цільовий порт Сервісу збігається з фактичним слухачем контейнера, чи NetworkPolicy дозволяє джерельному Поду дістатися Пода-призначення, і чи відповідає застосунок, коли до нього звертаються через тестовий Под усередині кластера. Об’єкт Сервісу дає вам шлях, але він не змушує контейнер слухати на правильному порту і не скасовує політику, яка навмисно блокує трафік.

Зіставлення портів, багатопортові Сервіси та прив’язка сесій

Розділ «Зіставлення портів, багатопортові Сервіси та прив’язка сесій»

Порти Сервісу є джерелом багатьох помилок, яких можна уникнути, бо назви полів описують різні боки з’єднання. port — це порт, який публікує Сервіс і який використовує клієнт. targetPort — це порт на обраному Поді, який отримує переслане з’єднання. nodePort — це порт, відкритий на вузлах для NodePort та багатьох Сервісів LoadBalancer. Якщо targetPort опущено, Kubernetes за замовчуванням робить його рівним значенню port, що зручно лише тоді, коли контейнер слухає на тому самому номері.

Уявіть три поля портів як три двері в коридорі. Зовнішні двері — це nodePort, коли увімкнено публікацію на рівні Ноди, двері коридору — це port Сервісу, а двері кімнати — це targetPort Пода. Запит може пройти перші двоє дверей і все одно зазнати невдачі біля кімнати, якщо контейнер там не слухає. Ця аналогія вберігає вас від припущення, що видимий порт Сервісу доводить коректність слухача бекенду.

Ця відмінність не косметична. Сервіс із port: 80 та targetPort: 8080 може бути цілком коректним для застосунку, що слухає на 8080, але він зазнає невдачі для застосунку, який слухає лише на 80. Помилка часто з’являється як тайм-аут чи відмова з’єднання залежно від того, де гине пакет, тож виправлення — це не сліпе перестворення Сервісу. Виправлення — порівняти цільовий порт Сервісу з фактичним портом, на якому слухає контейнер, та конфігурацією готовності.

Проби готовності заслуговують на перевірку з тією самою дисципліною, що й порти Сервісу. Под може слухати на порту застосунку, але провалити готовність, бо проба перевіряє інший шлях, хост або порт. У такому разі Сервіс правильно виключає Под, і список ендпоінтів каже правду, навіть якщо ручний тест kubectl exec міг би дістатися процесу. Гарна діагностика запитує, чи Сервіс не може знайти Поди, чи не може дістатися їхнього порту, чи навмисно утримує трафік, бо готовність каже, що вони не готові.

Багатопортові Сервіси корисні, коли один логічний бекенд має опублікувати окремі порти під тим самим стабільним іменем, наприклад трафік HTTP та збір метрик. Kubernetes вимагає, щоб кожен запис порту мав ім’я, коли Сервіс має більше ніж один порт. Ці імена — не прикраса; клієнти, проби та інші об’єкти можуть посилатися на іменовані порти, і ці імена тримають кожне зіставлення однозначним, коли люди оглядають YAML.

apiVersion: v1
kind: Service
metadata:
name: multi-port
spec:
selector:
app: my-app
ports:
- name: http # Name required for multi-port
port: 80
targetPort: 8080
- name: https
port: 443
targetPort: 8443

Іменовані порти стають особливо корисними, коли шаблони Подів еволюціонують. Якщо контейнер публікує порт з іменем http, Сервіс може використати targetPort: http замість буквального числа, дозволяючи шаблону Пода володіти числовим значенням. Початковий приклад використовує числові цільові порти, бо це найбільш пряма форма CKAD, але в реальних маніфестах іменований targetPort може зменшити випадковий дрейф між YAML робочого навантаження та Сервісу.

Імена портів мають описувати протокол чи призначення, а не поточне число. Ім’я на кшталт http чи metrics залишається змістовним, коли числовий порт змінюється, тоді як ім’я на кшталт port-8080 стає оманливим після першого рефакторингу. Kubernetes перевіряє імена портів за правилами, схожими на правила DNS-міток, тож короткі імена малими літерами з дефісами є найбезпечнішою звичкою. Ця невелика дисципліна іменування окупається, коли Сервіси, проби, монітори та політики усі мають посилатися на той самий слухач.

Прив’язка сесій змінює те, як Сервіс обирає бекенди для повторюваного трафіку від того самого IP клієнта. Поведінка за замовчуванням не дає жодної гарантії sticky-сесій; площина даних може розподіляти з’єднання між готовими ендпоінтами. З sessionAffinity: ClientIP Kubernetes намагається спрямовувати трафік від того самого IP клієнта до того самого бекенду протягом певного періоду часу, що може допомогти старішим застосункам, які тримають тимчасовий стан у пам’яті. Це не замінює зберігання сесій на рівні застосунку і може створювати нерівномірне навантаження, якщо багато користувачів приходять з-за невеликої кількості NAT-адрес.

apiVersion: v1
kind: Service
metadata:
name: sticky-service
spec:
selector:
app: my-app
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10800
ports:
- port: 80

Використовуйте прив’язку сесій свідомо, а не як першу реакцію на кожну переривчасту помилку. Якщо клієнти зазнають невдачі, бо Поди розходяться щодо стану, сильнішим виправленням зазвичай є спільне сховище, зовнішній стан сесії або проєктування без збереження стану. Прив’язка може зменшити помітну для користувача нестабільність під час міграції, але вона також приховує проблеми розподілу і може зробити навантаження менш рівномірним. На іспиті вам здебільшого потрібно розпізнати це поле та розуміти, що воно змінює вибір бекенду, а не DNS, селектори чи зіставлення портів.

Значення тайм-ауту — це ще одна підказка, що прив’язка сесій є маршрутизаційною підказкою, а не постійним призначенням. Бекенди можуть зникати під час викочувань, вузли можуть відмовляти, а набори ендпоінтів можуть змінюватися, поки клієнти продовжують надсилати запити. Сервіс не може тримати користувача прив’язаним до Пода, який більше не існує або більше не готовий. Проєктуйте поведінку зі станом так, щоб застосунок залишався коректним, коли прив’язка розривається, а потім використовуйте прив’язку лише тоді, коли вона покращує сумісність чи досвід користувача.

Короткий довідник нижче зберігає команди, які вам потрібні, коли важлива швидкість. Усе ж варто читати вивід команд замість того, щоб припускати успіх, бо сервер API може прийняти Сервіс, який згодом не матиме ендпоінтів або до якого не вдасться дістатися ззовні кластера. Трактуйте ці команди як відправну точку для перевірки, а не як доказ того, що трафік працює.

Terminal window
# Create Service
kubectl expose deployment NAME --port=80 --target-port=8080
kubectl expose deployment NAME --type=NodePort --port=80
kubectl expose deployment NAME --type=LoadBalancer --port=80
# View Services
kubectl get svc
kubectl describe svc NAME
# View Endpoints
kubectl get endpoints NAME
kubectl get ep NAME
# Debug DNS
kubectl run tmp --image=busybox --rm -i --restart=Never -- nslookup my-service
# Test connectivity
kubectl run tmp --image=busybox --rm -i --restart=Never -- wget -qO- my-service:80

Headless-Сервіси та зовнішнє виявлення

Розділ «Headless-Сервіси та зовнішнє виявлення»

Headless-Сервіси призначені для випадків, коли клієнтам потрібно виявляти окремі адреси Подів замість надсилання трафіку через одну віртуальну IP-адресу Сервісу. Встановлення clusterIP: None каже Kubernetes не виділяти ClusterIP для Сервісу. DNS тоді може повертати записи для Подів-бекендів, що корисно для StatefulSets, баз даних, систем виявлення вузлів-однолітків і протоколів, де клієнт має знати кожного учасника, а не будь-якого справного.

apiVersion: v1
kind: Service
metadata:
name: headless-svc
spec:
clusterIP: None # Makes it headless
selector:
app: my-app
ports:
- port: 80

Важливий компроміс полягає в тому, що headless-виявлення перекладає більше відповідальності на клієнта. Зі звичайним Сервісом ClusterIP клієнт може викликати одну адресу і дозволити площині даних Сервісу обрати бекенд. З headless-Сервісом клієнт може отримати кілька записів Подів і має вирішити, як з’єднуватися, повторювати спроби чи опрацьовувати зміни членства. Саме це потрібно деяким кластеризованим системам, але це непотрібна складність для простого вебзастосунку без збереження стану.

Headless не означає некерований. За наявності селектора Kubernetes усе ще відстежує, які Поди належать Сервісу, і публікує DNS-записи з цього обраного набору. Без селектора Сервіс можна поєднати з ендпоінтами чи EndpointSlices, керованими вручну, для складніших інтеграцій, але це поза звичайним шляхом для початківців і легко може бути використано неправильно. Для CKAD важливим варіантом є clusterIP: None плюс селектор, бо він демонструє пряме виявлення Подів, не відмовляючись від міток Kubernetes.

Terminal window
# Returns multiple A records (one per pod)
nslookup headless-svc.default.svc.cluster.local

Для StatefulSets headless-Сервіси також підтримують стабільну мережеву ідентичність у поєднанні з передбачуваними іменами Подів. До учасника бази даних можна звертатися за стабільним DNS-іменем, прив’язаним до його порядкового номера, що важливо для реплікації та членства серед вузлів-однолітків. Сервіс усе ж сам по собі не є рішенням для кластеризації бази даних; він лише надає підґрунтя для іменування та виявлення, яке може використовувати протокол застосунку.

Саме тому headless-Сервіси є точним інструментом, а не типом за замовчуванням. Застосунок без збереження стану зазвичай виграє від того, що не знає, яка репліка відповіла на запит, бо це дозволяє Kubernetes та площині даних розподіляти навантаження на будь-який готовий ендпоінт. Кластеризована база даних може вимагати протилежного, бо кожен учасник має роль, ідентичність чи позицію реплікації. Тип Сервісу має відповідати очікуванням протоколу застосунку, а не заганяти кожне робоче навантаження в один патерн публікації.

ExternalName належить до тієї самої ментальної родини, що й виявлення, а не балансування навантаження. Він дозволяє клієнтам усередині кластера використовувати ім’я у формі Сервісу для чогось поза кластером, але повернуте DNS-ім’я вказує деінде, і Kubernetes не створює об’єктів ендпоінтів. Це означає, що NetworkPolicy, готовність, цільові порти Сервісу та правила kube-proxy не діють на зовнішню ціль так, як вони діють на обрані Поди. Якщо вам потрібні політика та спостережуваність навколо вихідного трафіку, вам знадобляться додаткові засоби контролю поза ExternalName.

Який підхід ви обрали б тут і чому: застосунок-фронтенд без збереження стану, що викликає реплікований API; кластер бази даних, учасники якого мають знаходити одне одного; чи Под, якому потрібне стабільне внутрішнє ім’я для DNS-запису керованої бази даних? Найкращі відповіді — ClusterIP для виклику від фронтенду до API, headless-Сервіс для прямого виявлення учасників і ExternalName лише для зовнішнього DNS-псевдоніма. Міркування важливіше за імена, бо кожен варіант дає клієнтам інший контракт.

Патерни та антипатерни

Розділ «Патерни та антипатерни»

Гарне проєктування Сервісів починається з обсягу викликача. Внутрішні викликачі зазвичай мають отримувати внутрішнє ім’я і нічого більше, бо кожен ширший шлях публікації додає питання щодо інфраструктури, брандмауера, моніторингу та володіння. Зовнішня публікація не є помилкою, але її слід обирати, бо реальному клієнту вона потрібна, а не тому, що навчальний посібник використав її першою. Таблиця нижче перетворює це судження на повторювані патерни, які ви можете застосовувати під тиском іспиту та під час огляду.

ПатернКоли використовуватиЧому працюєМіркування щодо масштабування
ClusterIP для трафіку між сервісамиБекенди, які викликають лише Поди всередині кластераТримає публікацію внутрішньою, поки оновлення DNS та ендпоінтів слідують за ПодамиПоєднайте з NetworkPolicy, коли потрібно забезпечити дотримання меж простору імен
NodePort для контрольованої лабораторії чи інтеграції з інфраструктуроюВам потрібен простий TCP-шлях на рівні Ноди, і ви можете керувати правилами брандмауераБудується на ClusterIP та уникає вимоги хмарного балансувальника навантаженняПеревіряйте кожен шлях через вузол, якщо тільки не використовуєте політику трафіку, що звужує маршрутизацію
LoadBalancer для прямого зовнішнього доступу четвертого рівняПлатформа може виділити стабільну зовнішню адресу для TCP чи UDPПерекладає життєвий цикл публічної адреси на середовищеВикористовуйте Інгрес чи Gateway, коли багато маршрутів HTTP мають спільно використовувати одну точку входу
Headless-Сервіс для виявлення учасниківКлієнтам потрібні окремі записи Подів, а не одна віртуальна IPDNS відкриває адреси Подів-бекендів напрямуПереконайтеся, що клієнт опрацьовує кілька записів та зміни членства

Антипатерни зазвичай походять від згортання окремих рівнів в одну розпливчасту думку “Сервіс зламаний”. Сервіс може бути успішно створений, тоді як DNS зазнає невдачі в клієнтському Поді, селектори не збігаються з жодними готовими Подами, цільовий порт вказує на неправильний контейнерний порт або хмарний брандмауер блокує NodePort. Розділення цих рівнів дозволяє виправити вузьку проблему, не замінюючи робочі об’єкти.

АнтипатернЩо йде не такКращий варіант
Використання IP Подів у конфігурації застосункуПерезапуски та викочування замінюють IP Подів, тож клієнти тримають застарілі призначенняВикористовуйте DNS-ім’я Сервісу і дозволяйте ендпоінтам змінюватися за ним
Публікація кожного робочого навантаження через NodePortКожен Сервіс відкриває порт на рівні Ноди та збільшує поверхню брандмауераПочинайте з ClusterIP, потім додавайте Інгрес, Gateway чи LoadBalancer лише для зовнішніх викликачів
Вгадування селекторів за іменами об’єктівІмена та мітки розходяться, створюючи Сервіси без ендпоінтівПорівнюйте селектори kubectl describe svc з kubectl get pods --show-labels
Трактування ExternalName як проксіKubernetes лише повертає DNS-псевдонім і не перевіряє справність ціліВикористовуйте справжній проксі, шлюз чи контроль вихідного трафіку, коли потрібні справність і політика

Патерн, що масштабується найкраще, — зробити контракт Сервісу нудним. Дайте йому чітке ім’я, тримайте селектори стабільними, публікуйте якнайменше портів і пишіть маніфести так, щоб рецензент міг визначити, який клієнтський та бекендний бік описує кожен порт. Це не порада щодо стилю; вона зменшує кількість рівнів, які ви маєте перевіряти під час збою чи іспитового сценарію.

Одне питання для огляду виловлює багато помилок Сервісів: “Хто перший легітимний викликач?” Якщо відповідь — інший Под, ClusterIP зазвичай достатньо. Якщо відповідь — користувач в інтернеті, вам, імовірно, треба вибирати між LoadBalancer, Інгресом чи Gateway, а не одразу переходити до NodePort. Якщо відповідь — вузол-одноліток бази даних, headless-виявлення може бути правильним. Формулювання рішення навколо викликача запобігає механічному копіюванню виборів публікації між непов’язаними робочими навантаженнями.

Ще одне корисне питання для огляду — “Що має залишатися стабільним, коли Поди змінюються?” Для більшості застосунків стабільною річчю є ім’я та порт Сервісу, тоді як списку ендпоінтів дозволено рухатися. Для учасників StatefulSet окремі DNS-ідентичності також можуть потребувати стабільності. Для ExternalName стабільною річчю є внутрішньокластерний псевдонім, а не справність чи досяжність зовнішньої цілі. Чіткі вимоги до стабільності ведуть до чіткіших маніфестів.

Рамка для прийняття рішень

Розділ «Рамка для прийняття рішень»

Коли ви обираєте тип Сервісу, починайте з викликача і рухайтеся всередину. Перше питання — чи перебуває викликач усередині кластера, поза кластером, чи насправді взагалі не викликає бекенд Kubernetes. Друге питання — чи потрібен викликачу будь-який справний Под, конкретна ідентичність Пода, чи зовнішній DNS-псевдонім. Третє питання — чи може інфраструктура навколо кластера виділити та захистити ту публікацію, яку ви запитуєте.

ВимогаПеревагаУникатиПричина
Поди викликають реплікований бекенд у тому самому кластеріClusterIPNodePort за замовчуваннямВнутрішнього DNS та вибору ендпоінтів достатньо
Швидкій лабораторії потрібен доступ на рівні Ноди ззовніNodePortLoadBalancer, коли провайдера немаєNodePort явний і його легко перевірити
Продакшен-сервісу TCP потрібна публічна адресаLoadBalancerРучний curl до випадкових IP вузлівПлатформа володіє життєвим циклом зовнішньої адреси
Клієнти мають виявляти кожного учасника бази данихHeadless-СервісЗвичайний ClusterIPКлієнту потрібні записи Подів, а не одна віртуальна IP
Подам усередині кластера потрібне ім’я для зовнішньої DNS-ціліExternalNameХаки без селекторів із фейковими мітками ПодівDNS-псевдонімізація відповідає реальній задачі

Для діагностики переверніть цю саму рамку. Якщо клієнт усередині кластера, протестуйте DNS із тимчасового Пода, перш ніж досліджувати хмарні балансувальники навантаження. Якщо Сервіс не має ендпоінтів, перевірте мітки та готовність, перш ніж тестувати NodePort. Якщо NodePort працює на одному вузлі і не працює на іншому, перевірте зовнішній брандмауер і відмінності площини даних вузла, перш ніж змінювати Деплоймент. Кожна гілка звужує простір пошуку, запитуючи, де пакет має входити і де Kubernetes має обирати бекенд.

Зручний для CKAD потік достатньо простий, щоб його запам’ятати. По-перше, kubectl get svc підтверджує, що об’єкт існує, та показує його тип, ClusterIP, порти та стан зовнішньої адреси. По-друге, kubectl describe svc показує селектори та події. По-третє, kubectl get endpoints або EndpointSlices підтверджують готові бекенди. По-четверте, тестовий Под усередині кластера перевіряє DNS та досяжність HTTP. Лише після цих перевірок вам слід зосереджуватися на брандмауерах вузлів, виділенні хмарного балансувальника навантаження чи конфігурації на боці клієнта.

Такий порядок убезпечує вас від гонитви за найгучнішим симптомом. Помилку DNS не виправити редагуванням targetPort, порожній набір ендпоінтів не виправити відкриттям брандмауера, а зовнішню IP у стані pending не виправити перепризначенням міток Подам. Гарні оператори не запам’ятовують кожну можливу відмову; вони зберігають межу між виявленням, вибором, пересиланням та публікацією інфраструктури, а потім тестують одну межу за раз.

Для іспитової роботи рамка має стати коротким ментальним контрольним списком, а не довгим есеєм. Ім’я розв’язується, Сервіс існує, селектор збігається, ендпоінти існують, зіставлення портів коректне, а шлях публікації досяжний. Якщо один пункт провалюється, виправте цей пункт, перш ніж рухатися далі списком. Це швидше за випадкові правки, бо кожна команда або підтверджує, або відкидає одне конкретне припущення про шлях від викликача до Пода.

Для огляду продакшену додайте до того самого рішення володіння та радіус ураження. Сервіс ClusterIP зазвичай належить команді застосунку і захищений політикою простору імен. Сервіс LoadBalancer часто залучає квоти платформи, DNS-записи, сертифікати деінде у стеку та очікування щодо реагування на інциденти від команд поза простором імен. Сервіс NodePort може виглядати малим у YAML, водночас створюючи вхідну поверхню в масштабі всього вузла. Правильний тип Сервісу — той, чиї операційні власники справді можуть підтримувати публікацію, яку він створює.

  • kube-proxy насправді не проксіює більшість трафіку Сервісів у поширених режимах iptables та IPVS. Попри свою назву, він програмує правила пересилання в ядрі, щоб трафік міг текти через площину даних вузла, а не через тривалий проксі-процес у просторі користувача.
  • Сервіси — це об’єкти API з простором імен, але до їхнього ClusterIP можна дістатися між просторами імен, коли це дозволяють мережева політика та маршрутизація. Простір імен головно впливає на іменування та володіння, тому payments.billing та payments.default — це різні DNS-цілі.
  • NodePort за замовчуванням використовує всі вузли. За звичайної політики трафіку кластера навіть вузли без цільових Подів можуть приймати вузловий порт і пересилати трафік до готового ендпоінта деінде в кластері.
  • Діапазон NodePort за замовчуванням — 30000-32767, і адміністратори кластера можуть змінити його прапором --service-node-port-range для kube-apiserver. Цей діапазон достатньо високий, щоб уникнути більшості загальновідомих портів, водночас залишаючись передбачуваним для правил брандмауера.
ПомилкаЧому трапляєтьсяЯк виправити
Селектор не збігається з мітками ПодаОб’єкт Сервісу коректний, але контролер ендпоінтів не знаходить готових Подів для селектораЗапустіть kubectl describe svc NAME, порівняйте з kubectl get pods --show-labels, потім виправте селектор або мітки
Неправильний targetPortКлієнт досягає порту Сервісу, але трафік пересилається на порт, де контейнер не слухаєПеревірте порти контейнера та конфігурацію застосунку, потім встановіть targetPort на фактичний слухач бекенду
Використання IP Пода замість DNS СервісуСкопійований IP Пода працює недовго, а потім ламається після викочування, перепланування чи перезапускуНалаштуйте клієнтів на ім’я Сервісу, ім’я з кваліфікацією простору імен або повне FQDN Сервісу
Забування простору імен у DNSКороткі імена розв’язуються лише через список пошуку простору імен Пода-викликачаВикористовуйте service.namespace або service.namespace.svc.cluster.local для міжпросторових викликів
NodePort досяжний лише на одному вузліЗовнішній брандмауер, група безпеки чи стан площини даних вузла різняться між вузламиПідтвердьте призначений вузловий порт, протестуйте кілька вузлів і узгодьте правила брандмауера в межах пулу вузлів
Очікування, що ExternalName перевірятиме справність трафікуExternalName повертає DNS-псевдонім і не має ендпоінтів Kubernetes, щоб позначати готовністьВикористовуйте ExternalName лише для іменування і додайте проксі чи шлюз, якщо потрібні справність, політика чи повтори
Приховування кількох протоколів за неіменованими портамиБагатопортові Сервіси без чітких імен важко читати, і вони можуть бути відхилені APIДайте кожному порту Сервісу стабільне ім’я, як-от http, https чи metrics
Питання 1: Розробник створює Сервіс із `port: 80` та `targetPort: 8080`. Клієнти підключаються до Сервісу на порту `80`, але отримують відмову з'єднання, а Поди у стані Running, і застосунок слухає на порту `80`. Що не так і як це виправити?

Сервіс пересилає трафік на неправильний порт бекенду. port — це порт Сервісу, зверненого до клієнта, тоді як targetPort — це порт на боці Пода, тож цей Сервіс надсилає трафік на 8080, хоча контейнер слухає на 80. Виправте Сервіс так, щоб targetPort: 80, потім перевірте ендпоінти і протестуйте з Пода всередині кластера. Перестворення Подів не є основним виправленням, бо розбіжність — у зіставленні Сервісу, а не у плануванні Подів.

Питання 2: Після розгортання нового застосунку `kubectl get endpoints myservice` показує ``, хоча три Поди у стані Running та Ready. Сервіс було створено командою `kubectl expose deployment myapp --port=80`. Яка найімовірніша причина і які дві команди ви запустили б наступними?

Найімовірніша причина — розбіжність між селектором та мітками, особливо якщо Поди були створені окремо або мітки шаблону Пода змінилися після створення Сервісу. Запустіть kubectl describe svc myservice, щоб перевірити селектор, потім запустіть kubectl get pods --show-labels, щоб порівняти фактичні мітки Подів. Якщо мітки не збігаються, виправте селектор Сервісу або скоригуйте мітки Подів, щоб контролер ендпоінтів міг опублікувати готові адреси бекендів. Перевірки DNS та ClusterIP менш корисні, доки Сервіс не має ендпоінтів.

Питання 3: Под у просторі імен `orders` має викликати Сервіс із іменем `payments` у просторі імен `billing`. Розробник пробує `curl http://payments:80` і отримує помилку розв'язання DNS. Яку URL-адресу слід використати і чому?

Слід використати http://payments.billing:80 або повне ім’я http://payments.billing.svc.cluster.local:80. Коротке ім’я payments розв’язується через список пошуку простору імен Пода-викликача, тож Под у orders спершу шукає Сервіс із іменем payments у orders. Додавання простору імен каже DNS кластера, який об’єкт Сервісу розв’язувати. Зміна типу Сервісу не розв’язала б цю конкретну невдачу, бо клієнт ще не розв’язав правильне ім’я.

Питання 4: Команда публікує застосунок Сервісом NodePort. Зовнішні користувачі можуть дістатися `node1:30080`, але не `node2:30080`, тоді як обидва вузли у стані Ready, і Сервіс має ендпоінти. Що слід перевірити, перш ніж змінювати Деплоймент?

Перевірте зовнішній шлях до node2, особливо правила брандмауера, хмарні групи безпеки та справність площини даних на рівні вузла для призначеного вузлового порту. Сервіс NodePort за замовчуванням має слухати на кожному вузлі, навіть коли обраний Под працює деінде, тож відмова на одному вузлі часто вказує поза межі Деплойменту. Підтвердьте вузловий порт командою kubectl get svc, перевірте ендпоінти, потім порівняйте зв’язність до кожного вузла. Зміна реплік чи версій образу не усунула б заблокований вхідний шлях на рівні вузла.

Питання 5: Базі даних на основі StatefulSet потрібно, щоб кожен учасник виявляв інших учасників за ідентичністю Пода. Колега пропонує звичайний Сервіс ClusterIP, бо він дає застосунку одну стабільну IP. Чи прийняли б ви такий дизайн?

Для виявлення учасників headless-Сервіс зазвичай підходить краще, бо клієнту потрібні окремі записи Подів, а не одна віртуальна IP. Звичайний Сервіс ClusterIP навмисно приховує окрему ідентичність Пода за єдиною адресою Сервісу, що корисно для бекендів без збереження стану, але неправильно для протоколів, які явно керують вузлами-однолітками. StatefulSet можна поєднати з headless-Сервісом, щоб виробляти стабільні DNS-ідентичності для учасників. Вам усе одно знадобиться, щоб база даних коректно опрацьовувала членство та реплікацію, бо Kubernetes лише надає виявлення.

Питання 6: Сервіс публікує і трафік застосунку, і метрики. Маніфест має два записи `ports`, але без імен, і сервер API його відхиляє. Яку зміну вам слід зробити і які імена були б розумними?

Додайте унікальне поле name до кожного запису порту в багатопортовому Сервісі. Розумними іменами були б http для слухача застосунку та metrics для ендпоінта збору, або https, якщо другий порт несе трафік TLS. Kubernetes вимагає імен для багатопортових Сервісів, щоб кожне зіставлення було однозначним для клієнтів, контролерів та людей, які читають об’єкт. Зміна селектора не виправила б цю помилку валідації, бо проблема — у списку портів Сервісу.

Питання 7: Команда застосунку вмикає `sessionAffinity: ClientIP`, бо користувачі іноді втрачають стан, коли запити потрапляють на різні Поди. Який ризик вам слід озвучити під час огляду і який сильніший дизайн їм варто розглянути?

Прив’язка ClientIP може зменшити нестабільність, але вона також може створити нерівномірне навантаження на бекенди і приховати глибшу проблему: стан користувача живе всередині окремих Подів. Якщо багато користувачів приходять через ту саму NAT-адресу, вони можуть прилипнути до того самого бекенду і перевантажити його. Сильніший дизайн зберігає стан сесії поза Подом або робить застосунок без збереження стану, а потім дозволяє Сервісу розподіляти трафік звичайним чином. Прив’язка може бути тимчасовим вибором заради сумісності, але її не слід трактувати як заміну проєктуванню застосунку.

У цій вправі ви створите та протестуєте форми Сервісів з уроку, а потім навмисно зламаєте селектор, щоб попрактикувати діагностику ендпоінтів. Завдання рухаються від безпечного типу за замовчуванням, ClusterIP, до публікації на рівні Ноди, а потім до зламаного Сервісу, який виглядає коректним, але не має бекендів. Запускайте ці команди в одноразовому просторі імен або лабораторному кластері, бо очищення прибирає об’єкти, створені вправою.

Створіть та протестуйте різні типи Сервісів, пояснюючи, який рівень перевіряє кожна команда. Лабораторна робота навмисно використовує nginx, бо поведінка застосунку передбачувана, лишаючи механіку Сервісу видимою. Якщо команда зазнає невдачі, не пропускайте вперед; класифікуйте відмову як створення об’єкта, збіг селектора, розв’язання DNS, зіставлення портів чи зовнішню публікацію, перш ніж застосовувати виправлення.

  • Створити Деплоймент nginx із трьома готовими (Ready) репліками
  • Опублікувати Деплоймент Сервісом ClusterIP і протестувати DNS зсередини кластера
  • Замінити Сервіс ClusterIP Сервісом NodePort і визначити призначений вузловий порт
  • Створити зламаний Сервіс, довести, що він не має ендпоінтів, і відремонтувати його селектор
  • Прибрати Деплоймент і кожен Сервіс, створений під час вправи
  • Виконати щонайменше дві практичні вправи без використання псевдоніма kubectl у командах для запуску

Налаштування створює бекенд, на який націлюватиметься кожен наступний Сервіс. Очікування готовності перед публікацією Деплойменту тримає першу перевірку ендпоінтів легкою для інтерпретації: якщо селектор Сервісу коректний, ви маєте побачити готові ендпоінти невдовзі після створення Сервісу. У реальному викочуванні ви також перевіряли б події Подів та проби готовності, коли ендпоінти не з’являються.

Terminal window
# Create a deployment
kubectl create deployment web --image=nginx --replicas=3
# Wait for pods
kubectl wait --for=condition=Ready pod -l app=web --timeout=60s

Частина 1: Сервіс ClusterIP

Розділ «Частина 1: Сервіс ClusterIP»

Перше завдання використовує тип Сервісу за замовчуванням, бо клієнт — це інший Под усередині кластера. Зверніть увагу, що тестовий Под викликає web:80, а не IP Пода. Саме цю поведінку ви хочете, щоб застосунки копіювали: ім’я Сервісу стабільне, тоді як набір ендпоінтів може змінюватися за ним.

Розв'язання частини 1
Terminal window
# Create ClusterIP service
kubectl expose deployment web --port=80 --target-port=80
# Verify endpoints
kubectl get endpoints web
# Test from within cluster
kubectl run test --image=busybox --rm -i --restart=Never -- wget -qO- web:80
# Check DNS
kubectl run test --image=busybox --rm -i --restart=Never -- nslookup web.default.svc.cluster.local

Частина 2: Сервіс NodePort

Розділ «Частина 2: Сервіс NodePort»

Друге завдання замінює внутрішній лише Сервіс Сервісом NodePort. Мета — не покладатися на NodePort для кожного робочого навантаження, а побачити, як Kubernetes повідомляє призначений порт і чим вхідний шлях на рівні Ноди відрізняється від імені ClusterIP. Якщо ваше лабораторне середовище не відкриває IP вузлів вашому браузеру, підтвердження об’єкта Сервісу та призначеного вузлового порту все одно корисне.

Розв'язання частини 2
Terminal window
# Delete ClusterIP service
kubectl delete svc web
# Create NodePort service
kubectl expose deployment web --type=NodePort --port=80 --target-port=80
# Get assigned NodePort
kubectl get svc web -o jsonpath='{.spec.ports[0].nodePort}'
echo
# Test (if you have node access)
# curl http://<node-ip>:<nodeport>

Частина 3: Діагностика відсутності ендпоінтів

Розділ «Частина 3: Діагностика відсутності ендпоінтів»

Третє завдання створює Сервіс, який має коректний ClusterIP та коректний YAML, але не має збіжних Подів. Це найважливіший патерн діагностики у модулі, бо він відокремлює створення об’єкта від готовності трафіку. Ви маєте вміти пояснити відмову, перш ніж застосовувати патч: селектор запитує app: wrong-label, тоді як Поди Деплойменту мають мітку app: web.

Розв'язання частини 3
Terminal window
# Create service with wrong selector
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
name: broken-svc
spec:
selector:
app: wrong-label
ports:
- port: 80
EOF
# Check endpoints (should be empty)
kubectl get endpoints broken-svc
# Fix by patching selector
kubectl patch svc broken-svc -p '{"spec":{"selector":{"app":"web"}}}'
# Verify endpoints now exist
kubectl get endpoints broken-svc

Очищення є частиною вправи, бо застарілі Сервіси створюють оманливі DNS-імена та вивід ендпоінтів під час пізніших лабораторних робіт. Видаліть спершу робоче навантаження або спершу Сервіси; будь-який порядок прийнятний тут, бо об’єкти прості. Якщо видалення повідомляє, що об’єкта вже немає, перевірте решту Сервісів, перш ніж залишати середовище.

Команди очищення
Terminal window
kubectl delete deployment web
kubectl delete svc web broken-svc

Ці вправи зберігають короткі повторення на іспитову швидкість з оригінального модуля, але вони не повинні замінювати роботу з міркуваннями вище. Використовуйте їх після того, як ви вже можете пояснити шлях трафіку, бо команди цінні лише тоді, коли ви можете інтерпретувати їхній вивід. Цілі навмисно короткі, щоб напрацьовувати швидкість, а не для того, щоб натякати, що діагностику в продакшені слід поспішати.

Вправа 1: Створення Сервісу ClusterIP (Ціль: 1 хвилина)

Розділ «Вправа 1: Створення Сервісу ClusterIP (Ціль: 1 хвилина)»
Terminal window
kubectl create deployment drill1 --image=nginx
kubectl expose deployment drill1 --port=80
kubectl get svc drill1
kubectl get ep drill1
kubectl delete deploy drill1 svc drill1

Вправа 2: Створення Сервісу NodePort (Ціль: 2 хвилини)

Розділ «Вправа 2: Створення Сервісу NodePort (Ціль: 2 хвилини)»
Terminal window
kubectl create deployment drill2 --image=nginx
kubectl expose deployment drill2 --type=NodePort --port=80 --target-port=80
# Get NodePort
kubectl get svc drill2 -o jsonpath='{.spec.ports[0].nodePort}'
echo
kubectl delete deploy drill2 svc drill2

Вправа 3: Тестування розв’язання DNS (Ціль: 2 хвилини)

Розділ «Вправа 3: Тестування розв’язання DNS (Ціль: 2 хвилини)»
Terminal window
kubectl create deployment drill3 --image=nginx
kubectl expose deployment drill3 --port=80
# Test DNS
kubectl run dns-test --image=busybox --rm -i --restart=Never -- nslookup drill3
kubectl delete deploy drill3 svc drill3

Вправа 4: Сервіс з іменованим портом (Ціль: 2 хвилини)

Розділ «Вправа 4: Сервіс з іменованим портом (Ціль: 2 хвилини)»
Terminal window
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
name: drill4
spec:
selector:
app: drill4
ports:
- name: http
port: 80
targetPort: 80
- name: metrics
port: 9090
targetPort: 9090
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: drill4
spec:
replicas: 2
selector:
matchLabels:
app: drill4
template:
metadata:
labels:
app: drill4
spec:
containers:
- name: nginx
image: nginx
EOF
# Endpoints list both ports; nginx listens on 80 only — port 9090 appears before traffic succeeds
kubectl get svc drill4
kubectl get ep drill4
kubectl delete deploy drill4 svc drill4

Вправа 5: Діагностика зв’язності Сервісу (Ціль: 3 хвилини)

Розділ «Вправа 5: Діагностика зв’язності Сервісу (Ціль: 3 хвилини)»
Terminal window
# Create deployment and broken service
kubectl create deployment drill5 --image=nginx
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
name: drill5
spec:
selector:
app: wrong
ports:
- port: 80
EOF
# Debug
kubectl get ep drill5 # No endpoints
kubectl get pods --show-labels # Check pod labels
kubectl describe svc drill5 | grep Selector # Check service selector
# Fix
kubectl patch svc drill5 -p '{"spec":{"selector":{"app":"drill5"}}}'
kubectl get ep drill5 # Should now have endpoints
kubectl delete deploy drill5 svc drill5

Вправа 6: Міжпросторовий доступ до Сервісу (Ціль: 3 хвилини)

Розділ «Вправа 6: Міжпросторовий доступ до Сервісу (Ціль: 3 хвилини)»
Terminal window
# Create namespace and service
kubectl create ns drill6
kubectl create deployment drill6-app --image=nginx -n drill6
kubectl expose deployment drill6-app --port=80 -n drill6
# Access from default namespace
kubectl run test --image=busybox --rm -i --restart=Never -- wget -qO- drill6-app.drill6:80
kubectl delete ns drill6

Перевірка засвоєного

Розділ «Перевірка засвоєного»

Імперативна форма корисна під час іспиту CKAD, бо kubectl expose deployment копіює селектор Деплойменту у Сервіс. Це допомагає уникнути ручного введення міток селектора в умовах браку часу, але вам усе одно слід оглянути результат, бо скопійовані мітки допомагають лише тоді, коли мітки шаблону пода в Деплойменті вже коректні.

Модуль 5.2: Інгрес знайомить з маршрутизацією HTTP та термінацією TLS, спираючись на контракти Сервісів, які ви діагностували тут, щоб одна зовнішня точка входу могла маршрутизувати до багатьох внутрішніх бекендів.