Модуль 8. IaC + Compute: Terraform, K8s vs Serverless

Цель модуля: Научить студента проектировать и разворачивать инфраструктуру отказоустойчивой системы как код. Освоить Terraform для Yandex Cloud, применить принцип наименьших привилегий (IAM), настроить отказоустойчивость (Multi-AZ) и бэкапы (RPO/RTO), обосновать выбор платформы развертывания (K8s vs Serverless) с учетом FinOps и Cold Start. Связать IaC с безопасностью: SA, Lockbox, Security Groups.

Стадия роста проекта: Рост нагрузки ×10. Проект пережил интеграции, Outbox, Saga, Resilience, Observability и Security. Теперь система работает 24/7, метрики прозрачны, секреты в Lockbox. Но пришло время развернуть всё это в продакшене — и первое, что ломается — инфраструктура. ClickOps, дрейф, SA с правами editor, отсутствие Multi-AZ.

Перед началом: Модули 1–7 (особенно Observability — метрики для алертов, IAM — Lockbox) Инструменты: Terraform, Docker, Trivy, Yandex Cloud CLI Трудоёмкость: ~6 ч (теория 1.5 ч · практика 4 ч · ADR 0.5 ч)

🔥 Инцидент: когда «всё работает» — это уже проблема

Система защищена (Модуль 7). Но в понедельник — письмо от CFO: «Счёт за облако вырос в 2,3 раза. Объясните». В Billing: 12 ВМ по 8 ядер, загрузка 15–20% — греют воздух. Пики вечером досят до 60%, остальное время ты платишь за пустоту. Это FinOps-риск: хорошая архитектура не должна стоить как плохая эксплуатация.

И последняя ловушка: Terraform-код пишется «на коленке». Половина ресурсов — через Web-консоль (ClickOps). tfstate лежит локально у одного инженера. SA приложения имеет роль editor на всю папку — может удалять кластеры и менять IAM. terraform plan в CI нет — дрейф неизбежен: код один, реальность другая.

Четыре зоны риска:

  1. «Платим за воздух» — K8s + HPA для синхронных API (предсказуемый latency), Serverless для воркеров (оплата за вызов). Cold Start Go приемлем для воркеров.
  2. «Код ≠ реальность» — Terraform: tfstate в Object Storage, terraform plan в CI, SA с минимальными правами.
  3. «Нет Multi-AZ» — отказ зоны = простой. Managed PostgreSQL в 3 зонах, ALB балансирует.
  4. «SA с правами editor» — критическая уязвимость. Разделение SA для Terraform и приложения.

📖 Теория: Раскрытые термины

Глоссарий модуля:

  • IaC (Infrastructure as Code) — инфраструктура описана кодом (Terraform) и проходит ревью в Git, а не создаётся руками в консоли (ClickOps).
  • Drift (дрейф) — расхождение между кодом и реальным состоянием облака; детектируется terraform plan в CI.
  • SA (Service Account) — «робот»-аккаунт для сервисов; права выдаются по принципу least privilege.
  • Multi-AZ — размещение компонентов в нескольких зонах доступности (A/B/C) для отказоустойчивости.
  • Cold Start — задержка при первом запросе к «холодному» контейнеру (актуально для Serverless).
  • FinOps — финансовая осознанность в облаке: оценка стоимости решений и оптимизация счетов.
  • ClickOps — создание/изменение ресурсов через web-консоль вручную; антипаттерн при IaC (нет аудита, нет воспроизводимости).
  • RPO / RTO — Recovery Point/Time Objective: допустимая потеря данных / допустимое время простоя; настраиваются через бэкапы и failover.
  • Multi-stage Build — сборка в несколько слоев (компиляция → минимальный runtime), уменьшает размер образа и CVE-поверхность.

8.1. Infrastructure as Code (Terraform) и Drift Detection

Создание ресурсов руками через Web-консоль (ClickOps) — запрещено. Любое изменение должно быть описано в Terraform и пройти ревью в Git.

State-файл: Хранит текущее состояние. В команде он хранится в удаленном бэкенде (Object Storage) с блокировкой.

Drift (Рассинхрон): Если кто-то изменил настройку в консоли, код и стейт расходятся. В CI/CD обязательно должен быть шаг terraform plan для детекции дрифта перед применением изменений.

hcl
# terraform/backend.tf — remote state с блокировкой
terraform {
  backend "s3" {
    endpoint   = "storage.yandexcloud.net"
    bucket     = "terraform-state"
    region     = "ru-central1"
    key        = "production/terraform.tfstate"
    
    # Блокировка для предотвращения конкурентных изменений
    dynamodb_table = "terraform-locks"
  }
}

8.2. IAM: Разделение Service Accounts

SA для Terraform (CI/CD): Имеет права editor на папку, но токен короткоживущий (используется только в пайплайне).

