Розбираємо Kubernetes  зрозумілою мовою

💡 Усі статті, обговорення, новини про Front-end — в одному місці. Приєднуйтесь до Front-end спільноти!

Якщо ви чули слова «Kubernetes», «кластер», «pod», але далі цих слів справа не заходила  —  ця стаття саме для вас. Тут я спробую пояснити простими словами, що таке Kubernetes, навіщо він взагалі потрібен, і як з ним працювати на базовому рівні.

Без складних слів, тільки прості приклади й зрозумілі коментарі.

Що таке Kubernetes

Kubernetes (або просто K8s) — це система, яка дозволяє запускати і керувати контейнерами на великій кількості машин.

Контейнер — це спосіб запакувати ваш застосунок разом з усіма його залежностями, щоб він працював однаково в будь-якому середовищі. Найпопулярніший це Docker.

Але не хвилюйтесь: спершу це виглядає складно, але по факту все зводиться до кількох YAML-файлів, у яких ви описуєте, що хочете отримати  —  а Kubernetes вже сам вирішує, як це виконати.

Основні поняття

Pod  —  це те, що Kubernetes реально запускає. Це «обгортка» навколо одного або кількох контейнерів (Docker).

apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  containers:
    - name: my-app
      image: nginx:latest
      ports:
        - containerPort: 80

Що тут відбувається:

  1. apiVersion: v1 — вказує, що ми використовуємо першу версію API Kubernetes для опису ресурсу.
  2. kind: Pod — ми створюємо Pod (найменшу одиницю в Kubernetes, яка може запускатися). Pod може містити один або кілька контейнерів, які працюють разом.
  3. metadata.name: my-pod — задаємо ім’я Pod-у. Потім це ім’я можна буде використати для пошуку чи управління цим Pod-ом.
  4. spec — описує, що саме всередині Pod-а.
  5. containers — список контейнерів, які будуть запущені в Pod.
    • name: my-app ім’я контейнера.
    • image: nginx:latest образ контейнера, який буде запущено.
      • nginx популярний веб-сервер.
      • latest остання доступна версія.
      • Образ буде завантажено з Docker Hub.
    • containerPort: 80 порт, на якому контейнер слухає вхідні запити (всередині контейнера).

Service — доступ до вашого pod’а

У Kubernetes Pod-и мають тимчасові IP-адреси. Якщо Pod перезапуститься, його IP зміниться. Це проблема, бо інші контейнери з додатками або юзери не зможуть знайти його за старою адресою.

Service вирішує цю проблему: він дає постійну адресу (ім’я в DNS або IP), яка завжди вказує на потрібні Pod-и, навіть якщо ті перезапускаються.

Тому нам потрібно створити  Service:

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: my-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

Що тут важливо:

selector — вказуємо, що Service повинен знаходити й обслуговувати тільки ті Pod-и, у яких є тег my-app.

ports — Service слухає звернення зовні.

targetPort — порт усередині Pod-а, на який Service переадресує запит.

У нашому випадку це 80, бо Nginx працює саме на ньому.

Deployment  — автоматичне створення pod’ів

Якщо створювати Pod-и вручну, то у разі збою чи оновлення вам доведеться самому їх видаляти, перезапускати та стежити, щоб вони були в потрібній кількості.

Deployment це такий собі «менеджер» для Pod-ів. Ви описуєте, скільки і яких Pod-ів вам потрібно, а Kubernetes сам їх створює, стежить, щоб їх кількість відповідала тому, що ми прописали в конфігах, та оновлює їх без простоїв (по одному чи партіями, щоб сервіс не зупинявся).

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: my-app
  template: 
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: my-app
          image: nginx:latest
          ports:
            - containerPort: 80

Що тут головне:

  • replicas: 2 — означає, що ми хочемо два однакових Pod-и.
    Якщо один зламається, Kubernetes автоматично створить новий, щоб знову було два.
  • selector та template:
    • selector каже: «цей Deployment керує лише Pod-ами з лейблом my-app».
    • template — опис шаблону Pod-а, який Kubernetes створюватиме.
      Тут ми вказали контейнер Nginx, який слухає порт 80.

