Translate

четвер, 7 травня 2020 р.

Pulumi. Part I: Overview

Загальні практики вказують на те, що зберігати налаштування як код корисно навіть для будь-яких відносно простих систем. Тобто усі параметри, їх зміни та авторів досить легко переглядати і відслідковувати в системі контролю версій, а за необхідності їх можна повертати до попередніх значень, якщо щось пішло не за планом. У разі якщо стан системи описаний як код, дуже легко та швидко підняти аналогічну копію такої системи. Це чудова тенденція і вона прослідковується у всіх популярних продуктах для різних команд: наприклад у Jenkins є Pipelines, для побудови інфраструктур в основному використовується Terraform, а система моніторингу Prometheus взагалі розроблялась без відповідних GUI інтерфейсів і всі налаштування відбуваються через код. Загалом будь-який новий продукт розробляється із розрахунку на те, його налаштування буде зберігатись як код.

Часто кожний продукт пропонує свою специфічну мову (DSL, Domain-specific language) для опису логіки роботи систем. Наприклад, у Puppet є свій DSL для опису стану, котрий хоче набути ресурс, а у Terraform-a свій власний HCL (HashiCorp Domain Language) для опису ресурсів. DSL загалом використовується для декларативного підходу - цікавить саме ресурс, котрий буде побудовано, а не те як він буде побудовано і з якими етапами. В силу своєї специфічності DSL мови мають багато недоліків і якісь відносно складні речі на DSL виглядають просто жахливо. Модулі Terraform, що описують хоча б дещо комплексну систему, аж занадто не елегантні і їх часто соромно переглядати чи показувати комусь: те що в мові загального користування виглядає лаконічно - в Terraform-і це ж саме перетворюється на рядок в 100 символів і більше, який ще до того не можна розірвати! Тому не раджу нікому дивитись в чужі модулі Terraform, це може вплинути на психічний стан. Вже згаданий Puppet також далеко прорвався вперед і страждає від тих самих проблем, коли читабельно виглядають лише нескладні стандартні ресурси.

Я, як активний користувач Terraform, часто приходив до висновку, що Terraform-у складно виконувати всі покладені на нього завдання конфігурації інфраструктури та виливки додатків. Із однієї сторони в нього є купа різних провайдерів і автори його позиціонують як "швейцарський ніж" сучасного інженера, а із іншої сторони в Teraform-і у якості if використовують count. Із Terraform наприклад не можна реалізувати щось на зразок перевірки існування змінної чи запустити декілька раз той самий модуль (count недоступний під час запуску модуля), чи обійти два списки водночас (адже count лише один) і тому подібне.

Знову ж, я не хочу сказати, що Terraform поганий і з ним неможливо жити. Але варто одразу розуміти, що це DSL, і з однієї сторони його код досить простий і декларативний, а з іншої, по причинам описаними вище, із ним варто звикати до постійного copy-paste. Але може не потрібно? Можливо існує щось інше? Так існує, це Pulumi.

Перший публічний реліз Pulumi з'явився в 2018 році, завдяки зусиллям однойменного стартапу, що базується в Сіетлі. За ідею проекту було взято написання системи, що полишить практики використання специфічних DSL мов та буде ближчим для розробників, таким чином скоротивши розрив між ними та devops/operation командами, котрі займаються їх підтримкою. Хоча це досить контроверсійні аргументи, але не будемо наразі на цьому зупинятись. Перейдемо до більш конкретних речей.


1. INTRO

1.1. What is Pulumi?

Отже Pulumi - це фреймворк для опису інфраструктури із використанням мов загального користування. На даний момент підтримуються Python, Go, JavaScript, TypeScript та C#. Власне сам код Pulumi також декларативний (тобто описує лише стан, якого треба досягти), але разом із тим можна використовувати звичайні конструкції обраної мови та всі її доступні бібліотеки. Множина cloud-інфраструктур, із якими працює Pulumi, охоплює як популярні продукти (AWS, Google Cloud та Azure), так і менш відомі на кшталт Alibaba Cloud, vSphere та інші. Крім цього є також підтримка об'єктів Kubernetes, не залежно від платформи, на якій його було імплементовано.

Така велика кількість підтримуваних платформ пояснюється тим, що Pulumi часто використовує плагіни Terraform для звернення до їх API.

Структурно програма Pulumi складається із наступних частин:

  • Program. Набір файлів, що написані на мові програмування, що підтримує Pulumi.
  • Project. Директорія, що містить в собі всі вищезгадані файли та метадані, тож Pulumi знає як їх запускати.
  • Stack. Профіль запуску програми Pulumi. До нього входять окремі налаштування, секрети і т.п. Тобто наприклад проект може мати декілька стеків (dev, prod, dev_john) за кожним із яких прикріплені окремі значення змінних і вони в певному розумінні ізольовані. Це щось на зразок workspaces в Terraform.

