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

Модуль 1.5: Services — Стабільна мережа

Hands-On Lab Available
K8s Cluster beginner 25 min
Launch Lab ↗

Opens in Killercoda in a new tab

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

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

Попередні вимоги: Модуль 4 (Deployments)


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

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

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

  • Створити Services для надання доступу до Pod-ів та пояснити, чому Pod-ам потрібні Services (IP-адреси Pod-ів є ефемерними)
  • Обрати між ClusterIP, NodePort та LoadBalancer та пояснити, коли використовувати кожен з них
  • Перевірити зв’язок із Service зсередини кластера за допомогою curl та DNS-імен
  • Діагностувати Service, який не може підключитися до своїх Pod-ів, перевіряючи мітки (labels), селектори (selectors) та кінцеві точки (endpoints)

Це була Чорна п’ятниця, і платформа електронної комерції мала серйозні проблеми. Інженерна команда помітила, що їхні Pod-и обробки платежів постійно аварійно завершували роботу та перезапускалися через витоки пам’яті. Хоча Kubernetes успішно створював нові Pod-и для підтримки потужності, фронтенд-додаток мав жорстко прописані IP-адреси старих Pod-ів. Кожного разу, коли платіжний Pod перезапускався, транзакції переривалися, поки інженер вручну не оновлював конфігурацію фронтенду новою IP-адресою. Вони втрачали тисячі доларів щохвилини, тому що їхня внутрішня мережа не могла адаптуватися до ефемерної інфраструктури.

Pod-и ефемерні — вони з’являються та зникають, кожен з новою IP-адресою. Services забезпечують стабільну мережеву взаємодію: фіксовану IP-адресу та DNS-ім’я, що спрямовують трафік на ваші Pod-и, незалежно від їхньої кількості чи частоти змін.


Проблема, яку вирішують Services

Розділ «Проблема, яку вирішують Services»
┌─────────────────────────────────────────────────────────────┐
│ БЕЗ SERVICES │
├─────────────────────────────────────────────────────────────┤
│ │
│ IP-адреси Pod-ів постійно змінюються: │
│ │
│ Час 0: [Pod: 10.1.0.5] │
│ Час 1: Pod впав, перестворений │
│ Час 2: [Pod: 10.1.0.9] ← Інша IP! │
│ │
│ Проблема: Як іншим додаткам знайти ваш Pod? │
│ │
├─────────────────────────────────────────────────────────────┤
│ З SERVICES │
├─────────────────────────────────────────────────────────────┤
│ │
│ Service: my-app.default.svc.cluster.local │
│ ClusterIP: 10.96.0.100 (стабільна!) │
│ │ │
│ ┌────────┴────────┐ │
│ ▼ ▼ │
│ [Pod: 10.1.0.5] [Pod: 10.1.0.9] │
│ │
│ Service спрямовує трафік на здорові Pod-и, IP не важливі │
│ │
└─────────────────────────────────────────────────────────────┘

Зупиніться та подумайте: Якщо Deployment масштабується до 10 Pod-ів, скільки IP-адрес матиме пов’язаний з ним Service? (Відповідь: Тільки одну. Service підтримує єдину стабільну IP-адресу, розподіляючи трафік між усіма 10 фоновими Pod-ами.)


Спробуйте самі: Імперативний спосіб (швидко)

Розділ «Спробуйте самі: Імперативний спосіб (швидко)»
Terminal window
# Відкрити доступ до deployment
kubectl expose deployment nginx --port=80
# Зі специфічним типом
kubectl expose deployment nginx --port=80 --type=NodePort
# Перевірити сервіс
kubectl get services
kubectl get svc # Коротка форма

Спробуйте самі: Декларативний спосіб (для продакшену)

Розділ «Спробуйте самі: Декларативний спосіб (для продакшену)»
service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
selector:
app: nginx # Має збігатися з мітками pod
ports:
- port: 80 # Порт Service
targetPort: 80 # Порт контейнера
type: ClusterIP # Тип за замовчуванням
Terminal window
kubectl apply -f service.yaml

Порівняння типів Service

Розділ «Порівняння типів Service»

Вибір правильного типу Service є критично важливим для безпеки та архітектури.

ТипДоступністьНайкраще дляКомпроміс
ClusterIPТільки внутрішняБекенд бази даних, внутрішні APIНеможливо отримати доступ ззовні кластера.
NodePortЗовнішня (Високий порт)Швидка відладка, bare-metal кластериВідкриває високі порти (30000+), незручно для зовнішніх клієнтів.
LoadBalancerЗовнішня (Стандартний порт)Публічні веб-додатки у хмаріКоштує грошей за кожен Service, залежить від хмарного провайдера.

ClusterIP (За замовчуванням)

Розділ «ClusterIP (За замовчуванням)»

Тільки внутрішній доступ у межах кластера:

apiVersion: v1
kind: Service
metadata:
name: internal-api
spec:
type: ClusterIP # За замовчуванням, можна пропустити
selector:
app: api
ports:
- port: 80
targetPort: 8080
Terminal window
# Доступ тільки зсередини кластера
curl http://internal-api:80

Відкриває доступ на IP-адресі кожного вузла (node) на статичному порту:

apiVersion: v1
kind: Service
metadata:
name: web-nodeport
spec:
type: NodePort
selector:
app: web
ports:
- port: 80
targetPort: 80
nodePort: 30080 # Опціонально: 30000-32767
Terminal window
# Доступ ззовні кластера
curl http://<node-ip>:30080

Створює зовнішній балансувальник навантаження (у хмарних середовищах):

apiVersion: v1
kind: Service
metadata:
name: web-lb
spec:
type: LoadBalancer
selector:
app: web
ports:
- port: 80
targetPort: 80
Terminal window
# Отримати зовнішню IP (тільки в хмарі)
kubectl get svc web-lb
# Колонка EXTERNAL-IP покаже IP балансувальника

┌─────────────────────────────────────────────────────────────┐
│ ТИПИ SERVICE │
├─────────────────────────────────────────────────────────────┤
│ │
│ ClusterIP (Тільки внутрішній) │
│ ┌─────────────────────────────────────┐ │
│ │ ClusterIP:80 ──► Pod:8080 │ │
│ │ ──► Pod:8080 │ │
│ │ (Доступно тільки в межах кластера) │ │
│ └─────────────────────────────────────┘ │
│ │
│ NodePort (Зовнішній через вузол) │
│ ┌─────────────────────────────────────┐ │
│ │ <NodeIP>:30080 ──► ClusterIP:80 ──► Pods │
│ │ (Доступно ззовні) │ │
│ └─────────────────────────────────────┘ │
│ │
│ LoadBalancer (Хмарний зовнішній) │
│ ┌─────────────────────────────────────┐ │
│ │ <ExternalIP>:80 ──► NodePort ──► ClusterIP ──► Pods │
│ │ (Хмарний провайдер керує LB) │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

Виявлення сервісів (DNS)

Розділ «Виявлення сервісів (DNS)»

Kubernetes створює записи DNS для Services:

<service-name>.<namespace>.svc.cluster.local
Terminal window
# З будь-якого Pod-а ви можете звернутися до:
curl nginx # Той самий простір імен
curl nginx.default # Явний простір імен
curl nginx.default.svc # Більш детально
curl nginx.default.svc.cluster.local # Повне ім'я FQDN
Terminal window
# Створити deployment та service
kubectl create deployment nginx --image=nginx
kubectl expose deployment nginx --port=80
# Перевірити DNS з іншого Pod-а
kubectl run test --image=busybox --rm -it -- wget -qO- nginx
# Повертає HTML-код nginx!
# Перевірити за допомогою повного DNS-імені
kubectl run test --image=busybox --rm -it -- nslookup nginx.default.svc.cluster.local

Селектори: Як Services знаходять Pod-и

Розділ «Селектори: Як Services знаходять Pod-и»

Services використовують селектори міток (label selectors):

# Service
spec:
selector:
app: nginx
tier: frontend
# Pod (має мати ВСІ мітки)
metadata:
labels:
app: nginx
tier: frontend

Зупиніться та подумайте: Що станеться з вашим Service, якщо ви вручну відредагуєте запущений Pod і видалите мітку tier: frontend? (Відповідь: Service негайно видалить цей Pod зі свого списку кінцевих точок (endpoints), оскільки він більше не відповідає селектору, і трафік на нього більше не спрямовуватиметься.)

Terminal window
# Перевірити, на які Pod-и спрямований Service
kubectl get endpoints nginx
# Показує IP:Port відповідних Pod-ів

spec:
ports:
- port: 80 # Порт Service (використовується клієнтами)
targetPort: 8080 # Порт контейнера (де слухає додаток)
protocol: TCP # TCP (за замовчуванням) або UDP
┌─────────────────────────────────────────────────────────────┐
│ Клієнт ──► Service:80 ──► Pod:8080 │
│ │ │ │
│ "port" "targetPort" │
└─────────────────────────────────────────────────────────────┘

Історії з передової: Фантомний збій

Розділ «Історії з передової: Фантомний збій»

Велика стрімінгова компанія зіткнулася з дивним збоєм, коли рівно 10% запитів користувачів до їхнього каталогу відео завершувалися помилкою тайм-ауту з’єднання. Усі Pod-и виглядали здоровими, а Service був активним. Після кількох годин діагностики старший інженер виконав команду kubectl get endpoints catalog-service.