ConfigMap  —  змінні конфігурації

Іноді в застосунку треба вказати налаштування — наприклад, у якому режимі він працює (production, development) або до якого API підключається. Якщо ці значення захардкодити, то для зміни конфігурації доведеться збирати новий контейнер.

ConfigMap дозволяє винести ці налаштування назовні — у спеціальний об’єкт Kubernetes.

apiVersion: v1
kind: ConfigMap
metadata:
  name: my-config
data:
  APP_MODE: "production"
  API_URL: "https://api.example.com"
  • APP_MODE — приклад змінної, яка може визначати режим роботи застосунку.
  • API_URL — адреса API, до якого звертається застосунок.

А потім додати ці значення в контейнер:

envFrom:
  - configMapRef:
      name: my-config

Це скаже Kubernetes: «Візьми всі ключі з ConfigMap my-config і зроби їх змінними в контейнері». Тобто всередині контейнера можна буде звернутися до них, як до звичайних environment variables ($APP_MODE, $API_URL).

Secret — зберігаємо паролі

Чому не можна використовувати ConfigMap для паролів? ConfigMap зберігає дані у відкритому вигляді (plain text). Будь-хто, хто має доступ до кластеру, зможе побачити ці значення. Якщо нам треба зберігати конфіденційну інформацію, ті ж логіни, паролі або API-ключі, то для цього використовують зазвичай Secret.

Secret — це спеціальний тип об’єкта Kubernetes для зберігання більш чутливих даних.

Він не шифрує їх «по-справжньому», але зберігає у base64. Для повноцінного захисту треба ще додатково налаштовувати шифрування в Kubernetes (Encryption at Rest) або інтегруватися з одним із секрет-менеджерів (HashiCorp Vault, AWS Secrets Manager тощо).

apiVersion: v1
kind: Secret
metadata:
  name: my-secret
type: Opaque
data:
  DB_USER: dXNlcg==       # user (base64)
  DB_PASS: cGFzc3dvcmQ=   # password (base64)
  • DB_USER і DB_PASS — це ключі, які збережені у форматі base64.

Як можна передати Secret у контейнер?

envFrom:
  - secretRef:
      name: my-secret

Після цього $DB_USER та $DB_PASS будуть доступні як звичайні environment variables в середині контейнера.

Volume  —  збереження даних

Контейнери  —  тимчасові: якщо контейнер впаде або перезапуститься, усі дані всередині зникнуть.

Щоб цього уникнути (наприклад, для збереження логів або файлів), використовують volume — він зберігає дані незалежно від стану контейнера.

volumes:
  - name: my-volume
    emptyDir: {}      # тимчасове сховище

Якщо треба зберігати дані навіть після видалення Pod-а, використовують PersistentVolume (PV) це постійне сховище, яке може виступати або локальним диском, або хмарним (AWS EBS, GCP Persistent Disk, Azure Disk).

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  • storage: 1Gi — просимо 1 гігабайт пам’яті.
  • ReadWriteOnce — цей том можна підключити для читання/запису лише до одного вузла одночасно.

Як прикріпити volume до контейнера:

spec:
  containers:
    - name: my-app
      image: nginx
      volumeMounts:
        - mountPath: /data
          name: my-volume
  volumes:
    - name: my-volume
      persistentVolumeClaim:
        claimName: my-pvc
  • mountPath це шлях у контейнері, де будуть доступні файли з volume.
  • persistentVolumeClaim.claimName — назва PVC, який ми створили.

Ingress  —  доступ із зовнішнього світу

У Kubernetes кожен Pod має внутрішню IP-адресу, але вони недоступні напряму ззовні. Навіть якщо ми створили Service, він за замовчуванням доступний лише всередині кластера.