1.2. How It Works?

Pulumi використовує desired state модель для управління інфраструктурою. Отже, програма написана на мові загального користування, наприклад Python, інтерпретується за допомогою pulumi бібліотек (language host) і, визначивши desired state (бажаний стан інфраструктури), направляє його до deployment engine (загальний набір бібліотек, що не залежать від обраної мови). Останній порівнює бажаний стан інфраструктури (desired state) із станом який є наразі (current state) і приймає рішення, що необхідно оновити, створити чи видалити, в т.ч. чи може пройти певне оновлення ресурсу без його видалення. Deployment engine використовує набір ресурс-провайдерів (таких як AWS, Azure, Kubernetes) для управління відповідними сервісами. В процесі роботи він оновлює записи стану (state) як створених ресурсів, так і ресурсів, що перебувають в процесі створення. Надалі ця інформація використовується для подальшого управління об'єктами.


Language host складається із двох частин:

  • language executor. Бінарний файл із іменем pulumi-language-<language-name>, що розповсюджується із основним Pulumi CLI та використовується для запуску програм на мовах, що підтримуються (наприклад Node чи Python).
  • language runtime. Бібліотеки, що розповсюджуються через системи керування пакунками (pip, npm і т.п.). Відповідальний за підготовку програми до запуску та спостерігає за її виконанням. У разі виявлення запитів на створення чи зміну ресурсів (за допомогою new Resource() в JavaScript чи просто Resource(...) в Python) language runtime звертається із запитом реєстрації цього ресурсу до deployment engine.

Ресурс-провайдери також можна поділити на 2 основні частини:

  • resource plugin. Бінарний файл, котрий використовує deployment engine для управління ресурсами. Для кожного хмарного середовища - свій окремий плагін. За замовчуванням зберігаються в ~/.pulumi/plugins і всі можна вивести за допомогою команди pulumi plugin ls.
  • SDK, що забезпечує підтримку для кожного ресурсу, котрим вміє управляти провайдер.

За замовчуванням Pulumi намагається спочатку створити ресурс і вже потім видалити попередній, якщо оновити останній не вдається без його перестворення. Це забезпечується auto-naming функціоналом, коли Pulumi самостійно додає випадковий суфікс до імені об'єкта і таким чином це зменшує час простою інфраструктури. Цю можливість можливо вимкнути і, як наслідок, буде відбуватись спочатку видалення і вже потім створення нових ресурсів із аналогічним іменем.

Детальніше із процесом управління ресурсами можна ознайомитись за посиланням.


2. CREATING S3 BUCKET WITH PULUMI

Поглянемо як це все працює на простому прикладі створення AWS S3 bucket-у.

2.1. Pulumi Installation

Установка Pulumi проста і не має викликати складностей, у якості OS я використовую Ubuntu 18.04:

# apt install curl
$ curl -fsSL https://get.pulumi.com | sh
=== Installing Pulumi v2.1.0 ===
+ Downloading https://get.pulumi.com/releases/sdk/pulumi-v2.1.0-linux-x64.tar.gz...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 57.1M  100 57.1M    0     0  14.1M      0  0:00:04  0:00:04 --:--:-- 14.1M
+ Extracting to /home/user/.pulumi/bin

=== Pulumi is now installed! 🍹 ===
+ Please add /home/user/.pulumi/bin to your $PATH
+ Get started with Pulumi: https://www.pulumi.com/docs/quickstart

Установка програм таким способом мабуть не найкоректніша, тому за бажанням можна проінсталювати все вручну.

Додамо Pulumi до $PATH змінної оточення. Варіантів це зробити багато:

# vim /etc/environment
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/home/user/.pulumi/bin"
$ source /etc/environment

$ pulumi version
v2.1.0

Так як ми будемо працювати із Python біндингами, то нам будуть необхідні ще деякі пакети:

# apt update && apt upgrade
# apt install build-essential python3 python3-dev python3-venv

Тепер потрібно передати AWS Programmatic access в Pulumi. Як і в Terraform, зробити це можна багатьма способами, я просто додам дві змінні оточення:

$ export AWS_ACCESS_KEY_ID=AKIA1234563J76A
$ export AWS_SECRET_ACCESS_KEY=/xLmpmdp1V3abcdefghklmnopabcdefg2nKRDKO


2.2. Creating S3 bucket in AWS with Pulumi

Спочатку Pulumi для збереження стейтів/історії змін/і т.п. пропонував лише свій бекенд app.pulumi.com із наступними тарифними планами. У певному сенсі це було зроблено спеціально задля монетизації. Це досить зручний бекенд, якщо ваша компанія не проти зберігати sensitive дані на третій стороні, адже багатьох це справді не турбує. Нещодавно також була додана можливість зберігати state-дані на стороні cloud-провайдерів, але поговоримо про це пізніше. Цього разу спробуємо офіційний бекенд.

