Как стать автором
Обновить
827.91
OTUS
Цифровые навыки от ведущих экспертов

Универсальное описание инфраструктуры на Pulumi. Раскатываемся в Kubernetes, AWS, GCP с одной конфигурацией

Время на прочтение 6 мин
Количество просмотров 6K

Часто возникает ситуация, когда нужно развернуть приложение одновременно в нескольких облаках, совместить облачную инфраструктуру и управляемый 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. Узнать подробнее о курсе и зарегистрироваться на бесплатный урок можно по ссылке ниже.

Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
+4
Комментарии 0
Комментарии Комментировать

Публикации

Информация

Сайт
otus.ru
Дата регистрации
Дата основания
Численность
101–200 человек
Местоположение
Россия
Представитель
OTUS