Terraformer — Infrastruktura jako kod

Terraformer — Infrastruktura jako kod
Chciałbym opowiedzieć o nowym narzędziu CLI, które stworzyłem, aby rozwiązać pewien stary problem.

Problem

Terraform od dawna stał się standardem wśród społeczności DevOps/Cloud/IT. To bardzo wygodne i pomocne narzędzie do zarządzania infrastrukturą jako kodem. Terraform ma wiele zalet, a także wiele pułapek, niebezpiecznych sytuacji i problemów.
Z Terraformem bardzo łatwo jest tworzyć nowe rzeczy, a następnie nimi zarządzać, zmieniać lub usuwać. A co zrobić z ogromną infrastrukturą w chmurze, która nie została utworzona przez Terraform? Przepisywanie i rekonstruowanie całej chmury jest dość kosztowne i niebezpieczne.
Spotkałem się z tym problemem w dwóch firmach, najprostszym przykładem jest to, kiedy chcesz, aby wszystko było w Gitcie w formie plików Terraform, podczas gdy masz 250+ bucketów, a ręczne ich wpisywanie do Terraformu jest zbyt czasochłonne.
Tak issue jeszcze od 2014 roku w terrafomie, który zamknięto w 2016 roku z nadzieją na import.

Generalnie wszystko jest jak na obrazku, tylko z prawej do lewej.

Ostrzeżenie: Autor przez pół życia mieszka poza Rosją i mało pisze po rosyjsku. Uwaga na błędy ortograficzne.

Rozwiązania

1. Istnieją gotowe i stare rozwiązania dla AWS. terraforming. Kiedy próbowałem uzyskać moje 250+ bucketów przez niego, zrozumiałem, że sytuacja jest zła. AWS od dawna dodał wiele nowych opcji, a terraforming o nich nie wie, a poza tym to jest Ruby. szablon wygląda ubogo.. Po drugiej wieczorem wysłałem Pull request aby dodać więcej możliwości do tego i zrozumiałem, że takie rozwiązania w ogóle się nie nadają.
Jak działa terraforming? Bierze z SDK AWS dane i generuje tf i tfstate przez szablon.
Są trzy problemy:
1. Zawsze będzie tam opóźnienie w aktualizacjach.
2. Pliki tf czasami są uszkodzone.
3. tfstate zbiera się osobno od tf i nie zawsze się zgadza.
Generalnie trudno uzyskać wynik, przy którym `terraform plan` powie, że nie ma zmian.

2. `terraform import` — wbudowana komenda w terraformie. Jak to działa?
Tworzysz pusty plik TF z nazwą i typem zasobu, potem uruchamiasz `terraform import` i przekazujesz ID zasobu. Terraform zwraca się do dostawcy, pobiera dane i tworzy plik tfstate.
Są trzy problemy:
1. Otrzymujemy tylko plik tfstate, a plik tf jest pusty, więc trzeba go ręcznie napisać lub przekonwertować z tfstate.
2. Może pracować tylko z jednym zasobem za każdym razem i nie obsługuje wszystkich zasobów. I co znowu zrobić z 250+ bucketami?
3. Trzeba znać ID zasobów — to znaczy trzeba owinąć to w kod, który pobiera listę zasobów.
Generalnie wynik jest częściowy i nie skalowalny.

Moje rozwiązanie

Wymagania:
1. Możliwość tworzenia plików tf i tfstate na podstawie zasobów. Na przykład pobranie wszystkich bucketów/security group/load balancer i co `terraform plan` zwraca, że nie ma zmian.
2. Potrzebne są 2 chmury GCP + AWS.
3. Globalne rozwiązanie, które można łatwo aktualizować za każdym razem, bez tracenia czasu na każdy zasób przez 3 dni pracy.
4. Stworzyć Open Source — problem, który wszyscy mają.

Język Go — dlatego go lubię, i jest biblioteka do tworzenia plików HCL, która jest używana w terraform + dużo kodu w terraform, który może być przydatny.

Droga

