Skuteczne zarządzanie infrastrukturą wirtualną jest jednym z większych wyzwań, przed jakimi dziś stajemy. Mnogość i różnorodność rozwiązań, z jakimi mamy do czynienia przy jednoczesnym szybkim rozwoju branży IT, sprawia, że stajemy przed problemem, z którym niełatwo sobie poradzić. Co prawda chmury publiczne dostarczają nam niezbędnych narzędzi, dzięki którym planowanie, tworzenie czy modyfikacja zasobów staje się znacznie prostsze. Są to jednak zazwyczaj narzędzia dedykowane dla konkretnej platformy, zawierające specyficzne rozwiązania i podejścia. Co w przypadku infrastruktury on-premises lub infrastruktury hybrydowej, łączącej w sobie różne rozwiązania? Rozwój w zakresie produkcji oprogramowania wymusza na nas konieczność stawiania wielu środowisk deweloperskich oraz testowych. Nierzadko ma się to odbyć w krótkim czasie wraz z możliwością częstego ich parametryzowania. W odpowiedzi na te potrzeby firma HashiCorp stworzyła produkt o nazwie Terraform. To elastyczne, wieloplatformowe narzędzie pozwalające na planowanie i zarządzanie zasobami zarówno w środowiskach on-premises, jak i w chmurach publicznych oraz prywatnych.
Terraform to niezwykle uniwersalne narzędzie, które doskonale radzi sobie z wieloplatformowością, z jaką bardzo często mamy dziś do czynienia. Naturalnie, jeżeli korzystamy wyłącznie z jednego dostawcy chmurowego, który w dodatku udostępnia nam swoje dedykowane narzędzie – być może rzeczywiście warto z niego skorzystać. Jednak w ten sposób rezygnujemy z uniwersalności, jaką oferuje Terraform.
Elastyczność i wieloplatformowość
Czym właściwie jest wieloplatformowość? Terraform może współpracować z wieloma systemami. Zaczynając od tych najbardziej klasycznych, kojarzonych wprost z wirtualizacją (VMWare czy OpenStack), poprzez chmury publiczne (AWS, Azure czy GCP), na narzędziach pozornie niezwiązanych z infrastrukturą kończąc (GitHub czy Kubernetes). Aby to osiągnąć, Terraform korzysta z tak zwanych providerów, czyli wtyczek umożliwiających interakcję z API danego systemu.
Jak działają providerzy?
Provider to plugin będący pewnego rodzaju pośrednikiem pomiędzy Terraformem a platformą, na której budujemy nasze środowisko. Każda platforma wirtualizacyjna czy chmura publiczna ma swój odmienny charakter, wymagający indywidualnego podejścia. Dlatego też stworzenie jednego, uniwersalnego oprogramowania, które umożliwiłoby zarządzanie nimi wszystkimi, wydaje się być dosyć karkołomne. Z tego powodu zastosowano podejście oparte na providerach.
Providerzy umożliwiają Terraformowi zarządzanie zasobami w określonym środowisku. Mowa o chmurach publicznych (AWS, Azure, Google Cloud), platformach kontenerowych (Kubernetes), usługach baz danych (PostgreSQL, MySQL) czy lokalnej infrastrukturze (VMware, OpenStack). Lista providerów dostępnych w Terraformie jest imponująca i w momencie pisania tego artykułu oscyluje w granicach 4700. Mamy tutaj podział na rozwiązania oficjalne, dostarczane przez firmę HashiCorp oraz te, stworzone przez partnerów firmy. Jednak znakomita większość jest efektem bardzo prężnie działającego community.
Terraform Registry
Deklaratywność i idempotentność – kluczowe cechy Terraforma
Podejście deklaratywne, jakie oferuje Terraform, pozwala nam zdefiniować stan końcowy infrastruktury. Czyli tak naprawdę to, co chcielibyśmy otrzymać jako wynik jego działania. Terraform sam określi kroki niezbędne do osiągnięcia tego stanu. A co w przypadku, gdy dany zestaw kroków wykonamy ponownie, na istniejącej już infrastrukturze? To kolejna kluczowa cecha Terraforma, czyli idempotentność. Podczas pierwszego przebiegu zostaną wykonane wszystkie kroki niezbędne do utworzenia danego zasobu. W każdym następnym przebiegu tylko te, które są niezbędne do jego modyfikacji. Jeżeli nie będzie różnic pomiędzy stanem zadeklarowanym a faktycznym, Terraform nie podejmie żadnych działań.
Backend w Terraformie – lokalny czy zdalny?
Czym jest backend i jaką pełni rolę? Backend w Terraform to mechanizm, który odpowiada za przechowywanie pliku stanu. Powiemy o nim nieco dokładniej w dalszej części artykułu. Umożliwia on współdzielenie stanu między użytkownikami oraz wspiera operacje Terraform, takie jak plan i apply. W najbardziej klasycznym podejściu – i powiedzmy sobie wprost: najczęściej spotykanym – backend będzie lokalnym filesystemem. Jest to naturalne, ponieważ wystarczy utworzyć projekt i zabezpieczyć dane. Jednak ograniczenia, jakie posiada backend lokalny, uniemożliwiają chociażby pracę zespołową i współdzielenie wspomnianego pliku stanu. Dlatego też wychodząc naprzeciw potrzebom współpracy pomiędzy zespołami czy nawet w ramach jednego zespołu, HashiCorp dał nam możliwość pracy na backendzie zdalnym.
Firma w swoich produktach przyzwyczaiła nas do możliwości współpracy z wieloma różnymi backendami (tak jest na przykład w przypadku HashiCorp Vault). Nie inaczej jest w przypadku Terraforma. Mamy tutaj całą gamę rozwiązań. Począwszy od tych bardziej klasycznych (Google Cloud Storage czy Amazon S3), poprzez mniej intuicyjne (baza danych Postgres, sekrety Kubernetes czy nawet bazy takie jak etcs, czy ZooKeeper), na rozwiązaniach chmurowych HashiCorp skończywszy.
Współdzielenie pliku stanu i jego znaczenie
Immanentną cechą większości backendów zdalnych jest właśnie możliwość współdzielenia pliku stanu, a co za tym idzie możliwość współpracy przy projekcie. Dlaczego jest to w ogóle takie ważne i dlaczego backend jest kluczowym, a jakże często pomijanym elementem Terraforma? Terraform opiera swoje działanie na pliku stanu, który zawiera informacje o istniejących zasobach. Współpraca w ramach jednego projektu jest jak najbardziej możliwa. Wymaga jednak właśnie backendu zdalnego i to takiego, który na takie działanie pozwala. Utrata pliku stanu ma poważne konsekwencje. Bez niego Terraform nie będzie wiedział o istniejących zasobach. Nie będzie miał pojęcia, jakie zasoby utworzył wcześniej i potraktuje infrastrukturę jako nieistniejącą, próbując stworzyć wszystkie obiekty od nowa.
Jest to działanie zdecydowanie niepożądane. Odzyskanie kontroli nad środowiskiem wiązałoby bowiem się z koniecznością mozolnego importowania istniejących obiektów lub usunięcia całego projektu i utworzenia infrastruktury od nowa. Plik stanu jest krytyczny w całym projekcie i wybór odpowiedniego backendu, umożliwiającego z jednej strony współpracę, a z drugiej zapewniającego bezpieczeństwo, jest w tym przypadku kluczowe.
Deklaracja backendu zdalnego
Pierwsze kroki z Terraformem
Przed rozpoczęciem pracy z Terraformem zawsze warto sprawdzić wersję, jaką dysponujemy. Jest to o tyle ważne, że zmiany w zakresie zarządzania infrastrukturą postępują naprawdę szybko i zdecydowanie warto być na bieżąco z dokumentacją. Zarówno tą dotyczącą samego Terraforma, jak i providerów, z których korzystamy oraz modułów, o których powiemy sobie nieco później.
Wybór i deklaracja providera
Wyboru i deklaracji providera (lub providerów, gdyż jak powiedzieliśmy sobie wcześniej: nic nas nie ogranicza do jednego tylko dostawcy i śmiało możemy działać na wielu płaszczyznach, tworząc projekt wieloplatformowy) dokonujemy w pliku konfiguracyjnym, znajdującym się w głównym katalogu projektu. Tutaj dochodzimy do sedna, gdyż Terraform nie ogranicza nas do stosowania jednej, z góry przyjętej konwencji nazw. Mamy w tym względzie praktycznie dowolność. Chociaż, jak sobie powiemy przy okazji omawiania dobrych praktyk, warto się trzymać ustalonego nazewnictwa.
Terraform na etapie tworzenia planu parsuje wszystkie pliki z rozszerzeniem *.tf znajdujące się w głównym katalogu projektu. Następnie ustala odpowiednią kolejność działania. Musimy w tym miejscu powiedzieć, co właściwie robi Terraform i jak wygląda definicja środowiska, które zamierzamy stworzyć.
Deklaracja providera i utworzenie prostego zasobu
Po wybraniu i skonfigurowaniu providera, który, jak wspomniałem, jest pewnego rodzaju sterownikiem umożliwiającym dokonywanie zmian na współpracującej platformie, otrzymujemy dostęp do obiektów. Każdy z nich jest niezależnym bytem, jednak wszystkie ze sobą współpracują. Weźmy dla przykładu jednego z dostawców chmury publicznej. Tworząc maszynę wirtualną, nie jesteśmy w stanie odseparować jej od sieci, firewalla czy przestrzeni dyskowej. Wszystkie te elementy ściśle bowiem ze sobą współpracują. Stawiając infrastrukturę w sposób klasyczny, najprawdopodobniej zaczęlibyśmy od skonfigurowania sieci. Następnie utworzylibyśmy dyski (zakładając, iż podczas tworzenia wirtualki nie są one tworzone automatycznie), a na końcu dopiero przeszlibyśmy do powołania tej maszyny wirtualnej, aby móc podłączyć wszystkie elementy, które utworzyliśmy. Po powołaniu wirtualki dochodzi jeszcze konfiguracja firewalla i kontrola dostępu.
Zależności pomiędzy zasobami
Jakie działania musimy wykonać, aby dokonać jakiejkolwiek zmiany w dowolnym elemencie układanki, którą stworzyliśmy? W najlepszym wypadku musimy się upewnić, czy taka zmiana nie wpłynie na inne elementy. W przypadku niewielkiego środowiska zazwyczaj możemy to zweryfikować dosyć prosto. Jednak co w sytuacji, gdy nasz system jest mocno rozbudowany, a dodatkowo łączy się z innymi, tworząc pajęczynę połączeń pomiędzy obiektami?
Zaletą Terraforma jest to, iż radzi on sobie z tym problemem automatycznie. Kolejność tworzenia obiektów jest ustalana na podstawie ich zależności. Automatycznie określa on, które zasoby muszą zostać utworzone przed innymi. Dla przykładu, deklarując maszynę wirtualną podłączoną do sieci lokalnej, Terraform ustala, że sieć musi zostać utworzona w pierwszej kolejności. W przeciwnym wypadku jedynym sposobem na podłączenie sieci do wirtualki byłaby konieczność jej modyfikacji. W przypadku firewalla natomiast mamy dużo większą dowolność, gdyż nie jest on integralną częścią tworzonych obiektów. Określa jedynie sposób dostępu i może być zaimplementowany właściwie w dowolnym momencie.
Niejawna deklaracja zależności zasobów
Kolejność działań w Terraformie
Na ilustracji widzimy, w jaki sposób Terraform określa zależności pomiędzy obiektami. W pierwszej kolejności tworzony jest zasób VPC, ponieważ nie zależy on od żadnych innych obiektów. Jednocześnie jego identyfikator jest wykorzystywany przy tworzeniu podsieci. Na końcu tworzona jest maszyna wirtualna, do której podpięta jest nowo utworzona podsieć.
Pomimo pewnych elementów podanych w sposób jawny, jak identyfikator podsieci, Terraform sam ustala kolejność, w jakiej elementy będą tworzone. Możemy jednak na taką kolejność wpłynąć. Dzieje się tak zazwyczaj wtedy, gdy nie ma bezpośredniego odniesienia pomiędzy zasobami lub w sytuacji, gdy z pewnych względów nam na tym zależy.
Jawne ustalenie zależności pomiędzy obiektami
Jeden provider vs. wiele providerów
Korzystając z jednego providera, możemy łatwo określić powyższe zależności. Pewnych elementów po prostu nie uda nam się bowiem powołać bez utworzenia innych, nadrzędnych. W przypadku korzystania z wielu providerów, a przypomnijmy sobie ogrom systemów, z jakimi Terraform może współpracować, nie zawsze takie zależności możemy wykryć w sposób automatyczny.
Widzimy powyżej sytuację, w której grupa zasobów w chmurze Azure korzysta z bucketu s3 utworzonego w chmurze AWS. Nie ma tutaj możliwości niejawnego ustalenia zależności, gdyż korzystamy z dwóch różnych dostawców chmurowych, a co za tym idzie – z różnych providerów.
Także zależności danych są brane pod uwagę przy ustalaniu kolejności tworzenia obiektów. Na przykład, jeżeli instancja, którą tworzymy, korzysta w sposób jawny z obrazu, obraz ten jest pobierany jako pierwszy.
Zależności danych
Przy niszczeniu zasobów, bo przecież działanie Terraforma nie ogranicza się jedynie do budowania, stosuje on kolejność odwrotną. Jako pierwsze niszczone są zasoby zależne, a dopiero potem wszystkie te, od których zależą inne.
Uruchamiamy projekt
Deklaracja obiektów to pierwszy krok do powołania naszego środowiska. Abyśmy mogli uruchomić nasz projekt, niezbędne jest jego zainicjowanie. Jest to czynność, którą możemy bezpiecznie wykonać w każdej chwili. Inicjacja projektu ma za zadanie pobrać wszystkie niezbędne providery w odpowiednich wersjach, z których korzystamy w projekcie wraz z potrzebnymi modułami. Ma też ustawić lokalizację backendu, czyli miejsca, w którym przechowywany będzie plik stanu, a także utworzyć pliki pomocnicze niezbędne do dalszej pracy. Polecenie terraform init sprawdza, czy konfiguracja jest poprawna i zgodna z wymaganą wersją Terraform.
Przykład wyjścia plecenia terraform init
Tworzenie planu wdrożenia
Magia uruchomienia projektu i tworzenia zasobów zaczyna się po wykonaniu polecenia terraform apply, które niejawnie uruchamia drugie polecenie terraform plan. To tutaj ustalane są wszystkie jawne i niejawne zależności pomiędzy obiektami oraz przygotowywany jest plan wdrożenia.
Terraform opiera swoje działanie na pliku stanu, który tworzony jest automatycznie i aktualizowany przy każdym przebiegu. To właśnie plik stanu zawiera informacje o aktualnej konfiguracji tworzonych przez nas obiektów. Przygotowywany jest plan wdrożenia.
Plan wdrożenia
Następnie zmiany zostają wprowadzone w życie.
Uruchomienie projektu
Wcześniej powiedzieliśmy, że Terraform jest deklaratywny i idempotentny. Pozostańmy przy tym temacie nieco dłużej. Gdy tworzymy konfigurację naszego środowiska, deklarujemy jedynie stan, w którym chcielibyśmy, aby nasze obiekty się znalazły. Nie deklarujemy sposobu, w jaki Terraform ma postępować. Weźmy dla przykładu maszynę wirtualną, którą chcemy powołać do życia. Ustawiamy jej typ, liczbę procesorów, pamięć, podpiętą sieć czy dyski lokalne. Terraform podczas tworzenia planu weryfikuje, czy istnieje konieczność dokonania zmian. Jeżeli maszyna nie istnieje, wszystkie zmiany wprowadzane są w życie. Możemy to zaobserwować, analizując output polecenia terraform plan. Jednak gdy maszyna wirtualna już istnieje, wdrażane są jedynie różnice konfiguracyjne pomiędzy stanem faktycznym a zadeklarowanym.
W szczególnym przypadku, gdy stany się zgadzają, obiekt nie jest modyfikowany. To jest właśnie idempotentność. Kolejne uruchomienia projektu bez wprowadzania zmian konfiguracyjnych nie spowodują wdrożenia żadnych zmian w środowisku. Co w sytuacji, gdy utworzony przez nas obiekt zostanie ręcznie zmodyfikowany? Tutaj sprawa jest dosyć prosta i wydaje się intuicyjna. Terraform przywróci ręcznie zmodyfikowany zasób do stanu, jaki ma zadeklarowany. Jedynym prawidłowym stanem, w jakim dany obiekt powinien się znaleźć, jest bowiem właśnie ten zadeklarowany w plikach konfiguracyjnych.
Usuwanie obiektu
Pozostaje jeszcze kwestia usuwania zasobu. Działa to dokładnie tak, jak tworzenie czy modyfikacja. Przypomnijmy: działanie Terraforma opiera się na zadeklarowanym stanie. Dlatego też, jeżeli obiekt usuniemy z konfiguracji, zostanie on również usunięty.
Usunięcie obiektu
Import istniejących obiektów
Czy to, co powiedzieliśmy wyżej, oznacza, że w danej przestrzeni projektu chmurowego po uruchomieniu Terraforma będą istnieć tylko te obiekty, które zostały w nim zadeklarowane? Co w przypadku, gdy chcielibyśmy dodać utworzony wcześniej obiekt do konfiguracji projektu? Wyjaśnijmy sobie raz jeszcze, gdyż problem wydaje się dosyć interesujący: Terraforma interesują wyłącznie obiekty, które zostały w nim zadeklarowane.
Najprostszym sposobem deklaracji jest utworzenie obiektu. Wówczas Terraform niejako z automatu będzie monitorował wszystkie zmiany w obiekcie. Istnieje jednak mechanizm, dzięki któremu możemy zaimportować istniejący zasób do konfiguracji Terraforma, aby mógł się on stać integralną częścią naszego środowiska. W ten sposób nie musimy mozolnie tworzyć danego obiektu na nowo. Wykonanie polecenia terraform import spowoduje wczytanie stanu istniejącego obiektu do pliku stanu Terraforma. Musimy jednak pamiętać, że jego konfiguracja nie zostanie zaczytana. Aby móc zarządzać danym obiektem, musimy ręcznie uzupełnić konfigurację w plikach *.tf, by odzwierciedlała rzeczywiste właściwości obiektu.
Zaimportowanie istniejącego obiektu do Terraforma
Jeżeli nasza infrastruktura jest skomplikowana lub po prostu, gdy zajdzie taka potrzeba, warto prześledzić jej stan przy pomocy polecenia terraform show. Wyświetla ono szczegóły wszystkich zarządzanych przez Terraform obiektów.
Wynik polecenia terraform show
Język HCL
HashiCorp przyzwyczaił nas również do pewnej spójności w swoich produktach. HashiCorp Configuration Language to deklaratywny język konfiguracji, stworzony przez HashiCorp. Jest on używany do definiowania infrastruktury jako kod (IaC) w Terraform oraz w innych narzędziach HashiCorp, takich jak Vault, Nomad czy Consul. Umożliwia opisanie stanu docelowego infrastruktury, zamiast szczegółowego opisu kroków potrzebnych do jego osiągnięcia. Przy tym jest dosyć przyjazny dla użytkownika.
Składnia języka HCL przypomina nieco JSON-a, ale jest on dużo prostszy, bardziej zwięzły i bardziej czytelny. Dowolną konfigurację Terraforma możemy zapisać również w JSON-ie. Dzięki temu nasz kod będzie bardziej uniwersalny i możliwy do wykorzystania poza produktami HashiCorp.
Przykład konfiguracji w języku HCL
Przykład konfiguracji w języku JSON
Możliwość zapisywania konfiguracji zarówno w HCL, jak i JSON jest niewątpliwą zaletą, pomimo iż ten drugi jest znacznie bardziej skomplikowany. Mniejsza czytelność, zdecydowanie większa liczba koniecznych do zastosowania nawiasów czy brak komentarzy, sprawiają, że najczęściej traktujemy JSON-a jako łatwy do parsowania format wyjściowy. Dosyć przydatną i ciekawą funkcją, jaką Terraform oferuje, jest możliwość konwersji pomiędzy tymi dwoma formatami.
Konwersja konfiguracji z HCL na JSON
Moduły
Zarządzanie dużą, zwłaszcza wieloplatformową, infrastrukturą, składającą się z wielu różnorodnych obiektów, jest skomplikowane nawet w sytuacji, gdy mamy do dyspozycji takie narzędzia jak Terraform. Moduły w Terraformie ułatwiają zarządzanie skomplikowaną infrastrukturą, promując reużywanie i spójność konfiguracji. Są też podstawą do tworzenia bibliotek infrastruktury w organizacji.
Jak działają moduły?
Moduł to nic innego jak fragment kodu, który możemy wielokrotnie użyć. Gdy mamy za zadanie stworzyć wiele podobnych do siebie środowisk lub nawet pojedynczych obiektów różniących się od siebie tylko nieznacznie konfiguracją i parametrami, wykorzystanie modułów, czyli parametryzowanych przy pomocy zmiennych fragmentów kodu, wydaje się rozwiązaniem dosyć atrakcyjnym. Zresztą podobieństwo modułu do procedur czy funkcji w znanych językach programowania jest z pewnością nieprzypadkowe. Pozwala nie tylko zaoszczędzić czas, ale sprawia, że nasz projekt jest znacznie bardziej przejrzysty i uporządkowany.
Kiedy i jak możemy skorzystać z modułów?
Z modułów warto skorzystać w sytuacji, o której wspomniałem, tj. wtedy, gdy mamy wiele środowisk w zbliżonych konfiguracjach (np. środowiska developerskie i testowe). Także do zarządzania większymi projektami z podziałem na funkcjonalne elementy, jak sieci, bazy danych czy aplikacje. Użycie modułu w projekcie ogranicza się do jego zadeklarowania w głównym pliku Terraform. Natomiast należy zauważyć, że możemy skorzystać zarówno z modułów lokalnych, tworząc je we własnym zakresie, jak i wykorzystać tysiące gotowych, dostępnych w Rejestrze Terraform.
Rejestr modułów
W konfiguracji projektu wskazujemy na lokalizację modułu. W stosunku źródeł zewnętrznych mamy dużą dowolność. Możemy stosować zarówno oficjalny rejestr Terraforma, jak i repozytorium Git, a nawet lokalizację sieciową. Podajemy więc pełną ścieżkę, w której moduł się znajduje. W tym przypadku Terraform sam, po wykonaniu polecenia terrarform init, pobierze taki moduł i umieści go w stosownym katalogu.
Moduł z rejestru
Następnie:
Moduł z repozytorium Git
oraz:
Moduł z lokalizacji sieciowej
W przypadku, gdy moduł tworzymy sami lub też gdy został on już wcześniej pobrany, podajemy lokalizację w katalogu głównym projektu.
Moduł lokalny
Reużywalność
Tworzenie własnego modułu w gruncie rzeczy niewiele różni się od tworzenia samego projektu. Musimy tutaj jednak pamiętać o nadrzędnym celu istnienia modułu, mianowicie jego reużywalności. Moduł musi być napisany w taki sposób, aby był maksymalnie uniwersalny i parametryzowany. Jednocześnie jego zastosowanie nie może być zbyt szerokie. Jeżeli tworzymy moduł do budowania maszyn wirtualnych, skupmy się na tym, aby jedynymi parametrami, na jakie mamy wpływ, były właśnie parametry budowanej maszyny. Nic poza tym. Hardcodowanie danych w tym przypadku jest zaprzeczeniem dobrych praktyk.
Jak zatem osiągnąć uniwersalność i reużywalność kodu? Najprościej to zrobić poprzez wykorzystanie zmiennych. Naturalnie zmienne są integralną częścią Terraforma i możemy ich używać w dowolnym miejscu, sprawiając, że kod jest bardziej przejrzysty i zdecydowanie bardziej uniwersalny. Jednak to właśnie moduły są miejscem, w którym powinny się one zadomowić na dobre.
Deklarowanie zmiennych
Zmienne i dane wyjściowe w Terraformie
Wspomnieliśmy już nieco o zmiennych przy okazji omawiania modułów. Rzeczywiście jest to dosyć naturalne miejsce do ich wykorzystania. Jednak nie zapominajmy, że Terraform to deklaratywny język programowania. Umożliwia korzystanie z wielu dobrodziejstw obecnych praktycznie w każdym języku wysokiego poziomu.
Zmienne w Terraform umożliwiają parametryzację konfiguracji. Dzięki temu można łatwo zmieniać wartości używane w kodzie Terraform bez konieczności modyfikowania samej konfiguracji.
Deklaracja zmiennych
Wśród typów zmiennych znajdziemy praktycznie wszystkie znane z innych języków programowania, czyli strong, number, bool czy map. Wykorzystanie ich w kodzie wymaga użycia słowa kluczowego var.
Wykorzystanie zmiennych w konfiguracji
Jednak zmienne poza głównym kodem programu możemy też definiować w zewnętrznych plikach z rozszerzeniem *.tfvars. Dzięki temu kod będzie jeszcze bardziej czytelny, co w dużych projektach nie jest bez znaczenia. Terraform automatycznie wyszuka wszystkie pliki oraz przekaże je w wierszu poleceń podczas uruchamiania projektu.
Przekazywanie zmiennych podczas uruchamiania projektu
Poza zmiennymi deklarowanymi w kodzie programu Terraform odczytuje również zmienne środowiskowe zaczynające się od liter TF_VAR_.
Dane wyjściowe umożliwiają eksportowanie wyników z konfiguracji Terraform, na przykład identyfikatorów zasobów czy ich adresów IP. Dzięki temu można je łatwo uzyskać po wykonaniu konfiguracji. Dane wyjściowe deklarowane są przy pomocy bloku output, a następnie po uruchomieniu projektu wyświetlane są na ekranie.
Deklaracja danych wyjściowych
Pętle i warunki
Jak już wspomniałem, Terraform daje nam do dyspozycji dużą liczbę elementów sterowania znaną z języków programowania wysokiego poziomu. Znajdziemy tutaj takie elementy jak pętle count oraz for_each. Ich zasada działania nie odbiega w znaczący sposób od tych znanych z innych języków. Są także bloki dynamiczne pozwalające na generowanie konfiguracji wewnątrz zasobu.
Blok dynamiczny
Po wykonaniu powyższego bloku Terraform utworzy dwa bloki ingress – osobno dla każdego zadeklarowanego portu. W przypadku konieczności utworzenia wielu bardzo podobnych do siebie obiektów mechanizm bloków dynamicznych wydaje się być doskonałym rozwiązaniem zmniejszającym rozmiar konfiguracji, a jednocześnie poprawiającym jej czytelność.
Ostatnim elementem sterowania, o którym warto wspomnieć, są warunki logiczne. Pozwalają one na podejmowanie decyzji konfiguracji na podstawie zmiennych.
Warunki logiczne
Terraform Workspaces
Terraform poprzez przestrzenie robocze daje nam możliwość zarządzania wieloma środowiskami w jednym tylko katalogu. Ale po co w ogóle używać Workspaces i czy jest nam to do czegoś potrzebne? W gruncie rzeczy pytanie jest dobre. W podobny efekt z pozornie mniejszym nakładem pracy uzyskamy, stosując klasyczne podejście, w którym każde środowisko ma swój własny projekt. Nie musimy w ten sposób przełączać się pomiędzy środowiskami, co wpływa na prawdopodobieństwo popełnienia błędu. Łatwo bowiem w ten sposób uruchomić konfigurację na niewłaściwym środowisku.
Wyobraźmy sobie sytuację, w której mamy kilka różnych, bardzo zbliżonych do siebie środowisk deweloperskich i testowych, a nawet przedprodukcyjnych i produkcyjnych. Naturalnym rozwiązaniem jest użycie modułów i zmiennych, w tym zmiennych środowiskowych. Ponieważ środowiska zazwyczaj są do siebie podobne, a różnią się najczęściej parametrami związanymi ściśle z ich przeznaczeniem, korzystanie z funkcjonalności workspace’ów z całym ich dobrodziejstwem, jak pełna izolacja konfiguracji (każde środowisko ma swój własny plik stanu), możliwość reużycia konfiguracji czy łatwe przełączanie się pomiędzy projektami, wydaje się być dosyć naturalne. Z funkcjonalności tej korzystamy przecież nieświadomie, gdyż Terraform podczas inicjalizacji tworzy domyślny workspace o nazwie „default”.
Wartość zmiennej zależnej od workspace’u
W pliku variables.tf, o którym wspominaliśmy już wcześniej, deklarujemy zmienną, którą następnie możemy wykorzystać w kodzie programu. W zależności od środowiska, w którym aktualnie pracujemy, wartość zmiennej będzie się różnić.
Reasumując: Terraform Workspaces to potężne narzędzie umożliwiające nam zarządzanie wieloma środowiskami z jednego tylko folderu roboczego, bez konieczności dublowania fragmentów kodu czy modułów przy jednoczesnej izolacji plików stanu.
Integracja z procesami CI/CD
Nowoczesne technologie wymuszają na nas pewne praktyki. Jedną z nich jest automatyzacja procesów i integracja z posiadanymi narzędziami. Umożliwia to między innymi automatyczne tworzenie, aktualizowanie i usuwanie zasobów infrastrukturalnych na podstawie zmian w kodzie. Pozwala także na kontrolę wersji, dzięki której możemy śledzić zmiany, współpracować czy przywracać wcześniejsze wersje konfiguracji. Zapewnia także spójność, czyli zapewnienie, że infrastruktura jest zgodna i wersjonowana razem z aplikacją, a także bezpieczeństwo przejawiające się chociażby możliwością przeprowadzania i weryfikacji planów przed wykonaniem zmian.
Przechowywanie kodu w repozytorium Git umożliwia nam automatyzację pewnych działań każdorazowo po dokonywaniu zmian. W ten sposób możemy automatycznie, poprzez webhooki czy merge requesty, przeprowadzić walidację konfiguracji, generować plan wdrożenia czy nawet uruchamiać wdrożenie na środowisku. Wykorzystując podejście GitOps możemy wszystkie te czynności zautomatyzować w jednym pipeline’ie.
Przykład pipeline’u w repozytorium GitLab
Oczywiście integracja Terraforma z narzędziami CI/CD będzie od nas wymagać wprowadzenia pewnych zasad i dobrych praktyk. Na przykład przechowywania pliku stanu poza repozytorium czy użycia zewnętrznych mechanizmów do przechowywania informacji niejawnych. Mieści się to w kanonie dobrych praktyk opisanych w kolejnym rozdziale.
Dobre praktyki w pracy z Terraformem
Dobre praktyki w branży IT, a w szczególności w pracy z narzędziami, takimi jak Terraform, są niezwykle ważne i nadają się na materiał do osobnego artykułu. Nawet najlepsze bowiem narzędzie bez stosowania odpowiednich reguł i zasad, po pewnym czasie i licznych operacjach rozbudowywania i modyfikacji, przestanie być przejrzyste i klarowne. Zacznie przypominać pewien chaotyczny zbiór danych, które ciężko ze sobą powiązać, a w którym wprowadzanie zmian będzie niezwykle trudne.
Organizacja i bezpieczeństwo kodu
Jak to się ma w przypadku Terraforma? Terraform jest niezwykle elastycznym narzędziem, ale posiada też pewne zasady, o których warto pamiętać. Przede wszystkim organizacja kodu: używajmy modułów. Jest to szczególnie ważne zwłaszcza w dużych projektach, w których mamy do czynienia z wieloma powtarzalnym fragmentami.
Stosujmy pliki zgodnie z ich przeznaczeniem. Terraform automatycznie odczytuje wszystkie pliki z odpowiednim rozszerzeniem. Jest to bardzo przydatne, ale do nas należy zagwarantowanie, aby ich nazwy były jasne i wiele mówiące, jak main.tf czy variable.tf.
Zarządzanie stanem i wersjonowanie
Zarządzanie stanem infrastruktury jest istotne w kontekście bezpieczeństwa. Warto więc używać blokady stanu, wykorzystując zdalne backendy, które to umożliwiają oraz regularnie wykonywać kopie zapasowe pliku stanu. Nigdy też nie należy pliku stanu modyfikować ręcznie. Wszelkie jego modyfikacje trzeba wykonywać przy pomocy narzędzi Terraforma.
Ciągnąc wątek bezpieczeństwa, zadbajmy też o to, aby dane wrażliwe nie były przechowywane w postaci jawnego kodu. Także aby dostęp do naszego kodu był ograniczony wyłącznie do osób faktycznie pracujących przy projekcie. Pamiętajmy, że mamy do czynienia z konfiguracją infrastruktury informatycznej naszej firmy.
Wersjonowanie kodu to kolejna rzecz, na którą warto zwrócić uwagę. Jest to szczególnie ważne przy pracy zespołowej. Zadbajmy o to, aby nasz kod był dobrze opisany i abyśmy w każdej chwili mogli wrócić do działającej konfiguracji. Dotyczy to również modułów, których prawidłowe wersjonowanie i tagowanie jest kluczem do ich sprawnego wykorzystywania.
Testowanie, walidacja i analiza zmian
Używajmy wbudowanych narzędzi do testowania i walidacji kodu, zarówno pod kątem bezpieczeństwa czy jego poprawności (tflint czy checkov), jak i tych umożliwiających wizualne formatowanie kodu (terraform fmt). Dzięki temu stanie się on bardziej przejrzysty.
Efektywne zarządzanie projektami z Terraform
Zawsze przed wykonaniem zmian należy uruchomić i przeanalizować plan. Przy dużych projektach warto używać Terrarotm Workspaces i zintegrować projekt z systemem CI/CD. Jest to szczególnie ważne w sytuacjach, w których działamy wielopłaszczyznowo i infrastruktura tworzona przy pomocy Terraforma jest jednym z elementów większej całości.
Podsumowanie
HashiCorp Terraform daje nam możliwości zarządzania infrastrukturą informatyczną w sposób, jaki trudno sobie wyobrazić, stosując inne narzędzia. Pracując w chmurze publicznej często mamy do dyspozycji dedykowane narzędzia bezpośrednio od producenta. Jednak właśnie słowo „dedykowane” jest tutaj kluczowe. Gdy mamy do czynienia z jednym dostawcą chmurowym, takie rozwiązanie jest jak najbardziej akceptowalne. Co w przypadku infrastruktury hybrydowej czy nawet w sytuacji, gdy dostawca nie ma dedykowanego systemu automatyzacji? Infrastruktura hybrydowa jest jedną z częściej stosowanych praktyk i to nie tylko jako połączenie systemów on-premises z chmurami. Także jako łączenie różnych chmur czy nawet wielu wirtualizatorów.
Również konieczność integracji procesów i łączenia wykorzystywanych narzędzi, na przykład w procesie CI/CD, dodatkowo z koniecznością wersjonowania kodu czy automatyzacją testów, wymusza na nas poszukiwanie rozwiązań, które takie działania umożliwiają. HashiCorp Terraform dostarcza nam narzędzie uniwersalne i wieloplatformowe o niesamowitym wręcz potencjale integracji z innymi rozwiązaniami, a jednocześnie proste i intuicyjne.