🚀 Надёжная IT-инфраструктура для бизнеса

Строим облачную инфраструктуру, которая масштабируется

Проектируем, внедряем и поддерживаем отказоустойчивые решения на Kubernetes, AWS и Yandex Cloud. От стартапа до enterprise.

280+

Проектов реализовано

99.98%

Uptime SLA

45

Инженеров в команде

12 лет

На рынке

Услуги

Полный спектр облачных решений

Мы покрываем все этапы жизненного цикла инфраструктуры — от аудита и проектирования до эксплуатации и масштабирования

☁️

Cloud-архитектура

Проектируем отказоустойчивые и масштабируемые архитектуры для AWS, GCP, Yandex Cloud. IaC подход с Terraform.

Подробнее →
⚙️

DevOps и CI/CD

Настраиваем полный цикл непрерывной интеграции и доставки. GitLab CI, GitHub Actions, Jenkins, ArgoCD.

Подробнее →
🐳

Kubernetes и контейнеры

Развёртываем и управляем production-grade Kubernetes кластерами. Helm, Kustomize, service mesh.

Подробнее →
📊

Мониторинг и Observability

Внедряем Prometheus, Grafana, Loki, Jaeger. Полная наблюдаемость: метрики, логи, трейсы.

Подробнее →
🔒

Информационная безопасность

Аудит безопасности, внедрение Zero Trust, управление секретами через HashiCorp Vault, mTLS.

Подробнее →
🔄

Миграция в облако

Плановая миграция из on-premise в облако без простоев. Стратегии lift-and-shift, re-platform, re-architect.

Подробнее →
Процесс

Как мы работаем

Прозрачный и предсказуемый процесс от первого звонка до запуска в production

01

Аудит и анализ

Проводим глубокий аудит текущей инфраструктуры: архитектура, безопасность, производительность, стоимость. Формируем детальный отчёт с рекомендациями и roadmap.

02

Проектирование

Разрабатываем целевую архитектуру с учётом требований бизнеса, SLA и бюджета. Создаём IaC-кодовую базу, CI/CD пайплайны, стратегию мониторинга.

03

Внедрение

Поэтапно реализуем проект с минимальным влиянием на текущие процессы. Каждый спринт — демо и согласование. Blue-green или canary стратегия миграции.

04

Поддержка и развитие

Обеспечиваем 24/7 мониторинг и поддержку. SLA до 99.99%. Регулярные ревью, оптимизация стоимости, масштабирование под растущую нагрузку.

Технологии

Наш стек

Работаем с проверенными open-source и enterprise инструментами

☸️

Kubernetes

Оркестрация

🐳

Docker

Контейнеризация

🏗️

Terraform

IaC

🦊

GitLab CI

CI/CD

📈

Prometheus

Мониторинг

📊

Grafana

Визуализация

🔐

Vault

Секреты

🔄

ArgoCD

GitOps

🐧

Linux

ОС

🐘

PostgreSQL

СУБД

☁️

AWS

Облако

🤖

Ansible

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

Блог

Статьи и исследования

Делимся опытом, разбираем кейсы и пишем о технологиях, которые используем каждый день

Kubernetes

Оптимизация стоимости Kubernetes-кластера на 60%

Практические шаги по снижению расходов на инфраструктуру без потери производительности

8 мин28 марта 2026
Cloud

Multi-cloud стратегия: AWS + Yandex Cloud

Как построить отказоустойчивую архитектуру между облачными провайдерами

10 мин22 марта 2026
Kubernetes

Helm Charts: продвинутые паттерны

Шаблонизация, хуки, зависимости и стратегии версионирования

12 мин15 марта 2026
DevOps

ArgoCD: GitOps для 100+ микросервисов

Масштабирование GitOps-подхода на крупные проекты

11 мин8 марта 2026
Безопасность

TLS и mTLS: шифрование между сервисами

Реализация end-to-end шифрования в микросервисной архитектуре

9 мин1 марта 2026
Безопасность

Защита от DDoS: архитектура и инструменты

Многоуровневая защита от распределённых атак

10 мин22 февраля 2026
Мониторинг

Grafana: создание production-ready дашбордов

Лучшие практики визуализации метрик и алертинга

9 мин15 февраля 2026
Мониторинг

Loki: централизованный сбор логов

Альтернатива ELK-стеку для cloud-native окружений

8 мин8 февраля 2026
Архитектура

Istio Service Mesh: от теории к практике

Внедрение service mesh без боли и простоев

13 мин1 февраля 2026
Безопасность

HashiCorp Vault: управление секретами

Централизованное хранение и ротация секретов

10 мин25 января 2026
IaC

Ansible: автоматизация серверной инфраструктуры

Плейбуки, роли и best practices для production

11 мин18 января 2026
Разработка

Python asyncio: паттерны для высоких нагрузок

Асинхронное программирование для production-систем

12 мин12 января 2026
Разработка

Go: микросервисы с graceful shutdown

Паттерны корректного завершения работы и обработки сигналов

9 мин5 января 2026
Базы данных

ClickHouse: аналитика на миллиардах строк

Архитектура и оптимизация колоночной СУБД для OLAP

11 мин28 декабря 2025
Архитектура

Apache Kafka: потоковая обработка данных

Проектирование event-driven систем на базе Kafka

12 мин21 декабря 2025
Базы данных

MongoDB: шардирование и репликация

Масштабирование документоориентированной СУБД

10 мин14 декабря 2025
Архитектура

API Gateway: проектирование и rate limiting

Централизованная точка входа для микросервисов

9 мин7 декабря 2025
DevOps

Canary Deployments: безопасные релизы

Постепенная раскатка новых версий с минимальным риском

8 мин30 ноября 2025
SRE

Chaos Engineering: как ломать production правильно

Инструменты и методология контролируемых экспериментов

10 мин23 ноября 2025
SRE

Аудит инфраструктуры: чеклист из 50 пунктов

Полный чеклист проверки облачной инфраструктуры

14 мин16 ноября 2025
Отзывы

Что говорят клиенты

Нам доверяют компании из разных отраслей

★★★★★
«MaxTimes помогли нам мигрировать в Kubernetes за 3 месяца без единого простоя. Инфраструктурные расходы сократились на 40%. Команда работала как внутренний отдел — полное погружение в наши процессы.»
АК

Алексей Козлов

CTO, финтех-платформа

★★★★★
«Настроили полный observability стек: Prometheus, Grafana, Loki, Jaeger. Теперь мы видим каждый запрос насквозь. Время обнаружения инцидентов сократилось с часов до минут. Отличная экспертиза.»
МС

Мария Соколова

VP Engineering, медиа-холдинг

★★★★★
«Работаем с MaxTimes уже 3 года на поддержке. SLA 99.99% — не просто цифра в договоре, а реальный показатель. Команда реагирует мгновенно и всегда предлагает проактивные улучшения.»
ДВ

Дмитрий Волков

Директор IT, ритейл-сеть

Готовы обсудить ваш проект?

Свяжитесь с нами для бесплатной консультации. Разберём вашу инфраструктуру и предложим оптимальное решение.

hello@maxtimes.ru +7 (495) 123-45-67 · Москва
← Назад к блогу
Kubernetes

Оптимизация стоимости Kubernetes-кластера на 60%

Kubernetes стал стандартом де-факто для оркестрации контейнеров, но стоимость кластеров может выйти из-под контроля, если не уделять внимание оптимизации ресурсов. За последние два года мы провели десятки аудитов Kubernetes-инфраструктуры для наших клиентов и выработали системный подход, позволяющий снизить расходы на 40–60% без потери производительности и надёжности.

Проблема перепровизионирования

Главный источник избыточных расходов — перепровизионирование (overprovisioning). Разработчики часто устанавливают requests и limits «с запасом», опасаясь нехватки ресурсов. В результате кластер может использовать лишь 15–25% выделенных ресурсов CPU и памяти, при этом оплачивая 100%.

Представьте: у вас 10 нод с 8 vCPU и 32 ГБ RAM каждая. Общая ёмкость — 80 vCPU и 320 ГБ RAM. Но если средняя утилизация составляет 20%, вы реально используете лишь 16 vCPU и 64 ГБ RAM. Это значит, что 80% бюджета на инфраструктуру тратится впустую.

Для диагностики этой проблемы мы используем комбинацию метрик из Prometheus и инструмент Kubecost, который предоставляет детализированную информацию о стоимости каждого namespace, deployment и даже отдельного пода.

Правильная настройка requests и limits

Первый и самый важный шаг — корректная настройка resources requests и limits. Requests определяют гарантированные ресурсы для пода, а limits — максимально допустимые. Неправильное соотношение между ними приводит либо к перерасходу, либо к нестабильности.

Мы рекомендуем следующий подход: анализировать реальное потребление ресурсов за последние 2–4 недели с помощью Prometheus-запросов, а затем устанавливать requests на уровне P95 реального потребления, а limits — на уровне P99 с запасом в 20%.

apiVersion: v1
kind: Pod
metadata:
  name: app-server
spec:
  containers:
  - name: app
    image: app:latest
    resources:
      requests:
        cpu: "250m"
        memory: "256Mi"
      limits:
        cpu: "500m"
        memory: "512Mi"

Для автоматизации этого процесса мы внедряем Vertical Pod Autoscaler (VPA) в режиме рекомендаций. VPA анализирует историческое потребление и предлагает оптимальные значения requests и limits. Важно начинать с режима «Off» (только рекомендации) и переходить к «Auto» только после тщательного тестирования.

Horizontal Pod Autoscaler и KEDA

HPA (Horizontal Pod Autoscaler) позволяет автоматически масштабировать количество подов в зависимости от нагрузки. Вместо того чтобы держать максимальное количество реплик 24/7, вы можете настроить автоскейлинг по метрикам CPU, памяти или кастомным метрикам из Prometheus.

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: app-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: app-server
  minReplicas: 2
  maxReplicas: 20
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
      - type: Percent
        value: 10
        periodSeconds: 60

Для event-driven нагрузок мы используем KEDA (Kubernetes Event-Driven Autoscaling), который позволяет масштабировать поды на основе внешних событий: длины очереди в RabbitMQ или Kafka, количества сообщений в SQS, метрик из Prometheus и десятков других источников. KEDA поддерживает масштабирование до нуля реплик, что особенно ценно для batch-задач и периодических процессов.

Cluster Autoscaler и Spot-инстансы

Cluster Autoscaler автоматически добавляет и удаляет ноды из кластера в зависимости от запросов ресурсов. Чтобы он работал эффективно, нужно правильно настроить node groups с разными типами инстансов и задать адекватные параметры масштабирования.

Одна из самых эффективных стратегий экономии — использование Spot-инстансов (прерываемых виртуальных машин). В AWS они стоят на 60–90% дешевле on-demand инстансов. Для stateless-нагрузок это идеальный вариант. Мы используем Node Affinity и Taints/Tolerations для разделения нагрузки между on-demand и spot-нодами.

Критически важные сервисы (базы данных, stateful-приложения) размещаются на on-demand нодах с гарантированной доступностью, а stateless-микросервисы — на spot-нодах. При прерывании spot-инстанса Kubernetes автоматически переносит поды на другие доступные ноды.

Оптимизация хранилища

Persistent Volumes часто остаются без внимания при оптимизации затрат. Мы видели кластеры, где 50% PV не были подключены ни к одному поду, но продолжали начислять стоимость. Регулярный аудит неиспользуемых PV и их удаление может сэкономить значительную сумму.

Кроме того, выбор правильного типа хранилища имеет значение. Для данных, к которым обращаются редко, используйте более дешёвые классы хранения (например, HDD вместо SSD). Для логов и временных данных рассмотрите использование emptyDir или hostPath вместо сетевых PV.

Namespace-квоты и LimitRange

Для предотвращения неконтролируемого роста ресурсов важно установить ResourceQuota и LimitRange на уровне namespace. ResourceQuota ограничивает общий объём ресурсов, доступных в namespace, а LimitRange устанавливает значения по умолчанию и ограничения для отдельных подов.

apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-a-quota
  namespace: team-a
spec:
  hard:
    requests.cpu: "10"
    requests.memory: "20Gi"
    limits.cpu: "20"
    limits.memory: "40Gi"
    pods: "50"

Мониторинг стоимости

Невозможно оптимизировать то, что не измеряется. Мы внедряем дашборды стоимости на базе Kubecost или OpenCost, которые показывают расходы в разрезе namespace, deployment, label и даже отдельного пода. Это позволяет определить самые «дорогие» сервисы и расставить приоритеты оптимизации.

Также рекомендуем настроить алерты на аномалии: если стоимость namespace резко выросла или утилизация ресурсов упала ниже порогового значения — команда получит уведомление и сможет оперативно отреагировать.

Практические результаты

Комбинируя все эти подходы, наши клиенты обычно достигают экономии от 40 до 60%. Один из наших клиентов — e-commerce-платформа с 200+ микросервисами — сократил ежемесячные расходы на инфраструктуру с $45,000 до $18,000, при этом улучшив time-to-deploy и стабильность системы. Ключ к успеху — системный подход: одноразовая оптимизация быстро теряет эффект, необходим постоянный мониторинг и итеративное улучшение.

Мы рекомендуем проводить ревью стоимости ежемесячно, автоматизировать rightsizing с помощью VPA, использовать spot-инстансы для stateless-нагрузок и обязательно внедрять ResourceQuota для каждой команды. Эти простые, но эффективные меры позволят вам контролировать бюджет на инфраструктуру и направить сэкономленные средства на развитие продукта.

← Назад к блогу
Cloud

