Инфраструктура и CI/CD (Terraform)
Перед началом: Модули 1-8. Инструменты: Terraform, Yandex Cloud. Время: ~5 ч.
Из M8: мы настроили очередь (YMQ + триггер) через Terraform. Теперь опишем инфраструктуру целиком: YDB, API Gateway, Cloud Functions, IAM, Object Storage — и соберём CI/CD-пайплайн.
В этом модуле — «инфраструктура как код» (IaC) на Terraform и CI/CD-пайплайн для бэкенда чата. Фокус: инфраструктура — это код, воспроизводимый, проверяемый и откатываемый.
🔥 Зачем это вообще нужно
Инфраструктура создаётся через Web-консоль. Вчера кто-то удалил продакшен-базу «по ошибке». Восстановление из бэкапа — 2 часа даунтайма. Terraform: всё в Git, любое изменение — через PR, откат — git revert && terraform apply.
📖 Теория
Что описываем в Terraform
Все ресурсы Yandex Cloud — декларативно:
- YDB (serverless БД), Object Storage (бакет под статику PWA);
- API Gateway (HTTP/WebSocket-маршруты);
- Cloud Functions (Auth, Ingestion, Broadcast, Notification);
- YMQ + триггер (очередь и запуск Broadcast);
- Serverless Container (WS Gateway);
- IAM: сервисные аккаунты с минимальными ролями; Lockbox для секретов.
hcl# ── providers.tf ── terraform { required_providers { yandex = { source = "yandex-cloud/yandex", version = ">= 0.100" } } backend "s3" { bucket = "tf-state-chat" key = "backend/terraform.tfstate" endpoint = "storage.yandexcloud.net" region = "ru-central1" } } provider "yandex" { cloud_id = var.yc_cloud_id folder_id = var.yc_folder_id zone = "ru-central1-a" } # ── main.tf ── # Сервисный аккаунт для функций resource "yandex_iam_service_account" "worker" { name = "chat-worker" } resource "yandex_resourcemanager_folder_iam_member" "worker_roles" { for_each = toset(["ydb.editor", "ymq.writer", "ymq.reader", "functions.functionInvoker"]) folder_id = var.yc_folder_id role = each.value member = "serviceAccount:${yandex_iam_service_account.worker.id}" } # YDB (serverless) resource "yandex_ydb_database_serverless" "chat" { name = "chat-db" folder_id = var.yc_folder_id } # Object Storage (статика PWA) resource "yandex_storage_bucket" "frontend" { bucket = "chat-frontend-${var.project_id}" acl = "public-read" website { index_document = "index.html" error_document = "index.html" } } # Cloud Function (Ingestion) resource "yandex_function" "ingestion" { name = "ingestion" runtime = "go121" entrypoint = "main.HandleHTTP" memory = 128 execution_timeout = 10 service_account_id = yandex_iam_service_account.worker.id environment = { YDB_DATABASE = yandex_ydb_database_serverless.chat.database_path YMQ_QUEUE_URL = yandex_message_queue.chat_messages.id } content { zip_filename = "build/ingestion.zip" } } # API Gateway resource "yandex_api_gateway" "chat" { name = "chat-gateway" spec = templatefile("openapi.yaml.tpl", { ingestion_fn = yandex_function.ingestion.id auth_fn = yandex_function.auth.id ws_container = yandex_serverless_container.ws.id jwt_audience = "chat" jwt_issuer = "chat-auth" jwks_url = "https://chat-auth.yandexcloud.net/.well-known/jwks.json" }) } # Переменные (dev/prod) variable "yc_cloud_id" {} variable "yc_folder_id" {} variable "project_id" { default = "dev" }
CI/CD-пайплайн (GitHub Actions)
yaml# .github/workflows/deploy.yml name: Deploy Backend on: push: branches: [main] pull_request: jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: { go-version: '1.23' } - run: go test ./... -race -covermode=atomic terraform-plan: needs: test if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: hashicorp/setup-terraform@v3 - run: terraform init - run: terraform plan -var-file="dev.tfvars" env: { YC_TOKEN: '${{ secrets.YC_TOKEN }}' } deploy: needs: test if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: { go-version: '1.23' } - run: go build -o build/ingestion ./cmd/ingestion - run: zip -j build/ingestion.zip build/ingestion - uses: hashicorp/setup-terraform@v3 - run: terraform init - run: terraform apply -auto-approve -var-file="prod.tfvars" env: { YC_TOKEN: '${{ secrets.YC_TOKEN }}' } - name: Smoke test run: | curl -sf https://chat-api.yandexcloud.net/health || exit 1
State и окружения
- State хранится в remote backend (Object Storage) с блокировкой (lock) — чтобы два инженера не применили изменения одновременно.
terraform plan— посмотреть diff до применения;terraform apply— применить.- Окружения dev / prod — отдельные стейты и переменные (один модуль, разные
tfvars).
Не коммитьте
terraform.tfstateв репозиторий — там секреты и его нельзя править руками.
CI/CD-пайплайн
- push/PR → 2. lint + unit-test → 3. terraform plan (отчёт в PR) → 4. review & merge →
- terraform apply → 6. deploy функций + миграции → 7. smoke-тесты → ✅ релиз.
На провале smoke — rollback:
re-applyпредыдущей версии функций и tf-state.
Миграции БД применяются в пайплайне после apply, до smoke-тестов (с возможностью отката).
Безопасность
- Принцип минимальных привилегий: каждому сервисному аккаунту — только нужные роли
(
ydb.editor,functions.invoker,storage.adminи т.д.). - Секреты — в Yandex Lockbox, не в переменных окружения манифеста и не в коде.
- TLS: принудительный HTTPS/WSS (через API Gateway + Certificate Manager).
- Доступ к production-стейту — ограничен (отдельные права на backend-бакет).
Отказоустойчивость и DR
- Зоны доступности: YDB serverless реплицирует автоматически; контейнеры/функции — многозонные.
- Резервное копирование: регулярные бакапы критичных данных (YDB export в Object Storage).
- План восстановления (DR): зафиксировать RTO/RPO и последовательность восстановления.
Runbook
Короткий документ «инцидент с чатом»: что проверить первым (метрики, логи), как диагностировать (Yandex Monitoring/Logging), куда эскалировать, как откатить. Runbook сокращает время восстановления — его пишут заранее, а не в момент аварии.
💱 Компромиссы
- Terraform vs Web-консоль: воспроизводимо, reviewable, откатываемо — но порог входа и необходимость remote state.
- Remote state с блокировкой: командная работа без конфликтов, но добавляет зависимость (бакет + механизм блокировки).
terraform planв PR передapply: медленнее деплой, но ловит регрессии до прода.- Least privilege IAM: больше сервисных аккаунтов с узкими ролями — безопаснее, но сложнее управлять.
Практика
- Написать Terraform-модуль для YDB, Object Storage, одной Cloud Function и API Gateway.
- Настроить remote backend с блокировкой и два окружения (dev/prod) через
tfvars. - Собрать CI/CD (GitHub Actions/SourceCraft): plan в PR, apply после merge, smoke + rollback.
- Написать одностаничный Runbook инцидента.
🔗 К следующему модулю: Инфраструктура в коде. Масштабируем систему.
🧩 Проверь себя
🔗 Мостик к Модулю 10
Инфраструктура в коде. Масштабируем систему: что делать при росте до 100k пользователей.