Kubernetes

Локальне середовище — minikube, kind та k3s

Встановлення та налаштування локального Kubernetes-кластера для розробки та навчання

Локальне середовище — minikube, kind та k3s

Від теорії до практики

У попередніх двох статтях ми розглянули, навіщо потрібен Kubernetes і як він влаштований зсередини. Тепер настав час перейти від концепцій до реальної роботи: запустити власний кластер, виконати перші команди та побачити, як абстрактні компоненти — API-сервер, scheduler, kubelet — працюють разом.

Але виникає практичне питання: де взяти кластер для навчання? Розгортати production-кластер у хмарі (GKE, EKS, AKS) для експериментів — дорого і надмірно. Встановлювати повноцінний кластер на кількох серверах через kubeadm — складно і вимагає інфраструктури.

Саме для цього існують локальні дистрибутиви Kubernetes — інструменти, які дозволяють запустити повноцінний кластер на вашому ноутбуці за кілька хвилин. Вони не призначені для production, але ідеально підходять для розробки, тестування та навчання.

Ця стаття має практичний характер. Ви встановите один з інструментів, запустите кластер і виконаєте перші kubectl-команди. Рекомендуємо мати під рукою термінал та виконувати команди паралельно з читанням.

Вибір інструменту: порівняння варіантів

Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #ffffff

package "Локальні Kubernetes інструменти" {
    component "minikube" as mk #e3f2fd {
        [VM/Docker Container]
        [Full K8s Cluster]
        [Built-in Addons]
    }
    
    component "kind" as kd #fff3e0 {
        [Docker Containers]
        [Multi-node Support]
        [Fast Startup]
    }
    
    component "k3s" as k3 #f3e5f5 {
        [Lightweight Binary]
        [Edge Computing]
        [Minimal Resources]
    }
    
    component "Docker Desktop" as dd #e8f5e9 {
        [Built-in K8s]
        [One-click Enable]
        [Mac/Windows Only]
    }
}

note bottom of mk
  Час старту: 30-60 сек
  RAM: 2-4 ГБ
  Use case: Навчання
end note

note bottom of kd
  Час старту: 10-20 сек
  RAM: 1-2 ГБ
  Use case: CI/CD
end note

note bottom of k3
  Час старту: 15-30 сек
  RAM: 512 МБ - 1 ГБ
  Use case: Edge/IoT
end note

note bottom of dd
  Час старту: 40-60 сек
  RAM: 2-3 ГБ
  Use case: Швидкий старт
end note
@enduml

Існує кілька популярних інструментів для запуску Kubernetes локально. Кожен має свої переваги та обмеження. Розглянемо чотири основні варіанти.

minikube — класичний вибір для навчання

minikube — це офіційний інструмент Kubernetes для локальної розробки. Він створює віртуальну машину (або використовує Docker як backend) і розгортає всередині неї повноцінний однонодовий кластер.

Переваги:

  • Найбільш документований та підтримуваний спільнотою
  • Вбудовані addons (dashboard, metrics-server, ingress)
  • Підтримка різних драйверів (VirtualBox, Docker, Podman, Hyper-V)
  • Команди для роботи з кластером (minikube start, minikube stop, minikube delete)

Недоліки:

  • Повільніший старт порівняно з kind (30-60 секунд)
  • Споживає більше ресурсів через віртуалізацію
  • Один вузол (не можна тестувати multi-node сценарії без додаткових налаштувань)

Типовий use case: Навчання Kubernetes, розробка застосунків, тестування маніфестів перед деплоєм у production.

kind — Kubernetes in Docker

kind (Kubernetes IN Docker) — інструмент, який запускає Kubernetes-вузли як Docker-контейнери. Кожен вузол кластера — це окремий контейнер.

Переваги:

  • Дуже швидкий старт (10-20 секунд)
  • Легко створювати multi-node кластери (кілька control plane + worker вузлів)
  • Мінімальне споживання ресурсів
  • Ідеальний для CI/CD pipelines (тестування у GitHub Actions, GitLab CI)

Недоліки:

  • Менше вбудованих addons порівняно з minikube
  • Вимагає Docker Desktop або Docker Engine
  • Складніша налаштування мережі для доступу ззовні

Типовий use case: CI/CD тестування, розробка Kubernetes-операторів, тестування multi-node сценаріїв.

k3s — легкий Kubernetes для edge

k3s — це мінімалістичний дистрибутив Kubernetes від Rancher Labs. Він видаляє застарілі компоненти та зменшує бінарник до ~50 МБ (порівняно з ~1 ГБ у повному Kubernetes).

Переваги:

  • Найменше споживання ресурсів (працює навіть на Raspberry Pi)
  • Швидкий старт
  • Вбудований Traefik як Ingress Controller
  • Підходить для production на edge-пристроях

Недоліки:

  • Не 100% сумісний з upstream Kubernetes (деякі компоненти замінені)
  • Менша спільнота порівняно з minikube

Типовий use case: IoT, edge computing, локальна розробка на слабких машинах.

Docker Desktop Kubernetes

Docker Desktop (Windows/macOS) має вбудовану підтримку Kubernetes — можна увімкнути кластер одним чекбоксом у налаштуваннях.

Переваги:

  • Нульова конфігурація (якщо Docker Desktop вже встановлено)
  • Інтеграція з Docker CLI
  • Автоматичне оновлення разом з Docker Desktop

Недоліки:

  • Доступний лише на Windows та macOS (не Linux)
  • Повільніший старт порівняно з kind
  • Менше контролю над версією Kubernetes

Типовий use case: Швидкий старт для розробників, які вже використовують Docker Desktop.


Порівняльна таблиця

