
Часто возникает ситуация, когда нужно развернуть приложение одновременно в нескольких облаках, совместить облачную инфраструктуру и управляемый 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).
pulumi new yaml -n helloworld -d "Sample application" -s dev
main.py =================== from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return "Hello World!" if __name__ == "__main__": app.run(host='0.0.0.0')
и создадим соответствующий Dockerfile:
FROM python WORKDIR /opt RUN pip install flask ADD main.py /opt CMD python main.py
Мы можем определить конфигурацию инфраструктуры через yaml-манифест:
name: helloworld runtime: yaml description: Hello World resources: backend-image: type: docker:index:RemoteImage properties: name: dmitriizolotov/helloworld
При запуске Docker на Windows нужно дополнительно изменить конфигурацию Docker-провайдера (используется провайдер от Terraform):
pulumi config set docker:host npipe:////.//pipe//docker_engine
Соберем образ, отправим его в Docker hub и запустим наш проект:
docker build -t dmitriizolotov/helloworld . docker push dmitriizolotov/helloworld pulumi up -y
В результате получим план выполнения:

Теперь добавим конфигурацию для определения порта публикации контейнера:
name: helloworld runtime: yaml description: Hello World configuration: publishingPort: type: Number variables: internalPort: 5000 resources: backend-image: type: docker:index:Container properties: image: dmitriizolotov/helloworld name: helloworld ports: - internal: ${internalPort} external: ${publishingPort}
Переменные конфигурации должны быть заданы в определении стека (Pulumi.dev.yaml) или через pulumi config set:
pulumi config set publishingPort 8080
и применим изменение конфигурации через 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:
import pulumi from pulumi_docker import Image, DockerBuild, Container, ContainerPortArgs config = pulumi.Config() publishing_port = config.require_int("publishingPort") internal_port = 5000 image = "dmitriizolotov/helloworld" img = Image( "helloworld", image_name=image, build=DockerBuild(), skip_push=False, ) container = Container( resource_name="helloworld", name="helloworld", image=image, ports=[ContainerPortArgs(internal=internal_port, external=publishing_port)] )
Для корректной работы необходимо установить модули pulumi и pulumi_docker:
pip install pulumi pulumi_docker
Также изменим тип runtime в Pulumi.yaml:
name: helloworld runtime: python description: Hello World
И пересоздадим стек:
pulumi down pulumi up -y
Аналогично можно определить конфигурацию для используемых ресурсов и определения для облачных провайдеров (например, создание S3-бакетов), реализация может быть выполнена на одном из поддерживаемых runtime. Но основной вопрос для нас — как создать универсальное определение, которое можно было бы использовать на нескольких облаках. Здесь существует несколько возможных решений.
Наиболее простое — получить идентификатор облака из конфигурации и использовать возможности языка программирования для выбора корректной реализации. Например, для регистрации S3-бакета в облаках aws, digitalocean или yandex можно использовать следующий код:
import pulumi_aws as aws import pulumi_digitalocean as digitalocean from pulumi_gcp import storage bucket_name = "bucket" cloud = config.require("cloud") if cloud == "aws": bucket = aws.s3.Bucket(bucket_name) elif cloud == "do": bucket = digitalocean.SpacesBucket(bucket_name) elif cloud == "gcp": bucket = storage.Bucket(bucket_name)
Для переключения облака в стеке достаточно будет изменить конфигурацию:
pulumi config set cloud gcp
Если нужно реализовать сложную логику для управления ресурсами, можно создать собственный провайдер через создание класса-наследника 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. Узнать подробнее о курсе и зарегистрироваться на бесплатный урок можно по ссылке ниже.
ссылка на оригинал статьи https://habr.com/ru/company/otus/blog/674818/
Добавить комментарий