Робота з сервісами
Кожен под у Kubernetes має свою унікальну IP-адресу в межах кластера, яка дозволяє йому взаємодіяти з іншими подами. Але такий підхід не є дуже зручним, бо тоді потрібно знати точні IP-адреси всіх подів. Тут у нагоді нам стануть сервіси, які розв'язують задачу взаємодії подів, а також зовнішнього світу з подами.
DNS-ім'я сервісу та поду
Щоб використовувати сервіси, у його маніфесті потрібно вказати, на які поди він має балансувати трафік. Далі вся комунікація з цими подами буде відбуватись не по IP-адресі подів, а по DNS-імені сервісу. Кожен сервіс у Kubernetes має унікальне DNS-ім'я, що дозволяє подам легко взаємодіяти один з одним. Ці імена створюються в Kubernetes автоматично при створенні сервісу. Доменним іменем верхнього рівня за замовчуванням є cluster.local.
DNS-ім'я сервісу має формат ім'я сервісу, простір імен (namespace), тип ресурсу (у цьому випадку це сервіс), і далі доменне ім'я Kubernetes :
А DNS-ім'я пода має формат IP поду, простір імен (namespace), тип ресурсу (тепер це под), і далі доменне ім'я Kubernetes :
💡 Знати DNS-ім'я пода не дуже корисно, оскільки поди можуть бути перезапущені або переміщені між вузлами. З цієї причини більшість взаємодій відбувається через сервіси, які надають стабільний ендпоінт для доступу до групи подів.
Типи сервісів
Існують такі типи сервісів ⬇️
ClusterIP
Цей тип створює віртуальну внутрішню IP-адресу всередині кластера, яку можна використовувати для з'єднання з подами. Ця адреса доступна лише зсередини. ClusterIP може бути корисним для забезпечення комунікації між різними сервісами або компонентами додатка, які працюють у межах одного кластера.
Завдяки ClusterIP поди мають стабільний та надійний спосіб для звернення до інших сервісів без необхідності відстежувати зміни IP-адрес. Це зменшує складність мережевої конфігурації та збільшує надійність взаємодії сервісів. Цей тип також забезпечує ізоляцію мережевого трафіку всередині кластера, що є важливим з погляду безпеки. Таким чином внутрішні сервіси залишаються захищеними від небажаного доступу ззовні, що допомагає управляти доступом до чутливих компонентів.
Розглянемо приклад. У тебе є вебдодаток, розгорнутий у Kubernetes, який включає фронтенд (наприклад, React) та бекенд (наприклад, RESTful API на Node.js). Бекенд потребує бази даних, яка також розгорнута в Kubernetes окремими подами. Проблема: потрібно, щоб фронтенд міг взаємодіяти з бекендом, але ти не хочеш, щоб база даних була доступна ззовні кластера. Рішенням буде використати ClusterIP-сервіси для бекенду та бази даних. Оскільки ClusterIP не доступний ззовні кластера, це забезпечить безпечну внутрішню взаємодію компонентів.
NodePort
Цей сервіс розширює можливості ClusterIP, дозволяючи доступ до сервісів ззовні кластера. При створенні NodePort, Kubernetes автоматично виділяє порт у певному діапазоні портів (30000 — 32767 ) на кожному вузлі (node) кластера. Після цього, викликаючи ІР ноди та вказуючи вибраний порт, ми можемо отримати доступ до сервісу та його подів. Тобто коли зовнішній запит надходить на виділений NodePort на будь-якому з вузлів кластера, Kubernetes маршрутизує цей запит до відповідного пода. А оскільки NodePort містить функціонал ClusterIP, то сервіс також доступний і всередині кластера.
NodePort часто використовується для надання доступу до вебдодатків, розгорнутих у кластері з зовнішнього інтернету. Та найчастіше NodePort корисний під час тестування та розробки, коли потрібен швидкий та легкий доступ до додатків із зовнішніх мереж.
У NodePort є і недоліки. Порти NodePort обмежені діапазоном 30000 — 32767 , що може бути обмеженням у деяких випадках. Також використання NodePort не завжди є безпечним, оскільки він відкриває доступ до додатків на всіх вузлах кластера.
Load Balancer
Цей тип сервісу розширює функціонал NodePort та ClusterIP та дозволяє більш ефективне розподілення вхідного трафіку між подами. Load Balancer забезпечує інтеграцію з балансувальниками навантаження, які надаються хмарними провайдерами, такими як AWS, Google Cloud, Azure тощо. У результаті виходить більш ефективне управління навантаженням та краща доступність і надійність сервісу. Сервіс LoadBalancer забезпечує доступність і через зовнішній балансувальник навантаження, і через внутрішні механізми маршрутизації Kubernetes.
Розглянемо приклад. Коли ти створюєш сервіс типу LoadBalancer у Kubernetes, кластер автоматично взаємодіє з хмарним провайдером для створення зовнішнього балансувальника навантаження. Цей балансувальник отримує власну публічну IP-адресу, яку можна використовувати для доступу до сервісів ззовні кластера. LoadBalancer автоматично розподіляє вхідний трафік між подами, які належать до сервісу. Це забезпечує більш ефективне управління навантаженням і покращує загальну доступність і надійність сервісу.
ExternalName
На відміну від інших сервісів, ExternalName не налаштовує IP-адреси та не балансує навантаження. Замість цього він створює DNS-ім’я для зовнішнього сервісу. ExternalName не має IP-адреси, асоційованої з сервісом у Kubernetes, а просто використовує DNS-ім'я.
Якщо кластер потребує взаємодії із зовнішнім API, ти можеш використовувати ExternalName, щоб створити легкодоступний DNS-псевдонім для цього API всередині кластера. Але варто подумати про безпеку, оскільки зловмисники можуть використовувати DNS-маніпуляції для перенаправлення трафіку.
Далі ми створимо ClusterIP, NodePort та ExternalName, а до LoadBalancer ми повернемось далі у курсі.
Створення сервісу ClusterIP
У попередньому топіку ми написали маніфест для нашого додатка на Python, а тепер додамо йому ClusterIP. Цей сервіс створить статичну IP-адресу всередині кластера, яка буде доступна лише всередині самого кластера. Також сервіс отримає DNS-імʼя всередині кластера. ClusterIP нам потрібний, щоб балансувати навантаження на декілька подів і щоб мати стабільну комунікацію з подами.
Перед тим як ми напишемо маніфест сервісу, створимо ще один под додатка. Для цього відкриємо маніфест пода та змінимо назву пода з kube2py на kube2py-1. Потім виконаємо команду для створення пода:
Перевіримо статуси подів:
Тепер вкажемо неймспейс, який буде використовуватися за замовчуванням:
Ми модифікували контекст — конфігурацію, яка описує як конектитись до конкретного кластера. Контекстів може бути багато і між ними можна перемикатись. Та поки ми просто змінили поточний контекст і вказали там неймспейс за замовчуванням.
Тепер перевіримо поди:
Та логи:
Далі створимо сервіс, який дозволить нам балансувати трафік між цими двома подами та через який ми зможемо звертатись до додатка. У Visual Studio Code створи новий файл clusterIp.yml та вкажи версію API і тип об'єкта:
Далі вкажемо назву і неймспейс. За цим імʼям ми зможемо звернутися до сервісу всередині кластера:
Далі йде специфікація — селектор і мепінг портів. Завдяки селектору сервіс визначає, якими подами він має опікуватись. У нашому випадку ми вказуємо поди з лейблом app і значенням kube2py:
Далі додамо мепінг портів: протокол — TCP; порт, на якому буде слухат сам сервіс — 80, порт на подах, на який треба цей трафік переспрямомувати — 8080:
Наостанок вказуємо тип сервісу:
Тепер передамо цей маніфест на Kube-API Server для створення:
Подивимось на цей сервіс у кластері:
kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube2py-service ClusterIP 10.102.81.235 <none> 80/TCP 37s
Або:
kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube2py-service ClusterIP 10.102.81.235 <none> 80/TCP 32s
Якщо виконати команду kubectl get services -o wide, то ми зможемо побачити і селектор:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kube2py-service ClusterIP 10.102.81.235 <none> 80/TCP 108s app=kube2py
Тепер нам треба протестувати чи все працює, і зробимо ми це двома способами. Почнемо вже зі знайомого нам порт форварду, тільки цього разу змінимо тип:
Відкриємо браузер:

Другий спосіб: підʼєднаємось до пода з busybox-контейнером і виконаємо інтерактивну shell-команду:
Тепер виконаємо HTTP GET виклик за допомогою інструмента CURL:
curl http://kube2py-service.mateapp.svc.cluster.local
Docker is Awesome!
<pre> ## .</pre>
<pre> ## ## ## ==</pre>
<pre> ## ## ## ## ===</pre>
<pre> /""""""""""""""""\___/ ===</pre>
<pre> ~~~ (~~ ~~~~ ~~~ ~~~~ ~~ ~ / ===-- ~~~</pre>
<pre> \______ o __/</pre>
<pre> \ \ __/</pre>
<pre> \____\______/</pre>
Спробуємо знайти ці виклики у логах подів:


Можна побачити, що трафік дійсно балансується.
Створення сервісу NodePort
Тепер створимо і протестуємо сервіс типу NodePort, який зробить наші додатки доступними ззовні кластера Kubernetes, використовуючи IP-адресу кожного вузла на певному порті. Створимо новий файл маніфесту в Visual Studio Code, назвемо його nodeport.yml. У цьому файлі ми опишемо наш сервіс:
| Kubernetes service NodePort | |
|---|---|
- Оскільки NodePort створює і ClusterIP то цей параметр потрібен для внутрішньої взаємодії
- Це порт на який перенаправляється в в контейнері додатку
- А це порт для зовнішнього доступу
Ми вказали тип сервісу, вибрали селектор для визначення подів, які будуть обслуговуватися цим сервісом, і визначили мепінг портів. nodePort — це порт, доступний на кожному вузлі кластера, через який можна звертатися до нашого додатка. Застосуємо наш маніфест до кластера за допомогою команди:
Перевіримо створений сервіс:
Тепер наш додаток доступний ззовні кластера на порту, який ми вказали у nodePort. Це означає, що до додатка можна звернутись, використавши IP-адресу будь-якого вузла кластера.
Для тестування відкриємо браузер і введемо адресу у вигляді http://<NodeIP>:<NodePort>, замінивши <NodeIP> на реальну IP-адресу одного з вузлів кластера і <NodePort> на реальний порт. У нашому випадку локальний кластер не має публічно доступних IP-адрес, тому потрібно використовувати http://localhost:<NodePort>.
Цей метод легко надає доступ до додатків для зовнішніх користувачів, хоча для production-середовища рекомендується використовувати складніші методи, такі як Ingress (розглянемо далі у курсі).
Створення сервісу ExternalName
Сервіс ExternalName у Kubernetes дозволяє мапити сервіс у кластері на DNS-ім'я зовнішнього сервісу. Це зручно, коли потрібно надати подам у кластері доступ до зовнішнього сервісу та локальне ім'я для сервісу. Використання ExternalName спрощує конфігурацію, оскільки не потрібно вказувати зовнішні адреси у додатку.
Практичну реалізацію ми почнемо з модифікації додатка Python. Додамо нову залежність:
import requests
Далі допишемо нову функцію (external-call-handler) яка буде обробляти зовнішній запит (/external-call). Ця функція зчитує значення зовнішнього ендпоінта зі змінної середовища. Якщо значення у змінній середовища є, то робимо HTTP GET запит на цей URL, а якщо немає — повертаємо статус 500:
Додамо встановлення нової залежності в Dockerfile:
Тепер напишемо маніфест нового сервісу:
| Creating ExternalName | |
|---|---|
Додамо змінну оточення зі значенням API, яке треба викликати — httpbin-api. За допомогою цієї змінної ми будемо робити запити на сайт httpbin. Також вкажемо неймспейс mateapp, тип ExternalService, DNS-імʼя зовнішнього ресурсу, до якого ми будемо звертатись через цей сервіс — externalName: httpbin.org:
Тепер до тестування! Створимо новий імедж:
Зробимо push у репозиторій:
Поміняємо версію імеджа у маніфесті пода. Через те, що ми об'єднали опис двох подів у один маніфест (так можна робити, тільки не забудь розділити описи різних ресурсів трьома дефісами ), то міняємо версію імеджа в обох подах:
Створимо новий сервіс
Видалимо старі поди:
Виконаємо команду apply:
Для тестування можна використати NodePort Service, який створили раніше. Тож перейдемо за адресою http://localhost:30007/:

А тепер спробуємо виклик на зовнішній сервіс http://localhost:30007/external-call:

І у нас частково завантажився сам сайт httpbin.org