Часто возникает ситуация, когда нужно развернуть приложение одновременно в нескольких облаках, совместить облачную инфраструктуру и управляемый Kubernetes-кластер или предусмотреть возможную миграцию сервиса в будущем. Одним из возможных решений для создания универсальной конфигурации может быть использование проекта Pulumi, который позволяет публиковать приложения в разные облака (GCP, Amazon, Azure, AliCloud), Kubernetes, провайдеры (например, Linode, Digital Ocean), системы управления виртуальной инфраструктурой (OpenStack) и в локальный Docker. В этой статье мы рассмотрим основные идеи проекта, создадим универсальную конфигурацию для простого Python-приложения.   Для создания конфигурации мы будем использовать инструмент командной строки, который может быть установлен через пакетные менеджеры: Windows - choco install pulumi или winget install pulumi Linux - curl -fsSL https://get.pulumi.com | sh MacOS - brew install pulumi/tap/pulumi После установки выполним конфигурацию стека (на этом этапе будем использовать локальный Docker, запущенный на одной машине).  Конфигурация описывается yaml-файлом Pulumi.yaml, который определяет метаданные проекта и исходного текста для определения ресурсов, который может быть сгенерирован на следующих языках:   Go Python JavaScript TypeScript C# Java / Kotlin Yaml При генерации кода создается заготовка с использованием библиотек Pulumi для регистрации ресурсов развертывания nginx. В варианте сборки yaml в Pulumi.yaml добавляются дополнительные секции:   variables - определение переменных для подстановки в конфигурации; resources - определение ресурсов: type - определение типа объекта для плагина; deleteBeforeReplace - удалить перед созданием объекта; dependsOn - список ресурсов, от которых зависит этот (для определения последовательности создания и модификации); ignoreChanges - список полей, которые будут игнорироваться при определении обновлений; parent - родительский ресурс; provider / providers - определение, какие провайдеры будут использоваться с этим ресурсом; customTimeouts - определение таймаутов при управлении ресурсов: create - таймаут при операциях создания; delete - таймаут при удалении; update - таймаут при обновлении ресурса. properties - свойства ресурса (для Kubernetes повторяет схему yaml-ресурсов, при этом может использоваться интерполяция переменных из variables, в том числе составные yaml-объекты). configuration - настройки, которые могут быть использованы в сценарии развертывания; outputs - артефакты сборки, созданные на основе сценария. Также конфигурация развертывания может быть определена через манипуляцию объектами в коде (в Go и Java запускаются внутри функции pulumi.run, в остальных языках в точке входа в приложение).  Начнем с использования провайдера Docker и создадим простой веб-сервер на Python. Прежде всего создадим заготовку для проекта (она также зарегистрируется в облаке Pulumi, для чего может потребоваться выполнить авторизацию через pulumi config).  и создадим соответствующий Dockerfile:   Мы можем определить конфигурацию инфраструктуры через yaml-манифест:   При запуске Docker на Windows нужно дополнительно изменить конфигурацию Docker-провайдера (используется провайдер от Terraform):   Соберем образ, отправим его в Docker hub и запустим наш проект:   В результате получим план выполнения:   Теперь добавим конфигурацию для определения порта публикации контейнера:   Переменные конфигурации должны быть заданы в определении стека (Pulumi.dev.yaml) или через pulumi config set:   и применим изменение конфигурации через pulumi up (или можем запустить pulumi watch для непрерывного отслеживания изменений). Это становится возможным благодаря тому, что pulumi сохраняет текущее состояние стека и определяет миграции для перехода в новое ожидаемое состояние, описанное конфигурацией. Теперь можно убедиться, что сервер работает и опубликован на порт 8080 (http://localhost:8080). Этот подход очень похож на концепцию Terraform, ресурсы определяются в плагинах, которые можно посмотреть в реестре. Например, можно выполнять произвольные системные команды (command), управлять репозиториями (Gitlab, Github), управлять ресурсами серверов (RabbitMQ, Kafka, MySQL, PostgreSQL) и облачных провайдеров (AWS, Digital Ocean, Google Cloud Platform, Yandex Cloud, Microsoft Azure) и Kubernetes.  Но все же Pulumi более полезен при использовании описания инфраструктуры через код. Благодаря поддержки разных runtime конфигурация может быть описана на языке, привычном для разработчика (например, Java, Python, C#, JS/TS или Go). Каждый плагин экспортирует набор классов и структур данных для определения ожидаемого состояния стека. Рассмотрим пример ранее обозначенной конфигурации на Python: Для корректной работы необходимо установить модули pulumi и pulumi_docker:   Также изменим тип runtime в Pulumi.yaml:   И пересоздадим стек:   Аналогично можно определить конфигурацию для используемых ресурсов и определения для облачных провайдеров (например, создание S3-бакетов), реализация может быть выполнена на одном из поддерживаемых runtime. Но основной вопрос для нас - как создать универсальное определение, которое можно было бы использовать на нескольких облаках. Здесь существует несколько возможных решений. Наиболее простое - получить идентификатор облака из конфигурации и использовать возможности языка программирования для выбора корректной реализации. Например, для регистрации S3-бакета в облаках aws, digitalocean или yandex можно использовать следующий код: Для переключения облака в стеке достаточно будет изменить конфигурацию:   Если нужно реализовать сложную логику для управления ресурсами, можно создать собственный провайдер через создание класса-наследника ResourceProvider и реализацию методов create (создание ресурса), diff (определение разности), update (обновление), delete (удаление ресурса), read (получение состояния ресурса). Провайдер получает входы (inputs), которые могут быть произвольным объектом (заполняются через properties) и может вернуть данные (outputs), которые возвращаются в CreateResult (outs). Поскольку конфигурация размещается вместе с кодом сервиса, можно использовать возможности инструментов CI/CD. Например, для CircleCI существует orb pulumi/pulumi@1.0.0, для Github Actions - pulumi/actions@v3, для остальных инструментов можно организовать установку pulumi через пакетный менеджер выбранной технологии разработки в рамках конвейера CI/CD. Таким образом, Pulumi позволяет описывать сложные сценарии для развертывания ресурсов на основе кода и сохраняя все преимущества Terraform существенно повышает гибкость конфигурирования благодаря использованию возможностей языков программирования по реализации алгоритмов создания и модификации ресурсов, которые могут учитывать параметры конфигурации, а также выполнять итеративные операции (при необходимости запуска группы ресурсов). Статья подготовлена в преддверии старта курса Infrastructure as a code. Узнать подробнее о курсе и зарегистрироваться на бесплатный урок можно по ссылке ниже. Подробнее о курсе   Flutter Developer@YandexPro, DevOps, IT Architect