Pierwsza próba
Zacząłem od prostego rozwiązania. Interakcja z chmurą przez SDK, aby uzyskać potrzebny zasób i przekształcić go w pola dla terraform. Próba zakończyła się niepowodzeniem już przy security group, ponieważ nie podobało mi się 1,5 dnia przekształcania tylko security group (a zasobów jest dużo). To trwało długo i potem pola mogą się zmieniać/dodawać.

Druga próba
Opiera się na pomyśle opisanym. tutaj. Po prostu wziąć i skonwertować tfstate na tf. Wszystkie dane tam są i pola są te same. Jak uzyskać pełne tfstate dla wielu zasobów?? Tutaj z pomocą przyszła komenda `terraform refresh`. Terraform bierze wszystkie zasoby w tfstate i po ID wydobywa z nich dane i zapisuje wszystko w tfstate. To znaczy stworzyć pusty tfstate tylko z nazwami i ID, uruchomić `terraform refresh`, a otrzymujemy pełne tfstate. Hurra!
Teraz zajmiemy się rekurencyjnym pisaniem konwertera dla tfstate na tf. Dla tych, którzy nigdy nie czytali tfstate, to JSON, ale w szczególności.
Oto jego ważna część 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",

Tutaj jest:
1. id — string
2. metadata — tablica o rozmiarze 1, a w niej obiekt z polami opisanymi poniżej
3. spec — hash o rozmiarze 1, a w nim key,value
Krótko mówiąc, zabawny format, wszystko może być również zagnieżdżone na kilka poziomów.

                   "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",

Ogólnie, kto chce zrobić zadanie programistyczne na rozmowę kwalifikacyjną, niech po prostu poprosi o napisanie parsera na to 🙂
Po wielu próbach napisania parsera bez błędów, znalazłem jego część w kodzie terraform, a dokładniej w najważniejszej części. I wszystko wydawało się działać dobrze.

Próba trzecia
Dostawca terraform — to binarki, w których znajduje się kod ze wszystkimi zasobami i logiką do pracy z interfejsem API chmur. Każda chmura ma swojego dostawcę, a sam terraform tylko je wywołuje przez swój protokół RPC między dwoma procesami.
Teraz postanowiłem bezpośrednio komunikować się z dostawcami terraformu przez wywołania RPC. Wyszło to ładnie i dało możliwość zmiany dostawców terraformu na nowsze oraz uzyskania nowych funkcji bez zmiany kodu. Okazało się również, że nie wszystkie pola w tfstate muszą być w tf, a jak to sprawdzić? Tylko pytając dostawcę o to. Potem zaczęła się kolejna rekursywna pornografia przy zbieraniu wyrażeń regularnych z poszukiwaniem pól wewnątrz tfstate na wszystkich poziomach.

Na końcu wyszedł przydatny narzędzie CLI, które ma wspólną infrastrukturę dla wszystkich dostawców terraform i łatwo można dodać nowych. Również dodawanie zasobów zajmuje mało kodu. Plus różne dodatki, takie jak połączenia między zasobami. Oczywiście pojawiło się wiele różnych problemów, których nie sposób opisać.
Nazwałem stworzenie Terrafomer.

Finał

Za pomocą Terrformera wygenerowaliśmy 500-700 tysięcy linii kodu tf + tfstate w dwóch chmurach. Udało nam się wziąć legacy i zacząć je modyfikować tylko przez terraform, jak w najlepszych pomysłach infrastructure as code. To po prostu magia, gdy bierzesz ogromną chmurę i otrzymujesz przez komendę pliki terraform. A potem grep/replace/git i tak dalej.

Policzyłem i uporządkowałem, otrzymałem uprawnienia. Opublikowałem na GitHubie dla wszystkich w czwartek (02.05.19). github.com/GoogleCloudPlatform/terraformer
Dostałem już 600 gwiazdek, 2 pull requesty dodające wsparcie dla openstack i kubernetes. Dobre opinie. W ogóle projekt jest użyteczny dla ludzi.
Polecam wszystkim, którzy chcą zacząć pracować z Terraform i nie chcą przepisywać wszystkiego od nowa.
Będę wdzięczny za pull requesty, zgłoszenia, gwiazdki.

Demo
Terraformer — Infrastruktura jako kod

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster