Загрузка
Все статьи

Terraform vs Pulumi — Infrastructure as Code для Kotlin-команды

Сравниваем Terraform на HCL и Pulumi на Kotlin для управления Kubernetes-инфраструктурой — с реальными примерами кода, болью миграции и нашим итоговым выбором.

Наша команда из шести backend-разработчиков пишет на Kotlin. Когда пришло время вынести Kubernetes-манифесты из GitOps-репозитория в полноценный IaC, встал вопрос инструмента. Terraform — индустриальный стандарт, но HCL для нас чужой язык. Pulumi обещает infra-as-real-code на familiar-стеке. Я потратил две недели на прототипирование обоих подходов и поделюсь выводами.

Terraform: проверенный, но чужой

Terraform мы использовали полтора года для provisioning серверов в Yandex Cloud. HCL выучить несложно — это declarative-язык с минимальным синтаксисом. Но когда дело дошло до Kubernetes, начались проблемы. HCL не поддерживает циклы с условиями в полной мере, нет нормальной типизации, а отладка — это чтение логов на пол-экрана.

Вот наш типичный деплоймент на Terraform:

resource "kubernetes_namespace" "app" {
  metadata {
    name = "myapp"
  }
}

resource "kubernetes_deployment" "app" {
  metadata {
    name      = "myapp"
    namespace = kubernetes_namespace.app.metadata.0.name
  }
  spec {
    replicas = 3
    selector {
      match_labels = {
        app = "myapp"
      }
    }
    template {
      metadata {
        labels = {
          app = "myapp"
        }
      }
      spec {
        container {
          name  = "myapp"
          image = "cr.yandex/myproj/myapp:latest"
          port {
            container_port = 8080
          }
          resources {
            limits = {
              cpu    = "500m"
              memory = "512Mi"
            }
          }
        }
      }
    }
  }
}

Выглядит знакомо, но обратите внимание: нет никакой валидации на этапе компиляции. Опечатка в имени поля container_port обнаружится только при terraform apply. Для нас, привыкших к строгой типизации Kotlin, это было болью.

Pulumi: инфраструктура на Kotlin

Pulumi позволил нам описывать ту же инфраструктуру на Kotlin. Автодополнение в IDE, типобезопасность, возможность выносить общую логику в функции и классы — всё, к чему мы привыкли в прикладном коде.

import pulumi.kotlin.kubernetes.apps.v1.Deployment
import pulumi.kotlin.kubernetes.core.v1.Container
import pulumi.kotlin.kubernetes.core.v1.Namespace

val appNamespace = Namespace("app") {
    metadata { name = "myapp" }
}

Deployment("app") {
    metadata {
        name = "myapp"
        namespace = appNamespace.metadata.name
    }
    spec {
        replicas = 3
        selector {
            matchLabels = mapOf("app" to "myapp")
        }
        template {
            metadata {
                labels = mapOf("app" to "myapp")
            }
            spec {
                containers = listOf(Container {
                    name  = "myapp"
                    image = "cr.yandex/myproj/myapp:latest"
                    ports = listOf(Port { containerPort = 8080 })
                    resources = ResourceRequirements {
                        limits = mapOf(
                            "cpu" to Quantity("500m"),
                            "memory" to Quantity("512Mi")
                        )
                    }
                })
            }
        }
    }
}

Разница ощущается сразу. IDE подсвечивает ошибки до запуска. Я могу создать data class AppConfig и переиспользовать его для дева, стейджа и прода. Могу написать unit-тесты, которые проверяют, что деплоймент создан с правильным количеством реплик.

Управление состоянием

Terraform хранит state в .tfstate — JSON-файле, который нужно лочить через S3/DynamoDB при командной работе. Pulumi хранит state в своём SaaS-бэкенде (бесплатно для небольших команд) или в self-hosted сервисе. У обоих подходов есть backends с блокировками, но у Pulumi ещё и шифрование из коробки и удобный веб-интерфейс для просмотра стека.

Мы выбрали self-hosted Pulumi Backend в контейнере — он лёгкий и полностью под нашим контролем.

Тестирование инфраструктуры

С Pulumi на Kotlin мы смогли написать тесты с JUnit. Например, проверяем, что каждый деплоймент имеет ресурсные лимиты:

import org.junit.jupiter.api.Test
import pulumi.kotlin.testing.PulumiTest

class InfraTest : PulumiTest() {
    @Test
    fun `deployment has resource limits`() {
        val stack = testAsync { AppsStack(it) }
        val deployment = stack.resources
            .filterIsInstance()
            .first()
        
        val container = deployment.spec?.template?.spec?.containers?.first()
        assertNotNull(container?.resources?.limits)
        assertTrue(container!!.resources.limits!!.containsKey("memory"))
    }
}

С Terraform такие тесты возможны через terraform-validate или tflint, но это YAML-конфиги с regex-проверками — далёко от удобства JUnit.

Экосистема и кривая обучения

У Terraform огромная экосистема: тысячи провайдеров, модули на Terraform Registry, куча документации и stackoverflow-ответов. У Pulumi провайдеры — те же (он транслирует вызовы в Terraform-провайдеры), но модулей меньше, и документация по Kotlin-SDK местами скудная.

Кривая обучения: HCL — два дня для базовых вещей. Pulumi на Kotlin — три-четыре дня, но потом скорость роста гораздо выше, потому что ты работаешь в знакомой парадигме.

Наш выбор

Мы выбрали Pulumi. Главные причины: типобезопасность, возможность реального тестирования, единый язык для инфраструктуры и прикладного кода, и тот факт, что новым разработчикам в команде не нужно учить ещё один язык. Terraform остался у нас для cloud-level provisioning (сети, VPC, IAM) — там его declarative-стиль работает отлично. А Kubernetes-манифесты теперь живут в Kotlin-репозитории.

Поделиться статьёй

Комментарии

0
0 / 2000

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

Будьте первым, кто оставит комментарий к этой статье

Читайте также

Docker Compose для локальной разработки Kotlin-микросервисов — от 10 минут до 30 секунд старта

Как мы оптимизировали локальное окружение из 10+ микросервисов с помощью Docker Compose, multi-stage Dockerfile и правильных health checks.

15 июня 2026 10 мин

Nginx + PHP-FPM: тюнинг под высокую нагрузку — от 500 RPS до 5000 RPS

Сайт на PHP + Nginx выдерживал 500 RPS, а при 1000 начинал отдавать 502. Проблема была не в PHP-коде (он был оптимизирован), а в конфигурации Nginx и PHP-FPM: неправильный worker_connections, отсутствие keepalive к upstream, слишком маленький pm.max_children. Рассказываю пошагово, как мы увеличили пропускную способность в 10 раз.

23 мая 2026 11 мин

Gradle Kotlin DSL: кэширование и configuration avoidance — ускоряем сборку

Сборка Kotlin-проекта занимала 6 минут. После включения build cache, configuration cache и перехода на configuration avoidance API — 45 секунд. Рассказываю, что именно мы сделали, почему большинство туториалов по ускорению Gradle не работают, и как измерять реальный эффект от каждой оптимизации.

22 мая 2026 10 мин