Характеристикаminikubekindk3sDocker Desktop
Час старту30-60 сек10-20 сек15-30 сек40-60 сек
Споживання RAM2-4 ГБ1-2 ГБ512 МБ - 1 ГБ2-3 ГБ
Multi-nodeТак (складно)Так (легко)ТакНі
ПлатформиWin/Mac/LinuxWin/Mac/LinuxWin/Mac/LinuxWin/Mac
Вбудовані addonsБагатоМалоTraefikМало
СкладністьНизькаСередняНизькаДуже низька
РекомендаціяНавчанняCI/CD, тестиEdge, слабкі машиниШвидкий старт
Наша рекомендація для цього курсу: використовуйте minikube з Docker-драйвером. Він найкраще документований, має найбільшу спільноту та найпростіший для початківців. Якщо у вас вже встановлено Docker Desktop — можете використовувати його вбудований Kubernetes.

Встановлення kubectl

Перш ніж встановлювати сам кластер, потрібен інструмент для взаємодії з ним — kubectl. Це CLI, який ми вже згадували у попередній статті.

Аналогія з Docker: Якщо docker CLI — це інструмент для керування контейнерами на одному хості, то kubectl — інструмент для керування контейнерами (Podʼами) на цілому кластері. Синтаксис схожий: docker pskubectl get pods, docker logskubectl logs.

Встановлення на macOS

brew install kubectl

Встановлення на Windows

choco install kubernetes-cli

Встановлення на Linux

# Додати репозиторій Kubernetes
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl

curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg

echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list

sudo apt-get update
sudo apt-get install -y kubectl

Перевірка встановлення

Після встановлення перевірте версію:

kubectl version --client

Очікуваний вивід:

kubectl version
$ kubectl version --client
Client Version: v1.30.0
Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3
Прапорець --client показує лише версію kubectl, не намагаючись підключитись до кластера. Це корисно для перевірки встановлення до того, як кластер запущено.

Встановлення minikube

Тепер встановимо сам minikube — інструмент для запуску локального кластера.

Встановлення на macOS

brew install minikube

Встановлення на Windows

choco install minikube

Встановлення на Linux

curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube_latest_amd64.deb
sudo dpkg -i minikube_latest_amd64.deb

Перевірка встановлення

minikube version

Очікуваний вивід:

minikube version
$ minikube version
minikube version: v1.33.0
commit: 86fc9d54fca63f295d8737c8eacdbb7987e89c67

Запуск першого кластера

Настав момент істини — запустимо наш перший Kubernetes-кластер.

Крок 1: Запуск кластера

Виконайте команду:

minikube start --driver=docker
Прапорець --driver=docker вказує minikube використовувати Docker як backend. Це найшвидший та найстабільніший варіант, якщо у вас встановлено Docker Desktop або Docker Engine. Без цього прапорця minikube спробує автоматично визначити найкращий драйвер.

Процес займе 30-60 секунд. Ви побачите вивід, схожий на цей:

minikube start
$ minikube start --driver=docker
😄 minikube v1.33.0 on Darwin 14.4.1 (arm64)
Using the docker driver based on user configuration
📌 Using Docker Desktop driver with root privileges
👍 Starting control plane node minikube in cluster minikube
🚜 Pulling base image ...
🔥 Creating docker container (CPUs=2, Memory=4000MB) ...
🐳 Preparing Kubernetes v1.30.0 on Docker 26.0.1 ...
▪ Generating certificates and keys ...
▪ Booting up control plane ...
▪ Configuring RBAC rules ...
🔗 Configuring bridge CNI (Container Networking Interface) ...
🔎 Verifying Kubernetes components...
🏄 Done! kubectl is now configured to use "minikube" cluster

Що відбулося під капотом:

Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #ffffff

actor "Користувач" as user
participant "minikube CLI" as mk
participant "Docker Engine" as docker
database "Docker Container\n(minikube)" as container

user -> mk: minikube start --driver=docker
activate mk

mk -> docker: Створити контейнер "minikube"
activate docker
docker -> container: Запустити контейнер
activate container
docker --> mk: Контейнер створено
deactivate docker

mk -> container: Встановити Kubernetes v1.30.0
container -> container: Завантажити образи компонентів

mk -> container: Запустити Control Plane
container -> container: ✓ kube-apiserver
container -> container: ✓ etcd
container -> container: ✓ kube-scheduler
container -> container: ✓ kube-controller-manager

mk -> container: Запустити Worker Node компоненти
container -> container: ✓ kubelet
container -> container: ✓ kube-proxy
container -> container: ✓ containerd

mk -> mk: Налаштувати ~/.kube/config
mk --> user: ✅ Кластер готовий!
deactivate mk
deactivate container

note right of container
  Всередині одного Docker-контейнера
  працюють всі компоненти Kubernetes:
  - Control Plane (мозок)
  - Worker Node (виконавець)
end note
@enduml
  1. Створено Docker-контейнер з іменем minikube, який виконує роль єдиного вузла кластера
  2. Встановлено Kubernetes версії 1.30.0 всередині цього контейнера
  3. Запущено всі компоненти control plane: kube-apiserver, etcd, kube-scheduler, kube-controller-manager
  4. Запущено компоненти worker node: kubelet, kube-proxy, containerd
  5. Налаштовано kubectl для підключення до цього кластера

Перевірити, що контейнер запущено, можна через Docker:

docker ps --filter name=minikube
docker ps
$ docker ps --filter name=minikube
CONTAINER ID IMAGE STATUS PORTS NAMES
a3f8c9d12e45 gcr.io/k8s-minikube/... Up 2 minutes 127.0.0.1:32768->22/tcp, 127.0.0.1:32769->2376/tcp, ... minikube

Крок 2: Перевірка стану кластера

Тепер перевіримо, що кластер справді працює. Виконайте:

kubectl cluster-info

Очікуваний вивід:

kubectl cluster-info
$ kubectl cluster-info
Kubernetes control plane is running at https://127.0.0.1:32769
CoreDNS is running at https://127.0.0.1:32769/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.

Ця команда показує адресу API-сервера та системні сервіси. Зверніть увагу: kubectl спілкується з кластером через HTTPS на локальному порту.

Крок 3: Перегляд вузлів кластера

Згадайте з попередньої статті: кластер складається з вузлів (nodes). Подивимось, які вузли є у нашому кластері:

