Контейнеризация и оркестрация
Глава 15: Контейнеризация и оркестрация
Введение
Контейнеризация и оркестрация — это два столпа современной инфраструктуры, которые радикально изменили способ развёртывания и эксплуатации распределённых систем. Если предыдущие главы описывали что мы строим (базы данных, кэши, очереди, репликацию), то эта глава отвечает на вопрос как мы это запускаем и управляем этим в production.
Контейнер — это легковесная, изолированная среда для запуска приложения, которая включает в себя код, runtime, системные библиотеки и настройки. В отличие от виртуальных машин, контейнеры разделяют ядро хост-системы, что делает их значительно легче и быстрее.
Оркестрация — это автоматизация развёртывания, масштабирования и управления контейнеризированными приложениями. Kubernetes стал де-факто стандартом оркестрации, и понимание его архитектуры критически важно для любого System Design интервью.
Эволюция деплоя:
Bare Metal Virtual Machines Containers
(2000-е) (2005-2015) (2013+)
┌──────────┐ ┌──────────┐ ┌──────────┐
│ App A │ │ App A │ │ App A │
│ App B │ ├──────────┤ ├──────────┤
│ App C │ │ Guest OS │ │ Runtime │
├──────────┤ ├──────────┤ ├──────────┤
│ OS │ │Hypervisor│ │Container │
├──────────┤ ├──────────┤ │ Engine │
│ Hardware │ │ Host OS │ ├──────────┤
└──────────┘ ├──────────┤ │ Host OS │
│ Hardware │ ├──────────┤
└──────────┘ │ Hardware │
└──────────┘
Проблемы: Лучше, но: Легковесно:
- "works on my - Тяжёлые (GB) - Быстрый старт (мс)
machine" - Медленный старт (мин) - Маленький размер (MB)
- Конфликты - Overhead CPU/RAM - Минимальный overhead
зависимостей - Лицензии Guest OS - Portability
15.1. Docker: основы и best practices
Что такое Docker
Docker — это платформа для создания, доставки и запуска контейнеров. Docker популяризировал контейнеры, хотя технологии, лежащие в основе (Linux namespaces, cgroups), существовали и раньше.
Ключевые технологии Linux, используемые Docker:
┌──────────────────────────────────────────────┐
│ Контейнер │
│ │
│ Namespaces (изоляция): │
│ ├── PID — процессы │
│ ├── NET — сетевой стек │
│ ├── MNT — файловая система │
│ ├── UTS — hostname │
│ ├── IPC — межпроцессное взаимодействие │
│ └── USER — пользователи │
│ │
│ Cgroups (ограничение ресурсов): │
│ ├── CPU — лимиты и квоты │
│ ├── Memory — лимит памяти + OOM killer │
│ ├── I/O — ограничение диска │
│ └── Network — bandwidth limiting │
│ │
│ Union FS (слоистая файловая система): │
│ ├── Layer 3: app code (R/W) │
│ ├── Layer 2: pip install (R/O) │
│ ├── Layer 1: python:3.11 (R/O) │
│ └── Layer 0: debian:bookworm (R/O) │
│ │
└──────────────────────────────────────────────┘
Ключевые концепции Docker
| Концепция | Описание | Аналогия |
|---|---|---|
| Image | Неизменяемый шаблон с приложением и зависимостями | Класс в ООП |
| Container | Запущенный экземпляр image | Объект (инстанс класса) |
| Dockerfile | Инструкция для сборки image | Исходный код класса |
| Registry | Хранилище images (Docker Hub, ECR, GCR) | Maven/npm registry |
| Volume | Персистентное хранилище, существующее вне контейнера | Внешний диск |
| Network | Виртуальная сеть для связи контейнеров | VLAN |
Dockerfile: от простого к production-ready
# ❌ Наивный Dockerfile FROM python:3.11 COPY . /app WORKDIR /app RUN pip install -r requirements.txt CMD ["python", "main.py"] # Проблемы: # 1. Огромный размер (900+ MB из-за полного python image) # 2. Запуск от root # 3. Нет кэширования зависимостей # 4. Нет .dockerignore — копируются .git, __pycache__ и т.д.
# ✅ Production-ready Dockerfile (multi-stage build) # --- Stage 1: Build --- FROM python:3.11-slim AS builder WORKDIR /app # Копируем только файл зависимостей — кэширование слоёв! COPY requirements.txt . RUN pip install --no-cache-dir --user -r requirements.txt # Копируем код приложения COPY src/ ./src/ # --- Stage 2: Runtime --- FROM python:3.11-slim # Создаём непривилегированного пользователя RUN groupadd -r appuser && useradd -r -g appuser appuser WORKDIR /app # Копируем только установленные пакеты из builder stage COPY --from=builder /root/.local /home/appuser/.local COPY --from=builder /app/src ./src/ # Настраиваем PATH ENV PATH="/home/appuser/.local/bin:${PATH}" # Переключаемся на непривилегированного пользователя USER appuser # Health check HEALTHCHECK --interval=30s --timeout=3s --retries=3 \ CMD curl -f http://localhost:8080/health || exit 1 EXPOSE 8080 CMD ["python", "src/main.py"]
Best practices для Docker images
1. Минимизируйте размер image:
python:3.11 → 900 MB ❌
python:3.11-slim → 150 MB ✓
python:3.11-alpine → 50 MB ✓ (но возможны проблемы с musl libc)
Используйте multi-stage builds: отделяйте build-зависимости от runtime.
2. Оптимизируйте кэширование слоёв:
COPY requirements.txt . ← Сначала зависимости (меняются редко)
RUN pip install -r req.txt ← Кэшируется, если req.txt не изменился
COPY src/ ./src/ ← Код приложения (меняется часто)
Порядок инструкций = порядок инвалидации кэша!
3. Один процесс на контейнер:
❌ nginx + gunicorn + celery в одном контейнере
✅ Отдельные контейнеры для каждого процесса
4. Не храните секреты в image:
❌ ENV DATABASE_PASSWORD=secret123
✅ Передавайте через переменные окружения или secrets management
5. Используйте .dockerignore:
.git
__pycache__
*.pyc
.env
node_modules
.vscode
Docker Compose для локальной разработки
# docker-compose.yml version: "3.8" services: api: build: context: . dockerfile: Dockerfile ports: - "8080:8080" environment: - DATABASE_URL=postgresql://user:pass@db:5432/mydb - REDIS_URL=redis://cache:6379 depends_on: db: condition: service_healthy cache: condition: service_started db: image: postgres:16-alpine environment: POSTGRES_USER: user POSTGRES_PASSWORD: pass POSTGRES_DB: mydb volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U user"] interval: 5s timeout: 3s retries: 5 cache: image: redis:7-alpine ports: - "6379:6379" volumes: pgdata:
Docker Compose — инструмент для локальной разработки и тестирования. Для production используется оркестратор — Kubernetes.
Container Runtime: не только Docker
Docker — не единственный способ запускать контейнеры. Kubernetes взаимодействует с контейнерами через стандартизированный интерфейс CRI (Container Runtime Interface).
Эволюция container runtime в Kubernetes:
До K8s 1.20:
┌──────────┐ ┌────────────┐ ┌──────────┐
│ kubelet │────▶│ dockershim │───▶│ Docker │────▶ контейнер
└──────────┘ └────────────┘ │ Engine │
(встроен в K8s) └──────────┘
│
┌────┴─────┐
│containerd│ (Docker использует
└──────────┘ containerd внутри)
После K8s 1.24 (dockershim удалён):
┌──────────┐ ┌───────────┐
│ kubelet │────▶│containerd │────▶ контейнер
└──────────┘ └───────────┘
CRI (напрямую)
Альтернатива:
┌──────────┐ ┌───────────┐
│ kubelet │────▶│ CRI-O │────▶ контейнер
└──────────┘ └───────────┘
CRI (минималистичный runtime,
создан специально для K8s)
| Runtime | Описание | Используется в |
|---|---|---|
| containerd | Промышленный стандарт. Вырос из Docker, но работает самостоятельно | GKE, EKS, AKS, Docker Desktop |
| CRI-O | Минималистичный runtime, создан для K8s. Меньше поверхность атаки | OpenShift (Red Hat) |
| gVisor (runsc) | Sandbox-runtime с userspace-ядром. Дополнительная изоляция | GKE Sandbox, security-критичные нагрузки |
| Kata Containers | Микро-VM для каждого контейнера. VM-уровень изоляции | Multi-tenant облака, bare-metal |
| Firecracker | Micro-VM от Amazon. <125 мс старт, ~5 MB памяти | AWS Lambda, AWS Fargate |
Важно: удаление Docker из Kubernetes не означает, что Docker-образы перестали работать. Образы, собранные через
docker build, полностью совместимы с containerd и CRI-O — они все следуют стандарту OCI (Open Container Initiative).
Безопасность Docker-образов
Цепочка доверия к образам:
1. Сканирование уязвимостей (Vulnerability Scanning):
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ docker build │────▶│ Trivy / │────▶│ Registry │
│ │ │ Snyk / │ │ (если 0 │
│ │ │ Grype │ │ critical) │
└──────────────┘ └──────────────┘ └──────────────┘
Найдено:
- 2 Critical ❌ → блокирует push
- 5 High ⚠️ → предупреждение
- 12 Medium ℹ️ → информация
2. Подпись образов (Image Signing):
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ CI/CD │────▶│ Cosign / │────▶│ Registry │
│ pipeline │ │ Notary │ │ (подписан.) │
└──────────────┘ └──────────────┘ └──────────────┘
3. Admission Control (в Kubernetes):
┌──────────────┐ ┌───────────────────┐ ┌──────────┐
│ kubectl │────▶│ OPA Gatekeeper / │────▶│ Допущен │
│ apply │ │ Kyverno │ │ или │
│ │ │ │ │ отклонён│
└──────────────┘ │ Проверяет: │ └──────────┘
│ - образ подписан? │
│ - из разрешённого │
│ registry? │
│ - нет critical │
│ уязвимостей? │
│ - не запущен от │
│ root? │
└───────────────────┘
# Пример: Kyverno policy — запретить запуск от root apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata: name: disallow-root-user spec: validationFailureAction: Enforce rules: - name: check-runAsNonRoot match: any: - resources: kinds: - Pod validate: message: "Running as root is not allowed" pattern: spec: containers: - securityContext: runAsNonRoot: true
Docker Networking
Сетевые режимы Docker:
bridge (по умолчанию):
┌──────────────────────────────────────┐
│ Host │
│ ┌──────────┐ ┌──────────┐ │
│ │Container │ │Container │ │
│ │ 172.17. │ │ 172.17. │ │
│ │ 0.2 │ │ 0.3 │ │
│ └────┬─────┘ └────┬─────┘ │
│ │ │ │
│ ┌────┴───────────────┴────┐ │
│ │ docker0 bridge │ │
│ │ 172.17.0.1 │ │
│ └─────────┬───────────────┘ │
│ │ NAT │
│ ┌─────────┴───────────────┐ │
│ │ eth0 (host) │ │
└──┴─────────────────────────┴─────────┘
host:
Контейнер использует сетевой стек хоста напрямую.
Нет изоляции, но нет NAT overhead. Для high-performance.
none:
Контейнер полностью изолирован от сети.
Для batch-задач, не требующих сетевого доступа.
overlay (Docker Swarm / multi-host):
VXLAN-туннель между хостами для multi-host networking.
В Kubernetes заменяется CNI-плагинами.
15.2. Kubernetes: архитектура и компоненты
Kubernetes (K8s) — система оркестрации контейнеров, разработанная Google на основе внутренней системы Borg. Kubernetes абстрагирует инфраструктуру: вы описываете желаемое состояние (declarative), а K8s приводит к нему реальное состояние (reconciliation loop).
Архитектура кластера
Kubernetes Cluster Architecture:
┌──────────────────────────────────────────────────────────────────┐
│ Control Plane │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ API Server │ │ Scheduler │ │ Controller Manager │ │
│ │ (kube-api) │ │ │ │ │ │
│ │ │ │ Какой узел │ │ Reconciliation loops:│ │
│ │ Единая точка │ │ для нового │ │ - ReplicaSet ctrl │ │
│ │ входа. REST │ │ пода? │ │ - Deployment ctrl │ │
│ │ API + authz │ │ │ │ - Node ctrl │ │
│ └──────┬───────┘ └──────────────┘ │ - Job ctrl │ │
│ │ └──────────────────────┘ │
│ │ │
│ ┌──────┴───────┐ │
│ │ etcd │ Distributed key-value store │
│ │ │ Хранит ВСЁ состояние кластера │
│ │ (Raft │ Single source of truth │
│ │ consensus) │ │
│ └──────────────┘ │
└──────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Worker │ │ Worker │ │ Worker │
│ Node 1 │ │ Node 2 │ │ Node 3 │
│ │ │ │ │ │
│ ┌──────────┐ │ │ ┌──────────┐ │ │ ┌──────────┐ │
│ │ kubelet │ │ │ │ kubelet │ │ │ │ kubelet │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ Агент на │ │ │ │ Следит │ │ │ │ Запускает│ │
│ │ каждом │ │ │ │ за подами│ │ │ │ и убивает│ │
│ │ узле │ │ │ │ │ │ │ │ контейн. │ │
│ └──────────┘ │ │ └──────────┘ │ │ └──────────┘ │
│ ┌──────────┐ │ │ ┌──────────┐ │ │ ┌──────────┐ │
│ │kube-proxy│ │ │ │kube-proxy│ │ │ │kube-proxy│ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ Сетевые │ │ │ │ Правила │ │ │ │ iptables │ │
│ │ правила │ │ │ │ маршрут. │ │ │ │ / IPVS │ │
│ └──────────┘ │ │ └──────────┘ │ │ └──────────┘ │
│ │ │ │ │ │
│ ┌────┐ ┌────┐ │ │ ┌────┐ │ │ ┌────┐ ┌────┐ │
│ │Pod │ │Pod │ │ │ │Pod │ │ │ │Pod │ │Pod │ │
│ │ A │ │ B │ │ │ │ A │ │ │ │ B │ │ C │ │
│ └────┘ └────┘ │ │ └────┘ │ │ └────┘ └────┘ │
└───────────────┘ └───────────────┘ └───────────────┘
Компоненты Control Plane
| Компонент | Роль | Аналогия |
|---|---|---|
| API Server | Фронтенд кластера. Все команды (kubectl, контроллеры, kubelet) идут через него | API Gateway |
| etcd | Распределённое хранилище состояния кластера (Raft-консенсус) | База данных кластера |
| Scheduler | Назначает поды на узлы, учитывая ресурсы, affinity, taints | Диспетчер задач |
| Controller Manager | Запускает контроллеры — reconciliation loops, которые приводят текущее состояние к желаемому | Автопилот |
Reconciliation loop — сердце Kubernetes
Ключевая концепция K8s — декларативная модель. Вы описываете желаемое состояние, а контроллер непрерывно сравнивает текущее состояние с желаемым и вносит коррекции.
Reconciliation loop:
┌──────────────────────────────────────────────┐
│ │
│ ┌───────────┐ ┌───────────┐ │
│ │ Desired │ │ Current │ │
│ │ State │───?───▶│ State │ │
│ │ (etcd) │ == │ (cluster) │ │
│ └───────────┘ └───────────┘ │
│ │ │ │
│ │ Не совпадает? │ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌───────────────────────────────┐ │
│ │ Выполнить действие: │ │
│ │ - создать/удалить под │ │
│ │ - масштабировать replicas │ │
│ │ - перезапустить контейнер │ │
│ └───────────────────────────────┘ │
│ │ │
└─────────┘ (цикл повторяется каждые ~10с) │
└────────────────────────────────────┘
Пример:
Desired: replicas=3 Current: running=2 → создать ещё 1 под
Desired: replicas=3 Current: running=4 → удалить 1 под
Desired: image=v2 Current: image=v1 → rolling update
Основные объекты Kubernetes
Иерархия объектов K8s:
Deployment (управляет обновлениями)
└── ReplicaSet (гарантирует N реплик)
└── Pod (минимальная единица деплоя)
└── Container (один или несколько контейнеров)
StatefulSet (для stateful приложений)
└── Pod-0, Pod-1, Pod-2 (стабильные имена и хранилища)
DaemonSet (по одному поду на каждый узел)
└── Pod на Node 1, Pod на Node 2, ...
Job / CronJob (одноразовые и периодические задачи)
└── Pod (запускается, выполняет работу, завершается)
Сетевая модель Kubernetes
Kubernetes устанавливает три фундаментальных правила для сети:
- Каждый Pod получает уникальный IP-адрес.
- Все Pods могут общаться друг с другом без NAT (даже на разных узлах).
- Агенты на узле (kubelet, kube-proxy) могут общаться со всеми Pods на этом узле.
Эти правила реализуются через CNI (Container Network Interface) плагины.
CNI-плагины — реализация сетевой модели K8s:
┌─────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ │
│ Node 1 Node 2 │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Pod A │ │ Pod C │ │
│ │ 10.244.1.2 │──────────────▶│ 10.244.2.3 │ │
│ └──────────────┘ Прямая └──────────────┘ │
│ ┌──────────────┐ маршрутизация ┌──────────────┐ │
│ │ Pod B │ (без NAT!) │ Pod D │ │
│ │ 10.244.1.3 │ │ 10.244.2.4 │ │
│ └──────────────┘ └──────────────┘ │
│ │
│ CNI плагин обеспечивает связь между узлами через: │
│ - VXLAN overlay (Flannel, Calico VXLAN mode) │
│ - BGP маршрутизация (Calico, Cilium) │
│ - AWS VPC CNI (каждый Pod получает реальный VPC IP) │
│ - eBPF (Cilium — без iptables, высокая производительность)│
└─────────────────────────────────────────────────────────────┘
| CNI плагин | Механизм | Плюсы | Минусы |
|---|---|---|---|
| Flannel | VXLAN overlay | Простота настройки | Нет Network Policies, overhead от overlay |
| Calico | BGP / VXLAN | Network Policies, высокая производительность | Сложнее настройки |
| Cilium | eBPF | Без iptables, L7 policies, observability | Требует ядро Linux 4.9+ |
| AWS VPC CNI | Native VPC IPs | Нативная интеграция с AWS, нет overlay | Ограничение на кол-во IP на инстанс |
| Weave Net | Encrypted overlay | Шифрование из коробки | Overhead от шифрования |
Network Policies
Network Policies — это файрвол уровня Pod. По умолчанию все Pods могут общаться со всеми — Network Policies вводят ограничения.
# Пример: разрешить входящий трафик в api-server # только от pods с label role=frontend на порт 8080 apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: api-server-ingress namespace: production spec: podSelector: matchLabels: app: api-server policyTypes: - Ingress ingress: - from: - podSelector: matchLabels: role: frontend - namespaceSelector: matchLabels: env: production ports: - protocol: TCP port: 8080
Network Policy в действии:
Без Network Policy: С Network Policy:
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│Frontend│─▶│API │ │Frontend│─▶│API │
└────────┘ │Server │ └────────┘ │Server │
┌────────┐ │ │ ┌────────┐ │ │
│Random │─▶│ │ │Random │╳▶│ │ ← BLOCKED
│Pod │ └────────┘ │Pod │ └────────┘
└────────┘ └────────┘
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│API │─▶│Database│ │API │─▶│Database│
└────────┘ └────────┘ └────────┘ └────────┘
┌────────┐ │ │ ┌────────┐ │ │
│Frontend│─▶│ │ │Frontend│╳▶│ │ ← BLOCKED
└────────┘ └────────┘ └────────┘ └────────┘
Принцип наименьших привилегий: каждый Pod может
общаться только с теми, с кем ему действительно нужно.
Namespaces и multi-tenancy
Namespaces — логическое разделение кластера. Они обеспечивают изоляцию имён, но не сетевую или ресурсную изоляцию (для этого нужны Network Policies и ResourceQuotas).
Типичная структура namespaces:
┌───────────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ │
│ ┌────────────────┐ ┌────────────────┐ │
│ │ production │ │ staging │ │
│ │ ├─ api-server │ │ ├─ api-server │ │
│ │ ├─ web │ │ ├─ web │ │
│ │ └─ worker │ │ └─ worker │ │
│ └────────────────┘ └────────────────┘ │
│ │
│ ┌────────────────┐ ┌────────────────┐ │
│ │ monitoring │ │ kube-system │ │
│ │ ├─ prometheus │ │ ├─ coredns │ │
│ │ ├─ grafana │ │ ├─ kube-proxy │ │
│ │ └─ alertmgr │ │ └─ calico │ │
│ └────────────────┘ └────────────────┘ │
│ │
│ ┌────────────────┐ │
│ │ argocd │ │
│ │ └─ argocd-srv │ │
│ └────────────────┘ │
└───────────────────────────────────────────────────┘
# ResourceQuota — ограничение ресурсов для namespace apiVersion: v1 kind: ResourceQuota metadata: name: production-quota namespace: production spec: hard: requests.cpu: "40" # Макс. 40 CPU cores requests.memory: "80Gi" # Макс. 80 GB RAM limits.cpu: "80" limits.memory: "160Gi" pods: "200" # Макс. 200 подов services.loadbalancers: "5" # Макс. 5 LoadBalancer # LimitRange — ограничения по умолчанию для отдельных подов apiVersion: v1 kind: LimitRange metadata: name: default-limits namespace: production spec: limits: - default: # Limits по умолчанию (если не указаны) cpu: "500m" memory: "512Mi" defaultRequest: # Requests по умолчанию cpu: "100m" memory: "128Mi" max: # Максимум для одного контейнера cpu: "4" memory: "8Gi" min: # Минимум для одного контейнера cpu: "50m" memory: "64Mi" type: Container
Scheduling: Taints, Tolerations, Affinity
Kubernetes Scheduler решает, на какой узел поместить Pod. Помимо доступных ресурсов, можно явно управлять размещением.
Taints и Tolerations:
Taint на узле = "не пускать сюда поды, если они не толерантны"
Node A (taint: gpu=true:NoSchedule)
┌──────────────────────────────────┐
│ GPU Node │
│ │
│ [ML Pod] ✓ (toleration: gpu) │
│ [API Pod] X (нет toleration) │
└──────────────────────────────────┘
Node B (без taints)
┌──────────────────────────────────┐
│ General Node │
│ │
│ [API Pod] ✓ │
│ [ML Pod] ✓ (может быть и тут) │
└──────────────────────────────────┘
Affinity и Anti-Affinity:
Node Affinity: "хочу запускаться на узлах с SSD"
podAffinity: "хочу быть на том же узле, что и cache pod"
(для уменьшения latency)
podAntiAffinity: "не хочу быть на том же узле, что и другой
экземпляр моего же сервиса" (для отказоустойчивости)
# Пример: Pod Anti-Affinity для распределения реплик # по разным узлам и зонам доступности apiVersion: apps/v1 kind: Deployment metadata: name: api-server spec: replicas: 3 template: spec: affinity: # Не размещать 2 реплики api-server на одном узле podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - api-server topologyKey: kubernetes.io/hostname # Стараться разнести по разным availability zones preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - api-server topologyKey: topology.kubernetes.io/zone # Размещать на узлах с label disk=ssd nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: disk operator: In values: - ssd containers: - name: api image: api-server:2.1.0
Результат scheduling с anti-affinity:
AZ-a AZ-b AZ-c
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Node 1 │ │ Node 3 │ │ Node 5 │
│ [api-srv-0] │ │ [api-srv-1] │ │ [api-srv-2] │
│ │ │ │ │ │
│ Node 2 │ │ Node 4 │ │ Node 6 │
│ [worker] │ │ [worker] │ │ [worker] │
└──────────────┘ └──────────────┘ └──────────────┘
Если AZ-a полностью выходит из строя → 2 из 3 реплик продолжают работать.
Pod — минимальная единица деплоя в Kubernetes. Pod содержит один или несколько контейнеров, которые разделяют сетевое пространство (localhost) и volumes.
# Пример: Pod с sidecar-контейнером apiVersion: v1 kind: Pod metadata: name: web-app spec: containers: # Основное приложение - name: app image: myapp:1.0 ports: - containerPort: 8080 resources: requests: cpu: "250m" # 0.25 CPU core memory: "256Mi" # 256 MB RAM limits: cpu: "500m" memory: "512Mi" # Sidecar: сбор логов - name: log-collector image: fluentd:latest volumeMounts: - name: app-logs mountPath: /var/log/app volumes: - name: app-logs emptyDir: {}
15.3. Deployments, Services, Ingress
Это три ключевых объекта, с которыми вы будете работать в 90% случаев.
Deployment
Deployment описывает желаемое состояние приложения: какой image запускать, сколько реплик, как обновлять.
apiVersion: apps/v1 kind: Deployment metadata: name: api-server labels: app: api-server spec: replicas: 3 selector: matchLabels: app: api-server strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 # Макс. дополнительных подов при обновлении maxUnavailable: 0 # 0 = zero-downtime deployment template: metadata: labels: app: api-server spec: containers: - name: api image: api-server:2.1.0 ports: - containerPort: 8080 env: - name: DATABASE_URL valueFrom: secretKeyRef: name: db-credentials key: url readinessProbe: httpGet: path: /health/ready port: 8080 initialDelaySeconds: 5 periodSeconds: 10 livenessProbe: httpGet: path: /health/live port: 8080 initialDelaySeconds: 15 periodSeconds: 20 resources: requests: cpu: "500m" memory: "512Mi" limits: cpu: "1000m" memory: "1Gi"
Стратегии обновления
RollingUpdate (по умолчанию):
v1 v1 v1 v1 v1 v2 v1 v2 v2 v2 v2 v2
[A] [B] [C] → [A] [B] [D] → [A] [D] [E] → [D] [E] [F]
Постепенная замена подов. Zero-downtime, но на время
обновления работают обе версии (v1 и v2 одновременно).
Recreate:
v1 v1 v1 ─ ─ ─ v2 v2 v2
[A] [B] [C] → (все удалены) → [D] [E] [F]
Сначала удаляются все v1, потом создаются v2.
Простая стратегия, но есть downtime.
Blue-Green (реализуется через Service):
┌──────────────┐ ┌──────────────┐
│ Blue (v1) │ │ Green (v2) │
│ 3 pods │ │ 3 pods │
└──────┬───────┘ └──────┬───────┘
│ │
▼ ▼
┌──────────────────────────────────────┐
│ Service │
│ selector: version=v1 ──────────▶ │
│ (переключить на v2 одной командой) │
└──────────────────────────────────────┘
Canary (реализуется через Ingress или Service Mesh):
┌──────────────┐ 90% трафика
│ Stable (v1) │◀──────────────────┐
│ 9 pods │ │
└──────────────┘ ┌──────┴──────┐
┌──────────────┐ │ Ingress │
│ Canary (v2) │◀───────────│ Controller│
│ 1 pod │ 10% тр. └─────────────┘
└──────────────┘
Service
Service — это стабильная точка доступа к набору подов. Поды могут создаваться и удаляться, но IP-адрес Service остаётся неизменным.
apiVersion: v1 kind: Service metadata: name: api-server spec: selector: app: api-server # Все поды с этим label ports: - port: 80 # Порт Service targetPort: 8080 # Порт контейнера type: ClusterIP # Доступен только внутри кластера
Типы Service:
ClusterIP (по умолчанию):
┌──────────────────────────────────┐
│ Cluster │
│ │
│ ┌──────────┐ │
│ │ Service │ 10.96.0.1:80 │
│ │ ClusterIP│───┬───┬───┐ │
│ └──────────┘ │ │ │ │
│ ┌──┘ │ └───┐ │
│ ▼ ▼ ▼ │
│ [Pod 1] [Pod 2] [Pod 3]│
│ │
│ Доступен только внутри кластера │
└──────────────────────────────────┘
NodePort:
Открывает порт (30000-32767) на КАЖДОМ узле кластера.
Внешний трафик → Node IP:NodePort → Service → Pod
LoadBalancer:
Создаёт внешний балансировщик (AWS ALB/NLB, GCP LB).
Внешний трафик → Cloud LB → Service → Pod
ExternalName:
DNS CNAME на внешний сервис (например, external-db.example.com).
Ingress
Ingress — L7 (HTTP) маршрутизатор на входе в кластер. Один Ingress заменяет множество LoadBalancer-сервисов.
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: main-ingress annotations: nginx.ingress.kubernetes.io/ssl-redirect: "true" cert-manager.io/cluster-issuer: "letsencrypt-prod" spec: tls: - hosts: - api.example.com - web.example.com secretName: tls-secret rules: - host: api.example.com http: paths: - path: /v1 pathType: Prefix backend: service: name: api-server port: number: 80 - path: /v2 pathType: Prefix backend: service: name: api-server-v2 port: number: 80 - host: web.example.com http: paths: - path: / pathType: Prefix backend: service: name: web-frontend port: number: 80
Полный путь запроса:
Клиент
│
▼
DNS (api.example.com → Ingress LB IP)
│
▼
Cloud Load Balancer
│
▼
Ingress Controller (nginx / traefik / envoy)
│
│ Routing по host + path:
│ api.example.com/v1 → api-server Service
│ api.example.com/v2 → api-server-v2 Service
│ web.example.com → web-frontend Service
│
▼
Service (ClusterIP)
│
│ kube-proxy: iptables/IPVS rules
│ Балансировка по подам (round-robin)
│
▼
Pod (контейнер с приложением)
ConfigMaps и Secrets
ConfigMaps и Secrets — механизмы для отделения конфигурации от кода. Принцип twelve-factor app: конфигурация передаётся через окружение, а не зашивается в образ.
# ConfigMap — нечувствительная конфигурация apiVersion: v1 kind: ConfigMap metadata: name: app-config data: LOG_LEVEL: "info" MAX_CONNECTIONS: "100" # Можно хранить целые файлы nginx.conf: | server { listen 80; location / { proxy_pass http://api:8080; } } --- # Secret — чувствительные данные (base64, НЕ зашифровано!) apiVersion: v1 kind: Secret metadata: name: db-credentials type: Opaque data: username: cG9zdGdyZXM= # echo -n 'postgres' | base64 password: c3VwZXJzZWNyZXQ= # echo -n 'supersecret' | base64
Способы использования ConfigMap/Secret в Pod:
1. Переменные окружения:
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config
key: LOG_LEVEL
2. Volume mount (файл в контейнере):
volumeMounts:
- name: config
mountPath: /etc/config
volumes:
- name: config
configMap:
name: app-config
⚠️ Kubernetes Secrets — base64, а не шифрование!
Для настоящего шифрования используйте:
- Encryption at rest в etcd (EncryptionConfiguration)
- External Secrets Operator + Vault / AWS Secrets Manager
- Sealed Secrets (Bitnami) — шифрование в Git
DaemonSets, Jobs и CronJobs
DaemonSet — по одному поду на каждый (или выбранный) узел:
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Node 1 │ │ Node 2 │ │ Node 3 │ │ Node 4 │
│┌────────┐│ │┌────────┐│ │┌────────┐│ │┌────────┐│
││fluentd ││ ││fluentd ││ ││fluentd ││ ││fluentd ││
│└────────┘│ │└────────┘│ │└────────┘│ │└────────┘│
└──────────┘ └──────────┘ └──────────┘ └──────────┘
Типичные применения:
- Сбор логов (Fluentd, Filebeat)
- Мониторинг узлов (node-exporter, Datadog agent)
- CNI плагины (Calico, Cilium)
- Storage drivers (CSI node plugins)
# Job — одноразовая задача apiVersion: batch/v1 kind: Job metadata: name: db-migration spec: backoffLimit: 3 # Максимум 3 попытки при ошибке activeDeadlineSeconds: 600 # Таймаут 10 минут template: spec: restartPolicy: Never # Не перезапускать автоматически containers: - name: migrate image: myapp:2.1.0 command: ["python", "manage.py", "migrate"] env: - name: DATABASE_URL valueFrom: secretKeyRef: name: db-credentials key: url --- # CronJob — периодическая задача apiVersion: batch/v1 kind: CronJob metadata: name: nightly-backup spec: schedule: "0 2 * * *" # Каждый день в 2:00 UTC concurrencyPolicy: Forbid # Не запускать новый, если предыдущий ещё работает successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 5 jobTemplate: spec: template: spec: restartPolicy: OnFailure containers: - name: backup image: backup-tool:1.0 command: ["./backup.sh"]
Init Containers
Init-контейнеры запускаются до основных контейнеров пода, строго последовательно. Используются для подготовки окружения.
Жизненный цикл Pod с init-контейнерами:
┌─────────┐ ┌─────────┐ ┌──────────────────────────────┐
│ Init 1 │──▶│ Init 2 │──▶│ Main containers (параллельно)│
│ wait-db │ │ migrate │ │ app + sidecar │
│ │ │ │ │ │
│ Ждёт │ │ Миграция│ │ Основная работа │
│ пока DB │ │ схемы │ │ │
│ станет │ │ БД │ │ │
│ доступна│ │ │ │ │
└─────────┘ └─────────┘ └──────────────────────────────┘
(завершился) (завершился) (работает постоянно)
apiVersion: v1 kind: Pod metadata: name: api-server spec: initContainers: # Init 1: ждём, пока PostgreSQL станет доступна - name: wait-for-db image: busybox:1.36 command: ['sh', '-c', 'until nc -z postgres.default.svc.cluster.local 5432; do echo "Waiting for DB..."; sleep 2; done'] # Init 2: запускаем миграции - name: run-migrations image: myapp:2.1.0 command: ['python', 'manage.py', 'migrate'] env: - name: DATABASE_URL valueFrom: secretKeyRef: name: db-credentials key: url containers: - name: api image: myapp:2.1.0 ports: - containerPort: 8080
PersistentVolumes: хранилище в Kubernetes
Абстракция хранилища в K8s:
┌───────────────────────────────────────────────────────────┐
│ │
│ PersistentVolumeClaim (PVC) — "мне нужно 100Gi SSD" │
│ │ (запрос от разработчика)│
│ │ bind │
│ ▼ │
│ PersistentVolume (PV) — конкретный диск │
│ │ (создан админом или │
│ │ динамически) │
│ │ provision │
│ ▼ │
│ StorageClass — "как создавать диски" │
│ │ (gp3, io1, pd-ssd) │
│ │ │
│ ▼ │
│ Cloud Provider API — EBS, Persistent Disk, │
│ Azure Disk │
└───────────────────────────────────────────────────────────┘
Динамическое provisioning:
PVC создан → StorageClass → Cloud API создаёт диск → PV создан → bind
# StorageClass — определяет "тип" хранилища apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: fast-ssd provisioner: ebs.csi.aws.com # CSI driver для AWS EBS parameters: type: gp3 iops: "5000" throughput: "250" # MB/s reclaimPolicy: Retain # Не удалять диск при удалении PVC volumeBindingMode: WaitForFirstConsumer # Создать диск только когда Pod scheduled allowVolumeExpansion: true # Разрешить увеличение размера --- # PersistentVolumeClaim — запрос на хранилище apiVersion: v1 kind: PersistentVolumeClaim metadata: name: data-volume spec: accessModes: - ReadWriteOnce # Один узел для записи storageClassName: fast-ssd resources: requests: storage: 100Gi
Access Modes:
ReadWriteOnce (RWO) — один узел, чтение+запись
(EBS, Azure Disk, GCE PD)
ReadOnlyMany (ROX) — много узлов, только чтение
(NFS, CephFS)
ReadWriteMany (RWX) — много узлов, чтение+запись
(EFS, CephFS, NFS)
⚠️ Не поддерживается блочными хранилищами!
ReadWriteOncePod (RWOP) — ровно один Pod (K8s 1.27+)
Максимальная безопасность для stateful
RBAC: управление доступом в Kubernetes
RBAC (Role-Based Access Control) — механизм авторизации, определяющий кто и что может делать в кластере.
RBAC модель:
Subject (Кто?) Role (Что можно?) Binding (Связь)
┌─────────────────┐ ┌──────────────────┐ ┌──────────────┐
│ User: alice │ │ Role: developer │ │ RoleBinding: │
│ Group: dev-team │─────▶│ - get pods │◀────│ alice → │
│ ServiceAccount: │ │ - list services │ │ developer │
│ myapp │ │ - create deploy │ │ (namespace: │
└─────────────────┘ └──────────────────┘ │ production) │
└──────────────┘
Role / RoleBinding — в рамках одного namespace
ClusterRole / ClusterRoleBinding — на весь кластер
# Role — разрешения в namespace "production" apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: developer namespace: production rules: - apiGroups: [""] resources: ["pods", "services", "configmaps"] verbs: ["get", "list", "watch"] - apiGroups: ["apps"] resources: ["deployments"] verbs: ["get", "list", "watch", "update", "patch"] # Запрещено: create/delete deployments, secrets, etc. --- # RoleBinding — привязка Role к пользователю apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: alice-developer namespace: production subjects: - kind: User name: alice apiGroup: rbac.authorization.k8s.io - kind: Group name: dev-team apiGroup: rbac.authorization.k8s.io roleRef: kind: Role name: developer apiGroup: rbac.authorization.k8s.io
Типичная RBAC-стратегия:
┌──────────────┬─────────────────────────────────────────┐
│ Роль │ Разрешения │
├──────────────┼─────────────────────────────────────────┤
│ viewer │ get, list, watch на большинство ресурсов│
│ developer │ + create/update deployments, configmaps │
│ operator │ + delete pods, exec в контейнеры │
│ admin │ + manage secrets, RBAC, quotas │
│ cluster-admin│ полный доступ ко всему кластеру │
└──────────────┴─────────────────────────────────────────┘
ServiceAccounts для приложений:
Каждый Pod имеет ServiceAccount. Приложение может
использовать K8s API (например, leader election или
dynamic config) с минимальными привилегиями.
Pod Security
# SecurityContext — ограничения безопасности на уровне Pod/Container apiVersion: v1 kind: Pod metadata: name: secure-pod spec: securityContext: runAsNonRoot: true # Запретить запуск от root runAsUser: 1000 # Конкретный UID runAsGroup: 3000 fsGroup: 2000 # GID для volume mounts seccompProfile: type: RuntimeDefault # Ограничить syscalls containers: - name: app image: myapp:2.1.0 securityContext: allowPrivilegeEscalation: false # Нельзя получить root readOnlyRootFilesystem: true # Файловая система только для чтения capabilities: drop: - ALL # Убрать все Linux capabilities add: - NET_BIND_SERVICE # Разрешить только bind к port < 1024 volumeMounts: - name: tmp mountPath: /tmp # Нужен writable tmp volumes: - name: tmp emptyDir: {}
15.4. StatefulSets для stateful приложений
Большинство приложений — stateless: любой под взаимозаменяем, состояние хранится во внешней базе данных. Но сами базы данных, очереди и другие stateful-компоненты требуют стабильной идентичности и персистентного хранилища.
Проблемы Deployment для stateful нагрузок
Deployment (stateless):
Поды: api-server-7f8b9-abcde, api-server-7f8b9-xyz12
├── Имена случайные, меняются при пересоздании
├── Нет гарантий порядка запуска/остановки
├── Все поды идентичны
└── Нет привязки к конкретному хранилищу
StatefulSet (stateful):
Поды: postgres-0, postgres-1, postgres-2
├── Стабильные имена (индекс сохраняется)
├── Упорядоченный запуск: 0 → 1 → 2
├── Упорядоченная остановка: 2 → 1 → 0
├── Каждый под привязан к своему PersistentVolume
└── Стабильный сетевой идентификатор (DNS)
Пример: PostgreSQL кластер на StatefulSet
apiVersion: apps/v1 kind: StatefulSet metadata: name: postgres spec: serviceName: postgres # Headless Service для DNS replicas: 3 selector: matchLabels: app: postgres template: metadata: labels: app: postgres spec: containers: - name: postgres image: postgres:16 ports: - containerPort: 5432 env: - name: POSTGRES_PASSWORD valueFrom: secretKeyRef: name: pg-secret key: password - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name volumeMounts: - name: pgdata mountPath: /var/lib/postgresql/data # Каждый под получает свой PersistentVolumeClaim volumeClaimTemplates: - metadata: name: pgdata spec: accessModes: ["ReadWriteOnce"] storageClassName: ssd resources: requests: storage: 100Gi
StatefulSet + Headless Service:
Headless Service (clusterIP: None):
Не создаёт единый IP, а регистрирует DNS-записи для каждого пода.
DNS-записи:
postgres-0.postgres.default.svc.cluster.local → Pod IP
postgres-1.postgres.default.svc.cluster.local → Pod IP
postgres-2.postgres.default.svc.cluster.local → Pod IP
PersistentVolume привязка:
┌────────────┐ ┌──────────┐
│ postgres-0 │────▶│ PV-0 │ 100Gi SSD
└────────────┘ └──────────┘
┌────────────┐ ┌──────────┐
│ postgres-1 │────▶│ PV-1 │ 100Gi SSD
└────────────┘ └──────────┘
┌────────────┐ ┌──────────┐
│ postgres-2 │────▶│ PV-2 │ 100Gi SSD
└────────────┘ └──────────┘
Если postgres-1 перезапускается на другом узле,
он получит тот же PV-1 с теми же данными.
Когда использовать StatefulSet
| Используйте StatefulSet | Используйте Deployment |
|---|---|
| Базы данных (PostgreSQL, MySQL, MongoDB) | Веб-серверы, API |
| Очереди (Kafka, RabbitMQ) | Воркеры для фоновых задач |
| Распределённые кэши (Redis Cluster) | Stateless микросервисы |
| Elasticsearch, ZooKeeper | Batch-процессоры |
| Любой сервис, которому нужна стабильная идентичность | Всё, что не хранит состояние локально |
На практике многие команды предпочитают управляемые сервисы (RDS, CloudSQL, Managed Kafka) вместо самостоятельного запуска stateful-нагрузок в K8s. StatefulSets добавляют значительную операционную сложность.
15.5. Horizontal Pod Autoscaler
HPA (Horizontal Pod Autoscaler) — контроллер, который автоматически масштабирует количество подов на основе метрик.
Как работает HPA
HPA Reconciliation Loop:
┌──────────────┐ ┌─────────────────┐
│ Metrics │ │ HPA │
│ Server │─────▶│ Controller │
│ │ │ │
│ CPU: 80% │ │ target: 50% │
│ Memory: 60% │ │ min: 2, max: 10 │
│ Custom: ... │ │ │
└──────────────┘ └───────┬─────────┘
│
Текущий CPU: 80%
Target: 50%
Текущие реплики: 3
│
Желаемые реплики:
ceil(3 × 80/50) = ceil(4.8) = 5
│
▼
┌─────────────────┐
│ Deployment │
│ replicas: 3 → 5 │
└─────────────────┘
Конфигурация HPA
apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: api-server-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: api-server minReplicas: 2 maxReplicas: 20 behavior: scaleUp: stabilizationWindowSeconds: 60 # Ждать 60с перед scale up policies: - type: Percent value: 100 # Удвоить за один шаг periodSeconds: 60 scaleDown: stabilizationWindowSeconds: 300 # Ждать 5 мин перед scale down policies: - type: Pods value: 1 # Уменьшать по 1 поду periodSeconds: 60 metrics: # CPU utilization - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 60 # Custom metric: RPS per pod - type: Pods pods: metric: name: http_requests_per_second target: type: AverageValue averageValue: "1000"
Типы автоскейлинга в Kubernetes
┌──────────────────────────────────────────────────────────┐
│ │
│ HPA (Horizontal Pod Autoscaler) │
│ Меняет количество подов (replicas) │
│ 2 pods ──▶ 5 pods ──▶ 10 pods │
│ │
│ VPA (Vertical Pod Autoscaler) │
│ Меняет ресурсы пода (CPU/Memory requests/limits) │
│ 256Mi ──▶ 512Mi ──▶ 1Gi │
│ │
│ Cluster Autoscaler │
│ Меняет количество узлов (worker nodes) │
│ 3 nodes ──▶ 5 nodes ──▶ 10 nodes │
│ │
│ KEDA (Kubernetes Event-Driven Autoscaling) │
│ Скейлинг по внешним метрикам (Kafka lag, SQS depth) │
│ Может масштабировать до 0 (scale-to-zero) │
│ │
└──────────────────────────────────────────────────────────┘
Типичная комбинация:
HPA масштабирует поды → Cluster Autoscaler добавляет узлы,
если подам не хватает ресурсов на существующих узлах.
Важные нюансы HPA
-
Requests обязательны. HPA использует requests для расчёта utilization. Без указанных requests для CPU/Memory HPA не сможет вычислить процент использования.
-
Cooldown периоды. Без stabilization window HPA может «flap» — масштабироваться вверх-вниз. Рекомендуется: быстрый scale up (60с), медленный scale down (300с).
-
HPA и VPA несовместимы для одного ресурса (например, оба управляют CPU). Используйте HPA для горизонтального масштабирования и VPA только для рекомендаций.
15.6. Helm и GitOps
Helm: пакетный менеджер для Kubernetes
Helm решает проблему управления множеством YAML-файлов. Вместо десятков разрозненных манифестов вы создаёте chart — шаблонизированный пакет.
Helm Chart структура:
my-app/
├── Chart.yaml # Метаданные чарта (имя, версия)
├── values.yaml # Значения по умолчанию
├── values-prod.yaml # Override для production
├── values-staging.yaml # Override для staging
└── templates/
├── deployment.yaml # Шаблон с {{ .Values.xxx }}
├── service.yaml
├── ingress.yaml
├── hpa.yaml
├── configmap.yaml
└── _helpers.tpl # Переиспользуемые шаблоны
# values.yaml (значения по умолчанию) replicaCount: 2 image: repository: api-server tag: "1.0.0" resources: requests: cpu: "250m" memory: "256Mi" limits: cpu: "500m" memory: "512Mi" autoscaling: enabled: false minReplicas: 2 maxReplicas: 10
# values-prod.yaml (production overrides) replicaCount: 5 image: tag: "2.1.0" resources: requests: cpu: "1000m" memory: "1Gi" limits: cpu: "2000m" memory: "2Gi" autoscaling: enabled: true minReplicas: 5 maxReplicas: 50
# templates/deployment.yaml (шаблон) apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Release.Name }}-{{ .Chart.Name }} spec: replicas: {{ .Values.replicaCount }} template: spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" resources: {{- toYaml .Values.resources | nindent 12 }}
# Установка/обновление helm install my-app ./my-app -f values-prod.yaml helm upgrade my-app ./my-app -f values-prod.yaml # Откат helm rollback my-app 1 # Откатить к ревизии 1 # История helm history my-app
GitOps
GitOps — это операционная модель, в которой Git-репозиторий является единственным источником истины для желаемого состояния инфраструктуры. Инструменты (ArgoCD, Flux) следят за репозиторием и автоматически применяют изменения.
GitOps workflow:
Разработчик
│
│ 1. git push (изменил values.yaml: tag 2.0 → 2.1)
▼
┌──────────┐
│ Git │ Single source of truth
│ Repo │
└─────┬────┘
│
│ 2. ArgoCD обнаруживает diff
▼
┌──────────────┐
│ ArgoCD / │ Reconciliation:
│ Flux │ Git state ≠ Cluster state
│ │ → Apply changes
└──────┬───────┘
│
│ 3. kubectl apply (автоматически)
▼
┌──────────────┐
│ Kubernetes │ Desired state applied
│ Cluster │
└──────────────┘
Преимущества:
- Audit trail: git log = история всех изменений
- Rollback: git revert = откат инфраструктуры
- Consistency: нет "kubectl apply" руками
- PR review: изменения инфраструктуры проходят code review
ArgoCD: пример конфигурации
apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: api-server namespace: argocd spec: project: default source: repoURL: https://github.com/company/k8s-manifests.git targetRevision: main path: apps/api-server helm: valueFiles: - values-prod.yaml destination: server: https://kubernetes.default.svc namespace: production syncPolicy: automated: prune: true # Удалять ресурсы, которых нет в Git selfHeal: true # Откатывать ручные изменения в кластере syncOptions: - CreateNamespace=true
Типичная Git-структура для GitOps:
k8s-manifests/
├── apps/
│ ├── api-server/
│ │ ├── Chart.yaml
│ │ ├── values.yaml
│ │ ├── values-staging.yaml
│ │ ├── values-prod.yaml
│ │ └── templates/
│ ├── web-frontend/
│ │ └── ...
│ └── worker/
│ └── ...
├── infrastructure/
│ ├── cert-manager/
│ ├── ingress-nginx/
│ ├── prometheus/
│ └── argocd/
└── environments/
├── staging/
│ └── kustomization.yaml
└── production/
└── kustomization.yaml
Kustomize: альтернатива Helm
Kustomize встроен в kubectl и использует overlay-подход без шаблонизации — вместо {{ .Values.xxx }} используются патчи поверх базовых манифестов.
Kustomize структура:
k8s/
├── base/ # Базовые манифесты
│ ├── kustomization.yaml
│ ├── deployment.yaml
│ ├── service.yaml
│ └── hpa.yaml
├── overlays/
│ ├── staging/ # Overlay для staging
│ │ ├── kustomization.yaml
│ │ └── patch-replicas.yaml
│ └── production/ # Overlay для production
│ ├── kustomization.yaml
│ ├── patch-replicas.yaml
│ └── patch-resources.yaml
# base/kustomization.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - deployment.yaml - service.yaml - hpa.yaml --- # overlays/production/kustomization.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - ../../base namespace: production namePrefix: prod- patches: - path: patch-replicas.yaml - path: patch-resources.yaml images: - name: api-server newTag: "2.1.0" --- # overlays/production/patch-replicas.yaml apiVersion: apps/v1 kind: Deployment metadata: name: api-server spec: replicas: 10
# Применение kubectl apply -k overlays/production/ # Просмотр сгенерированных манифестов kubectl kustomize overlays/production/
Helm vs Kustomize:
┌──────────────┬─────────────────────┬──────────────────────┐
│ │ Helm │ Kustomize │
├──────────────┼─────────────────────┼──────────────────────┤
│ Подход │ Шаблонизация │ Overlay / Patching │
│ Язык │ Go templates │ Чистый YAML + патчи │
│ Установка │ Нужен Helm CLI │ Встроен в kubectl │
│ Пакетирование│ Charts, репозитории │ Нет пакетов │
│ Rollback │ helm rollback │ git revert │
│ Сложность │ Мощно, но сложно │ Просто, но ограничено│
│ Когда │ Переиспользуемые │ Внутренние проекты │
│ использовать │ пакеты, community │ с overlay по env │
└──────────────┴─────────────────────┴──────────────────────┘
15.7. Service Mesh
Service Mesh — выделенный инфраструктурный слой для управления межсервисным трафиком. Реализуется через sidecar-прокси, которые перехватывают весь сетевой трафик пода.
Архитектура Service Mesh:
Без Service Mesh:
┌──────────┐ ┌──────────┐
│ Service │ HTTP │ Service │
│ A │─────────▶│ B │
└──────────┘ └──────────┘
(retry? timeout? mTLS? tracing? — нужно реализовать в коде)
С Service Mesh (Istio):
┌──────────────────┐ ┌──────────────────┐
│ Pod │ │ Pod │
│ ┌──────┐┌──────┐ │ │ ┌──────┐┌──────┐ │
│ │Svc A ││Envoy │ │ mTLS │ │Envoy ││Svc B │ │
│ │ ││proxy │─┼──────────┼▶│proxy ││ │ │
│ │ app ││ │ │ encrypted│ │ ││ app │ │
│ └──────┘└──────┘ │ │ └──────┘└──────┘ │
└──────────────────┘ └──────────────────┘
▲ ▲
│ ┌──────────────┐ │
└─────│ Control Plane│─────────┘
│ (istiod) │
│ │
│ Конфигурация:│
│ - routing │
│ - retries │
│ - mTLS │
│ - rate limit │
└──────────────┘
Что даёт Service Mesh
| Возможность | Без Service Mesh | С Service Mesh |
|---|---|---|
| mTLS | Реализовать в каждом сервисе | Автоматически, transparent |
| Retries / Timeouts | Библиотека в коде | Конфигурация в YAML |
| Circuit Breaking | Библиотека (Hystrix, etc.) | Конфигурация |
| Traffic Splitting | Сложная настройка Ingress | VirtualService: 90/10 split |
| Distributed Tracing | Инструментация кода | Автоматические trace headers |
| Observability | Prometheus + manual metrics | Automatic L7 metrics (latency, errors, RPS) |
| Access Control | Network Policies (L3/L4) | L7 policies (по HTTP path, method) |
# Istio VirtualService — canary release apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: api-server spec: hosts: - api-server http: - route: - destination: host: api-server subset: stable weight: 90 - destination: host: api-server subset: canary weight: 10 retries: attempts: 3 perTryTimeout: 2s retryOn: 5xx,reset,connect-failure timeout: 10s --- # Istio DestinationRule — circuit breaker apiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: api-server spec: host: api-server trafficPolicy: connectionPool: tcp: maxConnections: 100 http: h2UpgradePolicy: DEFAULT http1MaxPendingRequests: 100 http2MaxRequests: 1000 outlierDetection: consecutive5xxErrors: 5 # 5 ошибок подряд interval: 10s # за 10 секунд baseEjectionTime: 30s # выкинуть на 30с maxEjectionPercent: 30 # макс. 30% подов subsets: - name: stable labels: version: v1 - name: canary labels: version: v2
Популярные Service Mesh решения:
┌────────────┬─────────────────────────────────────────────┐
│ Istio │ Самый функциональный. Envoy sidecar. │
│ │ Сложный в эксплуатации. Google-backed. │
├────────────┼─────────────────────────────────────────────┤
│ Linkerd │ Легковесный, простой. Rust-based proxy. │
│ │ Меньше overhead, быстрее развёртывание. │
│ │ CNCF graduated project. │
├────────────┼─────────────────────────────────────────────┤
│ Cilium │ eBPF-based. Может работать без sidecar. │
│ │ Высокая производительность. CNI + Mesh. │
├────────────┼─────────────────────────────────────────────┤
│ Consul │ HashiCorp. Service mesh + service discovery.│
│ Connect │ Multi-platform (K8s + VMs). │
└────────────┴─────────────────────────────────────────────┘
Trade-off:
Service Mesh добавляет latency (~1-3ms на hop) и
потребление ресурсов (sidecar ~50-100MB RAM на pod).
Оправдан при 10+ микросервисах, когда сложность
межсервисного общения становится проблемой.
15.8. Реальные инциденты и операционные уроки
Понимание типичных сбоев в production критически важно для System Design интервью.
Инцидент 1: OOM-каскад (реальный кейс — крупный e-commerce)
Сценарий:
1. Один Pod получил spike трафика и начал потреблять больше памяти
2. Превысил memory limit → OOM killed
3. Трафик перераспределился на оставшиеся поды
4. Они тоже начали OOM-килиться (каскад)
5. HPA начал создавать новые поды, но Cluster Autoscaler
не успевал добавлять узлы
6. Новые поды в статусе Pending (нет ресурсов)
Результат: 15 минут degraded service
Уроки:
✅ Правильные resource limits (с запасом для spike)
✅ PodDisruptionBudget (minAvailable: 50%)
✅ Cluster Autoscaler с достаточным headroom
✅ Overprovisioning: "пустые" placeholder-поды с низким приоритетом,
которые мгновенно вытесняются реальными подами
Инцидент 2: Deadlock при rolling update
Сценарий:
1. Deployment с replicas=3, maxSurge=1, maxUnavailable=0
2. Новая версия v2 не проходит readinessProbe (баг в health endpoint)
3. K8s создаёт Pod v2, ждёт readiness — не дожидается
4. Не может продолжить update (maxUnavailable=0 → нельзя убить v1)
5. Не может создать ещё v2 (maxSurge=1 → уже создан один)
6. Deployment застрял: v1 работает, v2 не стартует
Решение:
kubectl rollout undo deployment/api-server
(откат к предыдущей версии)
Уроки:
✅ Тестировать health endpoints в CI/CD
✅ progressDeadlineSeconds в Deployment (таймаут на rollout)
✅ Алерт на "deployment stuck" (rollout не завершён за N минут)
✅ kubectl rollout status --timeout=300s в CI/CD pipeline
Инцидент 3: etcd out of space
Сценарий:
1. Кластер с 500+ CronJobs, каждый создаёт Job-объекты
2. successfulJobsHistoryLimit и failedJobsHistoryLimit не настроены
3. etcd заполнился тысячами завершённых Job/Pod объектов
4. etcd перестал принимать записи → весь кластер read-only
5. Невозможно создавать/удалять любые ресурсы
Уроки:
✅ Всегда настраивать historyLimit для CronJobs
✅ Мониторить размер etcd (etcd_db_total_size_in_bytes)
✅ etcd compaction и defrag на регулярной основе
✅ Алерт: etcd usage > 70%
Инцидент 4: DNS storm при масштабировании
Сценарий:
1. HPA масштабировал сервис с 10 до 100 подов за минуту
2. Каждый под при старте делает DNS-запросы к зависимым сервисам
3. CoreDNS (2 реплики) получил 10x spike DNS-запросов
4. CoreDNS начал отвечать медленно → таймауты в приложениях
5. Приложения не могут resolve DNS → 500 ошибки
Уроки:
✅ Масштабировать CoreDNS с кластером (DNS Autoscaler)
✅ NodeLocal DNSCache (DaemonSet с DNS-кэшем на каждом узле)
✅ HPA behavior: ограничить скорость scale up
✅ DNS caching в приложении (TTL)
15.9. Оптимизация стоимости Kubernetes
Kubernetes в облаке может быть дорогим. Грамотная оптимизация снижает затраты на 30-60%.
Основные источники перерасхода:
┌──────────────────────────────────────────────────────────┐
│ │
│ 1. Over-provisioned resources │
│ requests: 4 CPU, 8Gi ← забронировано │
│ actual usage: 0.5 CPU, 1Gi ← реальное потребление │
│ → 87% ресурсов простаивает и стоит денег │
│ │
│ 2. Idle nodes │
│ Cluster Autoscaler не уменьшает кластер достаточно │
│ быстро после падения нагрузки │
│ │
│ 3. Одинаковые ресурсы для prod и non-prod │
│ staging с теми же resource limits, что и production │
│ │
│ 4. On-Demand инстансы для всего │
│ Spot/Preemptible инстансы дешевле на 60-90% │
│ │
└──────────────────────────────────────────────────────────┘
Стратегии оптимизации
1. Right-sizing с VPA recommendations:
kubectl top pods # Реальное потребление
kubectl get vpa api-server -o yaml # Рекомендации VPA
VPA в режиме "Off" — только рекомендации, без автоматического
изменения. Используйте для обоснованного ручного тюнинга.
2. Spot/Preemptible инстансы:
┌────────────────────────────────────────────────────┐
│ Node Pool 1: On-Demand (reliable) │
│ - Stateful нагрузки (databases, Kafka) │
│ - Критичные сервисы (payment, auth) │
│ - 30% кластера │
├────────────────────────────────────────────────────┤
│ Node Pool 2: Spot instances (-60-90% стоимости) │
│ - Stateless сервисы с graceful shutdown │
│ - Worker/batch нагрузки │
│ - 70% кластера │
│ - Используйте несколько instance types для │
│ уменьшения вероятности одновременного отзыва │
└────────────────────────────────────────────────────┘
3. Pod Priority и Preemption:
Overprovisioning через placeholder-поды:
┌───────────────────┐
│ Placeholder Pod │ priority: -1 (самый низкий)
│ requests: 4 CPU │ Занимает ресурсы, но вытесняется
│ (ничего не делает)│ мгновенно при появлении реальной нагрузки
└───────────────────┘
→ Узел уже запущен, не нужно ждать Cluster Autoscaler
4. Namespace-level ResourceQuotas:
Ограничить staging/dev — нельзя случайно запросить 100 CPU.
5. Инструменты анализа:
- Kubecost — расчёт стоимости по namespace/deployment/pod
- kubectl-cost — CLI для быстрого анализа
- Cloud provider cost explorer с K8s labels
Пример экономии на реальном кластере:
До оптимизации:
├── 50 On-Demand nodes (m5.2xlarge) = $14,400/мес
├── Avg CPU utilization: 15%
└── Avg Memory utilization: 25%
После оптимизации:
├── 10 On-Demand nodes (m5.xlarge) = $1,440/мес (critical workloads)
├── 15 Spot nodes (m5.xlarge) = $648/мес (stateless)
├── Right-sized requests = -40% nodes needed
├── Avg CPU utilization: 55%
└── Avg Memory utilization: 65%
Итого: $14,400 →$2,088/мес (-85%)
15.10. Практические паттерны и anti-patterns
Resource Requests и Limits
Правильная настройка ресурсов — критически важна:
❌ Без requests/limits:
Pod может занять все ресурсы узла → OOM другим подам
❌ Limits = Requests (слишком жёстко):
Нельзя использовать простаивающие ресурсы → перерасход инфраструктуры
✅ Requests < Limits (рекомендуется):
requests:
cpu: "500m" # Гарантированный минимум
memory: "512Mi"
limits:
cpu: "1000m" # Burst до 1 core
memory: "1Gi" # OOM kill при превышении
QoS классы в K8s:
┌─────────────┬──────────────────────────────────────────┐
│ Guaranteed │ requests == limits (для всех контейнеров)│
│ │ Последним убивается при нехватке ресурсов│
├─────────────┼──────────────────────────────────────────┤
│ Burstable │ requests < limits │
│ │ Может использовать больше, чем requests │
├─────────────┼──────────────────────────────────────────┤
│ BestEffort │ Нет requests и limits │
│ │ Первым убивается при нехватке ресурсов │
└─────────────┴──────────────────────────────────────────┘
Probes: Liveness, Readiness, Startup
┌──────────────────────────────────────────────────────────┐
│ │
│ startupProbe (K8s 1.18+): │
│ "Приложение запустилось?" │
│ Пока не пройдёт — liveness и readiness не проверяются. │
│ Нужен для медленно стартующих приложений (Java, .NET). │
│ │
│ livenessProbe: │
│ "Приложение живо?" │
│ Если не проходит → K8s перезапускает контейнер. │
│ Осторожно: слишком агрессивная проверка = restart loop. │
│ │
│ readinessProbe: │
│ "Приложение готово принимать трафик?" │
│ Если не проходит → под убирается из Service endpoints. │
│ Трафик перестаёт поступать, но контейнер НЕ убивается. │
│ │
└──────────────────────────────────────────────────────────┘
Типичная настройка:
startupProbe: failureThreshold: 30, periodSeconds: 10
(до 5 минут на старт)
livenessProbe: periodSeconds: 20, failureThreshold: 3
(перезапуск через 60с отсутствия отклика)
readinessProbe: periodSeconds: 5, failureThreshold: 3
(перестать слать трафик через 15с)
Anti-patterns
1. ❌ Latest tag в production
image: api-server:latest
Проблемы: неповторяемые деплои, невозможно откатиться.
✅ Используйте конкретные теги: api-server:2.1.0-sha-abc123
2. ❌ Один гигантский Pod со всеми сервисами
Нарушает принцип single responsibility.
✅ Отдельные Deployment для каждого сервиса.
3. ❌ Hardcoded конфигурация в image
ENV DATABASE_URL=prod-db.internal:5432 (внутри Dockerfile)
✅ ConfigMap + Secret + environment variables
4. ❌ Отсутствие PodDisruptionBudget
При обслуживании узлов все поды могут быть одновременно удалены.
✅ PodDisruptionBudget: minAvailable: 2
5. ❌ Игнорирование graceful shutdown
При остановке пода SIGTERM отправляется контейнеру.
Если приложение не обрабатывает SIGTERM — через 30с SIGKILL.
✅ Обрабатывайте SIGTERM: завершите текущие запросы, закройте соединения.
# Пример: graceful shutdown в Python (FastAPI) import signal import asyncio from fastapi import FastAPI app = FastAPI() shutdown_event = asyncio.Event() def handle_sigterm(signum, frame): print("Received SIGTERM, starting graceful shutdown...") shutdown_event.set() signal.signal(signal.SIGTERM, handle_sigterm) @app.get("/health/ready") async def readiness(): if shutdown_event.is_set(): return {"status": "shutting_down"}, 503 return {"status": "ready"} @app.on_event("shutdown") async def shutdown(): # Ждём завершения текущих запросов print("Closing database connections...") # await db.close() print("Shutdown complete")
15.11. Kubernetes в контексте System Design интервью
На System Design интервью вас не спросят «напишите YAML для Deployment». Но от вас ожидают понимания как ваш дизайн будет развёрнут и эксплуатироваться.
Что важно уметь объяснить
1. Деплоймент стратегия:
"Сервис развёртывается как Deployment с 3+ репликами,
rolling update с maxUnavailable=0 для zero-downtime."
2. Масштабирование:
"HPA масштабирует от 5 до 50 подов по CPU (target 60%)
и custom метрике RPS (target 1000/pod).
Cluster Autoscaler добавляет узлы при необходимости."
3. Stateful vs Stateless:
"API-серверы — stateless Deployment.
Kafka — StatefulSet с персистентными PV.
PostgreSQL — managed сервис (RDS) вне кластера."
4. Service Discovery:
"Внутренний трафик через ClusterIP Services.
Внешний — через Ingress с TLS termination."
5. Конфигурация:
"Секреты — через Kubernetes Secrets (или Vault).
Конфигурация — ConfigMap. Всё управляется через GitOps."
Пример: развёртывание системы из нескольких микросервисов
Типичная архитектура в K8s:
Internet
│
▼
┌─────────────────┐
│ Ingress (nginx) │ TLS termination, routing
└────────┬────────┘
│
┌────┴───────┐
▼ ▼
┌────────┐ ┌────────┐
│ API GW │ │ Web │ Deployment (stateless)
│ 3 pods │ │ 2 pods │ HPA enabled
└───┬────┘ └────────┘
│
┌─┴───────────┬────────────┐
▼ ▼ ▼
┌────────┐ ┌──────────┐ ┌──────────┐
│ Users │ │ Orders │ │ Payment │ Deployment (stateless)
│ 3 pods │ │ 5 pods │ │ 3 pods │ HPA enabled
└───┬────┘ └─────┬────┘ └────┬─────┘
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│PostgreSQL│ │ Kafka │ │ Redis │
│ (RDS) │ │StatefulSt│ │ (managed)│ Managed / StatefulSet
└──────────┘ │ 3 pods │ └──────────┘
└──────────┘
Вспомогательные компоненты (в кластере):
- Prometheus + Grafana (мониторинг)
- Fluentd + Loki (логирование)
- cert-manager (TLS сертификаты)
- ArgoCD (GitOps деплой)
15.12. Вопросы для самопроверки
-
В чём принципиальная разница между контейнером и виртуальной машиной? Какие Linux-примитивы (namespaces, cgroups) обеспечивают изоляцию контейнеров? Почему контейнеры стартуют быстрее?
-
Объясните, как работает Docker layer caching. Почему порядок инструкций в Dockerfile важен для скорости сборки? Напишите оптимальный Dockerfile для Go-приложения с multi-stage build.
-
Что произойдёт, если Pod не проходит readinessProbe, но проходит livenessProbe? А если наоборот? Приведите реальные сценарии, когда каждая ситуация возникает.
-
Чем StatefulSet отличается от Deployment? Приведите три примера нагрузок, для которых нужен StatefulSet. Почему многие компании предпочитают managed-сервисы вместо StatefulSets?
-
Опишите reconciliation loop в Kubernetes. Почему декларативная модель лучше императивной для управления инфраструктурой? Что произойдёт, если кто-то вручную удалит Pod, управляемый Deployment?
-
Вы деплоите новую версию сервиса, обрабатывающего платежи. Какую стратегию обновления выберете (RollingUpdate, Blue-Green, Canary) и почему? Как убедиться, что при RollingUpdate не потеряются in-flight запросы?
-
Как HPA вычисляет количество необходимых реплик? Почему важно задавать requests для CPU/Memory? Что такое stabilization window и зачем он нужен?
-
Что такое PodDisruptionBudget и зачем он нужен? Приведите пример, когда его отсутствие приводит к проблемам. Как PDB взаимодействует с
kubectl drain? -
Объясните разницу между ClusterIP, NodePort и LoadBalancer Service. Когда что использовать? Почему Ingress предпочтительнее, чем создание отдельного LoadBalancer для каждого сервиса?
-
Что такое GitOps? Какие преимущества даёт ArgoCD по сравнению с
kubectl applyиз CI/CD pipeline? Что такое selfHeal и prune в контексте ArgoCD? -
Объясните разницу между CNI-плагинами Flannel, Calico и Cilium. Когда стоит использовать eBPF-based networking?
-
Вы заметили, что стоимость Kubernetes-кластера выросла вдвое за последний квартал, хотя трафик не изменился. Какие шаги вы предпримете для диагностики и оптимизации?
-
Опишите, как Network Policies реализуют принцип наименьших привилегий. Почему по умолчанию в Kubernetes нет сетевой изоляции и чем это опасно?
-
Что такое Service Mesh? В каких ситуациях overhead от sidecar-прокси оправдан, а в каких — нет? Сравните Istio и Linkerd.
-
Объясните, как работает init-контейнер. Приведите три реальных сценария, когда init-контейнеры полезны. Чем они отличаются от sidecar-контейнеров?
15.13. Дополнительные ресурсы
Papers и документация
- Large-scale cluster management at Google with Borg (Verma et al., 2015) — внутренняя система Google, вдохновившая Kubernetes. Описывает задачи оркестрации контейнеров в масштабе сотен тысяч машин.
- Borg, Omega, and Kubernetes (Burns et al., ACM Queue, 2016) — уроки, извлечённые Google из трёх поколений систем оркестрации. Объясняет, почему Kubernetes выбрал декларативную модель.
- Kubernetes Documentation (kubernetes.io/docs) — официальная документация. Разделы Concepts и Tasks — обязательное чтение.
- Design Patterns for Container-based Distributed Systems (Burns, Oppenheimer, 2016) — паттерны sidecar, ambassador, adapter для контейнерных систем.
- An Updated Performance Comparison of Virtual Machines and Linux Containers (Felter et al., IBM Research, 2015) — количественное сравнение производительности контейнеров и VM.
- Autopilot: Workload Autoscaling at Google Scale (Rzadca et al., 2020) — как Google автоматически right-sizes ресурсы для контейнеров. Основа для VPA.
Блоги
- Kubernetes Blog (kubernetes.io/blog) — release notes, best practices, case studies.
- Brendan Burns "Designing Distributed Systems" (O'Reilly) — паттерны проектирования для контейнерных приложений.
- Airbnb Engineering: "Running Apache Airflow at Airbnb" — опыт эксплуатации stateful-нагрузок в Kubernetes.
- Spotify Engineering: "How We Use Kubernetes" — миграция на Kubernetes в масштабе тысяч микросервисов.
- Datadog: "Container Report" — ежегодный отчёт о трендах контейнеризации на основе миллиардов контейнеров. Статистика по runtime, оркестраторам и языкам.
- Cilium Blog — deep dive в eBPF networking, Service Mesh без sidecar, Kubernetes networking internals.
- Kubecost Blog — практические советы по оптимизации стоимости Kubernetes.
- Learnk8s — визуальные объяснения сложных концепций K8s (scheduling, networking, autoscaling).
Talks
- "Borg, Omega, and Kubernetes" — Brendan Burns (KubeCon 2016) — эволюция оркестрации контейнеров в Google от Borg до Kubernetes.
- "Kubernetes Deconstructed" — Carson Anderson (KubeCon 2017) — пошаговый разбор того, что происходит при
kubectl run. - "GitOps and Kubernetes" — Alexis Richardson (KubeCon 2018) — концепция GitOps и Flux.
- "Life of a Packet" — Michael Rubin (KubeCon 2017) — как сетевой пакет проходит через все слои Kubernetes: от Pod через Service, kube-proxy, iptables до внешней сети.
- "Containers From Scratch" — Liz Rice (Container Camp 2016) — live-coding контейнера с нуля на Go, используя namespaces и cgroups. Лучший способ понять, что такое контейнер на самом деле.
- "Kubernetes Failure Stories" — Henning Jacobs (KubeCon 2019) — коллекция реальных production-инцидентов в Kubernetes с разбором причин и уроков.
Инструменты для практики
- kind (Kubernetes in Docker) — запуск локального K8s-кластера в Docker-контейнерах. Идеально для тестирования.
- k3s — легковесный Kubernetes от Rancher. Один бинарник, 512 MB RAM. Для edge и IoT.
- minikube — локальный K8s-кластер для разработки. Поддерживает несколько узлов.
- Lens / k9s — GUI и TUI для управления кластерами. k9s — терминальный UI, незаменимый для отладки.
- kube-bench — проверка кластера на соответствие CIS Kubernetes Benchmark (безопасность).
- Polaris — проверка манифестов на best practices (resource limits, probes, security context).