SA для приложения: Долгоживущий, но с минимальными правами (lockbox.payloadViewer на конкретные секреты, managed-postgresql.user). Права editor у приложения — критическая уязвимость.

hcl
# SA для Terraform (CI/CD) — editor, но короткоживущий токен
resource "yandex_iam_service_account" "terraform" {
  name = "terraform-sa"
}

resource "yandex_resourcemanager_folder_iam_member" "terraform-editor" {
  folder_id = var.folder_id
  role      = "editor"
  member    = "serviceAccount:${yandex_iam_service_account.terraform.id}"
}

# SA для приложения — least privilege
resource "yandex_iam_service_account" "app" {
  name = "app-sa"
}

resource "yandex_resourcemanager_folder_iam_member" "app-lockbox" {
  folder_id = var.folder_id
  role      = "lockbox.payloadViewer"
  member    = "serviceAccount:${yandex_iam_service_account.app.id}"

  # condition: доступ только к нужным secret IDs
  condition {
    title      = "specific-secrets"
    expression = "resource.id in ['secret-db-password', 'secret-hmac-key']"
  }
}

8.3. Управляемая БД и Гарантии восстановления (RPO/RTO)

Использование Managed PostgreSQL в 3 Зонах Доступности (AZ) обеспечивает High Availability.

RPO (Recovery Point Objective): Допустимый объем потери данных (настраивается через частоту бэкапов, например, каждые 1 час).

RTO (Recovery Time Objective): Допустимое время простоя (зависит от автоматического failover).

hcl
resource "yandex_mdb_postgresql_cluster" "main" {
  name        = "main-pg-cluster"
  environment = "PRODUCTION"
  network_id  = yandex_vpc_network.main.id

  # Multi-AZ: хосты в 3 зонах
  host {
    zone      = "ru-central1-a"
    subnet_id = yandex_vpc_subnet.a.id
  }
  host {
    zone      = "ru-central1-b"
    subnet_id = yandex_vpc_subnet.b.id
  }
  host {
    zone      = "ru-central1-c"
    subnet_id = yandex_vpc_subnet.c.id
  }

  # Бэкапы: RPO = 1 час
  backup_window_start {
    hours   = 3
    minutes = 0
  }
  backup_retain_period_days = 7
}

8.4. Безопасность Docker-образов

Multi-stage build минимизирует размер.

Запрет Root: Контейнер должен запускаться от пользователя nobody или app.

Сканирование CVE: В CI/CD должен быть шаг с trivy или grype для проверки финального образа на уязвимости.

dockerfile
# Multi-stage build: компиляция в golang:alpine, запуск в scratch
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /app/server ./cmd/server

FROM scratch
COPY --from=builder /app/server /server
USER nobody
ENTRYPOINT ["/server"]

Фундамент готов: инфраструктура описана кодом, права разделены, образы безопасны. Но сколько это стоит?

8.5. Выбор среды: K8s vs Serverless (Cold Start)

Serverless Containers: Масштабирование от 0. Но есть Cold Start (задержка при первом запросе). Для Go-воркеров Cold Start приемлем (1–2 с). Для синхронных API с p99 < 100 мс — недопустим.

Managed Kubernetes: Постоянно работающие поды. Нет Cold Start, выше предсказуемость latency, но есть фиксированная стоимость (node hours).

Таблица критериев:

КритерийManaged Kubernetes (Путь A)Serverless Containers (Путь B)
Cold StartНет (поды всегда тёплые)Есть (десятки мс … секунды)
МасштабированиеHPA по метрикам, шаг — подыПод запрос (до 0), мгновенно
StatefulnessПодходит (StatefulSet, long-lived)Только stateless
Ops-нагрузкаВысокая (обслуживание кластера)Минимальная (управляемый сервис)
Стоимость при простоеПостоянная (плата за узлы)Почти нулевая (pay-per-invocation)
Стоимость при пикеПредсказуемаяМожет быть выше (за каждый запрос)
Контроль над рантаймомПолный (версии, node affinity)Ограниченный (квоты, лимиты YC)

Связь с Serverless MVP: если ты прошёл курс «Git Hosting: Serverless MVP», ты уже выбрал Serverless (Cloud Functions) для Git-хостинга MVP. Здесь мы обосновываем выбор для production: K8s для синхронных API, Serverless для воркеров. Паттерн тот же — инфраструктура разная.

8.6. Этот паттерн в архетипах

IaC + Compute проявляется по-разному в четырёх архетипах проектов:

АрхетипComputeIaC-спецификаПочему критично
ФинтехK8s (синхронные API, нет Cold Start)Terraform + Multi-AZ + PgBouncerДвойное списание недопустимо; RPO = 0
Git-хостингServerless для воркеров, K8s для APITerraform + NFS mount + CDNCold Start приемлем для Outbox Relay
МессенджерK8s (WebSocket, long-lived)Terraform + HPA + StatefulSetCold Start недопустим для real-time
ЛентаServerless для индексации, K8s для APITerraform + CDN + ClickHouseCold Start приемлем для фоновых задач