kubectl get nodes
kubectl get nodes
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
minikube Ready control-plane 2m v1.30.0

Бачимо один вузол з іменем minikube. Колонка ROLES показує control-plane — це означає, що цей вузол виконує роль і control plane, і worker node одночасно. У production-кластерах ці ролі розділені, але для локальної розробки це нормально.

Статус Ready означає, що вузол справний і готовий приймати Podʼи.

Крок 4: Перегляд системних Podʼів

Kubernetes сам використовує Podʼи для запуску своїх внутрішніх компонентів. Подивимось на них:

kubectl get pods -n kube-system
Прапорець -n kube-system вказує на namespace (простір імен) kube-system. Це критично важлива концепція ізоляції, яку ми детально розберемо у наступному розділі.

Очікуваний вивід:

kubectl get pods -n kube-system
$ kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-7db6d8ff4d-8xk2p 1/1 Running 0 3m
etcd-minikube 1/1 Running 0 3m
kube-apiserver-minikube 1/1 Running 0 3m
kube-controller-manager-minikube 1/1 Running 0 3m
kube-proxy-vx9qm 1/1 Running 0 3m
kube-scheduler-minikube 1/1 Running 0 3m
storage-provisioner 1/1 Running 0 3m

Впізнаєте ці імена? Це саме ті компоненти, які ми розглядали у попередній статті:

  • etcd-minikube — розподілена база даних стану
  • kube-apiserver-minikube — API-сервер
  • kube-controller-manager-minikube — менеджер контролерів
  • kube-scheduler-minikube — планувальник
  • kube-proxy-vx9qm — мережевий проксі
  • coredns-* — DNS-сервер для service discovery
  • storage-provisioner — компонент для автоматичного створення volumes

Колонка READY показує 1/1 — це означає, що у Podʼі один контейнер і він готовий до роботи. Статус Running підтверджує, що контейнер виконується.


Namespace — логічні кімнати кластера

Коли ви вперше заходите в кластер, він може здатися безкраїм полем. Але насправді Kubernetes розділений на "віртуальні зони", які називаються Namespaces (простори імен).

Уявіть кластер як велику офісну будівлю. Будівля одна (один кластер), але в ній багато кімнат:

  • У кімнаті 1 працює бухгалтерія.
  • У кімнаті 2 — відділ розробки.
  • У кімнаті 3 — серверна з обладнанням самої будівлі (вентиляція, ліфти).

Співробітники розробки не заважають бухгалтерам, хоча всі вони знаходяться під одним дахом.

Навіщо потрібні Namespace?

  1. Ізоляція імен: Ви можете мати Pod з іменем web-server у namespace project-a і ще один web-server у namespace project-b. Вони не будуть конфліктувати. Це працює як папки на вашому комп'ютері.
  2. Безпека та доступ: Ви можете дозволити розробникам створювати що завгодно у namespace dev, але заборонити їм навіть дивитися, що відбувається у production.
  3. Обмеження ресурсів (Quotas): Можна сказати: "Namespace testing не може споживати більше 10 ГБ оперативної пам'яті кластера", щоб тести не "з'їли" ресурси всього офісу.

Стандартні Namespace, які ви побачите завжди:

  • default — "вітальня". Якщо ви створюєте об'єкт і не вказуєте namespace, він потрапляє сюди.
  • kube-system — "серверна". Тут живуть компоненти самого Kubernetes (API-сервер, планувальник тощо). Краще сюди нічого свого не ставити.
  • kube-public — "хол". Ресурси, які мають бути видимі всім (навіть без автентифікації).
Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #ffffff

package "Kubernetes Cluster" {
    package "namespace: kube-system" #e9ecef {
        [API Server]
        [Scheduler]
    }
    
    package "namespace: project-alpha" #f8f9fa {
        [Frontend]
        [Backend]
    }
    
    package "namespace: project-beta" #f8f9fa {
        [Frontend]
        [Database]
    }
}

note bottom of "namespace: project-alpha"
  Два ресурси з однаковим ім'ям 'Frontend'
  можуть співіснувати у різних namespace.
end note
@enduml

Розуміння kubeconfig — як kubectl знає, куди підключатись

Коли ви виконуєте kubectl get nodes, як kubectl знає, до якого кластера звертатись? Відповідь — через файл конфігурації kubeconfig.

Структура kubeconfig

За замовчуванням kubectl читає конфігурацію з файлу ~/.kube/config. Цей файл містить три основні секції:

clusters
масив
Список кластерів, до яких ви можете підключатись. Хоча кластер — це сукупність багатьох серверів, для kubectl він завжди представлений однією точкою входу (адресою API-сервера). Це як «ресепшн» у великому готелі: вам не потрібно знати розташування всіх кімнат, ви просто звертаєтесь до адміністратора, а він сам керує процесами всередині.
users
масив
Список облікових записів (credentials) для автентифікації у кластерах. Може містити сертифікати, токени або інші методи автентифікації.
contexts
масив
Контекст — це «готовий набір» налаштувань, який поєднує кластер, користувача та namespace. Замість того, щоб щоразу вказувати адресу сервера та логін, ви просто перемикаєте контекст, який каже: «Зараз я хочу працювати в Кластері А від імені Користувача Б у Namespace В». Це як кнопка пам'яті в кріслі авто: один раз налаштували положення сидіння та дзеркал під себе, і далі просто активуєте свій профіль однією дією.
current-context
рядок
Ім'я активного контексту. Саме цей контекст використовується для всіх kubectl-команд.

Подивимось на структуру файлу:

kubectl config view
kubectl config view
$ kubectl config view
apiVersion: v1
clusters:
- cluster:
certificate-authority: /Users/user/.minikube/ca.crt
server: https://127.0.0.1:32769
name: minikube
contexts:
- context:
cluster: minikube
user: minikube
name: minikube
current-context: minikube
users:
- name: minikube
user:
client-certificate: /Users/user/.minikube/profiles/minikube/client.crt
client-key: /Users/user/.minikube/profiles/minikube/client.key
Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #ffffff