Multi-cloud стратегия: AWS + Yandex Cloud

В условиях возросших регуляторных требований и рисков vendor lock-in многие российские компании задумываются о multi-cloud стратегии. Построение инфраструктуры одновременно в AWS и Yandex Cloud позволяет достичь географической отказоустойчивости, соблюсти требования по локализации данных и получить лучшие условия от провайдеров.

Зачем нужен multi-cloud

Основные мотивации для multi-cloud стратегии включают: снижение зависимости от одного провайдера, соблюдение регуляторных требований по хранению персональных данных на территории РФ, обеспечение disaster recovery на уровне облачного провайдера, а также использование уникальных сервисов каждого облака.

Например, компания может хранить персональные данные российских пользователей в Yandex Cloud для соответствия 152-ФЗ, при этом используя глобальную CDN-инфраструктуру AWS CloudFront для обслуживания пользователей по всему миру. Или использовать Yandex DataSphere для ML-задач с русскоязычными данными, а AWS SageMaker — для глобальных моделей.

Архитектурные паттерны

Существует несколько основных паттернов multi-cloud архитектуры. Первый — active-passive: один облако является основным (primary), второе — резервным (standby). Данные реплицируются асинхронно, и переключение происходит только при серьёзной аварии основного провайдера. Этот паттерн проще всего реализовать, но он не использует ресурсы второго облака в нормальном режиме.

Второй паттерн — active-active: оба облака обслуживают трафик одновременно. Запросы распределяются через глобальный балансировщик (например, DNS-based load balancing) в зависимости от географии пользователя, нагрузки или политик маршрутизации. Этот паттерн обеспечивает лучшую утилизацию ресурсов, но требует более сложной синхронизации данных.

Третий паттерн — burst: основная нагрузка обслуживается в одном облаке, а при пиковых нагрузках часть трафика перенаправляется во второе. Это экономичный вариант для сервисов с непредсказуемыми пиками нагрузки.

Terraform для multi-cloud

Ключевой инструмент для управления multi-cloud инфраструктурой — Terraform. Он поддерживает провайдеры для обоих облаков и позволяет описывать инфраструктуру как код в единой кодовой базе. Мы используем модульную структуру с разделением по облакам.

# modules/aws/vpc/main.tf
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name        = "${var.project}-vpc"
    Environment = var.environment
  }
}

# modules/yandex/network/main.tf
resource "yandex_vpc_network" "main" {
  name = "${var.project}-network"
}

resource "yandex_vpc_subnet" "main" {
  name           = "${var.project}-subnet"
  zone           = var.zone
  network_id     = yandex_vpc_network.main.id
  v4_cidr_blocks = [var.subnet_cidr]
}

Для управления state-файлами в multi-cloud окружении мы используем разделённые бэкенды: S3 для AWS-ресурсов и Yandex Object Storage для Yandex Cloud-ресурсов. Это обеспечивает изоляцию и снижает риск случайного удаления ресурсов другого облака.

Сетевая связность

Одна из главных технических задач — обеспечение надёжной и безопасной сетевой связности между облаками. Мы используем VPN-туннели (IPsec site-to-site VPN) или выделенные каналы связи через провайдеров interconnect. VPN — проще и дешевле, но ограничен пропускной способностью и добавляет задержку. Выделенные каналы обеспечивают предсказуемую latency и высокую пропускную способность, но стоят значительно дороже.

Для сервисной коммуникации между облаками мы используем gRPC с mTLS, что обеспечивает шифрование и аутентификацию на уровне транспорта. Сертификаты управляются через HashiCorp Vault, который развёрнут в каждом облаке с кросс-репликацией.

Синхронизация данных

Синхронизация данных между облаками — наиболее сложный аспект multi-cloud архитектуры. Для реляционных баз данных мы используем логическую репликацию PostgreSQL, которая позволяет выборочно реплицировать таблицы между кластерами в разных облаках. Для NoSQL-данных — встроенные механизмы репликации конкретных СУБД (например, MongoDB change streams).

Для событийно-ориентированной архитектуры оптимальным решением является Apache Kafka с MirrorMaker 2 для репликации топиков между кластерами. Kafka обеспечивает at-least-once гарантию доставки и позволяет асинхронно синхронизировать данные с минимальной задержкой.

Кэширование также требует особого внимания. Мы используем двухуровневую архитектуру кэша: локальный кэш в каждом облаке (Redis) для снижения latency и глобальный источник истины в основном облаке. Инвалидация кэша происходит через events в Kafka.

CI/CD для multi-cloud

Пайплайн CI/CD для multi-cloud должен поддерживать деплой в оба облака с возможностью канареечной раскатки и быстрого роллбэка. Мы используем GitLab CI с разделением стадий по облакам и общим артефактом (Docker-образом), который хранится в обоих container registry.

ArgoCD развёрнут в каждом Kubernetes-кластере и синхронизирует состояние из единого Git-репозитория. Это позволяет поддерживать консистентность конфигурации между облаками и обеспечивает декларативный подход к управлению инфраструктурой.

Мониторинг и observability

Единый мониторинг — критически важен для multi-cloud. Мы разворачиваем Prometheus в каждом кластере для сбора метрик, а Thanos — для агрегации метрик из всех кластеров в единое представление. Grafana подключается к Thanos и предоставляет единый дашборд для всей инфраструктуры.

Для логов используется Loki с multi-tenant конфигурацией, где каждое облако является отдельным tenant. Трассировка реализуется через Jaeger или Tempo с propagation контекста между сервисами в разных облаках.

Выводы и рекомендации

Multi-cloud — это не серебряная пуля, а инструмент, который требует значительных инвестиций в архитектуру, автоматизацию и экспертизу команды. Мы рекомендуем начинать с active-passive паттерна и постепенно переходить к active-active по мере зрелости инфраструктуры. Обязательно используйте IaC (Terraform) для всех ресурсов, инвестируйте в единый мониторинг и автоматизируйте всё, что можно автоматизировать.

Наш опыт показывает, что правильно спроектированная multi-cloud архитектура окупается за 12–18 месяцев за счёт повышения устойчивости, оптимизации стоимости и гибкости в выборе сервисов. Ключ к успеху — планирование, автоматизация и постоянная проверка отказоустойчивости через Chaos Engineering эксперименты.

← Назад к блогу
Kubernetes

Helm Charts: продвинутые паттерны

Helm — стандартный пакетный менеджер для Kubernetes, но многие используют лишь малую часть его возможностей. В этой статье мы разберём продвинутые паттерны работы с Helm Charts, которые помогут вам создавать гибкие, переиспользуемые и безопасные чарты для production-окружений.

Структура production-ready чарта

Типичная ошибка начинающих — создание монолитных чартов со всей логикой в одном шаблоне. Production-ready чарт должен быть модульным, с чётким разделением ответственности. Каждый ресурс Kubernetes — в отдельном файле шаблона, вспомогательные функции — в _helpers.tpl, а тесты — в папке tests.

my-app/
├── Chart.yaml
├── Chart.lock
├── values.yaml
├── values-production.yaml
├── values-staging.yaml
├── templates/
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── hpa.yaml
│   ├── pdb.yaml
│   ├── serviceaccount.yaml
│   ├── configmap.yaml
│   ├── secret.yaml
│   ├── networkpolicy.yaml
│   └── tests/
│       └── test-connection.yaml
└── charts/

Продвинутая шаблонизация

Helm использует Go templates, которые предоставляют мощные возможности для условной логики, циклов и функций. Один из ключевых паттернов — использование named templates (define/include) для переиспользования блоков кода.

{{/* templates/_helpers.tpl */}}

