TSM - De la zero la IaC prin Terraform

Alexandru Dascăl - DevOps Engineer @ BoatyardX


Cred că nu mai e un secret faptul că orice business ce are în repertoriu servicii și resurse IT se orientează spre cloud și mai ales spre servicii cloud-based. În mod special, când vine vorba de companii de software, cred că principalul buzz-word pe care îl vom vedea in descrierea majorității acestora va fi cloud. Este, de altfel, o tranziție normală în perioada actuală când tot mai multe companii doresc să beneficieze de atuurile pe care marile cloud providers le oferă.

Cu toate acestea, realitatea ne arată că tranziția nu este una ușoară. Desigur, multe dintre provocările tipice ale IT-ului de odinioară- un exemplu sugestiv în acest sens se referă la menținerea unei baze imense de date, replicată în diverse părți ale globului în timp real- devin simple configurări în contextul de managed cloud services. Practic, avem luxul de a ne afla la un cont bancar generos distanță de a avea la dispoziție o infrastructură complexă ce ar rivaliza cu mari furnizori de servicii tehnologice din țară. Totodată, pe cât de ușor este să obținem o gamă extraordinar de diversă de servicii și tehnologii, pe atât este de greu să o menținem eficient, să construim arhitecturi rezistente în timp și să putem să ne bucurăm cu adevărat de flexibilitatea dorită.

Pot afirma că e aproape o certitudine că toată lumea care urmărește negreșit posibilitatea de a implementa schimbări rapid, cu impact minim, scalabilitate sporită, repetabilitatea rezultatelor și un cost asociat minim, are în vedere avantajele oferite de cloud. Desigur, nimic pe lume nu e perfect sau ideal, însă câteva aspecte din această listă se pot obține relativ ușor, dacă investim în cunoștințele potrivite.

Efortul de a migra spre cloud sau de a începe de la zero în acest spațiu apare descris și dezbătut într-o varietate de forme, în funcție de experiența fiecărei companii.

Dacă aceasta a fost una dezamăgitoare, e important de știut că există soluții care să transforme această experiență într-una plină de satisfacții.

Infrastructure as Code

Infrastructure as Code (IaC) e un principiu tot mai luat în considerare în lumea IT-ului, devenind un instrument de bază în arsenalul oricărui SRE/Devops eng. Motivul principal este destul de simplu: elimină interacțiunile manuale, repetate, cu un cloud provider, prin care se setează infrastructură. Evident că avantajele continuă cu repetabilitatea, care la rândul ei asigură scalabilitatea, și nu în ultimul rând, facilitează automatizarea.

Există o gamă tot mai largă de unelte pe piață construite în jurul acestui concept și care se ocupă de setarea resurselor de infrastructură folosind doar niște fișiere. Unele unelte se descurcă mai bine la construcția unor structuri complexe, în timp ce altele încearcă să ofere mai degrabă flexibilitatea de a interacționa cu o multitudine de provideri. Numitorul comun, chiar dacă există soluții ce nu se bucură de asta, este faptul că utilizatorul nu trebuie să cunoască detalii intime legate de procesul prin care se creează aceste resurse în cloud, ci mai degrabă se cere doar stadiul final în care ai vrea să fie componentele tale, ca urmare a execuției de cod.

În acest ultim punct de mai sus cred că se reflectă de fapt cel mai bine utilitatea imediată a IaC - nu mai trebuie sa ne gândim la călătorie, doar la destinație. Iar acea destinație vine declarată "pe hârtie", uneltele fac totul pentru noi, care ne putem bucura de rezultate imediat.

Terraform

În rândurile de mai jos, vom descrie una dintre uneltele de IaC. (Cu siguranță, preferata mea în momentul de față.)