package "kubeconfig (~/.kube/config)" #f8f9fa {
    component "Clusters" as C #e9ecef {
        [Local (minikube)]
        [Production (GKE)]
    }
    
    component "Users" as U #e9ecef {
        [minikube-user]
        [prod-admin]
    }
    
    component "Contexts (Профілі)" as CX #fff {
        [context: minikube]
        [context: prod-admin]
    }
}

[context: minikube] -up-> [Local (minikube)] : "Куди?"
[context: minikube] -up-> [minikube-user] : "Хто?"

[context: prod-admin] -up-> [Production (GKE)] : "Куди?"
[context: prod-admin] -up-> [prod-admin] : "Хто?"
@enduml

Команда minikube start працює як автоматичний налаштувальник. Вона сама заповнює всі складні технічні поля у вашому файлі конфігурації:

  1. Реєструє адресу: додає URL-адресу вашого локального кластера в розділ clusters.
  2. Створює "ключі": додає ваші сертифікати (перепустки) до списку users.
  3. Створює профіль: створює контекст minikube, який автоматично пов'язує цей кластер з цими ключами.
  4. Робить його активним: встановлює цей профіль як current-context, щоб ви могли відразу почати роботу.

Тобто Minikube повністю готує "міст" між вашим терміналом та кластером, позбавляючи вас ручної рутини.

Перемикання між контекстами

Якщо у вас кілька кластерів (наприклад, локальний minikube та production у GKE), ви можете перемикатись між ними:

# Переглянути всі доступні контексти
kubectl config get-contexts

# Перемкнутись на інший контекст
kubectl config use-context <назва-контексту>

# Подивитись поточний контекст
kubectl config current-context

Сценарій: "Де мої Pod-и?" та магія Namespace

Одна з найпоширеніших проблем новачків — ви створили Pod у контексті minikube, а потім не можете його знайти. Чому? Тому що в контексті можна задати namespace за замовчуванням.

Приклад робочого сценарію: У вас є контекст prod, але ви працюєте лише з сервісом замовлень у namespace orders. Ви можете налаштувати контекст так, щоб не писати кожного разу -n orders:

kubectl config set-context --current --namespace=orders

Тепер команда kubectl get pods буде автоматично показувати лише Pod-и з orders. Це дуже зручно, але небезпечно, якщо ви забули, у якому namespace зараз знаходитесь!

Як це виглядає на реальній роботі?

У досвідченого DevOps-інженера файл ~/.kube/config може містити десятки контекстів. Оскільки перемикатись через довгі команди kubectl config use-context ... незручно, у спільноті використовують два "must-have" інструменти:

kubectx

Дозволяє перемикати кластери (контексти) однією короткою командою. Наприклад: kubectx minikube.

kubens

Дозволяє миттєво перемикати namespace всередині поточного контексту. Наприклад: kubens monitoring.
Аналогія з автомобілем:
  • Clusters — це різні автомобілі у вашому гаражі (джип для гір, седан для міста).
  • Users — це різні ключі або водійські права.
  • Contexts — це збережені налаштування сидіння та дзеркал: один раз натиснули кнопку "Профіль 1", і машина сама підлаштувалась під водія та маршрут.

Перші команди kubectl

Тепер, коли кластер запущено і kubectl налаштовано, розглянемо базові команди для роботи з Kubernetes.

Структура команд kubectl

Як ми згадували у попередній статті, команди kubectl мають чітку структуру:

kubectl [дієслово] [тип-ресурсу] [імʼя] [прапорці]

Основні дієслова:

get

Отримати список ресурсів або деталі конкретного ресурсу. Найчастіша команда для перегляду стану.

describe

Детальна інформація про ресурс: події, стан, конфігурація. Використовується для діагностики.

apply

Створити або оновити ресурс з YAML/JSON файлу. Декларативний підхід.

delete

Видалити ресурс з кластера.

logs

Переглянути логи контейнера у Podʼі.

exec

Виконати команду всередині контейнера у Podʼі (аналог docker exec).

Приклади базових команд

# Переглянути всі Podʼи у поточному namespace (default)
kubectl get pods

# Переглянути Podʼи у всіх namespace
kubectl get pods --all-namespaces
# або скорочено:
kubectl get pods -A

# Переглянути вузли кластера
kubectl get nodes

# Переглянути сервіси
kubectl get services
# або скорочено:
kubectl get svc

Скорочення назв ресурсів

Kubernetes підтримує скорочення для типів ресурсів:

Повна назваСкорочення
podspo
servicessvc
deploymentsdeploy
replicasetsrs
namespacesns
nodesno
persistentvolumespv
persistentvolumeclaimspvc

Приклад:

# Ці команди еквівалентні:
kubectl get pods
kubectl get po

Запуск першого Podʼу

Настав час запустити наш перший застосунок у Kubernetes. Почнемо з найпростішого — одного Podʼу з Nginx.

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

Найшвидший спосіб запустити Pod — використати команду kubectl run:

kubectl run nginx --image=nginx:1.25
Аналогія з Docker: Ця команда схожа на docker run nginx:1.25, але замість запуску контейнера напряму, вона створює Pod у кластері. Kubernetes сам вирішує, на якому вузлі його розмістити.

Перевіримо, що Pod створено:

kubectl get pods
kubectl get pods
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx 0/1 ContainerCreating 0 3s

Статус ContainerCreating означає, що Kubernetes завантажує образ та запускає контейнер. Зачекайте кілька секунд і виконайте команду знову:

kubectl get pods (після завантаження)
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 45s

Статус змінився на Running, а колонка READY показує 1/1 — один контейнер з одного готовий.

Що відбулося під капотом?

Розглянемо покроково, що сталося після виконання kubectl run:

Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #ffffff

participant "kubectl" as kubectl
participant "API Server" as api
database "etcd" as etcd
participant "Scheduler" as scheduler
participant "Kubelet" as kubelet
participant "containerd" as containerd