Общий принцип: K8s для синхронных API (нет Cold Start), Serverless для воркеров (оплата за вызов). Различается только соотношение синхронных/асинхронных нагрузок.

8.7. Компромиссы (Trade-offs)

ПодходПлюсМинусКогда выбирать
Managed Kubernetesпредсказуемый latency (нет Cold Start); полный контроль; StatefulSetфиксированная стоимость (node-hours); сложнее в эксплуатациисинхронные API, p99 < 100ms, постоянная нагрузка
Serverless Containersмасштаб от 0; оплата за вызов; минимум эксплуатацииCold Start (приемлем для Go-воркеров, недопустим для синхронных API)асинхронные воркеры, спайк-нагрузка, free-tier
Terraform (IaC)воспроизводимость, аудит, Drift Detection в CIlearning curve; tfstate → remote backendproduction (ClickOps — запрещён)
Docker non-root + Trivyleast-privilege внутри контейнера; CVE-scan в CIчуть сложнее Dockerfile (USER nobody, multi-stage)всегда для production-образов

Вывод для курса: для Go-воркеров — Serverless Containers; для синхронных API — K8s (Cold Start недопустим). Terraform обязателен. См. ADR-8.


🛠 Практические задания

Примерный план работы:

  1. Docker multi-stage + Trivy → образ < 20 МБ (Go), USER nobody, CVE-scan в CI.
  2. Terraform: VPC + 3 subnet (A/B/C) + Managed PG Multi-AZ + backups (RPO/RTO) + ObjectStorage tfstate.
  3. ADR-8: K8s vs Serverless → таблица критериев, обоснование выбора, план миграции.
  4. IAM + Network: 2 SA (Terraform + app), Security Groups (ALB public, app private, DB no public).

Задача 1: Безопасный Docker-образ (Multi-stage + Trivy)

Go: Мультистейдж сборка. Компиляция в golang:alpine, запуск в scratch (образ < 20 МБ).

Настроить запуск от USER nobody.

Добавить в GitHub Actions шаг: trivy image --exit-code 1 --severity CRITICAL my-image:latest.

✅ Готово, когда:

  • Multi-stage сборка (Go: scratch < 20 МБ)
  • Контейнер запускается от USER nobody (не root)
  • GitHub Actions: trivy image --exit-code 1 --severity CRITICAL — билд падает при CRITICAL CVE

Задача 2: Terraform для сети и БД (Multi-AZ + RPO/RTO)

Описать IaC:

  • VPC, 3 подсети в зонах A, B, C.
  • Managed PostgreSQL кластер (хосты в 3 зонах). Настроить автоматические бэкапы (окно, retention 7 дней). Описать RPO/RTO в ADR.
  • Object Storage для tfstate с блокировкой.

✅ Готово, когда:

  • VPC + 3 подсети в зонах A, B, C
  • Managed PostgreSQL кластер: хосты в 3 зонах, автоматические бэкапы (retention 7 дней)
  • Object Storage для tfstate с блокировкой
  • RPO/RTO описаны в ADR

Задача 3: Вычислительная инфраструктура (Обоснование в ADR)

Студент должен выбрать путь, используя таблицу критериев, и описать выбор в ADR:

Путь A (K8s): Описать создание MCK. Написать Helm-чарт (Deployment, HPA).

Путь B (Serverless): Описать реестр и Trigger. Оценить Cold Start для Go-воркеров.

✅ Готово, когда:

  • Выбран путь (A: K8s или B: Serverless) и обоснован таблицей критериев из задания
  • ADR-8 написан: контекст, альтернативы, решение, метрики ($/мес, cold start, RTO), план миграции
  • Путь A: описан MCK + Helm-чарт (Deployment, HPA)
  • Путь B: описаны реестр + Trigger + оценка Cold Start

Задача 4: IAM и Сегментация сети

Создать SA для Terraform и SA для приложения с разделением прав.

Настроить Security Groups: ALB в public subnet, ВМ/Поды в private subnet. Запретить публичный доступ к БД.

✅ Готово, когда:

  • 2 отдельных SA: один для Terraform, один для приложения (разделение прав)
  • Security Groups: ALB — public subnet, ВМ/Поды — private subnet
  • Нет публичного доступа к БД (только из private subnet)

⚠️ Антипаттерн для разбора

Студенту дается фрагмент Terraform кода для анализа.

hcl
# Terraform Антипаттерн
resource "yandex_compute_instance" "app" {
  name = "app-server"
  # ❌ Нет zone_id и subnet_id (нет Multi-AZ)
  resources {
    cores  = 8
    # ❌ Core-fraction не указан, берет 100%, дорого (FinOps risk)
  }
}