Але якщо ми хочемо, щоб користувач зміг зайти на наш сайт — наприклад, myapp.com, нам потрібно щось більше. Тут вже з’являється Ingress.

Це просто набір правил, який описує:

  • як маршрутизувати HTTP(S)-запити на конкретні сервіси;
  • які хости обробляти (myapp.com, admin.myapp.com);
  • які шляхи куди направляти (/login → один сервіс, /api → інший);
  • і навіть HTTPS/TLS, редіректи, обробку помилок тощо.

Але Ingress сам нічого не робить 😅

Ingress це лише конфіг. Щоб він реально працював, у кластері має бути встановлений Ingress Controller — це спеціальний сервіс, який «слухає» ці правила і обробляє трафік.

Як це виглядає у схемі?

[Клієнт] --> [Ingress Controller] --> [Service] --> [Pod] --> [Контейнери]

Простий приклад Ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-ingress
spec:
  rules:
    - host: myapp.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-app-service
                port:
                  number: 80

Це означає:

  • Якщо користувач заходить на myapp.com (і DNS цього домену вказує на IP Ingress Controller), то:
  • Запит потрапляє на Service my-app-service.
  • Service перенаправляє його в потрібний Pod на порт 80.

Ingress Controller  —  без нього нічого не працює

Kubernetes не має вбудованого Ingress Controller  —  його треба встановити окремо.

Найпопулярніші варіанти:

Як встановити NGINX Ingress Controller

helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm install my-app-ingress ingress-nginx/ingress-nginx

Після цього у вас зʼявиться:

  • Deployment з Ingress Controller-ом.
  • Сервіс, через який він доступний ззовні — типу LoadBalancer або NodePort (залежить від середовища).

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

NodePort  —  відкриває порт на одному або кількох серверах (їх у Kubernetes називають нодами). До сервісу можна звернутися через IP-адресу такого сервера та номер порту.

LoadBalancer  —  працює лише в хмарі. Kubernetes просить у хмарного провайдера зовнішню IP-адресу та підключає її до сервісу автоматично.

У локальних середовищах, таких як Minikube, LoadBalancer зазвичай недоступний, тому використовується NodePort.

Багато хто плутає і думає, що достатньо просто створити Ingress, але Ingress не працює без контролера. Це як правила без поліції.

Helm — як npm для Kubernetes

YAML-файли — це круто, але коли їх багато, важко щось змінити. Тут приходить на допомогу Helm — пакетний менеджер для Kubernetes. Він дозволяє:

  • встановлювати готові «чарти» (наприклад, Redis, PostgreSQL, Nginx);
  • створювати свої шаблони з параметрами;
  • автоматизувати деплой.

Як встановити Helm

curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
sudo apt-get install apt-transport-https --yes
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
sudo apt-get update
sudo apt-get install helm

Пошук готових чартів

helm repo add bitnami https://charts.bitnami.com/bitnami
helm search repo nginx

Встановити nginx за одну команду

helm install my-nginx bitnami/nginx

Kubernetes сам створить pod-и, service-и, secrets і volume-и для nginx.

Як побачити, що він працює?

kubectl get pods -n my-nginx

А як створити свій Helm chart?

helm create my-app

І отримаємо структуру:

my-app/
  ├── charts/         # залежності
  ├── templates/      # YAML-шаблони
  ├── values.yaml     # параметри за замовчуванням

Приклад шаблону

У Helm Chart’ах шаблони пишуться з використанням {{ ... }}  —  це схоже на змінні, які підставляються з values.yaml.

Наприклад, у файлі templates/deployment.yaml може бути так:

# templates/deployment.yaml
spec:
  replicas: {{ .Values.replicaCount }}

А в values.yaml:

replicaCount: 2

Тут .Values.replicaCount означає: «підстав значення replicaCount з values.yaml».

Коли ми запускаємо:

helm install my-app ./my-app

Helm:

  1. читає всі файли з templates/;
  2. підставляє всі значення з values.yaml;
  3. генерує готові Kubernetes-ресурси (Deployment, Service, тощо);
  4. і відправляє їх у кластер.