kubectl -> api: POST /api/v1/pods\n(Pod: nginx)
activate api
api -> api: Authentication ✓\nAuthorization ✓
api -> etcd: Зберегти Pod\n(status: Pending)
activate etcd
etcd --> api: Збережено
deactivate etcd
api --> kubectl: Pod створено ✓
deactivate api

scheduler -> api: Watch: Pod без nodeName?
activate api
api --> scheduler: Pod "nginx"\n(nodeName: null)
deactivate api

activate scheduler
scheduler -> scheduler: Фільтрація вузлів\nОцінювання\nВибір: minikube
scheduler -> api: PATCH Pod\n(nodeName: minikube)
activate api
api -> etcd: Оновити Pod
activate etcd
etcd --> api: Оновлено
deactivate etcd
deactivate api
deactivate scheduler

kubelet -> api: Watch: Pod для minikube?
activate api
api --> kubelet: Pod "nginx"\n(nodeName: minikube)
deactivate api

activate kubelet
kubelet -> containerd: PullImage(nginx:1.25)
activate containerd
containerd --> kubelet: Image завантажено
kubelet -> containerd: CreateContainer(nginx)
containerd --> kubelet: Container створено
kubelet -> containerd: StartContainer(nginx)
containerd --> kubelet: Container запущено ✓
deactivate containerd

kubelet -> api: PATCH Pod\n(status: Running)
activate api
api -> etcd: Оновити стан
activate etcd
etcd --> api: Оновлено
deactivate etcd
deactivate api
deactivate kubelet

note right of scheduler
  Reconciliation Loop:
  Система постійно порівнює
  бажаний стан з поточним
  і усуває розбіжності
end note
@enduml
Loading diagram...
sequenceDiagram
    participant U as kubectl
    participant API as kube-apiserver
    participant E as etcd
    participant S as kube-scheduler
    participant K as kubelet
    participant C as containerd

    U->>API: "POST /api/v1/namespaces/default/pods<br/>(Pod spec: nginx)"
    API->>API: "Аутентифікація + Авторизація"
    API->>E: "Зберегти Pod (status: Pending)"
    API-->>U: "Pod створено ✓"
    
    S->>API: "Watch: нові Podʼи без nodeName?"
    API-->>S: "Pod \"nginx\" (nodeName: null)"
    S->>S: "Вибрати вузол (minikube)"
    S->>API: "PATCH: nodeName = \"minikube\""
    API->>E: "Оновити Pod"
    
    K->>API: "Watch: Podʼи для мого вузла?"
    API-->>K: "Pod \"nginx\" (nodeName: minikube)"
    K->>C: "Запустити контейнер nginx:1.25"
    C-->>K: "Контейнер запущено"
    K->>API: "PATCH: status = Running"
    API->>E: "Оновити стан"

Покрокове пояснення:

  1. kubectl → API-сервер: kubectl надсилає HTTP POST-запит до API-сервера з специфікацією Podʼу
  2. API-сервер → etcd: Після валідації API-сервер зберігає Pod у etcd зі статусом Pending (очікує призначення вузла)
  3. Scheduler спостерігає: kube-scheduler постійно відстежує (watch) нові Podʼи без призначеного вузла
  4. Scheduler призначає вузол: Scheduler аналізує доступні вузли та обирає minikube, оновлює поле nodeName через API-сервер
  5. Kubelet спостерігає: kubelet на вузлі minikube відстежує Podʼи, призначені його вузлу
  6. Kubelet запускає контейнер: kubelet інструктує containerd завантажити образ nginx:1.25 та запустити контейнер
  7. Kubelet звітує: Після успішного запуску kubelet оновлює статус Podʼу на Running

Це і є reconciliation loop у дії: система безперервно порівнює бажаний стан (Pod має бути запущений) з поточним (Pod ще не існує) і усуває розбіжності.

Детальна інформація про Pod

Подивимось детальніше на створений Pod:

kubectl describe pod nginx

Вивід буде великим, але найважливіші секції:

kubectl describe pod nginx (фрагмент)
Name: nginx
Namespace: default
Node: minikube/192.168.49.2
Status: Running
IP: 10.244.0.5
Containers:
nginx:
Image: nginx:1.25
Port: <none>
State: Running
Started: Thu, 07 May 2026 10:02:15 +0000
Events:
Type Reason Age From Message
Normal Scheduled 2m default-scheduler Successfully assigned default/nginx to minikube
Normal Pulling 2m kubelet Pulling image "nginx:1.25"
Normal Pulled 90s kubelet Successfully pulled image "nginx:1.25"
Normal Created 90s kubelet Created container nginx
Normal Started 90s kubelet Started container nginx

Секція Events показує хронологію подій — це найкорисніша частина для діагностики проблем.

Доступ до застосунку

Pod запущено, але як до нього звернутись? У Podʼа є внутрішня IP-адреса (10.244.0.5 у прикладі вище), але вона доступна лише всередині кластера.

Для тестування можна використати port-forward — тунель між вашою локальною машиною та Podʼом.

Зверніть увагу на синтаксис: pod/nginx. У Kubernetes команди часто будуються за принципом тип/імʼя. Це дозволяє чітко сказати: "Я хочу перенаправити порти саме для пода з іменем nginx".

kubectl port-forward pod/nginx 8080:80
kubectl port-forward
$ kubectl port-forward pod/nginx 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80
Handling connection for 8080

Тепер відкрийте браузер та перейдіть на http://localhost:8080 — ви побачите стандартну сторінку Nginx.

port-forward — це інструмент для розробки та діагностики, не для production. У наступних статтях ми розглянемо Service — правильний спосіб надати доступ до застосунків у Kubernetes.

Видалення Podʼу

Видалимо створений Pod:

kubectl delete pod nginx
kubectl delete pod
$ kubectl delete pod nginx
pod "nginx" deleted

Перевіримо:

kubectl get pods
kubectl get pods (після видалення)
$ kubectl get pods
No resources found in default namespace.

Декларативний підхід: YAML-маніфести

Команда kubectl run зручна для швидких тестів, але у реальних проєктах використовується декларативний підхід — опис ресурсів у YAML-файлах.