Вони виявили 10 кінцевих точок (endpoints), але одна з IP-адрес належала Pod-у, який був вручну видалений безпосередньо через середовище виконання контейнерів (Docker), оминаючи Kubernetes. Базові правила iptables для Service все ще спрямовували трафік на мертву IP-адресу! Рішення? Перезавантаження компонента kube-proxy на ураженому вузлі для оновлення застарілих правил маршрутизації. Урок: завжди дозволяйте Kubernetes керувати життєвим циклом ваших Pod-ів і завжди перевіряйте свої endpoints, коли трафік зникає!


  • Services використовують iptables або IPVS. kube-proxy налаштовує правила, які спрямовують IP-адреси Service на IP-адреси Pod-ів. Жоден реальний проксі-процес не обробляє кожне з’єднання.
  • ClusterIP є віртуальним. Жоден мережевий інтерфейс не має цієї IP-адреси. Вона існує лише в правилах iptables.
  • NodePort використовує УСІ вузли. Навіть ті вузли, на яких немає цільових Pod-ів, правильно спрямують трафік на потрібний вузол.
  • Services балансують навантаження випадковим чином за замовчуванням. Кожне нове з’єднання може потрапити на інший Pod.

ПомилкаЧому це поганоРішення
Селектор не збігається з мітками Pod-аService не має endpoints, і трафік зникає у «чорній дірі».Використовуйте kubectl get endpoints <service-name>, щоб перевірити збіг.
Неправильний targetPortПомилки “connection refused”, бо сервіс шле трафік туди, де ніхто не слухає.Переконайтеся, що targetPort збігається з портом, на якому реально працює додаток.
Використання IP Pod-а замість імені ServiceДодаток ламається в той самий момент, коли Pod перезапускається і отримує нову IP.Завжди налаштовуйте додатки на використання DNS-імені Service.
Забули встановити protocol: UDPDNS або кастомні UDP-сервіси не працюють, бо за замовчуванням Service використовує TCP.Явно вкажіть protocol: UDP у налаштуваннях порту.
Відкриття кожного мікросервісу як LoadBalancerВеличезні рахунки за хмару, бо кожен LoadBalancer коштує грошей.Використовуйте ClusterIP для внутрішніх сервісів та Ingress для маршрутизації HTTP.
Помилка в іменованих портахТрафік не проходить, якщо рядок targetPort не збігається з ім’ям порту контейнера.Ретельно перевіряйте написання та регістр між targetPort у Service та ports.name у Pod.
Використання NodePort для публічного трафікуСкладно керувати, потребує нестандартних портів (30000+), бракує гнучкої маршрутизації.Використовуйте LoadBalancer або Ingress для зовнішнього доступу у продакшені.

Контрольні запитання

