Kubernetes: 10 вещей, которые нужно решить до первого kubectl apply

Как стартап чуть не утонул в Kubernetes

Стартап «ТехноСтарт» — 8 разработчиков, два микросервиса на Go и фронтенд на React. Всё работало на трёх VPS с Docker Compose. CTO прочитал статью про Kubernetes, вдохновился и за выходные поднял кластер на kubeadm. В понедельник продакшен переехал в K8s. Через неделю — три инцидента: приложение падало от OOM без видимых причин, под с БД потерял данные после миграции на другую ноду, а баланс на облачном аккаунте улетел в минус из-за четырёх нод по 8 CPU, которые были нужны «для запаса».

Нас позвали разгребать последствия. Первое, что мы сказали: «Вам прямо сейчас Kubernetes не нужен». Но раз уже развернули — давайте сделаем правильно. Вот 10 вопросов, которые нужно было решить ДО запуска.

Вопрос 1: а нужен ли вам Kubernetes вообще?

Kubernetes — это операционная система для контейнеров. Как любая ОС, она решает определённый класс задач и создаёт собственные проблемы. K8s оправдан, когда:

  • У вас больше 5–7 сервисов, которые нужно независимо масштабировать.
  • Нагрузка неравномерная и требуется автоскейлинг (HPA).
  • Вам нужны zero-downtime деплои с канареечными релизами.
  • Команда больше 10 разработчиков, и нужна изоляция окружений через namespace.

K8s НЕ нужен, если:

  • У вас 1–3 сервиса — Docker Compose + systemd справится лучше.
  • Нагрузка стабильна — 2 VPS с балансировщиком достаточно.
  • Нет инженера, готового посвящать 30%+ времени инфраструктуре.
  • Бюджет ограничен — минимальный production-кластер K8s потребляет 3–5 нод, и control plane тоже ест ресурсы.

Для «ТехноСтарт» мы рекомендовали начать с managed Kubernetes у облачного провайдера (control plane бесплатно) и двух worker-нод. Это сэкономило 60% бюджета и убрало головную боль с обновлением etcd.

Вопрос 2: managed, kubeadm или k3s?

Три основных способа развернуть кластер:

СпособПлюсыМинусыКогда использовать
Managed (Yandex Cloud, VK Cloud, Selectel)Control Plane управляет провайдер, автоапдейты, бэкап etcdVendor lock-in, лимиты (32–100 нод), стоимость worker-нодСтартапы, малый бизнес, команды без K8s-экспертизы
kubeadmПолный контроль, никаких ограниченийВсё на вас: обновления, бэкап etcd, сертификаты, HABare metal, команды с K8s-опытом
k3s (Rancher)Легковесный (один бинарник 50 МБ), SQLite вместо etcd, быстрая установкаНе подходит для 100+ нод, ограниченная экосистемаEdge, dev-окружения, IoT, малые production

Для self-hosted продакшена мы рекомендуем Kubespray — набор Ansible-плейбуков, который автоматизирует установку через kubeadm:

# Установка Kubespray
git clone https://github.com/kubernetes-sigs/kubespray.git
cd kubespray
pip install -r requirements.txt

# Копируем конфигурацию
cp -rfp inventory/sample inventory/technostart

# Указываем ноды
declare -a IPS=(10.0.1.10 10.0.1.11 10.0.1.12 10.0.1.20 10.0.1.21)
CONFIG_FILE=inventory/technostart/hosts.yml python3 contrib/inventory_builder/inventory.py ${IPS[@]}

# Разворачиваем кластер
ansible-playbook -i inventory/technostart/hosts.yml \
  --become --become-user=root \
  cluster.yml

За 20 минут получаем HA-кластер с 3 control plane + 2 worker, containerd, Calico CNI и CoreDNS.

Вопрос 3: сеть и CNI-плагин

CNI (Container Network Interface) — это плагин, который обеспечивает сетевое взаимодействие между подами. Выбор CNI — одно из первых решений, которое потом очень дорого менять.

Три основных варианта:

  • Cilium — наша рекомендация. Использует eBPF вместо iptables, что даёт лучшую производительность и наблюдаемость. Встроенный Hubble показывает сетевые потоки между сервисами в реальном времени.
  • Calico — зрелый, стабильный, поддерживает BGP для bare metal. Хороший выбор, если нужна интеграция с существующей сетью.
  • Flannel — простейший вариант, работает через VXLAN. Для начинающих и dev-окружений.
# Установка Cilium через Helm
helm repo add cilium https://helm.cilium.io/

helm install cilium cilium/cilium --version 1.15.1 \
  --namespace kube-system \
  --set kubeProxyReplacement=true \
  --set k8sServiceHost=10.0.1.10 \
  --set k8sServicePort=6443 \
  --set hubble.relay.enabled=true \
  --set hubble.ui.enabled=true

# Проверяем статус
cilium status
# /¯¯\
# /¯¯\__/¯¯\    Cilium:       OK
# \__/¯¯\__/    Operator:     OK
# /¯¯\__/¯¯\    Hubble Relay: OK
# \__/¯¯\__/    Hubble UI:    OK
#  \__/