Чому YAML, а не команди?

Версіонування

YAML-файли зберігаються у Git разом з кодом. Ви бачите історію змін інфраструктури так само, як історію коду.

Відтворюваність

Один файл можна застосувати у dev, staging та production. Немає ризику забути прапорець у команді.

Code Review

Зміни в інфраструктурі проходять через Pull Request, як і зміни у коді. Команда бачить, що саме змінюється.

Автоматизація

CI/CD pipeline може автоматично застосовувати маніфести. Команди kubectl run не підходять для автоматизації.

Створення першого маніфесту

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

Створимо файл nginx-pod.yaml:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.25
    ports:
    - containerPort: 80

Уявіть цей файл як анкету для реєстрації нового мешканця в готелі (кластері). Розберемо її поля:

apiVersion
string
Версія правил. Це як версія закону або словника. Для різних об'єктів вона різна. Для Podʼів — це завжди v1.
kind
string
Тип об'єкта. Що ми будуємо? Будинок? Машину? У нашому випадку — Pod.
metadata
object
Паспорт об'єкта. Тут ми вказуємо ім'я (name), за яким Kubernetes буде відрізняти цей Pod від інших. Також тут живуть мітки (labels) — це як кольорові наклейки на коробці, щоб потім легко було знайти всі коробки з "написом" app: nginx.
spec
object
Технічне завдання (Специфікація). Найважливіша частина. Тут ми описуємо, що має бути всередині. Для Podʼу — це перелік контейнерів (containers), їхні образи (image) та порти. Це і є "бажаний стан", який Kubernetes буде підтримувати.

Застосування маніфесту

Застосуємо створений файл:

kubectl apply -f nginx-pod.yaml
kubectl apply
$ kubectl apply -f nginx-pod.yaml
pod/nginx created

Команда kubectl apply є ідемпотентною. Уявіть це як кнопку "Зберегти" у текстовому редакторі: якщо файлу ще немає — він створиться, якщо він уже є — він просто оновиться новими даними. Ви можете запускати цю команду скільки завгодно разів — Kubernetes просто переконається, що реальний стан кластера відповідає вашому файлу.

Перевіримо:

kubectl get pods
kubectl get pods
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 10s

Отримання YAML існуючого ресурсу

Якщо ви хочете побачити повну специфікацію Podʼу (включно з полями, які Kubernetes додав автоматично):

kubectl get pod nginx -o yaml

Вивід буде містити набагато більше полів, ніж ми задали у маніфесті:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  namespace: default
  uid: a3f8c9d1-2e45-4b6c-8d7e-9f0a1b2c3d4e
  creationTimestamp: "2026-05-07T10:02:15Z"
  labels:
    app: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.25
    ports:
    - containerPort: 80
      protocol: TCP
    resources: {}
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: kube-api-access-xxxxx
      readOnly: true
  nodeName: minikube
  # ... багато інших полів
status:
  phase: Running
  podIP: 10.244.0.5
  startTime: "2026-05-07T10:02:15Z"
  # ... детальний статус контейнерів

Зверніть увагу на секцію status — вона не задається оператором, а заповнюється Kubernetes автоматично і відображає поточний стан ресурсу.


Управління кластером minikube

Розглянемо корисні команди для управління локальним кластером.

Зупинка та запуск кластера

# Зупинити кластер (зберігає стан)
minikube stop

# Запустити знову
minikube start

Після minikube stop всі Podʼи зупиняються, але їхні дані зберігаються. При наступному minikube start кластер відновлюється у тому ж стані.

Видалення кластера

# Повністю видалити кластер
minikube delete
Ця команда видаляє всі дані кластера безповоротно. Використовуйте її, коли хочете почати з чистого аркуша або звільнити ресурси.

Доступ до Dashboard

Kubernetes має вбудований веб-інтерфейс для перегляду ресурсів кластера:

minikube dashboard

Ця команда автоматично відкриє браузер з Dashboard. Там ви можете переглядати Podʼи, логи, події — все через графічний інтерфейс.

SSH до вузла

Іноді потрібно зайти всередину вузла кластера (наприклад, для діагностики мережі):

minikube ssh

Ви опинитесь у оболонці всередині Docker-контейнера, який є вузлом кластера.

Перегляд логів кластера

Якщо щось йде не так з самим кластером (не з вашими Podʼами, а з компонентами Kubernetes):

minikube logs

Namespace — логічна ізоляція ресурсів

Ми вже кілька разів згадували namespace. Настав час розглянути цю концепцію детальніше.

Namespace (простір імен) — це механізм логічної ізоляції ресурсів у кластері. Один кластер може містити кілька namespace, і ресурси в одному namespace не бачать ресурси в іншому (якщо це не налаштовано явно).

Системні namespace

Kubernetes створює кілька namespace автоматично:

default
namespace
Namespace за замовчуванням. Якщо ви не вказуєте namespace явно — ресурси створюються тут.
kube-system
namespace
Системні компоненти Kubernetes (API-сервер, scheduler, CoreDNS тощо). Не чіпайте ресурси тут без необхідності.
kube-public
namespace
Публічний namespace, доступний для читання всім користувачам (навіть неавтентифікованим). Рідко використовується.
kube-node-lease
namespace
Технічний namespace для heartbeat-повідомлень від вузлів. Не для ручного використання.

Переглянути всі namespace:

kubectl get namespaces
kubectl get namespaces
$ kubectl get namespaces
NAME STATUS AGE
default Active 15m
kube-node-lease Active 15m
kube-public Active 15m
kube-system Active 15m

Створення власного namespace

Створимо namespace для нашого проєкту:

kubectl create namespace myapp

Або через YAML-маніфест:

apiVersion: v1
kind: Namespace
metadata:
  name: myapp
kubectl apply -f namespace.yaml

Робота з ресурсами у namespace

Створимо Pod у новому namespace:

kubectl apply -f nginx-pod.yaml -n myapp

Переглянути Podʼи у конкретному namespace:

kubectl get pods -n myapp

Переглянути Podʼи у всіх namespace:

kubectl get pods --all-namespaces
# або скорочено:
kubectl get pods -A

Зміна namespace за замовчуванням

Щоб не вказувати -n myapp у кожній команді, можна змінити namespace за замовчуванням для поточного контексту:

kubectl config set-context --current --namespace=myapp

Тепер всі команди kubectl працюватимуть з namespace myapp за замовчуванням.


Резюме

У цій статті ми перейшли від теорії до практики:

  • Встановили kubectl — інструмент для взаємодії з Kubernetes-кластерами
  • Встановили minikube — локальний дистрибутив Kubernetes для розробки
  • Запустили перший кластер і побачили, як компоненти з попередньої статті працюють разом
  • Розібрали kubeconfig — механізм підключення до кластерів
  • Виконали базові команди kubectl для перегляду ресурсів
  • Запустили перший Pod двома способами: імперативно (kubectl run) та декларативно (YAML-маніфест)
  • Познайомились з namespace — логічною ізоляцією ресурсів

Ключовий висновок: Kubernetes працює за принципом декларативної конфігурації. Ви описуєте бажаний стан у YAML-файлах, а система сама приводить реальний стан до бажаного через reconciliation loop.

У наступній статті ми детально розглянемо Pod — найменшу розгортану одиницю Kubernetes: його життєвий цикл, патерни використання (sidecar, init-контейнери) та обмеження, які призводять до необхідності вищих абстракцій.


Практичні завдання

Завдання 1: Встановлення та запуск локального кластера

Встановіть необхідні інструменти (minikube та kubectl) на вашій локальній машині, запустіть однонодовий кластер та перевірте його статус.

Вимоги:

  • Встановіть kubectl та minikube відповідно до інструкцій для вашої операційної системи.
  • Запустіть локальний кластер.
  • Виконайте команду перевірки підключення та стану вузлів (nodes).

Команди:

# Запуск кластера
minikube start

# Перевірка підключення та стану вузлів
kubectl get nodes

Завдання:

  • Переконайтеся, що статус вузла — Ready.
  • Зробіть скріншот виводу терміналу з успішним виконанням команди kubectl get nodes.

Завдання 2: Імперативний запуск та прокидання портів

Створіть свій перший Pod в імперативному стилі, перевірте його стан та налаштуйте локальний доступ до вебсервера всередині контейнера.

Вимоги:

  • Запустіть Pod з назвою apache-web та образом httpd:2.4 (Apache HTTP Server) за допомогою команди kubectl run.
  • Переконайтеся, що Pod перейшов у стан Running.
  • Створіть тунель для доступу до вебсервера з локальної машини за допомогою kubectl port-forward.

Команди:

# Імперативний запуск Pod'у
kubectl run apache-web --image=httpd:2.4

# Перевірка стану Pod'у
kubectl get pods

# Прокидання портів (локальний 8080 -> 80 всередині Pod'у)
kubectl port-forward pod/apache-web 8080:80

Завдання:

  • Відкрийте веббраузер за адресою http://localhost:8080 та переконайтеся, що відображається стандартна сторінка Apache ("It works!").
  • Перегляньте логи Pod'у під час надсилання запитів за допомогою команди kubectl logs apache-web.

Завдання 3: Декларативне створення Pod'у через YAML

Створіть декларативний опис (маніфест) для Pod'у з базою даних Redis, налаштуйте метадані (мітки) та застосуйте його до кластера.

Вимоги:

  • Створіть файл redis-pod.yaml з описом конфігурації Pod'у.
  • Визначіть назву Pod'у redis-cache, образ redis:7 та мітку app: cache.
  • Застосуйте конфігурацію до кластера.

redis-pod.yaml:

apiVersion: v1
kind: Pod
metadata:
  name: redis-cache
  labels:
    app: cache
spec:
  containers:
  - name: redis
    image: redis:7
    ports:
    - containerPort: 6379

Команди:

# Застосування маніфесту
kubectl apply -f redis-pod.yaml

# Перевірка Pod'ів разом з їхніми мітками
kubectl get pods --show-labels

Завдання:

  • Переконайтеся, що мітка app=cache успішно присвоєна Pod'у.
  • Видаліть створений Pod за допомогою YAML-файлу: kubectl delete -f redis-pod.yaml.

Завдання 4: Логічна ізоляція через Namespaces

Створіть два ізольованих середовища за допомогою Namespaces та запустіть у них Pod'и з однаковими іменами без виникнення конфліктів.

Вимоги:

  • Створіть простори назв development та production.
  • Запустіть по одному Pod'у з ім'ям api (образ nginx:1.25) у кожному з цих просторів.
  • Перевірте список Pod'ів у кожному окремому namespace та в усьому кластері глобально.

Команди:

# Створення просторів назв
kubectl create namespace development
kubectl create namespace production

# Запуск Pod'ів у відповідних namespace
kubectl run api --image=nginx:1.25 -n development
kubectl run api --image=nginx:1.25 -n production

Завдання:

  • Виведіть список усіх Pod'ів у просторі назв development.
  • Виведіть глобальний список усіх Pod'ів у кластері (у всіх просторах назв одночасно) та переконайтеся, що обидва Pod'и api працюють паралельно. Яка команда дозволяє це зробити?

Завдання 5: Перевірка DNS та виконання команд через kubectl exec

Запустіть службовий Pod в інтерактивному режимі, підключіться до нього та виконайте мережеву діагностику вбудованого DNS-сервісу Kubernetes.

Вимоги:

  • Створіть Pod dns-tools на базі образу busybox:1.36.
  • Оскільки за замовчуванням busybox одразу завершує роботу, передайте йому команду sleep 3600, щоб тримати контейнер активним.
  • Виконайте команду nslookup всередині контейнера для резолву внутрішнього сервісу kubernetes.default.

Команди:

# Запуск Pod'у з передачею аргументів командного рядка
kubectl run dns-tools --image=busybox:1.36 -- sleep 3600

# Виконання команди nslookup всередині запущеного контейнера
kubectl exec dns-tools -- nslookup kubernetes.default