Розділ «Контрольні запитання»
  1. Сценарій: Молодший розробник жорстко прописав IP-адресу Pod-а бази даних у конфігурацію фронтенду. Наступного дня фронтенд не може з’єднатися з базою, хоча Pod бази даних працює ідеально. Чому це сталося, і яке рішення є нативним для Kubernetes?

    Відповідь Pod-и ефемерні, тобто вони часто знищуються та створюються контролерами, такими як Deployments. Коли Pod бази даних був перестворений (через оновлення вузла або збій), він отримав нову IP-адресу, що зламало конфігурацію фронтенду. Рішенням є створення Kubernetes Service для бази даних, який забезпечує стабільну, незмінну IP-адресу та DNS-ім'я.
  2. Сценарій: Ви розгортаєте кеш Redis, до якого мають звертатися виключно ваші Pod-и бекенд API у тому самому кластері. Вимоги безпеки забороняють доступ до цього кешу з інтернету. Який тип Service ви оберете і чому?

    Відповідь Слід обрати `ClusterIP`, який є типом за замовчуванням. Service типу ClusterIP призначає внутрішню IP-адресу, яка доступна лише зсередини самого кластера. Це ідеально відповідає вимогам безпеки, запобігаючи зовнішньому трафіку та дозволяючи бекенд API безперешкодно спілкуватися з Redis.
  3. Сценарій: Ви розгорнули новий веб-додаток і створили для нього Service. Однак при спробі доступу ви отримуєте помилку “connection refused”. Команда kubectl get pods --show-labels показує, що ваші Pod-и мають мітки app=frontend,env=prod. Ваш Service має селектор app=frontend,tier=web. Чому трафік не проходить?

    Відповідь Services використовують селектори міток для визначення цільових Pod-ів. Щоб Service спрямував трафік на Pod, цей Pod повинен мати *всі* мітки, вказані в селекторі Service. У цьому сценарії Service шукає Pod-и з міткою `tier=web`, якої у Pod-ів немає. Як результат, Service не має кінцевих точок (endpoints) і скидає трафік. Потрібно оновити мітки Pod-ів або селектор Service.
  4. Сценарій: Розробник проводить діагностику всередині тестового Pod-а busybox у просторі імен default. Їм потрібно перевірити зв’язок із Service платіжного API, який знаходиться у просторі імен finance. Яке саме DNS-ім’я вони мають використати у команді curl?

    Відповідь Розробник має використати `payment-api.finance` або повне доменне ім'я (FQDN) `payment-api.finance.svc.cluster.local`. Оскільки тестовий Pod і цільовий Service знаходяться у різних просторах імен, звичайний `curl payment-api` не спрацює, бо за замовчуванням DNS Kubernetes шукає сервіси лише у поточному просторі імен Pod-а.
  5. Сценарій: Ваша команда переносить застарілий додаток у Kubernetes на AWS. Додаток має бути доступним зовнішнім клієнтам через інтернет на стандартному 80-му порту. Ви спробували NodePort, але команда безпеки заборонила відкривати порти в діапазоні 30000+. Який тип Service буде правильним архітектурним вибором?

    Відповідь Слід використовувати тип `LoadBalancer`. При створенні Service типу LoadBalancer у хмарному середовищі (як-от AWS, GCP або Azure), Kubernetes автоматично створює нативний хмарний балансувальник навантаження. Він спрямовує трафік зі стандартних портів (80 або 443) на публічній IP-адресі прямо у ваш кластер, задовольняючи вимоги безпеки.
  6. Сценарій: Ви створили Service під назвою auth-svc та Deployment з Pod-ами авторизації. Ви хочете переконатися, що Kubernetes успішно зв’язав Service із Pod-ами, перш ніж тестувати додаток з іншого мікросервісу. Яку команду kubectl слід виконати, щоб довести, що Service знайшов IP-адреси Pod-ів?

    Відповідь Потрібно виконати команду `kubectl get endpoints auth-svc` (або `kubectl describe svc auth-svc`). Об'єкт Endpoints автоматично створюється та оновлюється Kubernetes для ведення списку реальних IP-адрес Pod-ів, які відповідають селектору міток Service. Якщо список порожній — значить, є невідповідність міток або Pod-и не працюють.
  7. Сценарій: Ваш додаток на Node.js працює на порту 3000 всередині контейнера. Ви хочете, щоб інші Pod-и в кластері могли звертатися до нього за адресою http://node-backend:80. Як налаштувати port та targetPort у визначенні Service?

    Відповідь Ви маєте встановити у Service `port: 80` та `targetPort: 3000`. Поле `port` визначає порт, який сам Service відкриває для клієнтів (віртуальний порт). Поле `targetPort` визначає реальний порт, на якому слухає додаток у контейнері. Service виступає як внутрішній проксі, транслюючи трафік з 80-го порту на 3000-й порт Pod-а.

Завдання: Створити Deployment та надати доступу до нього через Service.

Terminal window
# 1. Створити deployment
kubectl create deployment web --image=nginx --replicas=3
# 2. Відкрити доступ як ClusterIP
kubectl expose deployment web --port=80
# 3. Перевірити сервіс
kubectl get svc web
kubectl get endpoints web
# 4. Тест зсередини кластера
kubectl run test --image=busybox --rm -it -- wget -qO- web
# 5. Створити NodePort сервіс
kubectl expose deployment web --port=80 --type=NodePort --name=web-external
# 6. Отримати NodePort
kubectl get svc web-external
# Зверніть увагу на порт у діапазоні 30000-32767
# 7. Очищення
kubectl delete deployment web
kubectl delete svc web web-external

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

  • Внутрішній Service web створено і йому призначено ClusterIP.
  • kubectl get endpoints web показує три різні IP-адреси Pod-ів.
  • Команда wget із тимчасового Pod-а успішно повертає вітальну HTML-сторінку Nginx.
  • Service web-external створено з типом NodePort та портом у діапазоні 30000-32767.

Services забезпечують стабільну роботу мережі:

Типи:

  • ClusterIP — тільки внутрішній (за замовчуванням)
  • NodePort — зовнішній через порт вузла (node port)
  • LoadBalancer — зовнішній через хмарний LB

Ключові концепції:

  • Селектори збігаються з мітками Pod-ів
  • DNS-імена для виявлення (discovery)
  • Мапінг портів (port → targetPort)
  • Endpoints показують відповідні Pod-и

Команди:

  • kubectl expose deployment NAME --port=PORT
  • kubectl get svc
  • kubectl get endpoints

Модуль 1.6: ConfigMaps та Secrets — Керування конфігурацією.