У результаті   Kubernetes отримає Deployment з replicas: 2.

Підсумок

Kubernetes — це не складно, якщо підходити поступово. YAML-файли — це просто опис того, що ти хочеш, а Kubernetes сам вирішить, як це зробити.

Ще раз:

  • Pod —  це контейнер.
  • Service — шлях до pod’ів.
  • Deployment — автоматизація запуску і оновлень.
  • ConfigMap/Secret  - конфігурації і паролі.
  • Volumes  - збереження даних.

P.S. Я фронтендщік, тому не сильно кидайте в мене помідорами :D

P.S.S. Також я ще пишу у своєму Medium, тому буду радий, якщо підпишетесь.

👍ПодобаєтьсяСподобалось70
До обраногоВ обраному45
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Будь якому сервісу можна прописати LoadBalancer або NodePort. Тоді він буде доступен ззовні навіть без Ingress.

LoadBalancer пpацює на будь-якому залізі, а не лише в клауді.

Якщо треба зберігати дані навіть після видалення Pod-а, використовують PersistentVolume (PV) це постійне сховище, яке може виступати або локальним диском, або хмарним (AWS EBS, GCP Persistent Disk, Azure Disk).

Розумію, що опис подано спрощено, та уточнив би, чому спочатку згадується PV, а наведено приклад конфігурації PVC. На практиці користувачі рідко створюють PV вручну. Зазвичай створюється PVC (запит на сховище), а Kubernetes сам підбирає або створює PV. Важливу роль тут відіграє StorageClass — ресурс, який визначає, як саме створюється PV (тип диска, політика видалення тощо). Якщо StorageClass є (у більшості кластерів він дефолтний), то при створенні PVC Kubernetes автоматично створює PV. Якщо ж StorageClass відсутній, PVC залишиться у стані Pending, доки не з’явиться відповідний вручну створений PV.

Ннайс! Гадав що як завжди «не подужаю», а прочитав на одному диханні :))
Тепер залишилось змусити себе потестити кубер 😅

Дякую за статтю! Все дуже просто та зрозуміло

Хочь щось технічне, вже добре.

Дякую за статтю. Хотілося б щоб було ще про механіку розгортування нод, як і чим читається yaml файл, де беруться образи ОС, чи можна їх перед-налаштувати тощо.

Дякую за фітбек, врахую це для наступної статті!

На прикладі AWS Elastic Kubernetes Service.

про механіку розгортування нод

— для кластеру налаштовується WorkerNode Group; по факту — звичайна AWS EC2 AutoScaling Group, але з AMI-образів з kubelet і параметрами, які ці EC2 додають до Kubernetes-кластеру (див. також Cluster Autoscaler та Karpenter)

як і чим читається yaml файл

— коли ми викликаємо kubectl apply -f deployment.yaml, то YAML конвертується в JSON і передається на API-ендпоінт Kubernetes-кластеру, де дані зберігаються в базі etcd, а далі компонент Scheduler бачить нові об’єкти (наприклад, Pods), і передає команди на відповідні EC2 до їхніх kubelet, а kubelet — команди до containerd, які вже запускають контейнери

де беруться образи ОС

— знов-таки у випадку з AWS — є готові образи з усіма компонентами; при створенні WorkerNode Group (або з конфігурацій Cluster Autoscaler та Karpente) вказується AMI, який використовувати

чи можна їх перед-налаштувати тощо

— можна мати власні AMI

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

Дякую за статтю!

Development/production треба робити через namespace

Для кращої ізоляції окремих середовищ краще тоді мати окремі кластери для dev/prod/staging/etc.

Так, у статті хотілось максимально спростити, але ваш поінт валідний!
хочеш «подешевше» — через namespace, хочеш «подорожче» — окремі кластери 🙂

Можна звернути увагу також на vClusters, який створює віртуальний кластер всередині вашого фізичного кластера www.vcluster.com

Підписатись на коментарі