Terraformer — Infrastructure To Code

Terraformer — Infrastructure To Code
Хотів би розповісти про новий CLI tool котрий я написав для вирішення однієї старої проблеми.

проблема

Terraform вже давно став стандартом у Devops/Cloud/IT спільноті. Річ дуже зручна і корисна, щоб займатися infrastructure as code. Є багато принад у Terraform і багато виделок, гострих ножів і граблів.
З Terraform дуже зручно робити нові речі і потім ними керувати, змінювати чи видаляти. А що робити тим, хто має величезну інфраструктуру в хмарі і не створено через Terraform? Переписувати і перестворювати всю хмару як дорого і небезпечно.
Я стикався з такою проблемою на 2 роботах, найпростіший приклад коли хочеш що все було в гіті вигляді тераформ файлів, а у тебе 250+ бакетів і писати їх у тераформа руками як багато.
є питання ще з 2014 року у terrafom що закрили у 2016 з надією що буде import.

Вообщем все як на картинці тільки з право на ліво

Попередження: Автор стать життя живе не в Росії і пише російською мало. Обережно помилки в орфографії.

Розв'язки

1. Є готове та старе рішення для AWS тераформування. Коли я намагався через нього отримати мої 250+ бакетів то зрозумів, що там все погано. AWS вже давно накидали багато нових опцій, а terraforming про них не знає і взагалі у нього ruby темплет виглядає мізерно. Після 2 увечері я послав Pull request Щоб додати більше можливостей туди і зрозумів, що таке рішення не підходить взагалі.
Як працює terraforming він бере з SDK AWS дані та генерує tf та tfstate через темплет.
Тут 3 проблеми:
1. Завжди там буде відставання в поновленнях
2. tf файли іноді виходять биті
3. tfstate збирається окремо від tf і не завжди сходиться
Взагалі складно отримає результат, при якому `terraform plan` скаже що не змін

2. `terraform import` - вбудована команда в terraform. Як працює?
Пишеш порожній TF файл з ім'ям та видом ресурсу, потім запускаєш `terraform import` та передаєш ID ресурсу. terraform звертається до провайдера отримує дані і робить tfstate файл.
Тут 3 проблеми:
1. Отримуємо тільки tfstate файл, а tf порожній треба руками писати або конвертувати з tfstate
2. Вміє працювати тільки з одним ресурсом щоразу і не підтримує всі ресурси. І що мені робити з 250+ бакетами
3. Треба знати ID ресурсів — тобто треба обмотувати його в код, який дістає список ресурсів
Взагалі результат частковий і не масштабується добре

Моє рішення

вимоги:
1. Можливість створення файлів tf і tfstate за ресурсами. Наприклад, скачати всі бакети/security group/load balancer і що `terraform plan` повертав що немає змін
2. Треба 2 хмари GCP + AWS
3. Глобальне рішення, яке легко оновлювати щоразу і не витрачати час на кожен ресурс по 3 дні роботи
4. Зробити Open source – проблема у всіх така

Мова Go — тому я люблю, і на ньому є бібліотека для створення HCL файлів, яка використовується в terraform + багато коду в terraform який може бути корисний

Шлях

спроба перша
Розпочав простий варіант. Звертання в хмару через SDK за потрібним ресурсом і конвертування його в поля для terraform. Спроба вмерла одразу на security group тому що мені не сподобалося 1.5 дня конвертувати тільки security group(а ресурсів багато). Довго і потім поля можуть змінювати/додаватись