Terraform folosește un limbaj declarativ prin care definim stadiul în care ar trebui să se afle resursele și nu procesul prin care ele ajung acolo. Folosind acest limbaj simplu, definim resurse de diverse tipuri, stabilite de către providerii cu care dorim să interacționăm. În mod practic, Terraform se bazează pe o serie de "plug-in-uri" numite providers pentru interacțiunea cu cloud providers, SaaS platforms sau alte tipuri de API-uri. Aceste plug-in-uri sunt declarate similar resurselor, necesitând în unele cazuri parametri de configurare pentru a specifica contextul interacțiunii. (De exemplu, ID-ul contului de AWS în care vrem să plasăm resursele). Acești providers sunt esențiali în lumea Terraform deoarece fără ei Terraform "nu știe" să interacționeze cu locurile unde dorim să provizionăm infrastructura.

Odată ce avem o parte de cod definită și rezolvată, mai e nevoie să executăm o serie scurtă de pași pentru a aplica munca depusă. Aici avem o serie scurtă de trei pași prin care transformăm o serie de code blocks în resurse cloud propriu-zise. În primul rând, e nevoie să inițializăm spațiul de lucru prin terraform init - această acțiune verifică codul scris și sintaxa, instalează providerii și modulele declarate (după caz) și inițializează un state file care va conține toate informațiile necesare despre statusul resurselor. Acest state file este mijlocul prin care Terraform știe diferențele între starea resurselor în cloud și modificările pe care le vom aplica.

După inițializare va trebui să creăm un plan oferit de comanda terraform plan. În urma acestui pas, utilitarul va afișa un plan detaliat despre schimbările pe care le vom aplica asupra infrastructurii. Scopul este acela de a revizui concret rezultatele produse de schimbările din cod.

În urma unui plan executat cu succes urmează doar să aplicăm schimbările direct în providerul ales. Comanda terraform apply se va asigura că suntem de acord cu procedura și va parcurge toți pașii necesari pentru a aduce resursele cloud în starea dorită, declarată în cod.

Finalul acestui proces în trei pași ne asigură că resursele dorite sunt acum în cloud providerul nostru și le putem folosi/interacționa cu ele imediat.

Cum revenim în viitor

Menționam anterior că unii dintre jucătorii de pe piața IT s-au confruntat cu situații nefaste. Am întâlnit destule exemple unde inițiativa companiilor a fost una excelentă, direcția bună, însă execuția finală a fost una care a rezolvat doar problemele imediate fără să abordeze și problemele de viitor sau flexibilitatea necesară pentru a rămâne relevanți în timp.

Un exemplu foarte bun este acela când companiile aleg să transfere infrastructura locală în cloud dar aleg, din varii motive, să își mențină neschimbate metodologiile de provizionare și management ale infrastructurii. Chiar dacă sunt unele metodologii care pot ține pasul cu spațiul cloud, multe dintre cele mai mature duc lipsă de flexibilitatea necesară.

Considerând acest exemplu, de multe ori gândul de a produce schimbările necesare pentru a schimba tehnologiile de mentenanță a infrastructurii este unul destul de intimidant. Cu atât mai mult pentru cei care deja au pus totul în cloud și au realizat mai târziu această nevoie.

O soluție pe care o propun în aceste cazuri este funcționalitatea de import construită în Terraform. Pe scurt, Terraform are capacitatea de a importa starea actuală a resurselor cloud în state file-ul propriu, urmând ca prin mici adiții de cod și ajustări care să reflecte acea stare actuală putem începe să menținem resurse cloud ca și cum ar fi fost scrise de la bun început în Terraform. Parcă dintr-o dată, problemele pe care le-am înșirat nu mai sună chiar atât de grave.

Avantajul suprem al acestei metodologii de tranziție este faptul că nu trebuie să irosim efortul deja depus pentru a crea resursele în cloud și timpul petrecut pentru a le optimiza/configura.

Terraform import - exemplu practic

Plecăm de la premisa că dorim să menținem o instanță EC2 din contul nostru AWS cu ajutorul Terraform .

Detaliile acesteia ar putea fi după cum urmează:

Name: MyVM
Instance ID: i-0b9be609418aa0609
Type: t2.micro
VPC ID: vpc-1827ff72

Trebuie în continuare să creăm spațiul în care vom importa resursa. Vom crea un director în care vom aveam un fișier numit main.tf:

// Provider configuration
terraform {
required_providers {
   aws = {
     source  = "hashicorp/aws"
     version = "~> 3.0"
   }
}
}

provider "aws" {
region = "eu-central-1"
}

Rulăm terraform init pentru a inițializa providerul și a crea state file. Rezultatul arată similar cu acesta:

Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 3.0"...
- Installing hashicorp/aws v3.51.0...
- Installed hashicorp/aws v3.51.0 (signed by HashiCorp)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

Acum trebuie să creăm o resursă potrivită cu cea pe care dorim să o importăm. În mod normal aceasta ar merge într-un fișier separat, destinat acesteia și altor resurse similare sau legate logic. Pentru moment, o putem adăuga în același main.tf:

resource "aws_instance" "myvm" {
    ami           = "unknown"
    instance_type = "unknown"
}

Acum urmează să importăm resursa cloud:

terraform import aws_instance.myvm 

aws_instance.myvm: Importing from ID "i-0b9be609418aa0609"...
aws_instance.myvm: Import prepared!
  Prepared aws_instance for import
aws_instance.myvm: Refreshing state... [id=i-0b9be609418aa0609]

Import successful!

The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.

Executăm un plan pentru a observa diferențele între starea de pe local și cea din cloud. Odată ce Terraform calculează diferențele putem să configurăm corect resursa local ca ea să reflecte întru totul ce există în cloud.

.
 } -> (known after apply)
 ~ throughput = 0 -> (known after apply)
 ~ volume_id  = "vol-0fa93084426be508a" -> (known after apply)

 ~ volume_size= 8 -> (known after apply)
 ~ volume_type= "gp2" -> (known after apply)
 }

 - timeouts {}
}

Plan: 1 to add, 0 to change, 1 to destroy.

Acum putem analiza cu atenție state file-ul și să transpunem toate diferențele pe care Terraform le-a semnalat în codul nostru. În mod practic, trebuie să identificăm setul minim de setări prin care Terraform va considera cele doua stări (local și remote/cloud) ca fiind identice - altfel riscăm ca o execuție cu apply să producă schimbări asupra instanței noastre. Schimbăm variabila ami din unkown în valoarea semnalată de Terraform și obținem un plan mai curat:

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # aws_instance.myvm will be updated in-place
  ~ resource "aws_instance" "myvm" {
   id                  = "i-0b9be609418aa0609"
  ~ instance_type      = "t2.micro" -> "unknown"
  ~ tags               = {
     - "Name" = "MyVM" -> null
     }
     ~ tags_all                             = {
     - "Name" = "MyVM"
     } -> (known after apply)
      # (27 unchanged attributes hidden)

      # (6 unchanged blocks hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

Completăm toate informațiile primite înapoi în main.tf:

resource "aws_instance" "myvm" {
ami           = "ami-00f22f6155d6d92c5"
instance_type = "t2.micro"

tags = {
     "Name": "MyVM"
  }
}

Acum ar trebui să fi ajuns la ținta de final în care terraform plan nu mai consideră că există o diferență între cele două locații:

aws_instance.myvm: Refreshing state... 
[id=i-0b9be609418aa0609]

No changes. Your infrastructure matches the 
configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.

Viitorul după IaC

Consider că procesul descris este unul destul de simplu atât de urmat cât și de implementat. Realitatea e că lucrurile se complică tot mai tare cu cât infrastructura este mai complexă și mai diversă. Totuși, avem o cale de început pentru a lucra cu IaC și Terraform mergând înainte.

Cheia succesului constă în a înțelege cât mai bine modul în care Terraform tratează starea resurselor și în a organiza componentele și resursele cât mai bine în cod. (În cazul acesta, bine înseamnă segregare logică și eficientă/fără duplicări.)

Acum că am ajuns în acest punct, viitorul constă în a folosi Terraform exclusiv pentru orice modificări dorite la nivelul resurselor. În acest fel menținem totul într-o formă ușoară de citit pentru toată lumea, putem afla cu certitudine starea infrastructurii prin intermediul Terraform și avem o formă foarte ușoară (în trei pași) de a crea/schimba orice am dori.

Happy terraforming!