Pod, Deployment, Service и как это всё работает.
🚀 Цель лекции
Понять, что такое Kubernetes (K8s) и зачем он нужен. Мы разберем базовые "кубики" архитектуры: что такое `Pod`, как `Deployment` им управляет, и как `Service` и `Ingress` открывают к ним доступ. Это обзор архитектуры, а не глубокое погружение в команды.
Kubernetes (K8s) — это "оркестратор" для контейнеров.
Проблема: У вас есть 100 Docker-контейнеров. Как их запускать, обновлять, следить, чтобы они не упали, и соединять их друг с другом? Делать это вручную — кошмар.
Решение (K8s): Вы не говорите "запусти контейнер". Вы декларативно (в YAML-файле) описываете "желаемое состояние": "Я хочу, чтобы у меня всегда было 3 копии этого веб-сервера". K8s сам разбирается, как это обеспечить.
Аналогия: K8s — это дирижер оркестра. Он не играет на инструментах, но говорит, кому, когда и как громко играть, чтобы получилась музыка (ваше приложение).
| Компонент |
Что это |
Роль |
| Control Plane (Мастер) |
"Мозг" кластера |
Принимает решения, хранит "желаемое состояние" (в `etcd`). |
| Nodes (Воркеры) |
"Рабочие" серверы |
Запускают ваши контейнеры (в `Pod`-ах) по команде Мастера. |
Pod (По-русски: "капсула", "стручок") — это самая маленькая единица, которую K8s может развернуть.
Это не контейнер. Это "домик" для одного или (редко) нескольких контейнеров.
- В 99% случаев у вас будет 1 Pod = 1 контейнер (ваше приложение).
- Контейнеры внутри одного Pod-а делят общий IP-адрес и могут общаться через `localhost`.
- K8s управляет Pod-ами, а не отдельными контейнерами.
Важно: Pod-ы "смертны". Если Node падает, Pod на ней умирает. Если Pod умирает, K8s создает новый (с новым IP), а не воскрешает старый.
# Пример минимального Pod-а
apiVersion: v1
kind: Pod
metadata:
name: my-nginx-pod
spec:
containers:
- name: my-nginx-container
image: nginx:latest
ports:
- containerPort: 80
Проблема: Мы только что узнали, что Pod-ы умирают. Если запустить Pod (как в примере выше) и он упадет, он не перезапустится. Как обеспечить стабильную работу?
Решение: Deployment. Это объект, который управляет Pod-ами.
Вы говорите Deployment-у:
- Какой Pod (шаблон) нужно запускать.
- Сколько копий (реплик) нужно: `replicas: 3`.
- Как проводить обновления (Rolling Update).
Аналогия: Deployment — это "начальник цеха". Он следит, чтобы 3 станка (Pod-а) всегда работали. Если один станок ломается, он его не чинит, а просто выбрасывает и заказывает новый.
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx-deployment
spec:
replicas: 3 # <-- Вот "желаемое состояние"
selector:
matchLabels:
app: nginx
template: # <-- А вот шаблон Pod-а, который нужно создавать
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
Проблема: У нас есть 3 Nginx Pod-а от Deployment-а. У каждого свой, постоянно меняющийся IP (т.к. Pod-ы умирают и создаются заново). Как другим Pod-ам (например, вашему Backend-у) найти эти Nginx Pod-ы?
Решение: Service.
Service — это стабильный внутренний IP-адрес и DNS-имя (`my-nginx-service`) для группы Pod-ов. Он работает как внутренний балансировщик нагрузки.
Аналогия: Service — это "внутренний номер отдела" в компании. Вам не нужно знать телефон каждого сотрудника (Pod-а) в отделе продаж. Вы звоните на общий номер (Service), и вас соединяют со свободным сотрудником.
apiVersion: v1
kind: Service
metadata:
name: my-nginx-service
spec:
type: ClusterIP # <-- Доступен только ВНУТРИ кластера
selector:
app: nginx # <-- "Искать все Pod-ы с этой меткой"
ports:
- protocol: TCP
port: 80 # <-- На какой порт приходят запросы
targetPort: 80 # <-- На какой порт Pod-а их отправлять
Проблема: `Service` (типа `ClusterIP`) дает доступ только *внутри* кластера. Как дать доступ пользователям из Интернета к нашему Nginx?
Решение: Ingress. Это "L7-маршрутизатор" (уровень HTTP). Он позволяет вам настроить правила, как внешний трафик должен попадать на ваши внутренние Service.
Он не работает сам по себе. Ему нужен Ingress Controller (например, `nginx-ingress`, `traefik`) — это, по сути, Pod с Nginx/Traefik, который смотрит на правила Ingress и применяет их.
Аналогия: Ingress — это "администратор" на входе в ваш бизнес-центр (кластер). Он спрашивает "Вы к кому?". "А, вы на `app.com/api`? Вам в `backend-service` (офис 101). А вы на `app.com`? Вам в `frontend-service` (офис 102)".
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app-ingress
spec:
rules:
- host: my-app.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-nginx-service # <-- Отправляем на наш Service
port:
number: 80
Проблема: Приложение нужно как-то конфигурировать (адрес базы данных, ключи API, пароли). "Зашивать" это в Docker-образ — ужасная практика (12 factors app).
Решение: Использовать K8s для "внедрения" конфигурации в Pod во время его запуска.
| Объект |
Назначение |
Пример |
| ConfigMap |
Для нечувствительных данных. |
`config.ini`, `APP_MODE=production`, URL-ы |
| Secret |
Для конфиденциальных данных. |
Пароль от БД, ключ API, TLS-сертификат |
Оба объекта можно "примонтировать" в Pod либо как переменные окружения (environment variables), либо как файлы (например, в `/etc/config/`).
💡 Pro-фишка: В чем "секрет" Secret-а?
По умолчанию, данные в `Secret` просто кодируются в Base64. Это не шифрование, а просто способ представить бинарные данные в тексте. Любой, у кого есть доступ к K8s API, может их прочитать. Для настоящей безопасности нужно настраивать шифрование `etcd` (базы данных K8s) и жестко ограничивать права доступа через RBAC.
Проблема: Pod-ы "смертны". Когда Pod (например, с базой данных) умирает, все данные на его диске исчезают. Как сохранить состояние?
Решение: `PersistentVolume` (PV) и `PersistentVolumeClaim` (PVC).
Это работает в двух частях — как "спрос" и "предложение":
- PersistentVolume (PV): "Предложение". Это сам "кусок" хранилища. Его настраивает Администратор кластера. (Например: "Вот 100GB на быстром SSD-диске в нашем NFS").
- PersistentVolumeClaim (PVC): "Спрос". Это "заявка" от вашего приложения (Pod-а). (Например: "Мне нужно 10GB хранилища для моей базы данных").
K8s "связывает" (bind) вашу заявку (PVC) с подходящим куском хранилища (PV). Ваш Pod "видит" только заявку (PVC).
Аналогия: PV — это "склад" с жесткими дисками. PVC — это ваша "заявка на выдачу" диска. Pod — это "компьютер", в который вы этот диск вставляете. Когда компьютер (Pod) сгорает, вы берете новый, вставляете в него тот же диск (PVC) и продолжаете работу.
# Заявка (PVC), которую мы пишем в YAML-е нашего приложения:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-db-pvc
spec:
accessModes:
- ReadWriteOnce # <-- Диск можно подключить к 1 Pod-у на R/W
resources:
requests:
storage: 10Gi # <-- "Прошу 10 Гигабайт"