# Тест связности
cilium connectivity test

Для публикации сервисов наружу на bare metal нужен MetalLB — эмулятор облачного LoadBalancer. В облаке LoadBalancer создаётся автоматически.

Вопрос 4: хранение данных

Это место, где «ТехноСтарт» потерял данные. Под с PostgreSQL работал на ноде 1, данные хранились на локальном диске (emptyDir). Kubernetes перенёс под на ноду 2 — и данные остались на ноде 1.

Правило: никогда не храните важные данные на локальных дисках нод. Используйте PersistentVolume (PV) с внешним хранилищем.

Варианты storage:

  • Облачные CSI-драйверы — самый простой путь. Yandex Disk, VK Cloud, AWS EBS. Создаёте PVC — провайдер автоматически создаёт и подключает диск.
  • Ceph + Rook — распределённое хранилище для bare metal. Rook — оператор, который разворачивает Ceph внутри K8s.
  • Longhorn — проще Ceph, от Rancher. Реплицирует данные между нодами.
# StorageClass для облачного провайдера (пример Yandex Cloud)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: yc-network-ssd
provisioner: disk-csi-driver.mks.ycloud.io
parameters:
  type: network-ssd
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true

---
# PVC для PostgreSQL
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-data
spec:
  storageClassName: yc-network-ssd
  accessModes: [ReadWriteOnce]
  resources:
    requests:
      storage: 50Gi

reclaimPolicy: Retain — при удалении PVC диск сохраняется (а не удаляется как при Delete). WaitForFirstConsumer — диск создаётся в той же зоне, где запустился под.

Вопрос 5: RBAC и namespaces

В «ТехноСтарт» все 8 разработчиков использовали один kubeconfig с cluster-admin. Один джуниор случайно удалил namespace production командой kubectl delete ns production. С правильным RBAC такого бы не произошло.

Стратегия namespaces:

# Создаём namespace для каждого окружения
kubectl create ns production
kubectl create ns staging
kubectl create ns monitoring

# Помечаем лейблами
kubectl label ns production env=production
kubectl label ns staging env=staging

RBAC: роли и привязки:

# Роль для разработчика — read-only в production
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: developer-readonly
  namespace: production