Спроба друга
Засновано на ідеї описаної тут. Просто взяти і конвертувати tfstate у tf. Усі дані там є й поля ті самі. Як отримати повний tfstate для багато ресурсів? Тут на допомогу прийшла команда `terraform refresh`. terraform бере всі ресурси в tfstate і ID витягує по них дані і пише все в tfstate. Тобто створити порожній tfstate тільки з іменами та ID, запустити `terraform refresh`, то отримуємо повні tfstate. Ура!
Тепер займемося рекурсивною порнографія написання конвертора для tfstate в tf. Для тих хто ніколи не читав tfstate це JSON, але особливий.
Ось його важлива частина attributes

 "attributes": {
                            "id": "default/backend-logging-load-deployment",
                            "metadata.#": "1",
                            "metadata.0.annotations.%": "0",
                            "metadata.0.generate_name": "",
                            "metadata.0.generation": "24",
                            "metadata.0.labels.%": "1",
                            "metadata.0.labels.app": "backend-logging",
                            "metadata.0.name": "backend-logging-load-deployment",
                            "metadata.0.namespace": "default",
                            "metadata.0.resource_version": "109317427",
                            "metadata.0.self_link": "/apis/apps/v1/namespaces/default/deployments/backend-logging-load-deployment",
                            "metadata.0.uid": "300ecda1-4138-11e9-9d5d-42010a8400b5",
                            "spec.#": "1",
                            "spec.0.min_ready_seconds": "0",
                            "spec.0.paused": "false",
                            "spec.0.progress_deadline_seconds": "600",
                            "spec.0.replicas": "1",
                            "spec.0.revision_history_limit": "10",
                            "spec.0.selector.#": "1",

Тут є:
1. id - string
2. metadata - array розміром 1 і в ньому об'єкт з полями який описаний нижче
3. spec - hash розміром 1 і в ньому key, value
Коротше веселий формат, все може бути в глиб теж на кілька рівнів

                   "spec.#": "1",
                            "spec.0.min_ready_seconds": "0",
                            "spec.0.paused": "false",
                            "spec.0.progress_deadline_seconds": "600",
                            "spec.0.replicas": "1",
                            "spec.0.revision_history_limit": "10",
                            "spec.0.selector.#": "1",
                            "spec.0.selector.0.match_expressions.#": "0",
                            "spec.0.selector.0.match_labels.%": "1",
                            "spec.0.selector.0.match_labels.app": "backend-logging-load",
                            "spec.0.strategy.#": "0",
                            "spec.0.template.#": "1",
                            "spec.0.template.0.metadata.#": "1",
                            "spec.0.template.0.metadata.0.annotations.%": "0",
                            "spec.0.template.0.metadata.0.generate_name": "",
                            "spec.0.template.0.metadata.0.generation": "0",
                            "spec.0.template.0.metadata.0.labels.%": "1",
                            "spec.0.template.0.metadata.0.labels.app": "backend-logging-load",
                            "spec.0.template.0.metadata.0.name": "",
                            "spec.0.template.0.metadata.0.namespace": "",
                            "spec.0.template.0.metadata.0.resource_version": "",
                            "spec.0.template.0.metadata.0.self_link": "",
                            "spec.0.template.0.metadata.0.uid": "",
                            "spec.0.template.0.spec.#": "1",
                            "spec.0.template.0.spec.0.active_deadline_seconds": "0",
                            "spec.0.template.0.spec.0.container.#": "1",
                            "spec.0.template.0.spec.0.container.0.args.#": "3",

Взагалі хто хоче завдання на програмування для співбесіди, то просто попросіть написати парсер на цю справу 🙂
Після довгих спроб написати парсер без багів я знайшов частину його в коді terraform, причому найважливішу частину. І все начебто працювало норм

Спроба три
terraform provider - це бінарки в яких є код з усіма ресурсами та логікою для роботи з API хмар. У кожної хмари є свій provider і сам terraform тільки викликає їх через свій протокол RPC між двома процесами.
Тепер я вирішив звертатися безпосередньо до terraform providers через RPC виклики. Так вийшло красиво та дало змогу міняти terraform providers на новіші та отримувати нові можливості не змінюючи код. Ще виявилося не всі поля в tfstate повинні бути в tf, а як це дізнатися? Тільки попросити provider про це. Потім почалася ще одна рекурсивна порнографія по збору регулярних виразів канитель з пошуком полів всередині tfstate на всіх рівнях в глиб.

Наприкінці вийшло корисний CLI tool у котрого загальна інфраструктура для всіх terraform providers та можна легко додати нового. Також додавання ресурсів займає мало коду. Плюс усілякі плюшки типу з'єднання між ресурсами. Звичайно було багато різних проблем, які все не описати.
Назвав звірятко Terrafomer.

Фінал

Ми з допомогою Terrafomer згенерували 500-700 тисяч рядків коду tf + tfstate з двох хмар. Змогли взяти легаси речі та почати їх чіпати тільки через terraform як у кращих ідеях infrastructure as code. Просто магія коли береш величезну хмару і отримуєш через команду його у вигляді terraform файлів робітників. А далі grep/replace/git і таке інше.

Вичухав і впорядкував, отримав дозволи. Випустив на гітхаб для всіх у четвер(02.05.19). github.com/GoogleCloudPlatform/terraformer
Отримав вже 600 зірок, 2 pull requests додавання підтримки openstack та kubernetes. Хороші відгуки. Взагалі проект корисний для людей
Раджу всім, хто хоче почати працювати з Terraform і не переписувати все для цього.
Буду радий pull requests, issues, stars.

демо
Terraformer — Infrastructure To Code

Джерело: habr.com

Додати коментар або відгук