resource "yandex_resourcemanager_folder_iam_binding" "admin" {
  folder_id = var.folder_id
  role      = "editor" # ❌ Слишком широкие права для SA приложения
  members   = ["serviceAccount:app-sa"]
}

Вопросы студенту:

  1. Что произойдет с инфраструктурой, если использовать права editor для SA приложения, и этот аккаунт утечет?
  2. Почему отсутствие зоны (zone_id) нарушает отказоустойчивость?
  3. Почему хранение terraform.tfstate в локальном Git-репозитории приведет к конфликту и потере Drift Detection?

📐 Задание по визуализации (Mermaid)

Студенту необходимо нарисовать топологию сети в Yandex Cloud с сегментацией (public/private).

Шаблон для ориентира:

Топология сети в Yandex Cloud (public/private)

🧩 Проверь себя

1. Service Account приложения получил роль editor. Это…
2. Как IaC проявляется в Git-хостинге?
3. Docker-образ: зачем non-root (USER nobody)?
4. Зачем нужен remote tfstate с блокировкой?
5. Drift Detection — как реализуется?
6. В чём риск Serverless Containers для синхронных API?

🤖 Инструкция для LLM (Coding Plan Assistant) — Режим Модуля 8

Привет! Я начинаю Модуль 8 курса «Архитектура распределённых систем».

Дай короткий ответ (2–3 предложения): почему создание ресурсов через Web-консоль (ClickOps) разрушает воспроизводимость и аудит, и зачем нужен remote tfstate с блокировкой?

Объясни разницу между Managed Kubernetes и Serverless Containers с точки зрения SLA, latency (Cold Start для Go) и возможности mTLS. Дай таблицу критериев и попроси меня выбрать путь (A или B) и обосновать в ADR.

Покажи фрагмент Антипаттерна Terraform (без zone_id, с editor-правами) и попроси объяснить риски отказоустойчивости и безопасности.

Дай описание задания: написать Dockerfile (multi-stage, non-root, Trivy-scan), описать VPC+PG+ALB в Terraform (Multi-AZ, RPO/RTO), настроить IAM (разделение SA Terraform и приложения).

НЕ ПИШИ за меня Dockerfile и Terraform-манифесты. Дождись моего ответа с архитектурным планом. После этого дай фидбек по чек-листу ментора и укажи на слепые зоны (например, «нет проверки drift», «SA слишком широкий», «нет RPO/RTO»).


📝 Чек-лист ментора

  • Docker: Multi-stage build. Запуск от non-root пользователя. В CI есть шаг trivy для проверки CVE.

  • IaC: Вся инфраструктура описана в Terraform. В CI/CD есть шаг terraform plan для Drift Detection.

  • State: terraform.tfstate хранится в Object Storage (S3) с блокировкой.

  • Multi-AZ & Network: ВМ/Поды и хосты БД распределены по 3 Зонам. Security Groups запрещают публичный доступ к БД (сегментация public/private).

  • IAM (Least Privilege): SA для Terraform (CI/CD) и SA для приложения разделены. У приложения нет прав editor.

  • PostgreSQL (RPO/RTO): Настроены бэкапы. В ADR описаны целевые RPO и RTO.

  • Serverless vs K8s: Выбор обоснован в ADR по таблице критериев (учтен Cold Start для Go).

  • FinOps: Обоснован выбор ресурсов (прерываемые ВМ для воркеров, core-fraction).

  • Mermaid-диаграмма: Отображает балансировку ALB, распределение по Зонам, Security Groups и поток секретов из Lockbox.

  • ADR-8: Student сформулировал ADR «K8s vs Serverless для Go» (контекст, альтернативы, решение, метрики, план миграции).

🔗 Мостик к Модулю 9 (Эволюция БД)

В конце ревью скажи студенту:

  • «Отлично, мы описали идеальную инфраструктуру в коде, обеспечили Multi-AZ и безопасность. Но БД — узкое место №1. В pg_stat_activity — 950 коннектов, таблица payments — 800 ГБ, VACUUM идёт часами. Команда предлагает «просто добавить реплику» — но реплика разгрузит чтение, а узкое место сейчас запись и CPU мастера. В Модуле 9 мы освоим эволюцию масштабирования БД: от PgBouncer до шардирования и CQRS. Не начинайте с шардирования — каждая ступень добавляет сложность. Исчерпайте предыдущую первой.»

Если ты прошёл курс «Git Hosting: Serverless MVP», ты уже видел этот паттерн на упрощённом стеке: Terraform для Cloud Functions + YDB Serverless + Message Queue. Паттерн тот же — инфраструктура разная. Здесь мы разбираем его на полном стеке, с глубиной и trade-offs.