rules:
  - apiGroups: [""]
    resources: ["pods", "services", "configmaps"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["apps"]
    resources: ["deployments", "replicasets"]
    verbs: ["get", "list", "watch"]
  # Разрешаем смотреть логи
  - apiGroups: [""]
    resources: ["pods/log"]
    verbs: ["get"]

---
# Роль для разработчика — полный доступ в staging
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: developer-full
  namespace: staging
rules:
  - apiGroups: ["*"]
    resources: ["*"]
    verbs: ["*"]

---
# Привязка к пользователю
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: dev-ivanov-production
  namespace: production
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: developer-readonly
subjects:
  - kind: User
    name: ivanov
    apiGroup: rbac.authorization.k8s.io

Принцип минимальных привилегий: разработчик видит логи и статус подов в production, но не может ничего менять. В staging — полная свобода. Деплой в production — только через CI/CD (ServiceAccount с ограниченными правами).

Вопрос 6: resource limits и requests

Именно из-за отсутствия limits «ТехноСтарт» ловил OOM-kill. Под без ограничений может съесть всю память ноды, и ядро Linux убьёт случайный процесс — часто не тот, что виноват.

# Deployment с правильными ресурсами
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
  namespace: production
spec:
  replicas: 3
  template:
    spec:
      containers:
        - name: api
          image: technostart/api:v1.4.2
          resources:
            requests:
              cpu: 100m        # Гарантированные 0.1 ядра
              memory: 128Mi    # Гарантированные 128 МБ
            limits:
              cpu: 500m        # Максимум 0.5 ядра
              memory: 512Mi    # Максимум 512 МБ — при превышении OOM-kill

Разница между requests и limits:

  • requests — сколько ресурсов K8s гарантирует контейнеру. Используется для планирования (scheduler размещает под на ноду, где есть запрошенные ресурсы).
  • limits — потолок. CPU throttling при превышении лимита CPU, OOM-kill при превышении лимита памяти.

Для предотвращения запуска подов без limits — используем LimitRange:

# Значения по умолчанию для namespace
apiVersion: v1
kind: LimitRange
metadata:
  name: default-limits
  namespace: production
spec:
  limits:
    - default:
        cpu: 200m
        memory: 256Mi
      defaultRequest:
        cpu: 50m
        memory: 64Mi
      max:
        cpu: "2"
        memory: 2Gi
      type: Container

А для ограничения общего потребления namespace — ResourceQuota:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: production-quota
  namespace: production
spec:
  hard:
    requests.cpu: "4"
    requests.memory: 8Gi
    limits.cpu: "8"
    limits.memory: 16Gi
    pods: "50"

Вопросы 7–10: мониторинг, health checks, CI/CD и приложение

Вопрос 7: мониторинг. Без мониторинга Kubernetes — чёрный ящик. Минимальный стек:

# Установка Prometheus + Grafana через kube-prometheus-stack
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts

helm install monitoring prometheus-community/kube-prometheus-stack \
  --namespace monitoring \
  --create-namespace \
  --set grafana.adminPassword=<пароль> \
  --set prometheus.prometheusSpec.retention=30d \
  --set prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.resources.requests.storage=50Gi

Вопрос 8: health checks. Kubernetes должен знать, жив ли ваш контейнер и готов ли он принимать трафик:

spec:
  containers:
    - name: api
      livenessProbe:
        httpGet:
          path: /healthz
          port: 8080
        initialDelaySeconds: 10
        periodSeconds: 15
        failureThreshold: 3    # 3 сбоя подряд → перезапуск контейнера
      readinessProbe:
        httpGet:
          path: /ready
          port: 8080
        initialDelaySeconds: 5
        periodSeconds: 5
        failureThreshold: 2    # 2 сбоя → под убирается из Service
      startupProbe:
        httpGet:
          path: /healthz
          port: 8080
        failureThreshold: 30
        periodSeconds: 2       # 60 секунд на запуск приложения

Три типа проб:

  • livenessProbe — «жив ли контейнер?». Если нет — перезапуск.
  • readinessProbe — «готов ли принимать трафик?». Если нет — убирается из балансировки, но не перезапускается.
  • startupProbe — «запустился ли?». Даёт время тяжёлым приложениям (Java с Spring Boot стартует 30–60 сек).

Вопрос 9: CI/CD. Деплой через kubectl apply с локальной машины — антипаттерн. Минимальный пайплайн:

# .gitlab-ci.yml (пример)
stages:
  - build
  - deploy-staging
  - deploy-production

build:
  stage: build
  script:
    - docker build -t registry.technostart.ru/api:$CI_COMMIT_SHA .
    - docker push registry.technostart.ru/api:$CI_COMMIT_SHA

deploy-staging:
  stage: deploy-staging
  script:
    - kubectl --context staging set image deployment/api api=registry.technostart.ru/api:$CI_COMMIT_SHA -n staging
    - kubectl --context staging rollout status deployment/api -n staging --timeout=120s

deploy-production:
  stage: deploy-production
  when: manual  # Только ручной запуск!
  script:
    - kubectl --context production set image deployment/api api=registry.technostart.ru/api:$CI_COMMIT_SHA -n production
    - kubectl --context production rollout status deployment/api -n production --timeout=120s

Вопрос 10: готовность приложения. Kubernetes требует от приложения:

  • Читать конфигурацию из переменных окружения или ConfigMap (не из локального config.xml).
  • Писать логи в stdout/stderr (не в файлы).
  • Быть stateless — состояние в базе данных или Redis, не на локальном диске.
  • Корректно обрабатывать SIGTERM для graceful shutdown.
  • Отдавать метрики на /metrics для Prometheus.
  • Реализовать эндпоинты /healthz и /ready для проб.

Если приложение не соответствует этим требованиям — сначала адаптируйте его, и только потом деплойте в K8s. Иначе вы получите все проблемы монолита на VPS плюс сложность Kubernetes сверху.

Часто задаваемые вопросы

Начните с Minikube или kind на локальной машине — это песочница для экспериментов. Разверните простое приложение, настройте Service, Ingress, ConfigMap. Затем переходите к managed Kubernetes у облачного провайдера. Не начинайте с kubeadm на bare metal — это уровень сложности, к которому нужно подготовиться.
Минимум для HA: 3 ноды control plane + 2 worker ноды. С managed Kubernetes: 2–3 worker ноды (control plane управляет провайдер). Для стартапа с 2–3 сервисами достаточно 2 worker по 4 CPU / 8 ГБ RAM. Не выделяйте ноды «с запасом» — используйте Cluster Autoscaler для автоматического масштабирования.
Cilium использует eBPF — технологию ядра Linux, которая работает эффективнее iptables (на которых основан Calico). На практике это даёт: меньшую латентность при большом количестве сервисов, встроенный Hubble для визуализации сетевых потоков, и L7 Network Policies (фильтрация по HTTP-путям). Calico стабильнее на старых ядрах (< 5.10) и лучше интегрируется с BGP.
Можно, но с осторожностью. Используйте операторы (CloudNativePG для PostgreSQL, Percona Operator для MySQL) — они автоматизируют репликацию, бэкапы и failover. Обязательно: PersistentVolume с reclaimPolicy Retain, anti-affinity для размещения реплик на разных нодах, и регулярные бэкапы за пределы кластера. Для малых команд проще использовать managed БД у провайдера.
Helm — для установки сторонних приложений (Prometheus, Nginx Ingress, cert-manager). Шаблонизация с values позволяет настраивать сложные чарты без правки YAML. Kustomize — для собственных приложений, где нужно накладывать патчи на базовые манифесты для разных окружений (dev/staging/production). Многие команды используют оба инструмента одновременно.

Нужна помощь с проектом?

Специалисты АйТи Фреш помогут с архитектурой, DevOps, безопасностью и разработкой — 15+ лет опыта

📞 Связаться с нами
#kubernetes#k8s подготовка#managed kubernetes#kubeadm#k3s#cilium cni#rbac#resource limits
Комментарии 0

Оставить комментарий

загрузка...