У попередніх статтях ми навчилися створювати Deployment з кількома репліками Pod. Але залишилося критично важливе питання: як інші компоненти застосунку можуть звертатись до цих Pod?
Уявіть типову архітектуру веб-застосунку:
Проблеми прямого звернення до Pod за IP:
Ефемерність IP-адрес
Кожен Pod отримує унікальну IP-адресу при створенні. Але ця адреса не стабільна:
Приклад: API Pod мав IP 10.244.1.10. Після rolling update він має 10.244.2.15. Frontend продовжує звертатись до старої адреси → помилки.
Відсутність балансування навантаження
Якщо у вас 3 репліки API, frontend має самостійно розподіляти запити між ними. Це означає:
Це складно, схильно до помилок та дублює функціональність, яку має надавати платформа.
Немає service discovery
У Docker Compose ви використовували DNS-імена сервісів:
services:
frontend:
environment:
- API_URL=http://api:8080
api автоматично резолвився у IP-адресу контейнера. У Kubernetes немає автоматичного DNS для Pod — потрібен механізм service discovery.
Складність конфігурації
Якщо frontend має знати IP кожного API Pod, конфігурація стає кошмаром:
env:
- name: API_ENDPOINTS
value: "10.244.1.10,10.244.1.11,10.244.1.12"
Після кожного rolling update потрібно оновлювати цю конфігурацію. Це не масштабується.
Нам потрібен механізм, який:
Саме це і робить Service.
Service — це абстракція Kubernetes, яка визначає логічний набір Pod та політику доступу до них. Service надає стабільну мережеву точку доступу (IP-адресу та DNS-ім'я) для групи ефемерних Pod.
services:
api:
image: myapi:1.0
deploy:
replicas: 3
api автоматично резолвився у IP одного з 3 контейнерів. Kubernetes Service робить те саме, але з більшим контролем та гнучкістю.Компоненти:
<service-name>.<namespace>.svc.cluster.local)Розглянемо базовий приклад Service:
apiVersion: v1
kind: Service
metadata:
name: api-service
namespace: default
spec:
selector:
app: api
ports:
- name: http
protocol: TCP
port: 80
targetPort: 8080
type: ClusterIP
Розберемо кожне поле детально.
v1 (core API group). На відміну від Deployment (apps/v1), Service — це базовий ресурс Kubernetes, який існує з самого початку.Service. Це вказує Kubernetes, що ви створюєте мережевий об'єкт для доступу до Pod.<name>.<namespace>.svc.cluster.local.Важливо: Ім'я має відповідати DNS-стандарту (малі літери, цифри, дефіси). Максимум 63 символи.selector мають збігатись з мітками у template.metadata.labels Deployment.Приклад:# Service
selector:
app: api
# Deployment
template:
metadata:
labels:
app: api # ← Має збігатись!
name — ім'я порту (опціонально, але рекомендується для багатопортових Service)protocol — протокол (TCP або UDP, за замовчуванням TCP)port — порт Service (на якому Service слухає)targetPort — порт Pod (на який перенаправляється трафік)ports:
- name: http
port: 80 # Service слухає на порту 80
targetPort: 8080 # Трафік йде на порт 8080 Pod
api-service:80 перенаправляється на <pod-ip>:8080.ClusterIP — внутрішній IP, доступний лише всередині кластера (за замовчуванням)NodePort — експонує Service на статичному порту кожного вузлаLoadBalancer — створює зовнішній load balancer (у хмарних провайдерів)ExternalName — маппінг на зовнішнє DNS-ім'яРозберемо детально, як працює port та targetPort:
Важливі моменти:
port — це порт, на якому Service слухає. Клієнти звертаються до Service на цьому порту.targetPort — це порт, на якому Pod слухає. Service перенаправляє трафік на цей порт.containerPort (у Pod spec) — це порт, який контейнер експонує. Має збігатись з targetPort.Типова помилка новачків:
# Service
ports:
- port: 80
targetPort: 8080
# Pod (у Deployment)
containers:
- name: api
ports:
- containerPort: 80 # ← ПОМИЛКА! Має бути 8080
У цьому випадку Service перенаправляє трафік на порт 8080 Pod, але контейнер слухає на порту 80. Запити не доходять до застосунку.
targetPort у Service має збігатись з containerPort у Pod spec. Інакше трафік не дійде до застосунку.Правильна конфігурація:# Service
spec:
ports:
- port: 80
targetPort: 8080
# Deployment
spec:
template:
spec:
containers:
- name: api
ports:
- containerPort: 8080 # ← Збігається з targetPort
Замість числа можна використовувати ім'я порту:
# Deployment
spec:
template:
spec:
containers:
- name: api
ports:
- name: http # ← Ім'я порту
containerPort: 8080
# Service
spec:
ports:
- port: 80
targetPort: http # ← Посилання на ім'я
Переваги:
Kubernetes підтримує чотири типи Service, кожен для різних сценаріїв.
Тип сервісу, який виділяє для вашого набору Pod'ів єдину внутрішню IP-адресу (ClusterIP), доступну виключно всередині самого Kubernetes-кластера.
ClusterIP. Тепер ваш фронтенд-контейнер, що працює всередині кластера, може спокійно звертатися до бази за адресою postgres-service, тоді як будь-які зовнішні запити з інтернету будуть заблоковані на рівні мережі кластера. Це стандарт де-факто для безпечної внутрішньої взаємодії.YAML:
apiVersion: v1
kind: Service
metadata:
name: api-service
spec:
type: ClusterIP # За замовчуванням, можна не вказувати
selector:
app: api
ports:
- port: 80
targetPort: 8080
Що відбувається:
10.96.0.10)api-service.default.svc.cluster.local → 10.96.0.10Візуалізація:
Коли використовувати:
Коли ви створюєте Service у Kubernetes, вбудована служба DNS (зазвичай CoreDNS) автоматично створює для нього внутрішній DNS-запис. Це дозволяє Pod'ам спілкуватися між собою не за IP-адресами (які постійно змінюються при перезапуску контейнерів), а за постійними, зрозумілими іменами.
Повне доменне ім'я (FQDN) для будь-якого сервісу формується за шаблоном:
[назва-сервісу].[namespace].svc.cluster.local
Для нашого сервісу повна адреса буде: api-service.default.svc.cluster.local.
http://api-service?/etc/resolv.conf) суфікси пошуку. Вони включають поточний namespace (наприклад, default.svc.cluster.local).
Тому, якщо ваші контейнери знаходяться в одному namespace, DNS-клієнт автоматично підставить суфікс. Вам достатньо написати лише коротку назву сервісу — api-service — і CoreDNS успішно розпізнає повну адресу.api-service ми вказали зовнішній порт сервісу port: 80. Оскільки порт 80 є стандартним для протоколу http://, його не потрібно вказувати явно в URL.
Рядок http://api-service під капотом резолвиться в запит до IP-адреси сервісу на 80-й порт (наприклад, http://10.96.0.10:80).Приклад використання у .NET:
var builder = WebApplication.CreateBuilder(args);
// Frontend звертається до API через Service DNS
var apiUrl = builder.Configuration["ApiUrl"] ?? "http://api-service";
builder.Services.AddHttpClient("ApiClient", client =>
{
client.BaseAddress = new Uri(apiUrl);
});
var app = builder.Build();
app.Run();
ConfigMap для frontend:
apiVersion: v1
kind: ConfigMap
metadata:
name: frontend-config
data:
ApiUrl: "http://api-service" # DNS-ім'я Service
Тип сервісу, який відкриває певний фіксований порт (у діапазоні 30000-32767) на абсолютно кожному сервері (вузлі/node) вашого Kubernetes-кластера. Будь-який зовнішній трафік, що приходить на цей порт будь-якого сервера, автоматично перенаправляється на ваші Pod'и.
NodePort та вкажете nodePort: 32000, ваш сайт стане доступним за адресою комп'ютера у локальній мережі: наприклад, http://192.168.1.150:32000. Будь-яка Node кластера візьме цей запит і перенаправить його до потрібного контейнера.YAML:
apiVersion: v1
kind: Service
metadata:
name: api-service
spec:
type: NodePort
selector:
app: api
ports:
- port: 80
targetPort: 8080
nodePort: 30080 # Опціонально, Kubernetes виділить автоматично (30000-32767)
Що відбувається:
30080)<NodeIP>:30080 перенаправляється на Service, який перенаправляє на PodВізуалізація:
Діапазон NodePort:
За замовчуванням Kubernetes виділяє порти з діапазону 30000-32767. Це можна змінити у конфігурації API Server, але не рекомендується.
Коли використовувати:
Коли НЕ використовувати:
Приклад використання:
minikube service api-service
http://<minikube-ip>:<node-port>).Тип сервісу, який інтегрується з хмарним провайдером (наприклад AWS, Google Cloud, Azure) і автоматично замовляє у нього реальний, зовнішній мережевий балансувальник навантаження. Провайдер виділяє публічну статичну IP-адресу, і весь трафік з інтернету через цю IP надходить безпосередньо у ваш Kubernetes-кластер.
LoadBalancer, AWS автоматично запустить для вас сервіс Classic/Network Load Balancer та надасть публічну адресу (наприклад, http://a84729...us-east-1.elb.amazonaws.com або статичний IP 1.2.3.4). Тепер будь-який користувач у світі може зайти на цей сайт. Балансувальник прийме запит, рівномірно розподілить його між працюючими репліками вашого застосунку і забезпечить відмовостійкість: якщо один із серверів (Node) раптово вимкнеться, балансувальник миттєво перенаправить трафік на інші робочі машини.YAML:
apiVersion: v1
kind: Service
metadata:
name: api-service
spec:
type: LoadBalancer
selector:
app: api
ports:
- port: 80
targetPort: 8080
Що відбувається:
Візуалізація:
Коли використовувати:
Коли НЕ використовувати:
Приклад використання:
<pending>. Для локальної розробки використовуйте:minikube tunnel
127.0.0.1/8. Команда має працювати постійно у фоні.Вартість LoadBalancer:
Кожен LoadBalancer Service створює окремий load balancer у хмарі, що коштує грошей:
Якщо у вас 10 сервісів, це $180-250/місяць лише за load balancers. Для економії використовуйте Ingress (один load balancer для багатьох сервісів).
Особливий тип сервісу, який виступає в ролі внутрішнього DNS-аліасу (псевдоніма) і перенаправляє запити на якесь зовнішнє доменне ім'я (поза межами вашого кластера). Він не виділяє жодних IP-адрес і не проксує трафік самостійно — замість цього він просто повертає стандартний DNS CNAME-запис.
prod-db-948a.mongodb.net. Замість того, щоб зашивати це довге і нестабільне ім'я прямо в конфігураційні файли вашого коду, ви створюєте сервіс типу ExternalName із назвою my-database та externalName: prod-db-948a.mongodb.net. Тепер у вашому .NET-коді рядок підключення буде виглядати максимально просто: mongodb://my-database. Якщо ви вирішите змінити базу даних на інший сервер або переїдете на іншого провайдера, ви просто зміните значення externalName в одному YAML-маніфесті, не перезбираючи та не перезапускаючи сам додаток.YAML:
apiVersion: v1
kind: Service
metadata:
name: external-api
spec:
type: ExternalName
externalName: api.example.com
Що відбувається:
external-api.default.svc.cluster.local → api.example.comexternal-api резолвяться у api.example.com та йдуть напряму до зовнішнього сервісуВізуалізація:
Коли використовувати:
database-service, а не до prod-db.us-east-1.rds.amazonaws.comПриклад використання:
# ExternalName для зовнішньої бази даних
apiVersion: v1
kind: Service
metadata:
name: postgres-service
spec:
type: ExternalName
externalName: prod-db.us-east-1.rds.amazonaws.com
У .NET коді:
var builder = WebApplication.CreateBuilder(args);
// Код звертається до Service, а не до зовнішнього DNS
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
// "Host=postgres-service;Database=mydb;Username=user;Password=pass"
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql(connectionString));
Переваги:
externalName у ServiceClusterIP
Використання: Внутрішня комунікація між сервісами
Доступність: Лише всередині кластера
IP-адреса: Внутрішня ClusterIP
Вартість: Безкоштовно
Приклад: API → Database, Frontend → API
NodePort
Використання: Локальна розробка, debugging, on-premise
Доступність: Через <NodeIP>:<NodePort>
IP-адреса: ClusterIP + порт на кожному вузлі
Вартість: Безкоштовно
Приклад: Доступ до API з локальної машини
LoadBalancer
Використання: Production у хмарі, публічний доступ
Доступність: Через публічну IP load balancer
IP-адреса: ClusterIP + NodePort + зовнішня IP
Вартість: ~$18-25/місяць за load balancer
Приклад: Публічний API, веб-сайт
ExternalName
Використання: Доступ до зовнішніх сервісів
Доступність: DNS CNAME до зовнішнього сервісу
IP-адреса: Немає (лише DNS alias)
Вартість: Безкоштовно
Приклад: AWS RDS, зовнішній API
Таблиця порівняння:
| Характеристика | ClusterIP | NodePort | LoadBalancer | ExternalName |
|---|---|---|---|---|
| ClusterIP | ✅ Так | ✅ Так | ✅ Так | ❌ Ні |
| NodePort | ❌ Ні | ✅ Так (30000-32767) | ✅ Так (автоматично) | ❌ Ні |
| Зовнішня IP | ❌ Ні | ❌ Ні | ✅ Так | ❌ Ні |
| Балансування | ✅ Так | ✅ Так | ✅ Так | ❌ Ні |
| Health checks | ✅ Так | ✅ Так | ✅ Так | ❌ Ні |
| Доступ ззовні | ❌ Ні | ✅ Так | ✅ Так | N/A |
| Вартість | Безкоштовно | Безкоштовно | $18-25/міс | Безкоштовно |
Тепер розберемо, як саме Kubernetes перенаправляє трафік від Service до Pod. За це відповідає компонент kube-proxy.
kube-proxy підтримує три режими:
# kube-proxy ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: kube-proxy
namespace: kube-system
data:
config.conf: |
mode: "ipvs"
ipvs:
scheduler: "rr" # round-robin
Давайте подивимося, які саме правила створює kube-proxy:
Що означають ці правила:
Балансування:
Це дає рівномірний розподіл 33.33% / 33.33% / 33.33%.
DNAT правило для Pod:
Це правило змінює destination IP:Port з 10.96.0.10:80 (Service) на 10.244.1.10:8080 (Pod).
Ключові моменти:
Одна з найважливіших можливостей Service — автоматичний service discovery через DNS. За це відповідає CoreDNS.
CoreDNS — це DNS-сервер, який працює у Kubernetes кластері та автоматично створює DNS-записи для всіх Service.
Kubernetes створює DNS-записи у наступному форматі:
<service-name>.<namespace>.svc.<cluster-domain>
Компоненти:
metadata.name)cluster.local)Приклади:
| Service | Namespace | Повне DNS-ім'я |
|---|---|---|
| api-service | default | api-service.default.svc.cluster.local |
| postgres | database | postgres.database.svc.cluster.local |
| redis | cache | redis.cache.svc.cluster.local |
Kubernetes підтримує скорочені форми для зручності:
// Pod у namespace "default" звертається до Service "api-service" у "default"
var apiUrl = "http://api-service";
api-service.default.svc.cluster.local ✅// Pod у namespace "frontend" звертається до Service "postgres" у "database"
var dbHost = "postgres.database";
postgres.database.svc.cluster.local ✅var apiUrl = "http://api-service.default.svc.cluster.local";
Кожен Pod автоматично налаштовується використовувати CoreDNS як DNS-сервер:
Що означають ці рядки:
Як працює search:
Коли Pod робить DNS-запит api-service, резолвер пробує:
api-service.default.svc.cluster.local ✅ (знайдено)api-service.svc.cluster.localapi-service.cluster.localapi-service (як є)Це дозволяє використовувати короткі імена (api-service замість повного DNS-імені).
ClusterIP Service
DNS-запис: A-запис (IP-адреса)
Приклад:
nslookup api-service.default.svc.cluster.local
# Name: api-service.default.svc.cluster.local
# Address: 10.96.0.10
Що повертається: ClusterIP Service
Headless Service (ClusterIP: None)
DNS-запис: A-записи для кожного Pod
Приклад:
nslookup postgres.database.svc.cluster.local
# Name: postgres.database.svc.cluster.local
# Address: 10.244.1.10 (Pod 1)
# Address: 10.244.1.11 (Pod 2)
# Address: 10.244.1.12 (Pod 3)
Що повертається: IP-адреси всіх Pod (детально розглянемо далі)
ExternalName Service
DNS-запис: CNAME-запис
Приклад:
nslookup external-api.default.svc.cluster.local
# external-api.default.svc.cluster.local canonical name = api.example.com
# Name: api.example.com
# Address: 34.56.78.90
Що повертається: CNAME на зовнішнє DNS-ім'я
Давайте протестуємо DNS resolution у реальному кластері:
Headless Service — це Service без ClusterIP (clusterIP: None). Замість балансування навантаження через Service IP, DNS повертає IP-адреси всіх Pod напряму.
StatefulSet
Для stateful застосунків (бази даних, черги) потрібен доступ до конкретного Pod за стабільним DNS-іменем. Headless Service надає DNS-запис для кожного Pod: <pod-name>.<service-name>.<namespace>.svc.cluster.local.
Приклад: PostgreSQL primary/replica — клієнт має звертатись до primary для запису, до replica для читання.
Service Discovery
Застосунок сам хоче керувати балансуванням навантаження або вибором Pod. Headless Service надає список всіх IP-адрес Pod, а застосунок сам вирішує, до якого звертатись.
Приклад: Elasticsearch cluster — клієнт отримує список всіх вузлів та сам розподіляє запити.
Peer Discovery
Застосунки, які потребують знати про всіх інших членів кластера (наприклад, для формування quorum або gossip protocol).
Приклад: Kafka, Cassandra, etcd — кожен вузол має знати про інших.
apiVersion: v1
kind: Service
metadata:
name: postgres-headless
spec:
clusterIP: None # ← Це робить Service headless
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432
Що відбувається:
DNS-записи:
postgres-headless.default.svc.cluster.local
→ 10.244.1.10, 10.244.1.11, 10.244.1.12
postgres-0.postgres-headless.default.svc.cluster.local → 10.244.1.10
postgres-1.postgres-headless.default.svc.cluster.local → 10.244.1.11
postgres-2.postgres-headless.default.svc.cluster.local → 10.244.1.12
Приклад: PostgreSQL primary/replica:
var builder = WebApplication.CreateBuilder(args);
// Primary для запису
var primaryHost = "postgres-0.postgres-headless.default.svc.cluster.local";
var primaryConnectionString = $"Host={primaryHost};Database=mydb;Username=user;Password=pass";
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql(primaryConnectionString));
// Replica для читання
var replicaHost = "postgres-1.postgres-headless.default.svc.cluster.local";
var replicaConnectionString = $"Host={replicaHost};Database=mydb;Username=user;Password=pass";
builder.Services.AddDbContext<ReadOnlyDbContext>(options =>
options.UseNpgsql(replicaConnectionString));
var app = builder.Build();
app.Run();
Приклад: Service Discovery (отримання всіх Pod):
using System.Net;
var serviceName = "postgres-headless.default.svc.cluster.local";
var addresses = await Dns.GetHostAddressesAsync(serviceName);
foreach (var address in addresses)
{
Console.WriteLine($"Pod IP: {address}");
}
// Output:
// Pod IP: 10.244.1.10
// Pod IP: 10.244.1.11
// Pod IP: 10.244.1.12
Endpoints — це об'єкт Kubernetes, який містить список IP:Port всіх Pod, які відповідають селектору Service.
Коли ви створюєте Service, Kubernetes автоматично створює об'єкт Endpoints з тим самим іменем:
Детальна інформація:
Важливі поля:
Endpoints Controller постійно стежить за Pod та автоматично оновлює Endpoints:
Важливо: Лише Pod, які пройшли readiness probe, додаються до Addresses. Pod, які не готові, потрапляють до NotReadyAddresses та не отримують трафік.
Проблема Endpoints: Якщо у Service 1000 Pod, об'єкт Endpoints містить 1000 IP-адрес. При кожній зміні (додавання/видалення Pod) весь об'єкт оновлюється, що створює навантаження на API Server та etcd.
Рішення: EndpointSlices (Kubernetes 1.21+) — розбиває Endpoints на менші частини (slices).
Переваги EndpointSlices:
Перегляд EndpointSlices:
Іноді потрібен Service, який не автоматично вибирає Pod за селектором. Наприклад:
Рішення: Створити Service без selector та вручну створити Endpoints.
Service без selector:
apiVersion: v1
kind: Service
metadata:
name: external-postgres
spec:
# Немає selector!
ports:
- port: 5432
targetPort: 5432
Endpoints (створюємо вручну):
apiVersion: v1
kind: Endpoints
metadata:
name: external-postgres # Має збігатись з іменем Service
subsets:
- addresses:
- ip: 192.168.1.100 # IP зовнішньої БД
ports:
- port: 5432
Що відбувається:
10.96.0.50)external-postgres.default.svc.cluster.local → 10.96.0.5010.96.0.50:5432 перенаправляється на 192.168.1.100:5432Візуалізація:
Коли використовувати:
Переваги:
Тепер створимо повний приклад з TodoApi та різними типами Service.
postgres-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16
ports:
- containerPort: 5432
env:
- name: POSTGRES_DB
value: tododb
- name: POSTGRES_USER
value: todouser
- name: POSTGRES_PASSWORD
value: todopass
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
volumes:
- name: postgres-storage
emptyDir: {}
postgres-service.yaml:
apiVersion: v1
kind: Service
metadata:
name: postgres-service
spec:
type: ClusterIP # За замовчуванням, можна не вказувати
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432
api-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: todoapi
spec:
replicas: 3
selector:
matchLabels:
app: todoapi
template:
metadata:
labels:
app: todoapi
spec:
containers:
- name: todoapi
image: todoapi:1.0.0
ports:
- containerPort: 8080
env:
- name: ASPNETCORE_ENVIRONMENT
value: "Production"
- name: ASPNETCORE_URLS
value: "http://+:8080"
- name: ConnectionStrings__DefaultConnection
value: "Host=postgres-service;Database=tododb;Username=todouser;Password=todopass"
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
api-service.yaml:
apiVersion: v1
kind: Service
metadata:
name: api-service
spec:
type: ClusterIP
selector:
app: todoapi
ports:
- name: http
port: 80
targetPort: 8080
frontend-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
replicas: 2
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/conf.d
volumes:
- name: nginx-config
configMap:
name: nginx-config
nginx-configmap.yaml:
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-config
data:
default.conf: |
upstream api {
server api-service:80;
}
server {
listen 80;
location /api/ {
proxy_pass http://api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
}
frontend-service.yaml (для production з LoadBalancer):
apiVersion: v1
kind: Service
metadata:
name: frontend-service
spec:
type: LoadBalancer
selector:
app: frontend
ports:
- port: 80
targetPort: 80
frontend-service-nodeport.yaml (для Minikube):
apiVersion: v1
kind: Service
metadata:
name: frontend-service
spec:
type: NodePort
selector:
app: frontend
ports:
- port: 80
targetPort: 80
nodePort: 30080 # Доступ через http://<minikube-ip>:30080
Тепер виконайте завдання для закріплення знань про Service та мережі у Kubernetes.
Мета: Зрозуміти різницю між ClusterIP, NodePort та LoadBalancer.
Завдання:
Очікуваний результат: Ви зрозумієте, коли використовувати кожен тип Service.
nginx-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-test
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
clusterip-service.yaml:
apiVersion: v1
kind: Service
metadata:
name: nginx-clusterip
spec:
type: ClusterIP
selector:
app: nginx
ports:
- port: 80
targetPort: 80
nodeport-service.yaml:
apiVersion: v1
kind: Service
metadata:
name: nginx-nodeport
spec:
type: NodePort
selector:
app: nginx
ports:
- port: 80
targetPort: 80
nodePort: 30080
loadbalancer-service.yaml:
apiVersion: v1
kind: Service
metadata:
name: nginx-loadbalancer
spec:
type: LoadBalancer
selector:
app: nginx
ports:
- port: 80
targetPort: 80
Команди:
# Створення
kubectl apply -f nginx-deployment.yaml
kubectl apply -f clusterip-service.yaml
kubectl apply -f nodeport-service.yaml
kubectl apply -f loadbalancer-service.yaml
# Перегляд Service
kubectl get services
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
# nginx-clusterip ClusterIP 10.96.0.10 <none> 80/TCP
# nginx-nodeport NodePort 10.96.0.20 <none> 80:30080/TCP
# nginx-loadbalancer LoadBalancer 10.96.0.30 <pending> 80/TCP
# Тест 1: ClusterIP (з іншого Pod)
kubectl run test-pod --image=curlimages/curl --rm -it --restart=Never -- \
curl http://nginx-clusterip
# Працює ✅
# Тест 2: NodePort (з локальної машини)
curl http://$(minikube ip):30080
# Працює ✅
# Тест 3: LoadBalancer (для Minikube потрібен tunnel)
minikube tunnel # У окремому терміналі
kubectl get service nginx-loadbalancer
# EXTERNAL-IP тепер має значення (наприклад, 127.0.0.1)
curl http://127.0.0.1
# Працює ✅
# Очищення
kubectl delete deployment nginx-test
kubectl delete service nginx-clusterip nginx-nodeport nginx-loadbalancer
Мета: Навчитись використовувати DNS для service discovery.
Завдання:
api у namespace backendfrontend у namespace frontendapi.backend)api.backend.svc.cluster.local)Очікуваний результат: Ви зрозумієте, як працює DNS resolution між namespace.
Команди:
# Створення namespace
kubectl create namespace backend
kubectl create namespace frontend
# API у namespace backend
kubectl create deployment api --image=nginx:1.27 --replicas=2 -n backend
kubectl expose deployment api --port=80 --target-port=80 -n backend
# Frontend у namespace frontend
kubectl create deployment frontend --image=nginx:1.27 --replicas=2 -n frontend
# Тестування DNS з frontend Pod
kubectl run test-dns -n frontend --image=curlimages/curl --rm -it --restart=Never -- sh
# Тест 1: Коротка форма (не працює)
$ nslookup api
# Server: 10.96.0.10
# ** server can't find api: NXDOMAIN
# ❌ Не працює — різні namespace
# Тест 2: З namespace
$ nslookup api.backend
# Name: api.backend.svc.cluster.local
# Address: 10.96.0.50
# ✅ Працює
# Тест 3: Повна форма
$ nslookup api.backend.svc.cluster.local
# Name: api.backend.svc.cluster.local
# Address: 10.96.0.50
# ✅ Працює
# Тест 4: HTTP-запит
$ curl http://api.backend
# <!DOCTYPE html>...
# ✅ Працює
# Очищення
kubectl delete namespace backend frontend
Мета: Навчитись використовувати Headless Service для доступу до конкретних Pod.
Завдання:
clusterIP: None)Очікуваний результат: Ви зрозумієте, як Headless Service надає стабільні DNS-імена для Pod.
nginx-statefulset.yaml:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx
spec:
serviceName: nginx-headless
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
nginx-headless-service.yaml:
apiVersion: v1
kind: Service
metadata:
name: nginx-headless
spec:
clusterIP: None # Headless Service
selector:
app: nginx
ports:
- port: 80
targetPort: 80
Команди:
# Створення
kubectl apply -f nginx-headless-service.yaml
kubectl apply -f nginx-statefulset.yaml
# Очікування готовності
kubectl wait --for=condition=ready pod -l app=nginx --timeout=60s
# Перегляд Pod
kubectl get pods -l app=nginx
# NAME READY STATUS RESTARTS AGE
# nginx-0 1/1 Running 0 30s
# nginx-1 1/1 Running 0 25s
# nginx-2 1/1 Running 0 20s
# Тестування DNS
kubectl run test-dns --image=curlimages/curl --rm -it --restart=Never -- sh
# Тест 1: Service DNS (повертає всі Pod)
$ nslookup nginx-headless.default.svc.cluster.local
# Name: nginx-headless.default.svc.cluster.local
# Address: 10.244.1.10 (nginx-0)
# Address: 10.244.1.11 (nginx-1)
# Address: 10.244.1.12 (nginx-2)
# Тест 2: Pod DNS (конкретний Pod)
$ nslookup nginx-0.nginx-headless.default.svc.cluster.local
# Name: nginx-0.nginx-headless.default.svc.cluster.local
# Address: 10.244.1.10
$ nslookup nginx-1.nginx-headless.default.svc.cluster.local
# Name: nginx-1.nginx-headless.default.svc.cluster.local
# Address: 10.244.1.11
# Тест 3: HTTP-запит до конкретного Pod
$ curl http://nginx-0.nginx-headless
# <!DOCTYPE html>...
# ✅ Працює
$ curl http://nginx-1.nginx-headless
# <!DOCTYPE html>...
# ✅ Працює
# Очищення
kubectl delete statefulset nginx
kubectl delete service nginx-headless
Мета: Навчитись створювати Service для зовнішніх ресурсів.
Завдання:
8.8.8.8 — Google DNS для тесту)Очікуваний результат: Ви навчитесь інтегрувати зовнішні сервіси у Kubernetes через Service.
external-service.yaml:
apiVersion: v1
kind: Service
metadata:
name: external-dns
spec:
# Немає selector!
ports:
- port: 53
targetPort: 53
protocol: UDP
external-endpoints.yaml:
apiVersion: v1
kind: Endpoints
metadata:
name: external-dns # Має збігатись з іменем Service
subsets:
- addresses:
- ip: 8.8.8.8 # Google DNS
ports:
- port: 53
protocol: UDP
Команди:
# Створення
kubectl apply -f external-service.yaml
kubectl apply -f external-endpoints.yaml
# Перегляд Service та Endpoints
kubectl get service external-dns
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
# external-dns ClusterIP 10.96.0.100 <none> 53/UDP
kubectl get endpoints external-dns
# NAME ENDPOINTS AGE
# external-dns 8.8.8.8:53 10s
# Тестування
kubectl run test-dns --image=curlimages/curl --rm -it --restart=Never -- sh
# DNS resolution
$ nslookup external-dns.default.svc.cluster.local
# Name: external-dns.default.svc.cluster.local
# Address: 10.96.0.100
# Тест DNS-запиту через Service (перенаправляється на 8.8.8.8)
$ nslookup google.com external-dns.default.svc.cluster.local
# Server: external-dns.default.svc.cluster.local
# Address: 10.96.0.100:53
#
# Name: google.com
# Address: 142.250.185.46
# ✅ Працює — запит пішов на 8.8.8.8 через Service
# Очищення
kubectl delete service external-dns
kubectl delete endpoints external-dns
Практичне застосування:
Замість 8.8.8.8 використовуйте IP вашої зовнішньої бази даних:
apiVersion: v1
kind: Endpoints
metadata:
name: external-postgres
subsets:
- addresses:
- ip: 192.168.1.100 # IP вашої БД
ports:
- port: 5432
Тепер код може використовувати external-postgres як звичайний Service:
var connectionString = "Host=external-postgres;Database=mydb;Username=user;Password=pass";
У цій статті ми детально вивчили Service — мережеву абстракцію для Pod у Kubernetes. Ось що ми розглянули:
Проблема ефемерних IP-адрес
Що таке Service
Типи Service
kube-proxy та iptables
CoreDNS та Service Discovery
Headless Service
Endpoints та EndpointSlices
Service без selector
Практичний приклад
Практичні завдання
Ви вивчили основи Service та мережі у Kubernetes. Наступні теми для поглибленого вивчення:
Для швидкого доступу — всі команди для роботи з Service:
# Створення Service з YAML
kubectl apply -f service.yaml
# Створення Service з kubectl expose
kubectl expose deployment <name> --port=80 --target-port=8080
# Перегляд Service
kubectl get services
kubectl get svc # скорочена форма
# Детальна інформація
kubectl describe service <name>
# Перегляд у форматі YAML
kubectl get service <name> -o yaml
# Перегляд Endpoints
kubectl get endpoints <service-name>
# Детальна інформація
kubectl describe endpoints <service-name>
# Перегляд EndpointSlices
kubectl get endpointslices -l kubernetes.io/service-name=<name>
# Запуск тестового Pod з curl та nslookup
kubectl run test-dns --image=curlimages/curl --rm -it --restart=Never -- sh
# DNS resolution
nslookup <service-name>
nslookup <service-name>.<namespace>
nslookup <service-name>.<namespace>.svc.cluster.local
# HTTP-запит через DNS
curl http://<service-name>
curl http://<service-name>.<namespace>
# Перегляд логів CoreDNS
kubectl logs -n kube-system -l k8s-app=kube-dns
# Перегляд конфігурації kube-proxy
kubectl get configmap kube-proxy -n kube-system -o yaml
# Перегляд iptables правил (на вузлі)
sudo iptables -t nat -L KUBE-SERVICES
# Port-forward до Service
kubectl port-forward service/<name> 8080:80
Попередня стаття: Rolling Updates та управління життєвим циклом Deployment
Наступна стаття: ConfigMap та Secret — управління конфігурацією
Rolling Updates та управління життєвим циклом Deployment
Оновлення застосунків без downtime — від теорії до практики з детальною візуалізацією, математичними розрахунками та реальними прикладами
1. Аналіз предметної області. Експертні знання та складність
В 1986 році Фред Брукс, автор книги «Міфічний людино-місяць», опублікував статтю під назвою "No Silver Bullet — Essence and Accident in Software Engineering".