Цей самий урок, але англійською можна пройти на сайті Pulumi https://www.pulumi.com/docs/get-started/aws/install-pulumi/

Отже нам потрібний обліковий запис на app.pulumi.com. Після реєстрації та авторизації необхідно згенерувати API ключ за адресою https://app.pulumi.com/account/tokens. Тепер ми можемо продовжити.

$ mkdir quickstart
$ cd quickstart

Наступна команда проведе авторизацію до щойноствореного акаунту, створить проект/стек та темплейт проекту на мові Python для AWS:

$ pulumi new aws-python

Manage your Pulumi stacks by logging in.
Run `pulumi login --help` for alternative login options.
Enter your access token from https://app.pulumi.com/account/tokens
    or hit <ENTER> to log in using your browser: your_api_key_is_here
...

This command will walk you through creating a new Pulumi project.

Enter a value or leave blank to accept the (default), and press <ENTER>.
Press ^C at any time to quit.

project name: (quickstart
project description: (A minimal AWS Python Pulumi program) 
Created project 'quickstart'

Please enter your desired stack name.
To create a stack in an organization, use the format <org-name>/<stack-name> (e.g. `acmecorp/dev`).
stack name: (dev
Created stack 'dev'

aws:region: The AWS region to deploy into: (us-east-1
Saved config
...

Отже у моєму випадку новий стек матиме вигляд hello-world/quickstart/dev, адже hello-world - це ім'я моєї організації (аккаунту). Тепер переглянемо вміст quickstart директорії після ініціалізації:

$ ls
Pulumi.dev.yaml  Pulumi.yaml  __main__.py  __pycache__  requirements.txt  venv

У requirements.txt, як і у звичайного Python проекту, знаходяться необхідні залежності для запуску програми. У Pulumi.yaml знаходиться базова інформація про майбутню інфраструктуру проекту:

$ cat Pulumi.yaml
name: quickstart
runtime: python
description: A minimal AWS Python Pulumi program

Тобто ім'я проекту, за допомогою якої мови буде відбуватись його запуск та короткий опис. У цьому файлі не має бути більш нічого.

У Pulumi.dev.yaml знаходяться всі змінні, що стосуються стеку dev, котрий було створено вище:

$ cat Pulumi.dev.yaml
config:
  aws:region: us-east-1

Наразі тут тільки AWS регіон. Нові параметри можуть бути додані як через CLI, так і звичайним редагуванням цього файлу.

Файл Pulumi.<stack>.yaml створюється автоматично після створення стеку. Стеки - це базовий спосіб Pulumi розділяти середовища і специфічні змінні для кожного із них.

І нарешті переглянемо сам код __main.py__:

$ cat __main.py__

import pulumi
from pulumi_aws import s3

# Create an AWS resource (S3 Bucket)
bucket = s3.Bucket('my-bucket')

# Export the name of the bucket
pulumi.export('bucket_name', bucket.id)

Тобто ми лиш створюємо новий бакет my-bucket і виводимо його ім'я. Все поки просто.

Активуємо venv для Pulumi коду та встановлюємо залежності requirements.txt для роботи Python-біндингів Pulumi:

$ python3 -m venv venv
$ source venv/bin/activate
$ pip3 install -r requirements.txt

Нарешті все готово для створення нового ресурсу в AWS. Запускаємо preview (аналог terraform plan):

$ pulumi preview
Previewing update (dev):
     Type                 Name            Plan     
 +   pulumi:pulumi:Stack  quickstart-dev  create   
 +   └─ aws:s3:Bucket     my-bucket       create   

Resources:
    + 2 to create

Permalink: https://app.pulumi.com/hello-world/quickstart/dev/previews/afd7512f-6a66-4538-927f-abd32a8c9e0c

Чудово, виглядає як і заплановано! Тепер все ж створимо новий бакет:

$ pulumi up
Previewing update (dev):
     Type                 Name            Plan     
 +   pulumi:pulumi:Stack  quickstart-dev  create   
 +   └─ aws:s3:Bucket     my-bucket       create   

Resources:
    + 2 to create

Do you want to perform this update? yes
Updating (dev):
     Type                 Name            Status     
 +   pulumi:pulumi:Stack  quickstart-dev  created   
 +   └─ aws:s3:Bucket     my-bucket       created   

Outputs:
    bucket_name: "my-bucket-c7f09dc"

Resources:
    + 2 created

Duration: 40s

Permalink: https://app.pulumi.com/hello-world/quickstart/dev/updates/1

Бакет my-bucket-c7f09dc (де c7f09dc - це робота auto-naming опції) створено. Outputs - це аналог output Teraform-у, тобто ці дані можна буде використовувати як вхідні у інших Pulumi програмах.

Тепер давайте зробимо наш бакет ліпшим і додамо до нього шифрування. Для цього відредагуємо __main__.py:

$ vim __main__.py

import pulumi
from pulumi_aws import kms, s3

# Create a KMS Key for S3 server-side encryption
key = kms.Key('my-key')

# Create an AWS resource (S3 Bucket)
bucket = s3.Bucket('my-bucket',
    server_side_encryption_configuration={
        'rule': {
            'apply_server_side_encryption_by_default': {
                'sse_algorithm': 'aws:kms',
                'kms_master_key_id': key.id
            }
        }
    })

# Export the name of the bucket
pulumi.export('bucket_name', bucket.id)

І запустимо знову pulumi up:

$ pulumi up
Previewing update (dev):
     Type                 Name            Plan       Info
     pulumi:pulumi:Stack  quickstart-dev           
 +   ├─ aws:kms:Key       my-key          create   
 ~   └─ aws:s3:Bucket     my-bucket       update     [diff: +serverSideEncryptionConfiguration]

Resources:
    + 1 to create
    ~ 1 to update
    2 changes. 1 unchanged

Do you want to perform this update? yes
Updating (dev):
     Type                 Name            Status      Info
     pulumi:pulumi:Stack  quickstart-dev             
 +   ├─ aws:kms:Key       my-key          created   
 ~   └─ aws:s3:Bucket     my-bucket       updated     [diff: +serverSideEncryptionConfiguration]

Outputs:
    bucket_name: "my-bucket-c7f09dc"

Resources:
    + 1 created
    ~ 1 updated
    2 changes. 1 unchanged

Duration: 49s

Permalink: https://app.pulumi.com/hello-world/quickstart/dev/updates/2

Отже був створений новий AWS KMS ключ і ним було зашифровано my-bucket-c7f09dc. Важливо зауважити, що даний процес відбувся без перестворення бакету.

Переглянемо що з'явилось нового на бекенді https://app.pulumi.com/. На першій сторінці перерахунок всіх стеків організації:


Конфігурація, теги і т.п.:


Перелік усіх оновлень стеку dev:


Погодьтесь, досить зручно переглядати хто, коли і що вилив нового:


Перелік усіх ресурсів, що були створені в даному Pulumi стеку:


Видалимо всі виществорені ресурси, адже вони більше нам не потрібні:

$ pulumi destroy
Previewing destroy (dev):
     Type                 Name            Plan     
 -   pulumi:pulumi:Stack  quickstart-dev  delete   
 -   ├─ aws:s3:Bucket     my-bucket       delete   
 -   └─ aws:kms:Key       my-key          delete   

Outputs:
  - bucket_name: "my-bucket-c7f09dc"

Resources:
    - 3 to delete

Do you want to perform this destroy? yes
Destroying (dev):
     Type                 Name            Status     
 -   pulumi:pulumi:Stack  quickstart-dev  deleted   
 -   ├─ aws:s3:Bucket     my-bucket       deleted   
 -   └─ aws:kms:Key       my-key          deleted   

Outputs:
  - bucket_name: "my-bucket-c7f09dc"

Resources:
    - 3 deleted

Duration: 18s

Permalink: https://app.pulumi.com/hello-world/quickstart/dev/updates/3
The resources in the stack have been deleted, but the history and configuration associated with the stack are still maintained.
If you want to remove the stack completely, run 'pulumi stack rm dev'.

І як радить попередній вивід pulumi, видалимо вже непотрібний стек:

$ pulumi stack rm dev
This will permanently remove the 'dev' stack!
Please confirm that this is what you'd like to do by typing ("dev"): dev
Stack 'dev' has been removed!

На цьому схоже все. Надіюсь, вистачило терпіння дочитати до кінця. У наступній статті поговоримо про більш комплексний варіант використання цього інструменту вже зі збереженням стейтів на S3. Тому підписуйтесь на канал, тисніть на дзвіночок...

Посилання:
https://www.pulumi.com/docs/get-started/aws/install-pulumi/
https://www.reddit.com/r/devops/comments/bcdwsn/pulumi/
https://www.pulumi.com/docs/intro/concepts/programming-model/
https://techcrunch.com/2018/06/18/pulumi-wants-to-let-you-manage-your-infrastructure-with-code/
http://joeduffyblog.com/2018/06/18/hello-pulumi/
https://thenewstack.io/pulumi-uses-real-programming-languages-to-enforce-cloud-best-practices/
https://medium.com/@kscloud/how-to-program-infrastructure-with-pulumi-part-1-a47d5edb913f
https://medium.com/@kscloud/how-to-program-infrastructure-with-pulumi-part-2-3d7d64e69146
https://itnext.io/infrastructure-as-code-using-pulumi-to-provision-and-bootstrap-a-gcp-instance-318f06c61a03

Немає коментарів:

Дописати коментар