{{- define "app.labels" -}}
app.kubernetes.io/name: {{ include "app.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
helm.sh/chart: {{ include "app.chart" . }}
{{- end }}

{{- define "app.selectorLabels" -}}
app.kubernetes.io/name: {{ include "app.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{- define "app.resources" -}}
resources:
  requests:
    cpu: {{ .Values.resources.requests.cpu | default "100m" }}
    memory: {{ .Values.resources.requests.memory | default "128Mi" }}
  limits:
    cpu: {{ .Values.resources.limits.cpu | default "500m" }}
    memory: {{ .Values.resources.limits.memory | default "512Mi" }}
{{- end }}

Другой полезный паттерн — использование функции toYaml с nindent для вставки вложенных структур из values.yaml. Это позволяет пользователям чарта передавать произвольные структуры без необходимости модификации шаблонов.

Зависимости и sub-charts

Helm поддерживает зависимости между чартами через секцию dependencies в Chart.yaml. Это позволяет собирать сложные приложения из независимых компонентов. Например, ваш чарт может зависеть от PostgreSQL, Redis и Kafka, которые будут автоматически установлены как sub-charts.

# Chart.yaml
apiVersion: v2
name: my-app
version: 1.0.0
appVersion: "2.5.0"
dependencies:
  - name: postgresql
    version: "12.x.x"
    repository: "https://charts.bitnami.com/bitnami"
    condition: postgresql.enabled
  - name: redis
    version: "17.x.x"
    repository: "https://charts.bitnami.com/bitnami"
    condition: redis.enabled

Используйте condition для необязательных зависимостей. Это позволяет пользователям включать или отключать компоненты через values.yaml. Например, в production-окружении PostgreSQL может управляться как внешний managed-сервис, и sub-chart будет отключён.

Helm hooks

Hooks позволяют выполнять действия в определённые моменты жизненного цикла релиза: перед установкой, после обновления, перед удалением и т.д. Типичные use cases — миграции базы данных, проверка предусловий и очистка ресурсов.

apiVersion: batch/v1
kind: Job
metadata:
  name: {{ include "app.name" . }}-db-migrate
  annotations:
    "helm.sh/hook": pre-upgrade,pre-install
    "helm.sh/hook-weight": "-5"
    "helm.sh/hook-delete-policy": before-hook-creation
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: migrate
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
        command: ["python", "manage.py", "migrate"]
        env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: {{ include "app.name" . }}-secret
              key: database-url

Обратите внимание на hook-weight — он определяет порядок выполнения хуков. Отрицательные значения выполняются первыми. Hook-delete-policy определяет, когда удалять ресурс, созданный хуком. Значение before-hook-creation удалит предыдущий Job перед созданием нового, что предотвращает конфликты имён.

Стратегии версионирования

Правильное версионирование чартов критически важно для управления релизами. Мы следуем SemVer 2.0 с чёткими правилами: MAJOR — breaking changes в values.yaml или поведении, MINOR — новые features с обратной совместимостью, PATCH — баг-фиксы. Версия чарта (version) и версия приложения (appVersion) управляются независимо.

Для автоматизации версионирования в CI/CD мы используем conventional commits и автоматическое определение типа версии. Чарты публикуются в OCI-совместимый registry (например, Harbor или AWS ECR) при каждом мерже в main-ветку.

Тестирование чартов

Тестирование Helm-чартов включает несколько уровней. Первый — линтинг с помощью helm lint, который проверяет синтаксис и структуру чарта. Второй — unit-тестирование шаблонов с помощью helm-unittest, который позволяет писать assertions для сгенерированных манифестов. Третий — интеграционное тестирование с helm test, который запускает поды-тесты в кластере.

Мы также используем chart-testing (ct) от Helm для автоматизации тестирования в CI/CD. Ct определяет изменённые чарты, проверяет версионирование, запускает lint и install/upgrade тесты в эфемерном Kind-кластере.

Безопасность

Security context, network policies и pod security standards должны быть частью каждого production чарта. Мы включаем их по умолчанию в values.yaml с возможностью кастомизации. NetworkPolicy ограничивает сетевой доступ к поду только необходимыми портами и источниками.

# values.yaml (defaults)
securityContext:
  runAsNonRoot: true
  runAsUser: 1000
  fsGroup: 1000
  readOnlyRootFilesystem: true
  allowPrivilegeEscalation: false
  capabilities:
    drop:
      - ALL

networkPolicy:
  enabled: true
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              name: ingress-nginx
      ports:
        - port: 8080

Также рекомендуем использовать Helm Secrets или SOPS для шифрования чувствительных значений в values-файлах. Это позволяет хранить секреты в Git-репозитории в зашифрованном виде и расшифровывать их только при деплое.

Заключение

Продвинутое использование Helm позволяет создавать чарты enterprise-уровня, которые легко поддерживать, тестировать и масштабировать. Ключевые принципы: модульность, переиспользование через named templates, правильное версионирование, многоуровневое тестирование и security-by-default. Инвестиции в качество чартов окупаются многократно при масштабировании количества сервисов и окружений.

← Назад к блогу
DevOps

ArgoCD: GitOps для 100+ микросервисов

GitOps — парадигма, при которой Git является единственным источником истины для декларативной инфраструктуры и приложений. ArgoCD — один из самых зрелых инструментов для реализации GitOps в Kubernetes. В этой статье мы делимся опытом масштабирования ArgoCD для управления более чем 100 микросервисами в production.

Почему GitOps

Традиционный push-based CI/CD (когда пайплайн деплоит в кластер) имеет несколько проблем в масштабе: необходимость давать CI/CD системе доступ к кластеру, отсутствие механизма drift detection (обнаружение расхождений между желаемым и фактическим состоянием), сложность аудита изменений. GitOps решает эти проблемы, инвертируя модель: агент внутри кластера (ArgoCD) вытягивает желаемое состояние из Git и применяет его.

Каждое изменение в инфраструктуре проходит через pull request, получает code review, записывается в git history и может быть откачено простым git revert. Это обеспечивает полную аудируемость и воспроизводимость.

Архитектура для масштаба

Для управления 100+ микросервисами важно правильно организовать Git-репозитории и ArgoCD Applications. Мы используем паттерн App of Apps, где корневое Application содержит ссылки на все дочерние Applications. Это позволяет управлять всей инфраструктурой из одной точки.

# apps/root-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: root-app
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://gitlab.com/org/k8s-apps.git
    targetRevision: main
    path: apps
  destination:
    server: https://kubernetes.default.svc
    namespace: argocd
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

Для ещё большего масштаба мы используем ApplicationSet — контроллер, который автоматически генерирует Applications на основе шаблонов и генераторов. Например, Git Directory Generator создаёт Application для каждой директории в репозитории, а Matrix Generator — для комбинации кластеров и сервисов.

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: microservices
  namespace: argocd
spec:
  generators:
  - git:
      repoURL: https://gitlab.com/org/k8s-apps.git
      revision: main
      directories:
      - path: services/*
  template:
    metadata:
      name: '{{path.basename}}'
    spec:
      project: default
      source:
        repoURL: https://gitlab.com/org/k8s-apps.git
        targetRevision: main
        path: '{{path}}'
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{path.basename}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
        - CreateNamespace=true

Структура репозитория

Для 100+ микросервисов структура Git-репозитория имеет решающее значение. Мы используем monorepo для Kubernetes-манифестов с разделением по сервисам и окружениям. Каждый сервис — отдельная директория с Kustomize overlays для разных окружений.

Base-конфигурация содержит общие ресурсы (Deployment, Service, ConfigMap), а overlays переопределяют специфичные для окружения параметры (количество реплик, переменные окружения, ресурсы). Это позволяет поддерживать DRY-принцип и минимизировать дублирование.

Стратегии синхронизации

ArgoCD поддерживает несколько стратегий синхронизации: manual (ручная), automated (автоматическая) и automated with self-heal (автоматическая с восстановлением). Для production мы используем automated with self-heal, что означает: ArgoCD автоматически применяет изменения из Git и откатывает любые ручные изменения в кластере.

Для критических сервисов мы добавляем sync waves и hooks. Sync waves определяют порядок применения ресурсов (например, ConfigMap и Secrets перед Deployment). Hooks позволяют выполнять pre-sync и post-sync действия (например, миграции БД перед деплоем нового кода).

Notifications и интеграции

ArgoCD Notifications позволяет отправлять уведомления о событиях (sync started, sync succeeded, sync failed, health degraded) в Slack, Telegram, email и другие каналы. Мы настраиваем разные каналы для разных окружений: staging-уведомления идут в общий канал команды, а production — в канал on-call дежурных.

Интеграция с GitLab/GitHub через webhooks обеспечивает мгновенное обнаружение изменений в репозитории вместо поллинга по расписанию. Это сокращает время от мержа PR до применения изменений с минут до секунд.

RBAC и multi-tenancy

При 100+ микросервисах критически важно разграничить доступ. ArgoCD поддерживает RBAC с интеграцией через SSO (OIDC, SAML, LDAP). Мы создаём ArgoCD Projects для каждой команды, ограничивая доступ к определённым namespace и source-репозиториям.

Каждая команда может видеть и управлять только своими Applications, при этом platform-team имеет доступ ко всему. Это обеспечивает автономность команд без компромиссов в безопасности.

Производительность и оптимизация

При большом количестве Applications ArgoCD может испытывать проблемы с производительностью. Мы применяем несколько оптимизаций: увеличение количества реплик application-controller и repo-server, настройка sharding для распределения Applications между инстансами контроллера, использование Redis для кэширования манифестов.

Также важно оптимизировать Git-репозиторий: использовать shallow clone, минимизировать размер репозитория, разделять большие monorepo на несколько при необходимости. ArgoCD 2.8+ поддерживает Git submodules и multiple source, что расширяет возможности организации кода.

Disaster recovery

Одно из главных преимуществ GitOps — простота disaster recovery. Если кластер полностью потерян, восстановление сводится к: создание нового кластера, установка ArgoCD, подключение к Git-репозиторию — и все Applications автоматически восстанавливаются. Мы регулярно тестируем этот сценарий в рамках DR-учений.

ArgoCD позволяет нам управлять сложной микросервисной инфраструктурой с минимальным overhead. GitOps-подход обеспечивает аудируемость, воспроизводимость и надёжность, а ApplicationSet — масштабируемость. Наш опыт показывает, что инвестиции в правильную настройку ArgoCD окупаются в первые же месяцы за счёт сокращения инцидентов и ускорения деплоя.

← Назад к блогу
Безопасность

TLS и mTLS: шифрование между сервисами

В микросервисной архитектуре десятки и сотни сервисов обмениваются данными по сети. Без шифрования злоумышленник, получивший доступ к сегменту сети, может перехватить трафик между сервисами, включая учётные данные, персональные данные пользователей и конфиденциальную бизнес-информацию. TLS (Transport Layer Security) и mTLS (mutual TLS) обеспечивают защиту этого трафика.

TLS vs mTLS

Обычный TLS (используемый, например, для HTTPS) обеспечивает шифрование канала и аутентификацию сервера: клиент проверяет сертификат сервера, но сервер не проверяет клиента. mTLS (взаимный TLS) добавляет аутентификацию клиента: обе стороны предъявляют и проверяют сертификаты друг друга.

mTLS гарантирует, что только авторизованные сервисы могут устанавливать соединения. Даже если злоумышленник проникнет в сеть, он не сможет подключиться к сервисам без валидного клиентского сертификата. Это ключевой элемент Zero Trust архитектуры, где мы не доверяем ничему внутри периметра сети.

Инфраструктура PKI

Для реализации mTLS необходима инфраструктура открытых ключей (PKI). Нам нужен корневой Certificate Authority (CA), который выпускает сертификаты для каждого сервиса. В Kubernetes-окружении мы используем cert-manager — оператор, который автоматизирует выпуск, обновление и ротацию сертификатов.

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: internal-ca
spec:
  ca:
    secretName: internal-ca-key-pair

---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: api-service-tls
  namespace: production
spec:
  secretName: api-service-tls
  duration: 720h    # 30 дней
  renewBefore: 168h # обновить за 7 дней
  issuerRef:
    name: internal-ca
    kind: ClusterIssuer
  commonName: api-service.production.svc.cluster.local
  dnsNames:
  - api-service
  - api-service.production
  - api-service.production.svc
  - api-service.production.svc.cluster.local

Cert-manager автоматически обновляет сертификаты перед истечением срока действия и создаёт Kubernetes Secrets с ключами и сертификатами, которые приложения монтируют как volumes или используют через environment variables.

Реализация в приложении

На стороне приложения необходимо настроить TLS-сервер и TLS-клиент. Для Go-сервисов это делается через стандартную библиотеку crypto/tls. Для Python — через ssl модуль или библиотеки вроде aiohttp с SSL-контекстом. Каждый сервис должен загружать свой сертификат, приватный ключ и CA-сертификат для проверки пиров.

Важный нюанс — горячая перезагрузка сертификатов. При ротации сертификатов (что происходит автоматически через cert-manager) приложение должно подхватывать новые сертификаты без перезапуска. Это можно реализовать через file watcher или периодическую перезагрузку TLS-конфигурации.

Service Mesh подход

Альтернативный подход к реализации mTLS — использование service mesh (Istio, Linkerd). Service mesh внедряет sidecar-прокси (Envoy) рядом с каждым подом, который автоматически обеспечивает mTLS для всего межсервисного трафика. Приложение работает с plain HTTP, а шифрование происходит на уровне прокси.

Istio предоставляет PeerAuthentication ресурс для управления политиками mTLS на уровне namespace или отдельных сервисов. В режиме STRICT все соединения должны использовать mTLS, а в режиме PERMISSIVE — принимаются как mTLS, так и plain-text соединения (полезно для постепенной миграции).

Ротация сертификатов

Регулярная ротация сертификатов — критически важная практика безопасности. Мы рекомендуем устанавливать срок действия сертификатов в 30 дней для рабочих нагрузок и обновлять их за 7 дней до истечения. Короткий срок действия минимизирует окно уязвимости при компрометации ключа.

Для корневого CA используются более длительные сроки (1–3 года), но ротация CA — значительно более сложная процедура, требующая cross-signing и постепенной замены trust chain. Мы автоматизируем этот процесс и тестируем его в staging-окружении перед применением в production.

Мониторинг TLS

Необходимо мониторить сроки истечения сертификатов, ошибки TLS handshake и использование устаревших версий протокола. Prometheus-метрики cert-manager предоставляют информацию о статусе всех сертификатов в кластере. Мы настраиваем алерты для сертификатов, которые не удалось обновить, и для приближающихся сроков истечения.

Внедрение mTLS — один из самых эффективных шагов для повышения безопасности микросервисной архитектуры. При правильной автоматизации через cert-manager или service mesh overhead на управление сертификатами минимален, а защита — максимальна. Это обязательный элемент для любой production-системы, обрабатывающей чувствительные данные.

← Назад к блогу
Безопасность

Защита от DDoS: архитектура и инструменты

DDoS-атаки (Distributed Denial of Service) остаются одной из самых распространённых и разрушительных угроз для онлайн-сервисов. Современные атаки достигают терабитных объёмов и используют сложные многовекторные техники. В этой статье мы разберём многоуровневую архитектуру защиты от DDoS, от edge-уровня до уровня приложения.

Типы DDoS-атак

DDoS-атаки классифицируются по уровням модели OSI. Volumetric-атаки (L3/L4) — UDP flood, SYN flood, DNS amplification — направлены на переполнение сетевого канала. Protocol-атаки эксплуатируют слабости протоколов (Slowloris, Ping of Death). Application-layer атаки (L7) — HTTP flood, API abuse — имитируют легитимный трафик и наиболее сложны для обнаружения.

Каждый тип атаки требует своего подхода к защите. Volumetric-атаки фильтруются на уровне провайдера или CDN, protocol-атаки — на уровне балансировщика, а L7-атаки — на уровне WAF (Web Application Firewall) и приложения.

Уровень 1: Edge Protection

Первая линия защиты — распределённая CDN-сеть (Cloudflare, AWS CloudFront, Qrator). CDN-провайдеры обладают огромной пропускной способностью (десятки терабит/с) и специализированным оборудованием для фильтрации volumetric-атак. Атакующий трафик поглощается на ближайшей к источнику точке присутствия CDN, не достигая вашей инфраструктуры.

Для российского рынка мы рекомендуем комбинацию международного CDN (Cloudflare) и российского AntiDDoS-провайдера (Qrator, DDoS-Guard). Это обеспечивает защиту от глобальных атак и оптимальную производительность для российских пользователей.

Уровень 2: Сетевая инфраструктура

На уровне сетевой инфраструктуры мы используем BGP blackholing для сброса трафика при масштабных volumetric-атаках, rate limiting на уровне сетевого оборудования и географическую фильтрацию трафика. Если ваш сервис обслуживает только российских пользователей, трафик из стран-источников атак может быть заблокирован на уровне файрвола.

Kubernetes Network Policies и Calico/Cilium предоставляют дополнительный уровень сетевой изоляции внутри кластера. Мы ограничиваем входящий трафик только необходимыми портами и протоколами, что снижает поверхность атаки.

Уровень 3: WAF и Rate Limiting

Web Application Firewall (WAF) анализирует HTTP-запросы и блокирует вредоносные паттерны. ModSecurity с Core Rule Set (CRS) — open-source решение, которое мы разворачиваем как sidecar или отдельный сервис перед приложением. Для облачных решений используем AWS WAF или Yandex SmartWebSecurity.

Rate limiting — обязательный компонент защиты L7-атак. Мы реализуем его на нескольких уровнях: на уровне Ingress Controller (NGINX rate limiting), на уровне API Gateway и на уровне приложения. Каждый уровень использует свои лимиты и окна (per-IP, per-user, per-endpoint).

Уровень 4: Приложение

На уровне приложения мы внедряем circuit breakers для защиты от каскадных отказов, очереди с ограничением длины для контроля конкурентности и graceful degradation — отключение некритичных функций при высокой нагрузке. Это позволяет сервису оставаться доступным для легитимных пользователей даже во время атаки.

CAPTCHA и proof-of-work challenges помогают отличить легитимных пользователей от ботов. Мы используем адаптивный подход: CAPTCHA показывается только при превышении пороговых значений подозрительного трафика, чтобы не ухудшать пользовательский опыт в нормальном режиме.

Автоматизация реагирования

Обнаружение и реагирование на DDoS-атаки должно быть автоматизировано. Мы используем Prometheus-метрики для обнаружения аномалий в трафике (резкое увеличение RPS, рост error rate, увеличение latency) и автоматические реакции: включение stricter rate limiting, активация CAPTCHA, переключение на DDoS-mitigated DNS-запись.

Runbooks с автоматизированными шагами реагирования в PagerDuty или Opsgenie помогают дежурной команде быстро принимать решения даже в стрессовой ситуации. Каждый runbook тестируется в рамках регулярных DR-учений.

Тестирование устойчивости

Мы регулярно проводим нагрузочное тестирование и DDoS-симуляции для проверки эффективности защиты. Инструменты вроде Locust, k6 и Gatling позволяют симулировать различные паттерны атак в контролируемой среде. Результаты тестирования используются для настройки пороговых значений, оптимизации rate limiting и улучшения runbooks.

Защита от DDoS — непрерывный процесс, а не одноразовая настройка. Атаки эволюционируют, и защита должна эволюционировать вместе с ними. Регулярный аудит, тестирование и обновление — ключ к устойчивости.

← Назад к блогу
Мониторинг

Grafana: создание production-ready дашбордов

Grafana стала стандартом для визуализации метрик в cloud-native инфраструктуре. Однако создание дашбордов, которые реально помогают команде обнаруживать и расследовать инциденты, — это искусство, требующее понимания принципов визуализации данных, знания метрик и опыта incident response.

Принципы хороших дашбордов

Первый принцип — целевая аудитория. Дашборд для SRE-дежурного отличается от дашборда для product-менеджера. Дежурному нужны operational-метрики с алертами и drill-down, менеджеру — business-метрики с трендами. Мы создаём иерархию дашбордов: overview → service → component, где каждый уровень добавляет детализацию.

Второй принцип — USE и RED методы. USE (Utilization, Saturation, Errors) для инфраструктурных ресурсов и RED (Rate, Errors, Duration) для сервисов. Каждый production-дашборд должен начинаться с этих базовых метрик, которые покрывают 90% типичных инцидентов.

Структура service-дашборда

Service-дашборд должен отвечать на три вопроса: «Работает ли сервис?», «Насколько быстро?» и «Есть ли ошибки?». Мы структурируем дашборд в виде рядов (rows): первый ряд — Golden Signals (request rate, error rate, latency P50/P95/P99, saturation), второй — ресурсы (CPU, memory, disk, network), третий — бизнес-метрики (количество транзакций, конверсия).

{
  "panels": [
    {
      "title": "Request Rate",
      "type": "timeseries",
      "targets": [{
        "expr": "sum(rate(http_requests_total{service=\"$service\"}[5m]))",
        "legendFormat": "{{method}} {{status}}"
      }],
      "fieldConfig": {
        "defaults": {
          "unit": "reqps",
          "custom": {
            "drawStyle": "line",
            "fillOpacity": 10
          }
        }
      }
    },
    {
      "title": "Error Rate",
      "type": "stat",
      "targets": [{
        "expr": "sum(rate(http_requests_total{service=\"$service\",status=~\"5..\"}[5m])) / sum(rate(http_requests_total{service=\"$service\"}[5m])) * 100"
      }],
      "fieldConfig": {
        "defaults": {
          "unit": "percent",
          "thresholds": {
            "steps": [
              {"value": 0, "color": "green"},
              {"value": 1, "color": "yellow"},
              {"value": 5, "color": "red"}
            ]
          }
        }
      }
    }
  ]
}

Переменные и template variables

Template variables позволяют создавать переиспользуемые дашборды. Вместо жёстко закодированных значений (имя сервиса, namespace, кластер) используются переменные, которые пользователь выбирает из выпадающих списков. Это позволяет одному дашборду работать для всех сервисов.

Мы используем chained variables: выбор кластера фильтрует список namespace, выбор namespace фильтрует список сервисов. Также полезны interval variables для адаптивного разрешения: при просмотре данных за час используется 15-секундный интервал, за сутки — 5-минутный, за неделю — 30-минутный.

Алерты в Grafana

Grafana 9+ предоставляет мощную систему алертинга, которая может заменить отдельный Alertmanager для многих use cases. Мы определяем alert rules на основе PromQL-выражений с многоуровневой эскалацией: warning → critical → page. Notification policies маршрутизируют алерты в нужные каналы (Slack, PagerDuty, Telegram) в зависимости от severity и labels.

Важно избегать alert fatigue — состояния, когда команда получает слишком много алертов и начинает их игнорировать. Мы рекомендуем: каждый алерт должен требовать действия, используйте grouping для объединения связанных алертов, настраивайте inhibition для подавления вторичных алертов при первичной проблеме.

Dashboard as Code

Для production мы управляем дашбордами как кодом через Grafonnet (Jsonnet-библиотека для Grafana) или Terraform Grafana Provider. Это обеспечивает версионирование, code review и воспроизводимость дашбордов. При пересоздании Grafana все дашборды автоматически восстанавливаются из Git.

Grafonnet позволяет создавать переиспользуемые компоненты дашбордов: стандартные панели для HTTP-метрик, ресурсов контейнера, SLO. Команды используют эти компоненты как строительные блоки для своих дашбордов, обеспечивая консистентность визуализации по всей организации.

Оптимизация производительности

Дашборды с большим количеством панелей и сложных запросов могут быть медленными. Мы используем recording rules в Prometheus для предвычисления часто используемых агрегаций, ограничиваем time range по умолчанию (6 часов вместо 24), используем $__rate_interval вместо фиксированных интервалов и минимизируем количество серий в одном запросе через label matching.

Хороший дашборд — это инструмент, который ускоряет обнаружение и расследование инцидентов. Инвестируйте время в его создание, тестируйте на реальных инцидентах и итеративно улучшайте. Помните: лучший дашборд — тот, который помогает команде спать спокойно.

← Назад к блогу
Мониторинг

Loki: централизованный сбор логов

Grafana Loki — система агрегации логов, разработанная по принципу «like Prometheus, but for logs». В отличие от Elasticsearch, Loki не индексирует содержимое логов, а индексирует только метаданные (labels). Это делает его значительно дешевле в эксплуатации и проще в управлении, сохраняя при этом высокую скорость поиска.

Почему Loki, а не ELK

ELK-стек (Elasticsearch, Logstash, Kibana) долгое время был стандартом для централизованного логирования. Однако Elasticsearch требует значительных ресурсов для индексации и хранения данных, сложен в администрировании (управление шардами, репликацией, mapping) и дорог в масштабировании. Для типичного production-кластера с 100 сервисами затраты на Elasticsearch могут составлять 30–40% от общего бюджета на мониторинг.

Loki использует объектное хранилище (S3, GCS, MinIO) для хранения данных, что на порядок дешевле. Индексация только по labels означает минимальные требования к памяти и CPU. Нативная интеграция с Grafana позволяет корреллировать логи с метриками на одном дашборде.

Архитектура Loki

Loki состоит из нескольких компонентов: Distributor принимает входящие логи и распределяет их по Ingester'ам. Ingester буферизирует логи в памяти и периодически сбрасывает chunk'и в объектное хранилище. Querier выполняет запросы, читая данные из Ingester'ов (свежие данные) и object storage (исторические данные). Query Frontend оптимизирует запросы через кэширование и разделение на подзапросы.

Для production мы развёртываем Loki в microservices mode, где каждый компонент масштабируется независимо. Это позволяет оптимизировать ресурсы: Ingester'ы получают больше памяти для буферизации, Querier'ы — больше CPU для обработки запросов.

Сбор логов с Promtail

Promtail — агент для сбора логов, который разворачивается как DaemonSet на каждой ноде Kubernetes-кластера. Он автоматически обнаруживает поды через Kubernetes API, читает лог-файлы контейнеров и отправляет их в Loki с метаданными (namespace, pod name, container name).

# promtail-config.yaml
server:
  http_listen_port: 9080

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://loki:3100/loki/api/v1/push

scrape_configs:
  - job_name: kubernetes-pods
    kubernetes_sd_configs:
      - role: pod
    pipeline_stages:
      - cri: {}
      - json:
          expressions:
            level: level
            msg: msg
            trace_id: trace_id
      - labels:
          level:
      - timestamp:
          source: time
          format: RFC3339Nano

Pipeline stages в Promtail позволяют парсить, фильтровать и трансформировать логи перед отправкой в Loki. Мы используем JSON-парсинг для извлечения структурированных полей, regex для unstructured логов и drop stage для фильтрации шумных записей.

LogQL: язык запросов

LogQL — язык запросов Loki, вдохновлённый PromQL. Он поддерживает фильтрацию по labels, full-text search, парсинг и агрегации. Базовый запрос выглядит так: {namespace="production", app="api-server"} |= "error" — найти все лог-строки с labels namespace=production и app=api-server, содержащие слово error.

Продвинутые запросы позволяют агрегировать логи как метрики: sum(rate({namespace="production"} |= "error" [5m])) by (app) — частота ошибок по каждому приложению за последние 5 минут. Это мощный инструмент для создания алертов на основе логов.

Retention и стоимость

Правильная настройка retention (срока хранения) критически важна для контроля стоимости. Мы используем table manager или compactor для автоматического удаления старых данных. Типичная конфигурация: 7 дней для debug-логов, 30 дней для info, 90 дней для error и critical.

Использование object storage с lifecycle policies позволяет автоматически перемещать старые chunk'и в более дешёвые классы хранения (S3 Glacier, Yandex Cold Storage). Это значительно снижает стоимость долгосрочного хранения логов.

Интеграция с Grafana

Нативная интеграция Loki с Grafana — одно из его главных преимуществ. На одном дашборде можно отобразить метрики из Prometheus, логи из Loki и трейсы из Tempo, связав их через common labels (trace_id, service_name). При клике на аномалию в метриках можно мгновенно перейти к соответствующим логам.

Loki Explore view в Grafana предоставляет интерактивный интерфейс для ad-hoc запросов к логам. Это незаменимый инструмент при расследовании инцидентов, когда нужно быстро найти нужную информацию без написания сложных запросов.

Loki — отличный выбор для cloud-native окружений, где важны стоимость, простота эксплуатации и интеграция с Prometheus/Grafana. Для большинства компаний он полностью заменяет ELK-стек с экономией до 70% на инфраструктурных расходах.

← Назад к блогу
Архитектура

Istio Service Mesh: от теории к практике

Service mesh — инфраструктурный слой, который обеспечивает безопасную, надёжную и наблюдаемую коммуникацию между микросервисами. Istio — наиболее функциональная реализация service mesh для Kubernetes, предоставляющая traffic management, security, observability из коробки. В этой статье мы разберём практические аспекты внедрения Istio в production.

Что решает service mesh

Без service mesh каждый микросервис самостоятельно реализует cross-cutting concerns: retry logic, circuit breaking, mTLS, distributed tracing, rate limiting. Это приводит к дублированию кода, несогласованности реализаций и сложности обновления. Service mesh выносит эту функциональность в инфраструктурный слой, освобождая разработчиков от необходимости думать о сетевых аспектах.

Istio внедряет Envoy proxy как sidecar-контейнер рядом с каждым подом. Весь входящий и исходящий трафик проходит через Envoy, который применяет политики маршрутизации, безопасности и мониторинга. Приложение работает с plain HTTP на localhost, не зная о существовании mesh.

Установка и настройка

Для production мы используем Istio Operator или Helm для установки, что обеспечивает декларативное управление конфигурацией. Minimal profile подходит для начала, production profile включает все компоненты включая telemetry и policy enforcement.

Sidecar injection настраивается на уровне namespace через label istio-injection=enabled. При создании пода в таком namespace Istio mutating webhook автоматически добавляет контейнер istio-proxy (Envoy). Для постепенного внедрения можно включать injection по одному namespace за раз.

Traffic Management

VirtualService и DestinationRule — основные ресурсы для управления трафиком. VirtualService определяет правила маршрутизации (по URL path, headers, percentage), а DestinationRule — политики для destination (load balancing, circuit breaking, connection pooling).

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: api-service
spec:
  hosts:
  - api-service
  http:
  - match:
    - headers:
        x-canary:
          exact: "true"
    route:
    - destination:
        host: api-service
        subset: canary
  - route:
    - destination:
        host: api-service
        subset: stable
      weight: 95
    - destination:
        host: api-service
        subset: canary
      weight: 5
    retries:
      attempts: 3
      perTryTimeout: 2s
      retryOn: 5xx,reset,connect-failure

---
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: api-service
spec:
  host: api-service
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 100
      http:
        h2UpgradePolicy: DEFAULT
        http1MaxPendingRequests: 100
        http2MaxRequests: 1000
    outlierDetection:
      consecutive5xxErrors: 5
      interval: 30s
      baseEjectionTime: 30s
  subsets:
  - name: stable
    labels:
      version: stable
  - name: canary
    labels:
      version: canary

Безопасность

Istio автоматически обеспечивает mTLS между всеми сервисами в mesh. PeerAuthentication ресурс позволяет настроить режим (STRICT или PERMISSIVE) на уровне mesh, namespace или workload. AuthorizationPolicy предоставляет fine-grained access control: можно разрешить доступ только определённым сервисам к определённым endpoints.

RequestAuthentication позволяет валидировать JWT-токены на уровне mesh, без модификации приложения. Это полезно для external traffic, где End-user аутентификация проверяется на уровне ingress gateway.

Observability

Istio предоставляет три столпа observability из коробки. Метрики: Envoy автоматически генерирует метрики для каждого запроса (latency, error rate, request count) в формате Prometheus. Трейсы: propagation headers (x-request-id, x-b3-traceid) передаются между сервисами, и Envoy генерирует span'ы для каждого hop. Логи: access logs для каждого запроса с деталями маршрутизации.

Kiali — дашборд для визуализации service mesh. Он показывает граф сервисов с метриками трафика, позволяет обнаруживать аномалии (ошибки, высокую latency) и визуализирует политики безопасности. Это незаменимый инструмент для понимания сложных взаимосвязей в микросервисной архитектуре.

Производительность и overhead

Sidecar proxy добавляет latency к каждому запросу (обычно 1–3ms). Для большинства приложений это допустимо, но для latency-critical сервисов может быть значительным. Мы используем Istio ambient mesh (без sidecar) или waypoint proxy для таких случаев, что снижает overhead до sub-millisecond уровня.

Memory overhead sidecar составляет около 50–100MB на pod. Для кластеров с тысячами подов это значительный объём. Мы оптимизируем потребление через настройку concurrency, ограничение discovery scope и использование Sidecar resource для ограничения набора сервисов, о которых знает каждый Envoy.

Рекомендации по внедрению

Начинайте с observability — включите mesh для одного namespace и используйте метрики и трейсы для понимания трафика. Затем включите mTLS в PERMISSIVE режиме. После верификации — переключите на STRICT. Traffic management (canary, retries) внедряйте последними, когда команда освоит базовые концепции.

Istio — мощный инструмент, но он добавляет сложность. Убедитесь, что ваша команда готова к его эксплуатации: нужны навыки troubleshooting Envoy, понимание networking на уровне L7 и знакомство с PromQL для анализа метрик mesh. Инвестиции в обучение окупаются через повышение надёжности и безопасности системы.

← Назад к блогу
Безопасность

HashiCorp Vault: управление секретами

Управление секретами — одна из ключевых задач безопасности в современной инфраструктуре. Пароли к базам данных, API-ключи, TLS-сертификаты, токены доступа — все эти данные должны храниться безопасно, ротироваться регулярно и предоставляться приложениям без участия человека. HashiCorp Vault — промышленный стандарт для решения этих задач.

Проблема хранения секретов

Типичные антипаттерны: секреты в environment variables в CI/CD, захардкоженные credentials в коде, общие пароли, которые никогда не ротируются. Каждый из этих подходов создаёт риски: утечка через логи, через git history, через доступ к CI/CD системе. Vault решает эти проблемы, предоставляя централизованное хранилище с аудитом доступа и автоматической ротацией.

Архитектура Vault

Vault состоит из storage backend (хранилище зашифрованных данных), secret engines (механизмы управления секретами), auth methods (методы аутентификации) и policies (политики доступа). Storage backend может быть Consul, PostgreSQL, Raft (встроенный). Для production мы используем Raft — он не требует внешних зависимостей и обеспечивает HA через консенсус.

В Kubernetes мы разворачиваем Vault через официальный Helm-чарт с Raft storage, auto-unseal через cloud KMS (AWS KMS или Yandex KMS) и HA-конфигурацией из 3–5 реплик. Auto-unseal избавляет от необходимости ручного unsealing при перезапуске подов.

Secret Engines

Vault поддерживает различные secret engines. KV (Key-Value) — самый простой, хранит статические секреты. Database — динамически создаёт учётные записи для баз данных с ограниченным TTL. PKI — управляет сертификатами. Transit — шифрование как сервис (encryption as a service).

# Включение Database Secret Engine
vault secrets enable database

# Настройка PostgreSQL
vault write database/config/myapp-db \
  plugin_name=postgresql-database-plugin \
  allowed_roles="myapp-role" \
  connection_url="postgresql://{{username}}:{{password}}@db.example.com:5432/myapp" \
  username="vault-admin" \
  password="vault-admin-password"

# Создание роли с TTL
vault write database/roles/myapp-role \
  db_name=myapp-db \
  creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
  default_ttl="1h" \
  max_ttl="24h"

# Получение динамических credentials
vault read database/creds/myapp-role
# username: v-myapp-role-abc123
# password: A1b2C3d4E5-randomized
# ttl: 1h

Динамические секреты — одно из главных преимуществ Vault. Вместо использования одного общего пароля каждый экземпляр приложения получает уникальные credentials с ограниченным сроком действия. При компрометации одного экземпляра доступ автоматически истекает, и остальные экземпляры не затронуты.

Интеграция с Kubernetes

Vault Agent Injector автоматически внедряет sidecar-контейнер в поды, который аутентифицируется в Vault через Kubernetes ServiceAccount, получает секреты и записывает их в shared volume. Приложение читает секреты из файлов, не зная о существовании Vault.

Альтернативный подход — Vault CSI Provider, который монтирует секреты через Container Storage Interface. Третий вариант — External Secrets Operator, который синхронизирует секреты из Vault в Kubernetes Secrets. Каждый подход имеет свои преимущества: Agent Injector обеспечивает автоматическую ротацию, CSI Provider — нативную интеграцию с Kubernetes volumes, ESO — совместимость с приложениями, ожидающими Kubernetes Secrets.

Политики доступа

Vault использует HCL (HashiCorp Configuration Language) для описания политик доступа. Каждая политика определяет, какие пути (paths) доступны и какие операции (read, create, update, delete, list) разрешены. Мы следуем принципу least privilege: каждое приложение получает доступ только к своим секретам.

# policy: myapp-production.hcl
path "secret/data/production/myapp/*" {
  capabilities = ["read", "list"]
}

path "database/creds/myapp-production" {
  capabilities = ["read"]
}

path "pki/issue/myapp" {
  capabilities = ["create", "update"]
}

Аудит и мониторинг

Vault audit log записывает каждую операцию: кто запросил какой секрет, когда и откуда. Мы отправляем audit logs в Loki для поиска и анализа, а метрики Vault — в Prometheus для мониторинга производительности и здоровья кластера. Алерты настроены на: failed auth attempts, token expiration, seal status changes.

Vault — критический компонент инфраструктуры. Его правильное внедрение существенно повышает безопасность, а динамические секреты и автоматическая ротация минимизируют риски, связанные с утечкой credentials. Инвестиции в Vault окупаются при первом же предотвращённом инциденте безопасности.

← Назад к блогу
IaC

Ansible: автоматизация серверной инфраструктуры

Ansible — инструмент автоматизации IT-инфраструктуры, работающий по принципу push-модели через SSH. В отличие от Puppet и Chef, Ansible не требует установки агентов на управляемых серверах, что упрощает начальное внедрение. В этой статье мы разберём best practices для production-использования Ansible: структура проекта, роли, тестирование и интеграция с CI/CD.

Структура проекта

Для production-проектов мы используем структуру, рекомендованную Ansible Best Practices, с чётким разделением на inventory (описание серверов), roles (переиспользуемые модули), playbooks (сценарии) и group_vars/host_vars (переменные).

ansible/
├── ansible.cfg
├── inventory/
│   ├── production/
│   │   ├── hosts.yml
│   │   └── group_vars/
│   │       ├── all.yml
│   │       ├── webservers.yml
│   │       └── databases.yml
│   └── staging/
│       ├── hosts.yml
│       └── group_vars/
├── playbooks/
│   ├── site.yml
│   ├── webservers.yml
│   └── databases.yml
├── roles/
│   ├── common/
│   ├── nginx/
│   ├── postgresql/
│   └── monitoring/
└── requirements.yml

Написание ролей

Роли — основной строительный блок в Ansible. Хорошая роль должна быть идемпотентной (повторное применение не изменяет результат), параметризуемой (все значения — через defaults), документированной и протестированной.

# roles/nginx/tasks/main.yml
---
- name: Install NGINX
  apt:
    name: "nginx={{ nginx_version }}"
    state: present
    update_cache: yes
  notify: restart nginx

- name: Configure NGINX
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
    owner: root
    group: root
    mode: '0644'
    validate: 'nginx -t -c %s'
  notify: reload nginx

- name: Configure virtual hosts
  template:
    src: vhost.conf.j2
    dest: "/etc/nginx/sites-available/{{ item.name }}.conf"
    owner: root
    group: root
    mode: '0644'
  loop: "{{ nginx_vhosts }}"
  notify: reload nginx

- name: Enable virtual hosts
  file:
    src: "/etc/nginx/sites-available/{{ item.name }}.conf"
    dest: "/etc/nginx/sites-enabled/{{ item.name }}.conf"
    state: link
  loop: "{{ nginx_vhosts }}"
  notify: reload nginx

- name: Ensure NGINX is running
  service:
    name: nginx
    state: started
    enabled: yes

Обратите внимание на validate в модуле template — он проверяет синтаксис конфигурации перед применением. Если проверка не проходит, файл не изменяется, и сервис продолжает работать со старой конфигурацией. Handlers (notify/restart/reload) вызываются только при изменении, что обеспечивает идемпотентность.

Ansible Vault для секретов

Ansible Vault (не путать с HashiCorp Vault) — встроенный механизм шифрования файлов и переменных. Мы используем его для шифрования чувствительных переменных (пароли, API-ключи) в group_vars и host_vars. Файлы шифруются с помощью AES-256 и могут безопасно храниться в Git.

Для CI/CD vault password передаётся через environment variable или файл, что позволяет автоматизировать деплой без ручного ввода пароля. При использовании нескольких vault passwords мы используем vault-id для разграничения доступа между окружениями.

Тестирование с Molecule

Molecule — фреймворк для тестирования Ansible-ролей. Он создаёт эфемерные инстансы (Docker-контейнеры или виртуальные машины), применяет роль и запускает тесты. Тесты пишутся на Testinfra (Python) или используют встроенный verifier.

Мы интегрируем Molecule в CI/CD: каждый pull request с изменениями в ролях автоматически запускает тесты на нескольких платформах (Ubuntu 22.04, Ubuntu 24.04, CentOS Stream 9). Это обеспечивает совместимость и предотвращает регрессии.

Оптимизация производительности

Ansible по умолчанию выполняет задачи последовательно на каждом хосте. Для ускорения мы используем: forks (параллельное выполнение на нескольких хостах), pipelining (уменьшение количества SSH-соединений), mitogen (стратегия, которая значительно ускоряет выполнение за счёт минимизации overhead).

Async tasks позволяют запускать долгие операции (обновление пакетов, компиляция) асинхронно и продолжать выполнение других задач. Это особенно полезно при обновлении большого парка серверов, где каждый сервер обновляется несколько минут.

Интеграция с Terraform

Terraform и Ansible дополняют друг друга: Terraform создаёт инфраструктуру (серверы, сети, DNS), а Ansible конфигурирует её (устанавливает ПО, настраивает сервисы). Мы используем Terraform provisioner для автоматического запуска Ansible после создания серверов, или динамический inventory, который читает output Terraform для получения списка серверов.

Ansible остаётся одним из самых популярных инструментов автоматизации благодаря своей простоте (YAML-синтаксис, agentless), огромной экосистеме модулей и гибкости. Для задач конфигурации серверов и application deployment он остаётся оптимальным выбором, особенно в гибридных окружениях, где не вся инфраструктура работает в Kubernetes.

← Назад к блогу
Разработка

Python asyncio: паттерны для высоких нагрузок

Python asyncio позволяет обрабатывать тысячи concurrent connections на одном процессе, что делает его идеальным для I/O-bound задач: HTTP-серверов, WebSocket-приложений, интеграций с внешними API и очередями сообщений. В этой статье мы разберём паттерны asyncio, которые мы используем в production-системах, обрабатывающих миллионы запросов в день.

Основы event loop

Event loop — сердце asyncio. Он отслеживает асинхронные операции и вызывает callback'и при их завершении. В отличие от потоков, coroutines выполняются в одном потоке, переключаясь кооперативно через await. Это исключает проблемы race conditions и делает код более предсказуемым, но требует дисциплины: одна блокирующая операция может заморозить весь event loop.

Ключевое правило: никогда не выполняйте CPU-bound или blocking I/O операции в event loop. Для CPU-bound задач используйте ProcessPoolExecutor, для blocking I/O — ThreadPoolExecutor через loop.run_in_executor.

Structured concurrency с TaskGroup

Python 3.11 ввёл TaskGroup (аналог trio nursery) для structured concurrency. В отличие от голых create_task, TaskGroup гарантирует, что все задачи завершатся (или будут отменены) перед выходом из контекстного менеджера. Это предотвращает утечку задач и упрощает обработку ошибок.

import asyncio
import aiohttp

async def fetch_url(session: aiohttp.ClientSession, url: str) -> dict:
    async with session.get(url, timeout=aiohttp.ClientTimeout(total=10)) as resp:
        return {"url": url, "status": resp.status, "body": await resp.text()}

async def fetch_all(urls: list[str]) -> list[dict]:
    results = []
    async with aiohttp.ClientSession() as session:
        async with asyncio.TaskGroup() as tg:
            tasks = [
                tg.create_task(fetch_url(session, url))
                for url in urls
            ]
    results = [task.result() for task in tasks]
    return results

async def main():
    urls = [
        "https://api.service-a.internal/health",
        "https://api.service-b.internal/health",
        "https://api.service-c.internal/health",
    ]
    results = await fetch_all(urls)
    for r in results:
        print(f"{r['url']}: {r['status']}")

Semaphore для контроля конкурентности

При массовых запросах к внешним API важно ограничивать конкурентность, чтобы не перегрузить целевой сервис и не исчерпать файловые дескрипторы. asyncio.Semaphore — примитив синхронизации, который ограничивает количество одновременных coroutines.

async def fetch_with_limit(
    session: aiohttp.ClientSession,
    semaphore: asyncio.Semaphore,
    url: str
) -> dict:
    async with semaphore:
        async with session.get(url) as resp:
            return await resp.json()

async def process_batch(urls: list[str], max_concurrent: int = 50):
    semaphore = asyncio.Semaphore(max_concurrent)
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_with_limit(session, semaphore, url) for url in urls]
        return await asyncio.gather(*tasks, return_exceptions=True)

Graceful shutdown

Production-сервисы должны корректно завершать работу при получении сигнала SIGTERM (стандартный сигнал от Kubernetes при остановке пода). Корректное завершение включает: прекращение приёма новых запросов, ожидание завершения текущих запросов с таймаутом, закрытие соединений с БД и очередями, flush метрик и логов.

import asyncio
import signal
from contextlib import asynccontextmanager

class GracefulShutdown:
    def __init__(self):
        self._shutdown_event = asyncio.Event()
        self._tasks: set[asyncio.Task] = set()

    def is_shutting_down(self) -> bool:
        return self._shutdown_event.is_set()

    async def wait_for_shutdown(self):
        await self._shutdown_event.wait()

    def trigger_shutdown(self):
        self._shutdown_event.set()

    def track_task(self, task: asyncio.Task):
        self._tasks.add(task)
        task.add_done_callback(self._tasks.discard)

    async def wait_for_tasks(self, timeout: float = 30.0):
        if self._tasks:
            done, pending = await asyncio.wait(
                self._tasks, timeout=timeout
            )
            for task in pending:
                task.cancel()
            if pending:
                await asyncio.wait(pending)

async def main():
    shutdown = GracefulShutdown()
    loop = asyncio.get_event_loop()
    for sig in (signal.SIGTERM, signal.SIGINT):
        loop.add_signal_handler(sig, shutdown.trigger_shutdown)

    server = await start_server(shutdown)
    await shutdown.wait_for_shutdown()
    await shutdown.wait_for_tasks(timeout=30)
    await server.cleanup()

Connection pooling

Эффективное управление соединениями критически важно для производительности. aiohttp ClientSession поддерживает connection pooling через TCPConnector. Мы настраиваем limit (максимальное количество одновременных соединений), limit_per_host (ограничение на один хост) и keepalive_timeout.

Для баз данных используем asyncpg (PostgreSQL) или aiomysql (MySQL) с пулом соединений. asyncpg Pool автоматически управляет жизненным циклом соединений, переподключается при разрыве и ограничивает максимальное количество одновременных запросов к БД.

Error handling и retry

В распределённых системах ошибки — норма. Сеть может быть нестабильной, сервисы — временно недоступны. Мы используем библиотеку tenacity для retry с exponential backoff и jitter, что предотвращает thundering herd problem при массовых повторных запросах.

Asyncio предоставляет мощные инструменты для построения высоконагруженных Python-сервисов. Ключ к успеху — правильное использование примитивов конкурентности, graceful shutdown, connection pooling и robust error handling. Эти паттерны позволяют нам строить системы, обрабатывающие десятки тысяч запросов в секунду на одном Python-процессе.

← Назад к блогу
Разработка

Go: микросервисы с graceful shutdown

Go — один из самых популярных языков для разработки микросервисов благодаря нативной поддержке конкурентности, статической типизации и быстрой компиляции в single binary. Однако правильная реализация graceful shutdown — корректного завершения работы без потери запросов — требует понимания сигналов, context propagation и lifecycle management.

Почему graceful shutdown важен

В Kubernetes при обновлении deployment старые поды получают SIGTERM и имеют ограниченное время (terminationGracePeriodSeconds, по умолчанию 30 секунд) для корректного завершения. Если сервис не обрабатывает SIGTERM, после таймаута он получает SIGKILL — принудительное завершение. Все текущие запросы обрываются, транзакции откатываются, данные могут быть потеряны.

Базовая реализация

package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        w.Write([]byte("OK"))
    })
    mux.HandleFunc("/api/data", handleData)

    server := &http.Server{
        Addr:         ":8080",
        Handler:      mux,
        ReadTimeout:  15 * time.Second,
        WriteTimeout: 15 * time.Second,
        IdleTimeout:  60 * time.Second,
    }

    // Канал для OS-сигналов
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGTERM, syscall.SIGINT)

    // Запуск сервера в горутине
    go func() {
        log.Printf("Server starting on %s", server.Addr)
        if err := server.ListenAndServe(); err != http.ErrServerClosed {
            log.Fatalf("Server failed: %v", err)
        }
    }()

    // Ожидание сигнала
    sig := <-quit
    log.Printf("Received signal %v, shutting down...", sig)

    // Graceful shutdown с таймаутом
    ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second)
    defer cancel()

    if err := server.Shutdown(ctx); err != nil {
        log.Printf("Server shutdown error: %v", err)
    }
    log.Println("Server stopped")
}

Многоступенчатое завершение

Реальные микросервисы имеют несколько компонентов, требующих корректного завершения: HTTP-сервер, gRPC-сервер, подключения к БД, Kafka consumer, background workers. Порядок завершения важен: сначала прекращаем приём новых запросов, затем дожидаемся завершения текущих, затем закрываем соединения с зависимостями.

type App struct {
    httpServer  *http.Server
    grpcServer  *grpc.Server
    dbPool      *pgxpool.Pool
    kafkaReader *kafka.Reader
    wg          sync.WaitGroup
}

func (a *App) Shutdown(ctx context.Context) error {
    var errs []error

    // 1. Прекращаем приём новых запросов
    if err := a.httpServer.Shutdown(ctx); err != nil {
        errs = append(errs, fmt.Errorf("http shutdown: %w", err))
    }
    a.grpcServer.GracefulStop()

    // 2. Останавливаем Kafka consumer
    if err := a.kafkaReader.Close(); err != nil {
        errs = append(errs, fmt.Errorf("kafka close: %w", err))
    }

    // 3. Ожидаем завершения background workers
    done := make(chan struct{})
    go func() {
        a.wg.Wait()
        close(done)
    }()
    select {
    case <-done:
    case <-ctx.Done():
        errs = append(errs, fmt.Errorf("workers timeout"))
    }

    // 4. Закрываем соединения с БД
    a.dbPool.Close()

    return errors.Join(errs...)
}

Readiness probe и pre-stop hook

В Kubernetes важно координировать shutdown с readiness probe и service endpoints. Когда под получает SIGTERM, он должен начать возвращать unhealthy status на readiness probe. Kubernetes уберёт под из endpoints Service, и новые запросы перестанут направляться на него. Однако между SIGTERM и обновлением endpoints есть задержка (1–3 секунды), поэтому мы добавляем preStop hook с небольшой задержкой.

Также важно настроить terminationGracePeriodSeconds в соответствии со временем, необходимым для завершения самых долгих запросов. Если ваш сервис обрабатывает запросы до 60 секунд, terminationGracePeriodSeconds должен быть не менее 65 секунд.

Context propagation

Go context.Context — механизм propagation отмены и deadline'ов через весь стек вызовов. При graceful shutdown контекст сервера отменяется, и все handler'ы получают cancelled context. Важно, чтобы все операции (запросы к БД, HTTP-вызовы, Kafka-производство) использовали request context и корректно реагировали на отмену.

Тестирование

Graceful shutdown нужно тестировать. Мы пишем интеграционные тесты, которые запускают сервер, отправляют long-running запросы и одновременно инициируют shutdown. Тест проверяет, что все запросы завершились успешно и ни один не был оборван. Это особенно важно перед изменениями в lifecycle management.

Правильная реализация graceful shutdown — обязательное требование для production-ready Go-микросервисов. Она обеспечивает zero-downtime deploys, предотвращает потерю данных и улучшает пользовательский опыт при обновлениях.

← Назад к блогу
Базы данных

ClickHouse: аналитика на миллиардах строк

ClickHouse — колоночная СУБД, разработанная в Яндексе для аналитических запросов (OLAP). Она способна обрабатывать миллиарды строк в секунду на одном сервере, что делает её идеальной для аналитики, метрик, логов и event-tracking. В этой статье мы разберём архитектуру ClickHouse, оптимизацию запросов и лучшие практики для production.

Колоночное хранение

В отличие от строковых СУБД (PostgreSQL, MySQL), ClickHouse хранит данные по столбцам. Это означает, что при выполнении запроса SELECT count(*) FROM events WHERE event_type = 'purchase' с диска читается только столбец event_type, а не все столбцы таблицы. Для аналитических запросов, которые обычно затрагивают 5–10% столбцов, это даёт ускорение в 10–100 раз.

Колоночное хранение также обеспечивает отличную компрессию: значения одного столбца обычно имеют схожий тип и распределение, что позволяет алгоритмам сжатия (LZ4, ZSTD) достигать коэффициента 5–10x. Меньший объём данных на диске — меньше I/O — быстрее запросы.

Движки таблиц

ClickHouse поддерживает множество движков таблиц, каждый из которых оптимизирован для определённого use case. MergeTree — основной движок для production: поддерживает primary key для быстрой фильтрации, partitioning для эффективного удаления старых данных и TTL для автоматической очистки.

CREATE TABLE events (
    event_date Date,
    event_time DateTime,
    user_id UInt64,
    event_type LowCardinality(String),
    page_url String,
    duration_ms UInt32,
    country LowCardinality(String),
    device LowCardinality(String)
)
ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_type, user_id, event_time)
TTL event_date + INTERVAL 6 MONTH
SETTINGS index_granularity = 8192;

ORDER BY определяет primary key и порядок хранения данных. Правильный выбор ORDER BY критически важен для производительности: столбцы, по которым чаще всего фильтруются запросы, должны стоять первыми. LowCardinality — специальный тип для столбцов с небольшим количеством уникальных значений, обеспечивающий dictionary encoding.

Оптимизация запросов

Первое правило оптимизации в ClickHouse — минимизировать объём сканируемых данных. Используйте PARTITION BY для ограничения сканируемых партиций (WHERE event_date >= '2026-01-01' пропустит все партиции до 2026 года). Используйте ORDER BY для ограничения сканируемых granules (фильтрация по первым столбцам ORDER BY наиболее эффективна).

-- Медленно: full scan по всем партициям
SELECT count(*) FROM events WHERE user_id = 12345;

-- Быстро: ClickHouse использует partition pruning + primary key
SELECT count(*) FROM events
WHERE event_date >= '2026-03-01'
  AND event_type = 'purchase'
  AND user_id = 12345;

-- Агрегация с GROUP BY
SELECT
    toStartOfHour(event_time) AS hour,
    event_type,
    count() AS cnt,
    uniq(user_id) AS unique_users,
    avg(duration_ms) AS avg_duration
FROM events
WHERE event_date = today()
GROUP BY hour, event_type
ORDER BY hour DESC;

Materialized Views

Materialized Views в ClickHouse — это триггеры, которые автоматически агрегируют данные при вставке. Они идеальны для предвычисления агрегатов, которые запрашиваются часто. В отличие от materialized views в PostgreSQL, в ClickHouse они обновляются инкрементально, без полного пересчёта.

CREATE MATERIALIZED VIEW events_hourly_mv
ENGINE = SummingMergeTree()
PARTITION BY toYYYYMM(hour)
ORDER BY (event_type, hour)
AS SELECT
    toStartOfHour(event_time) AS hour,
    event_type,
    count() AS cnt,
    uniq(user_id) AS unique_users
FROM events
GROUP BY hour, event_type;

Репликация и шардирование

Для production используется ReplicatedMergeTree с ClickHouse Keeper (или ZooKeeper) для координации реплик. Репликация обеспечивает отказоустойчивость: при падении одной реплики данные доступны на другой. Шардирование (Distributed table) распределяет данные по нескольким серверам для горизонтального масштабирования.

Мы рекомендуем начинать с 2 реплик без шардирования и добавлять шарды по мере роста данных. Один сервер ClickHouse способен обрабатывать десятки терабайт данных, поэтому шардирование требуется далеко не сразу.

Интеграция с аналитикой

ClickHouse интегрируется с Grafana (через ClickHouse datasource), Metabase, Apache Superset для визуализации. Для ETL используется clickhouse-client, HTTP-интерфейс или Kafka engine для потоковой загрузки данных. В нашей практике типичная архитектура: Kafka → ClickHouse Kafka Engine → Materialized View → Grafana.

ClickHouse — мощный инструмент для аналитики, который при правильной настройке обеспечивает скорость, недостижимую для традиционных СУБД. Ключ к эффективному использованию — понимание колоночной модели, правильный выбор ORDER BY и PARTITION BY, и использование materialized views для предвычисления частых агрегатов.

← Назад к блогу
Архитектура

Apache Kafka: потоковая обработка данных

Apache Kafka — распределённая платформа потоковой обработки данных, которая стала стандартом для event-driven архитектуры. Kafka обеспечивает высокую пропускную способность (миллионы сообщений в секунду), низкую задержку (миллисекунды), надёжность (репликация, at-least-once/exactly-once semantics) и масштабируемость (горизонтальное добавление брокеров). В этой статье мы разберём проектирование event-driven систем на базе Kafka.

Базовые концепции

Kafka оперирует понятиями: Topic (логический канал для сообщений), Partition (физический раздел топика для параллелизма), Producer (отправитель сообщений), Consumer (получатель) и Consumer Group (группа consumer'ов, обрабатывающих топик совместно). Каждая партиция — упорядоченный, append-only лог сообщений. Сообщения идентифицируются offset'ом — последовательным номером внутри партиции.

Проектирование топиков

Правильное проектирование топиков — фундамент эффективной Kafka-архитектуры. Мы рекомендуем один топик на один тип события (orders.created, payments.processed, users.registered). Это обеспечивает чёткое разделение ответственности, независимое масштабирование и управление retention.

Количество партиций определяет максимальный параллелизм consumer'ов: в Consumer Group не может быть больше consumer'ов, чем партиций. Мы рекомендуем начинать с количества партиций, равного количеству consumer'ов × 2 (для запаса при масштабировании). Увеличение партиций возможно без даунтайма, но уменьшение — нет.

# Создание топика
kafka-topics.sh --bootstrap-server kafka:9092 \
  --create \
  --topic orders.created \
  --partitions 12 \
  --replication-factor 3 \
  --config retention.ms=604800000 \
  --config cleanup.policy=delete \
  --config min.insync.replicas=2

# Конфигурация producer
bootstrap.servers=kafka-1:9092,kafka-2:9092,kafka-3:9092
acks=all
retries=3
retry.backoff.ms=100
max.in.flight.requests.per.connection=5
enable.idempotence=true
compression.type=lz4

Partitioning strategy

Key-based partitioning гарантирует, что все сообщения с одним ключом попадут в одну партицию и будут обработаны в порядке отправки. Для заказов ключом обычно выступает order_id или user_id — в зависимости от требований к ordering. Если ordering не важен, используется round-robin для равномерного распределения.

Custom partitioner полезен для сложных сценариев: например, если нужно гарантировать, что все события одного tenant'а обрабатываются одним consumer'ом для обеспечения consistency. Будьте осторожны с hot partitions — если один ключ генерирует непропорционально много событий, соответствующая партиция может стать узким местом.

Consumer patterns

Основные паттерны потребления: simple consumer (один consumer — один топик), consumer group (группа consumer'ов для параллельной обработки), CQRS (разделение записи и чтения через events), event sourcing (восстановление состояния из последовательности событий).

При реализации consumer'а важно обрабатывать rebalancing — перераспределение партиций между consumer'ами при добавлении/удалении инстансов. Cooperative Sticky Assignor минимизирует количество перемещаемых партиций при rebalancing, что снижает задержку.

Exactly-once semantics

Kafka поддерживает exactly-once semantics (EOS) через комбинацию idempotent producer и transactional API. Idempotent producer гарантирует, что каждое сообщение записывается ровно один раз, даже при retry. Transactional API позволяет атомарно записывать сообщения в несколько топиков и коммитить consumer offset.

EOS особенно важен для финансовых операций, где дублирование или потеря сообщений недопустимы. Однако EOS добавляет overhead (latency и throughput), поэтому используйте его осознанно, только для критических потоков данных.

Мониторинг Kafka

Ключевые метрики для мониторинга: consumer lag (разница между последним сообщением в партиции и текущим offset consumer'а — показывает, насколько consumer отстаёт от producer'а), under-replicated partitions (индикатор проблем с репликацией), request latency, network throughput. Мы используем Burrow для мониторинга consumer lag и Prometheus JMX exporter для сбора метрик брокеров.

Kafka — фундаментальный компонент event-driven архитектуры, который обеспечивает надёжную и масштабируемую передачу событий между сервисами. Правильное проектирование топиков, partitioning strategy и consumer patterns — ключ к эффективному использованию Kafka в production.

← Назад к блогу
Базы данных

MongoDB: шардирование и репликация

MongoDB — документоориентированная СУБД, популярная для приложений с гибкой схемой данных, высокой нагрузкой на запись и горизонтальным масштабированием. В этой статье мы разберём две ключевые возможности MongoDB для production: репликацию (для отказоустойчивости) и шардирование (для масштабирования).

Replica Set

Replica Set — группа из нескольких mongod-инстансов, хранящих одинаковые данные. Один из них является Primary (принимает записи), остальные — Secondary (реплицируют данные из Primary). При падении Primary происходит автоматический election нового Primary из Secondary. Минимальный production replica set состоит из 3 членов (или 2 + arbiter).

Read preference определяет, с каких членов replica set читаются данные. Primary — все чтения с Primary (строгая консистентность). PrimaryPreferred — Primary, если доступен; иначе — Secondary. Secondary — все чтения с Secondary (снижение нагрузки на Primary, но eventual consistency). SecondaryPreferred и Nearest — для оптимизации latency.

Write concern определяет гарантии записи. w: 1 — подтверждение от Primary. w: "majority" — подтверждение от большинства членов (обеспечивает durability при failover). j: true — запись в journal (обеспечивает durability при crash). Для production мы рекомендуем {w: "majority", j: true}.

Шардирование

Шардирование — горизонтальное распределение данных по нескольким серверам (шардам). Каждый шард содержит подмножество данных коллекции. Mongos — роутер, который направляет запросы к нужным шардам на основе shard key. Config servers хранят метаданные о распределении данных.

Выбор shard key — самое важное решение при шардировании. Shard key определяет, как данные распределяются по шардам. Хороший shard key обеспечивает: равномерное распределение данных (avoiding hot spots), locality запросов (запрос затрагивает минимальное количество шардов), масштабируемость записи (записи распределяются по шардам).

// Включение шардирования для базы данных
sh.enableSharding("myapp")

// Создание индекса для shard key
db.orders.createIndex({ "customer_id": "hashed" })

// Шардирование коллекции
sh.shardCollection("myapp.orders", { "customer_id": "hashed" })

// Проверка распределения данных
db.orders.getShardDistribution()

Паттерны shard key

Hashed shard key — хеш от поля, обеспечивает равномерное распределение, но не поддерживает range queries. Ranged shard key — диапазонное разбиение, поддерживает range queries, но может создавать hot spots (например, monotonically increasing _id). Compound shard key — комбинация полей, позволяющая балансировать между distribution и query locality.

Для time-series данных мы используем compound shard key: {tenant_id: 1, timestamp: 1}. Tenant_id обеспечивает распределение по шардам, а timestamp — эффективные range queries внутри шарда. Для данных без natural partition key используем hashed sharding.

Monitoring и операции

Ключевые метрики: replication lag (задержка Secondary относительно Primary), chunk distribution (равномерность распределения chunk'ов по шардам), slow queries, connection pool utilization. MongoDB Atlas предоставляет built-in мониторинг, для self-hosted мы используем Prometheus с MongoDB Exporter.

Балансировщик chunk'ов автоматически перемещает данные между шардами для поддержания равномерного распределения. В production важно запланировать окна балансировки на периоды минимальной нагрузки, так как перемещение chunk'ов потребляет ресурсы.

Best practices

Используйте WiredTiger storage engine (по умолчанию). Создавайте индексы для всех частых запросов. Ограничивайте размер документа (16MB лимит, но оптимально — до нескольких КБ). Используйте connection pooling. Настройте readConcern и writeConcern в соответствии с требованиями consistency/performance. Регулярно делайте бэкапы (mongodump или continuous backup).

MongoDB — отличный выбор для приложений с гибкой схемой, высокой нагрузкой и потребностью в горизонтальном масштабировании. Правильная настройка репликации и шардирования обеспечивает отказоустойчивость и производительность на любом масштабе.

← Назад к блогу
Архитектура

API Gateway: проектирование и rate limiting

API Gateway — единая точка входа для всех клиентских запросов к микросервисам. Он берёт на себя cross-cutting concerns: аутентификацию, авторизацию, rate limiting, request routing, protocol translation, response caching и observability. В этой статье мы разберём проектирование API Gateway и реализацию rate limiting.

Зачем нужен API Gateway

Без API Gateway каждый клиент (web, mobile, external partners) должен знать адреса всех микросервисов, реализовывать retry logic, обрабатывать разные протоколы. API Gateway абстрагирует внутреннюю архитектуру: клиент взаимодействует с одним endpoint'ом, а gateway маршрутизирует запросы к нужным сервисам, агрегирует ответы и применяет политики.

Популярные решения: Kong (Lua/OpenResty), Ambassador/Emissary (Envoy-based), AWS API Gateway (managed), Traefik, NGINX Plus. Для Kubernetes-окружений мы чаще всего используем Kong или Emissary-Ingress.

Паттерны маршрутизации

Path-based routing — маршрутизация по URL-пути: /api/users → user-service, /api/orders → order-service. Header-based routing — по заголовкам: версионирование API через Accept-Version header. Canary routing — перенаправление процента трафика на новую версию сервиса для постепенного rollout. A/B testing — маршрутизация на основе user segments для экспериментов.

Rate Limiting

Rate limiting ограничивает количество запросов от клиента за единицу времени. Это защищает backend от перегрузки, обеспечивает справедливое распределение ресурсов между клиентами и предотвращает abuse. Существует несколько алгоритмов rate limiting.

Token Bucket — клиент получает фиксированное количество токенов в единицу времени. Каждый запрос потребляет один токен. Когда токены заканчиваются, запросы отклоняются (429 Too Many Requests). Этот алгоритм допускает burst'ы (кратковременные всплески) при наличии накопленных токенов.

Sliding Window — подсчитывает запросы в скользящем окне (например, 100 запросов в 60-секундном окне). Более точный, чем fixed window, так как не имеет проблемы boundary conditions (когда клиент отправляет 100 запросов в конце одного окна и 100 в начале следующего).

# Kong rate limiting plugin configuration
apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
  name: rate-limit-api
config:
  minute: 100
  hour: 5000
  policy: redis
  redis_host: redis.default.svc
  redis_port: 6379
  redis_database: 0
  fault_tolerant: true
  hide_client_headers: false
plugin: rate-limiting

Уровни rate limiting

Rate limiting может применяться на разных уровнях. Global — общий лимит для всего API. Per-consumer — лимит для каждого API-ключа или пользователя. Per-endpoint — разные лимиты для разных endpoint'ов (POST обычно имеет более строгие лимиты, чем GET). Per-plan — разные лимиты для разных тарифных планов (free, basic, enterprise).

Мы рекомендуем комбинировать уровни: глобальный лимит для защиты от DDoS, per-consumer для справедливого распределения, per-endpoint для защиты тяжёлых операций. Response headers (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset) информируют клиента о текущих лимитах.

Authentication и Authorization

API Gateway — оптимальное место для аутентификации. JWT-валидация, OAuth 2.0 token introspection, API key verification выполняются на gateway, освобождая backend-сервисы от этой логики. Авторизация (проверка прав доступа) может выполняться как на gateway (coarse-grained), так и на backend (fine-grained).

Observability

API Gateway — центральная точка для сбора метрик. Request count, latency distribution, error rate, response size — все эти метрики агрегируются на gateway и предоставляют полную картину здоровья API. Distributed tracing начинается на gateway: он генерирует trace ID и передаёт его upstream-сервисам через заголовки.

Паттерн BFF

Backend for Frontend (BFF) — паттерн, при котором для каждого типа клиента (web, mobile, IoT) создаётся отдельный API Gateway. Web BFF может агрегировать данные из нескольких сервисов для рендеринга страницы, mobile BFF — оптимизировать payload для мобильных сетей, IoT BFF — использовать MQTT вместо HTTP.

API Gateway — критический компонент микросервисной архитектуры, обеспечивающий безопасность, надёжность и наблюдаемость. Rate limiting, authentication и intelligent routing делают API предсказуемым и защищённым от злоупотреблений.

← Назад к блогу
DevOps

Canary Deployments: безопасные релизы

Canary deployment — стратегия выкатки, при которой новая версия приложения сначала получает небольшой процент трафика (1–5%), и при отсутствии проблем процент постепенно увеличивается до 100%. Это минимизирует blast radius ошибочного релиза: если новая версия содержит баг, он затронет только малую часть пользователей, а не всех сразу.

Canary vs Blue-Green vs Rolling

Rolling update — стандартная стратегия Kubernetes: поды постепенно заменяются новой версией. Проста в реализации, но при обнаружении бага откат требует времени, и часть пользователей уже затронута. Blue-Green — полная копия production-окружения с новой версией; переключение трафика мгновенное, но требует двойных ресурсов. Canary — компромисс: минимальные ресурсы, контролируемый risk, автоматизация через метрики.

Реализация через Kubernetes

Базовая реализация canary в Kubernetes — два Deployment с разными версиями и общий Service. Пропорция трафика определяется количеством реплик: если stable имеет 9 реплик, а canary — 1, canary получает ~10% трафика. Однако этот подход грубый и не позволяет точно контролировать процент.

Для точного контроля мы используем Istio VirtualService с weight-based routing или Argo Rollouts — контроллер, специально разработанный для progressive delivery.

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: api-service
spec:
  replicas: 10
  strategy:
    canary:
      canaryService: api-service-canary
      stableService: api-service-stable
      trafficRouting:
        istio:
          virtualService:
            name: api-service-vsvc
            routes:
            - primary
      steps:
      - setWeight: 5
      - pause: {duration: 5m}
      - setWeight: 20
      - pause: {duration: 5m}
      - setWeight: 50
      - pause: {duration: 10m}
      - setWeight: 80
      - pause: {duration: 5m}
      analysis:
        templates:
        - templateName: success-rate
        startingStep: 2
        args:
        - name: service-name
          value: api-service-canary
  selector:
    matchLabels:
      app: api-service
  template:
    metadata:
      labels:
        app: api-service
    spec:
      containers:
      - name: api
        image: api-service:2.0.0
        ports:
        - containerPort: 8080

Automated Analysis

Argo Rollouts поддерживает automated analysis — автоматическую проверку health canary на основе метрик. AnalysisTemplate определяет PromQL-запрос и пороговые значения. Если метрика выходит за пределы допустимого, rollout автоматически откатывается.

apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: success-rate
spec:
  args:
  - name: service-name
  metrics:
  - name: success-rate
    interval: 60s
    successCondition: result[0] >= 0.99
    failureLimit: 3
    provider:
      prometheus:
        address: http://prometheus:9090
        query: |
          sum(rate(http_requests_total{
            service="{{args.service-name}}",
            status!~"5.."
          }[2m])) /
          sum(rate(http_requests_total{
            service="{{args.service-name}}"
          }[2m]))

Observability во время canary

Во время canary deployment критически важно иметь чёткую визуализацию разницы в поведении stable и canary версий. Мы создаём специальный Grafana-дашборд, который показывает side-by-side сравнение: latency distribution, error rate, CPU/memory usage, custom business metrics. Любое значимое отклонение canary от stable — повод для расследования.

Также важно различать errors и anomalies. Error — явная ошибка (5xx response). Anomaly — статистически значимое отклонение метрики (увеличение latency P99 на 30%, даже если нет ошибок). Automated analysis должен ловить оба типа проблем.

Feature Flags

Canary deployment часто комбинируется с feature flags. Новая версия деплоится для всех пользователей, но новая функциональность активируется только для определённого сегмента через feature flag. Это позволяет отделить deployment (DevOps concern) от feature release (product concern).

Rollback

Быстрый rollback — обязательное требование. С Argo Rollouts откат происходит автоматически при failure analysis или вручную через kubectl argo rollouts abort. Traffic мгновенно переключается обратно на stable версию, и canary поды останавливаются.

Canary deployments — золотой стандарт для production-релизов в компаниях, где downtime или баги стоят дорого. Автоматизация через Argo Rollouts и automated analysis делает процесс надёжным и предсказуемым, позволяя командам деплоить с уверенностью.

← Назад к блогу
SRE

Chaos Engineering: как ломать production правильно

Chaos Engineering — дисциплина экспериментирования с распределёнными системами для обнаружения скрытых уязвимостей до того, как они проявятся в виде реальных инцидентов. Идея проста: если вы не знаете, как ваша система поведёт себя при отказе компонента, значит, она не готова к production. Контролируемые эксперименты позволяют обнаружить и устранить слабые места проактивно.

Принципы Chaos Engineering

Netflix, пионер Chaos Engineering, сформулировал четыре принципа. Первый — определите steady state (нормальное поведение системы через бизнес-метрики: conversion rate, throughput, latency). Второй — сформулируйте гипотезу: «При отказе service-A система продолжит работать с деградацией, но без полного отказа». Третий — внесите реалистичное возмущение (kill pod, inject latency, corrupt network). Четвёртый — измерьте влияние и сравните с steady state.

Chaos Engineering — не случайное разрушение. Это научный метод: гипотеза → эксперимент → наблюдение → выводы. Каждый эксперимент должен быть спланирован, иметь blast radius (scope воздействия), emergency stop (кнопку отмены) и runbook для реагирования на неожиданные последствия.

Инструменты

Litmus Chaos — cloud-native chaos engineering платформа для Kubernetes. Она предоставляет каталог готовых экспериментов (pod-delete, container-kill, network-latency, disk-fill, cpu-stress) и ChaosEngine CRD для декларативного управления экспериментами.

apiVersion: litmuschaos.io/v1alpha1
kind: ChaosEngine
metadata:
  name: api-service-chaos
  namespace: production
spec:
  appinfo:
    appns: production
    applabel: app=api-service
    appkind: deployment
  engineState: active
  chaosServiceAccount: litmus-admin
  experiments:
  - name: pod-delete
    spec:
      components:
        env:
        - name: TOTAL_CHAOS_DURATION
          value: "60"
        - name: CHAOS_INTERVAL
          value: "10"
        - name: FORCE
          value: "false"
      probe:
      - name: api-health-check
        type: httpProbe
        httpProbe/inputs:
          url: http://api-service.production:8080/health
          method:
            get:
              criteria: ==
              responseCode: "200"
        mode: Continuous
        runProperties:
          probeTimeout: 5
          interval: 5
          retry: 2

Chaos Mesh — альтернатива от PingCAP, также для Kubernetes. Поддерживает сетевые fault'ы (partition, delay, loss, duplicate, corrupt), I/O fault'ы (latency, errors), time skew, JVM fault injection. Chaos Mesh Dashboard предоставляет визуальный интерфейс для управления экспериментами.

Типы экспериментов

Infrastructure chaos: kill node, kill pod, exhaust CPU/memory, fill disk, network partition. Эти эксперименты проверяют устойчивость к отказам инфраструктуры: правильно ли работают readiness probes, корректно ли Kubernetes перезапускает поды, не теряются ли данные.

Application chaos: inject latency into service calls, return errors from dependencies, corrupt messages in queue. Эти эксперименты проверяют устойчивость приложения: работают ли circuit breakers, корректно ли обрабатываются таймауты, есть ли graceful degradation.

Dependency chaos: database failover, Redis timeout, Kafka broker loss. Эти эксперименты проверяют поведение при отказе внешних зависимостей: переключается ли приложение на replica, работает ли кэш-fallback, корректно ли обрабатывается недоступность очереди.

GameDay

GameDay — практика проведения запланированных chaos-сессий с участием всей команды. В течение нескольких часов команда последовательно выполняет эксперименты, наблюдает за поведением системы, обнаруживает проблемы и документирует findings. GameDay — отличный способ обучения: новые инженеры видят реальное поведение системы под нагрузкой и при отказах.

Мы проводим GameDay ежемесячно, чередуя focus areas: один месяц — infrastructure resilience, другой — application resilience, третий — disaster recovery. Каждый GameDay заканчивается action items: задачи по исправлению обнаруженных проблем с deadline и ответственным.

Chaos в CI/CD

Для зрелых команд мы рекомендуем интеграцию chaos-тестов в CI/CD pipeline. При деплое новой версии автоматически запускаются базовые chaos-эксперименты (pod-delete, network-latency) и проверяется, что сервис продолжает соответствовать SLO. Если chaos-тест не проходит — деплой блокируется.

Культура

Chaos Engineering требует культуры blameless postmortem и psychological safety. Команда должна чувствовать себя комфортно, проводя эксперименты, которые могут выявить проблемы в их коде. Обнаружение проблемы — это победа, а не повод для обвинений. Leadership должен поддерживать и поощрять Chaos Engineering как практику повышения надёжности.

Chaos Engineering — инвестиция в надёжность. Каждый обнаруженный и устранённый failure mode — это предотвращённый инцидент в будущем. Начните с простых экспериментов (pod-delete) в staging, постепенно переходите к production и интегрируйте chaos в повседневную инженерную практику.

← Назад к блогу
SRE

Аудит инфраструктуры: чеклист из 50 пунктов

Аудит инфраструктуры — системная проверка всех аспектов вашей IT-инфраструктуры: безопасность, надёжность, производительность, стоимость, соответствие стандартам. Мы проводим десятки аудитов ежегодно и выработали комплексный чеклист из 50 пунктов, покрывающий все критические области. В этой статье мы детально разберём каждую категорию.

Безопасность (пункты 1–15)

1. Все сервисы используют TLS/mTLS для межсервисного трафика. Проверьте, что нет plain-text HTTP-соединений между сервисами, особенно для данных, содержащих PII (personally identifiable information). Используйте service mesh или cert-manager для автоматического управления сертификатами.

2. Секреты не хранятся в коде, переменных окружения CI/CD или конфиг-файлах. Используйте HashiCorp Vault, AWS Secrets Manager или Kubernetes Secrets (с шифрованием at rest через KMS). Проверьте git history на утечки секретов с помощью gitleaks или truffleHog.

3. Реализован принцип least privilege для всех service accounts. Каждый сервис имеет доступ только к ресурсам, необходимым для его работы. Проверьте IAM-роли в облаке, Kubernetes RBAC и политики Vault.

4. Настроены Network Policies в Kubernetes. Ingress и egress трафик ограничен только необходимыми портами и источниками. Default deny policy применена ко всем namespace.

5. Container images сканируются на уязвимости. Используйте Trivy, Snyk или Grype для сканирования в CI/CD pipeline. Блокируйте деплой образов с critical-уязвимостями.

6. Pods не запускаются от root. SecurityContext с runAsNonRoot: true, readOnlyRootFilesystem: true, capabilities drop ALL применены ко всем рабочим нагрузкам.

7. Pod Security Standards применены (Restricted или Baseline). Используйте Pod Security Admission (PSA) или OPA/Gatekeeper для enforcement.

8. Audit logging включен для Kubernetes API server, облачных сервисов и баз данных. Логи хранятся в защищённом хранилище с ограниченным доступом.

9. Ротация credentials автоматизирована. Пароли баз данных, API-ключи, TLS-сертификаты обновляются регулярно без ручного вмешательства.

10. WAF настроен для внешних endpoint'ов. OWASP Core Rule Set или аналог активирован, custom rules для специфичных атак добавлены.

11. DDoS-защита включена. CDN или AntiDDoS-провайдер поглощает volumetric-атаки, rate limiting защищает от L7-атак.

12. Backup'ы шифруются и хранятся в отдельном регионе/аккаунте. Restoration process тестируется регулярно (не реже 1 раза в квартал).

13. SSH-доступ к серверам ограничен bastion host или VPN. Ключи ротируются, password authentication отключен.

14. DNS-записи защищены DNSSEC. Мониторинг DNS-изменений настроен.

15. Incident response plan документирован и протестирован. Команда знает, кого звонить и что делать при security-инциденте.

Надёжность (пункты 16–25)

16. SLO (Service Level Objectives) определены для каждого критического сервиса. Availability, latency, error rate — с конкретными числовыми значениями. SLO мониторятся и отображаются на дашборде.

17. Horizontal Pod Autoscaler настроен для stateless-сервисов. Параметры масштабирования протестированы нагрузочным тестированием.

18. PodDisruptionBudget настроен для всех критических сервисов. Kubernetes не удалит больше подов, чем допускает PDB, при maintenance или rollout.

19. Readiness и liveness probes настроены корректно. Readiness — проверяет готовность обслуживать трафик. Liveness — проверяет, что процесс не завис. Startup probe — для приложений с долгой инициализацией.

20. Anti-affinity правила предотвращают размещение реплик на одной ноде. Pod topology spread constraints распределяют поды по зонам доступности.

21. Circuit breakers и retry с backoff настроены для всех external dependencies. Таймауты установлены на каждом уровне (HTTP client, database, queue).

22. Graceful shutdown реализован во всех сервисах. PreStop hooks и terminationGracePeriodSeconds настроены корректно.

23. Disaster Recovery план протестирован. RTO (Recovery Time Objective) и RPO (Recovery Point Objective) определены и достижимы. Backup restoration, failover, traffic switching — протестированы.

24. Database failover протестирован. При падении primary replica — автоматическое переключение на standby. Приложение корректно переподключается.

25. Chaos Engineering эксперименты проводятся регулярно. Результаты документируются, action items отрабатываются.

Производительность (пункты 26–35)

26. Resources requests и limits настроены для всех контейнеров. Requests на уровне P95 реального потребления, limits — с запасом для handling spikes.

27. Database queries оптимизированы. Slow query log включен и анализируется. Индексы покрывают частые запросы. N+1 problem устранена.

28. Caching strategy реализована. Redis или Memcached для hot data. Cache invalidation корректна. Cache hit rate мониторится.

29. CDN настроен для статических ресурсов. Правила кэширования оптимизированы (long TTL для immutable assets, short для dynamic content).

30. Connection pooling настроен для баз данных, HTTP-клиентов и очередей. Pool size соответствует нагрузке.

31. Нагрузочное тестирование проводится перед каждым major-релизом. Performance baseline установлен и отслеживается.

32. Profiling доступен для production (async-profiler, pprof). Flame graphs используются для обнаружения bottleneck'ов.

33. Compression включена для HTTP responses (gzip/brotli) и для данных в transit между сервисами.

34. Log level в production — INFO или WARN. Debug-логирование включается по запросу через feature flag или динамическую конфигурацию.

35. Garbage collection и memory management оптимизированы. JVM heap size, Go GOGC, Python GC — настроены под нагрузку.

Observability (пункты 36–42)

36. Три столпа observability реализованы: metrics (Prometheus), logs (Loki/ELK), traces (Jaeger/Tempo). Все три связаны через common labels (trace_id, service_name).

37. Алерты настроены и маршрутизируются правильно. Critical — PagerDuty, Warning — Slack. Алерты actionable (каждый алерт имеет runbook с шагами реагирования).

38. Дашборды покрывают все критические сервисы. Golden signals (rate, errors, latency, saturation) отображаются для каждого сервиса.

39. Retention настроен: метрики — 90 дней, логи — 30 дней (info), 90 дней (error), трейсы — 7 дней. Стоимость хранения оптимизирована.

40. Synthetic monitoring проверяет доступность ключевых user journeys (login, checkout, API calls) из нескольких географических точек.

41. Error tracking (Sentry, Rollbar) настроен для backend и frontend. Ошибки группируются, приоритизируются и назначаются ответственным.

42. Status page (Statuspage.io, Cachet) настроен для внешних пользователей. Обновляется автоматически на основе мониторинга.

Стоимость (пункты 43–47)

43. Tagging/labeling strategy реализована. Все облачные ресурсы и Kubernetes workloads имеют labels для attribution затрат по команде, проекту, окружению.

44. Неиспользуемые ресурсы удаляются регулярно. Unattached volumes, idle load balancers, orphaned snapshots — ежемесячный ревью.

45. Reserved instances или committed use discounts используются для предсказуемых нагрузок. Spot/preemptible instances — для stateless и fault-tolerant workloads.

46. Cost alerts настроены. При превышении бюджета на 10% — warning, на 20% — critical.

47. Rightsizing проводится ежеквартально. VPA recommendations анализируются, resources requests корректируются.

Процессы (пункты 48–50)

48. IaC покрывает 100% инфраструктуры. Никакие ресурсы не создаются вручную (кроме emergency). Terraform state защищён и бэкапится.

49. CI/CD pipeline включает: lint, unit tests, integration tests, security scanning, deploy to staging, smoke tests, deploy to production (canary). Rollback автоматизирован.

50. Documentation актуальна: architecture diagrams, runbooks, on-call procedures, post-mortems. Ведётся knowledge base с решениями типичных проблем.

Использование чеклиста

Мы рекомендуем проводить аудит по этому чеклисту ежеквартально. Для каждого пункта фиксируйте статус (pass/fail/partial), severity (critical/high/medium/low) и action item с deadline. Начинайте с critical-пунктов (безопасность) и двигайтесь к оптимизационным (стоимость). За 2–3 итерации ваша инфраструктура достигнет production-ready уровня, и аудит станет профилактической мерой, а не стрессовым событием.