Питання:

  • Яку IP-адресу повернув nslookup для імені kubernetes.default? Що це за адреса та якому компоненту вона належить?
  • Як підключитися до оболонки (shell) цього Pod'у в інтерактивному режимі (TTY)?

Завдання 6: Діагностика помилок та аналіз подій (Events)

Навчіться знаходити причини проблем у роботі ресурсів, навмисно створивши несправний Pod із некоректними параметрами.

Вимоги:

  • Запустіть Pod broken-pod з неіснуючим тегом образу, наприклад nginx:nonexistent-tag.
  • Дочекайтеся, поки стан Pod'у зміниться на помилковий.
  • Використайте команду детального опису ресурсу для діагностики проблеми.

Команди:

# Створення несправного Pod'у
kubectl run broken-pod --image=nginx:nonexistent-tag

# Перевірка статусу Pod'ів
kubectl get pods

# Отримання детальної інформації про Pod та його життєвий цикл
kubectl describe pod broken-pod

Питання:

  • Який статус отримав Pod broken-pod через кілька хвилин після створення?
  • Знайдіть у виводі kubectl describe секцію Events. Які події (Events) вказують на конкретну помилку та що саме пішло не так?

Завдання 7: Експорт живої конфігурації та автозгенеровані поля

Дослідіть, як Kubernetes доповнює ваші декларативні маніфести службовою та системною інформацією після їх застосування в кластері.

Вимоги:

  • Візьміть будь-який запущений Pod (наприклад, redis-cache з Завдання 3).
  • Експортуйте його повну поточну конфігурацію з кластера у форматі YAML.
  • Порівняйте отриманий файл із вашим початковим маніфестом.

Команди:

# Отримання повної живої специфікації Pod'у у форматі YAML
kubectl get pod redis-cache -o yaml

Питання:

  • Знайдіть у згенерованому YAML-файлі щонайменше три поля, які ви не описували вручну (наприклад, metadata.uid, spec.nodeName, status.podIP або metadata.resourceVersion).
  • За що відповідає кожне з цих полів та яке їхнє призначення з точки зору Kubernetes?

Завдання 8: Дослідження середовища вузла через SSH

Загляньте "під капот" локального Kubernetes-вузла, щоб побачити реальні контейнери, які запускаються низькорівневим рушієм (CRI).

Вимоги:

  • Підключіться до віртуальної машини (або контейнера) minikube через вбудований SSH-клієнт.
  • Використайте утиліту контейнерного середовища виконання (docker або crictl), щоб переглянути список запущених контейнерів на рівні ОС вузла.
  • Знайдіть контейнери, що відповідають вашим Pod'ам, а також системні контейнери Kubernetes.

Команди:

# Підключення до оболонки вузла minikube
minikube ssh

# Всередині SSH-сесії вузла: перегляд процесів контейнерів
docker ps
# або якщо minikube використовує containerd:
sudo crictl ps

Питання:

  • Чому кількість фізичних контейнерів у виводі docker ps / crictl ps значно більша, ніж кількість створених вами Pod'ів у кластері?
  • Які системні компоненти Kubernetes (з тих, що ми вивчали в попередній статті) ви помітили у виводі команди?

Завдання 9: Анатомія kubeconfig та криптографічний доступ

Дослідіть структуру конфігураційного файлу, за допомогою якого kubectl автентифікується в API-сервері Kubernetes.

Вимоги:

  • Відкрийте файл конфігурації config, який знаходиться в директорії .kube у вашій домашній директорії, у текстовому редакторі:
    • macOS/Linux: ~/.kube/config (або /Users/<ім'я_користувача>/.kube/config)
    • Windows: C:\Users\<ім'я_користувача>\.kube\config (або %USERPROFILE%\.kube\config)
  • Вивчіть його структуру: знайдіть секції clusters, users та contexts.

Команди:

# Перегляд поточної конфігурації через kubectl (безпечний спосіб без виводу приватних ключів)
kubectl config view

Питання:

  • Знайдіть у секції clusters або users посилання на сертифікати (наприклад, certificate-authority або client-certificate). Навіщо вони потрібні?
  • Чому Kubernetes використовує сертифікати (mTLS) та токени для взаємодії з API, а не традиційні пари логін/пароль? Які переваги це надає для розподіленої системи з точки зору безпеки та автоматизації?

Завдання 10: Спільний мережевий простір у багатоконтейнерному Pod'і

Реалізуйте патерн багатоконтейнерного Pod'у (Multi-container Pod) та перевірте, як контейнери всередині одного Pod'у взаємодіють між собою через локальну мережу.

Вимоги:

  • Створіть файл multi-container.yaml для Pod'у з двома контейнерами: вебсервером Nginx та утилітою Busybox.
  • Переконайтеся, що обидва контейнери успішно запускаються в межах одного Pod'у.
  • Підключіться до контейнера з Busybox та виконайте HTTP-запит до Nginx за адресою localhost.

multi-container.yaml:

apiVersion: v1
kind: Pod
metadata:
  name: multi-app
spec:
  containers:
  - name: web-server
    image: nginx:1.25
    ports:
    - containerPort: 80
  - name: helper-tools
    image: busybox:1.36
    command: ["sleep", "3600"]

Команди:

# Застосування маніфесту багатоконтейнерного Pod'у
kubectl apply -f multi-container.yaml

# Виконання запиту з контейнера helper-tools до сусіднього контейнера web-server
kubectl exec multi-app -c helper-tools -- wget -O- http://localhost:80

Питання:

  • Чому контейнер helper-tools (Busybox) зміг успішно звернутися до Nginx через localhost:80, якщо це абсолютно різні, ізольовані контейнери з різними образами?
  • Яка концепція побудови мережі в Kubernetes (Network Namespace / Pod Network) забезпечує таку поведінку?
Ці практичні завдання допоможуть вам освоїти базові команди kubectl та роботу з локальним середовищем Minikube, заклавши фундамент для глибшого вивчення ресурсів Kubernetes.