''Trebuie să facem pe Arrakis ceva ce nu a mai fost încercat pe o întreagă planetă, a spus tatăl său. 'Trebuie să folosim omul ca forța constructivă ecologică -- inserând viață adaptată pentru terraformare: o plantă aici, un animal acolo, un om în acel loc -- pentru a transforma ciclul apei, pentru a construi un nou tip de environment." ~ Dune, Frank Herbert
Acest articol se dorește a fi o prezentare a aplicației Terraform produsă de HashiCorp și a conceptului de Infrastructure as Code. Vom folosi Terraform pentru a crea un nou environment în AWS și vom analiza alte soluții pentru a obține acest rezultat.
Infrastructure as Code este un proces prin care managementul infrastructurii cloud este realizat prin cod. Datele din aceste fișiere, definite folosind DSL (Domain Specific Language), sunt interpretate de un tool și aplicate în environment. Acest mod de lucru este diferit de modul clasic, în care infrastructura era modificată manual, luând rând pe rând fiecare componentă/resursă. Resursele pot să fie mașini virtuale, rețele virtuale, grupuri de securitate etc. Prin managementul automat al infrastructurii și eliminarea pașilor manuali, se poate observa un prim câștig: rapiditatea, consistența și eliminarea greșelilor umane. Schimbările sunt aplicate peste tot, în același mod și pot fi cerute/generate chiar de programatorii care lucrează la proiect, astfel încât nu se ajunge în problema clasică în care sistemul de deployment are alte cerințe decât sistemul folosit de programatori și testeri .(De exemplu, ar fi catastrofal pentru o aplicație Java creată cu Java 11, ca în ziua de release să se afle că serverele de producție suportă doar Java 7). Problemele care apar sunt mai mult legate de competențe, deoarece o parte din competențele IT Ops. se mută către programatori - învățarea de limbaje specializate și securizarea infrastructurii. Se poate găsi o cale de mijloc prin crearea de blueprint/frameworkuri de IaC de către experți și refolosirea/ adaptarea acestui blueprint/framework la necesitățile fiecărui proiect.
Terraform oferă posibilitatea descrierii infrastructurii dorite folosind un limbaj specific numit HCL (HashiCorp Configuration Language), care pentru a fi mai ușor de parsat, folosește JSON. Se poate observa un exemplu mai jos:
# Un AMI
variable „ami” {
description = „care AMI să fie fo
losit, de ex. ami-09e4b
40ce58bb7360”
}
resource „aws_instance” „web” {
ami= „${var.ami}”
count= 2
source_dest_check = false
connection {
user = „root”
}
}
Fiecare cloud oferă posibilitatea de a face provisioning prin mai multe metode gen UI, CLI sau template-uri ( de tipul, AWS CloudFormation, Google Cloud Deployment Manager, Azure Resource Manager). Cu toate acestea, de ce am folosi Terraform în locul unui tool nativ? Terraform oferă mai multe avantaje, cum ar fi:
suport - resursele disponibile în Terraform acoperă toți providerii mari (AWS, GCP, GitHub, Docker etc.) și automatizează crearea acelor resurse;
nu este dependent de platformă- fiecare platformă are provideri proprii ( care pot fi văzuți ca niște pluginuri), care automatizează crearea resurselor pentru acele platforme. Trebuie menționat faptul că modulele oferite de fiecare provider pot avea configurări diferite. De exemplu, pentru a configura imaginea folosită pentru o mașină virtuală în resursa aws_instance, oferită de providerul aws (adică pentru cloudul AWS) se folosește parametrul ami, după cum s-a putut observa mai sus. În timp ce pentru a configura aceeași informație în GCP, se folosește parametrul image din google_compute_instance/boot disk
resource „google_compute_instance” „web” {
name = „test”
machine_type = „e2-medium”
zone = „us-central1-a”
tags = [„foo”, „bar”]
boot_disk {
initialize_params {
image = „ubuntu-os-cloud/ubuntu-1804-lts”
}
}
}
starea infrastructurii - se ocupă de asemenea de starea infrastructurii; acest lucru se poate înțelege prin faptul că menține o imagine a sistemului (starea curentă a infrastructurii aplicate incluzând id-urile resurselor). Iar această imagine este folosită ulterior pentru a detecta schimbări (pot fi schimbări manuale în cloud sau schimbări în descrierea sistemului, adică în codul Terraform). Aceste schimbări sunt descoperite în pasul de planificare a schimbărilor și odată aplicate în sistem, starea nouă este salvată. Vom vorbi mai pe larg despre starea sistemului în una din secțiunile următoare.
orchestrare și idempotență - orchestrează deploymentul resurselor. Utilizatorul nu trebuie să găsească pașii corecți pentru a aduce sistemul la forma dorită. Dacă o resursă există deja, Terraform nu o va modifica.
managementul infrastructurii în mod unificat - oferă posibilitate unificării workflow-ului. Acest lucru este foarte important în cazul cloudului hibrid sau multi-cloudului.
dependențe - poate genera un grafic cu toate resursele și dependințele acestora.
versionare și audit - la fel ca orice soluție IaC, are avantajul de a putea urmări deciziile luate în cod înapoi către cerințe (requirements tracing).
Încă de acum patru ani, conform studiului DevOps Pulse 2016 [13], 40% dintre respondenți au răspuns atunci că obișnuiau să facă deploy de câteva ori pe săptămână, iar mai mult de 80% foloseau sau erau în proces de implementare a unei strategii de Continuous Integration (CI). Acest lucru nu e posibil fără a folosi anumite forme de automatizare, printre care managementul configurației sistemelor folosite. Astăzi, conform studiului DevOps Pulse 2020 [12], acest lucru este și mai important, deoarece 56% dintre respondenți au jumătate din infrastructură în cloud, iar odată cu maturizarea containerizării unde 50% dintre respondenți având jumătate din aplicații rulate din containere, devine și mai important managementul infrastructurii. Unul dintre primele unelte pentru managementul configurației a fost CFEngine (1993), apoi au urmat și altele ca: Puppet (2005) [15], Bamboo Configuration Management (2007), Chef (2009) [14] , AWS CloudFormation (2011), SaltStack (2011) [17], Rudder (2011), Juju (2012), Ansible (2012) [16], Terraform (2014) și SolarWinds (2018).
Terraform este diferit față de unele din aceste unelte prin [8]:
funcția pe care o servește - Chef, Puppet, Ansible și SaltStack au ca funcție principală managementul configurației, pe când Terraform sau CloudFormation, sunt unelte de provisioning, ca pregătirea și configurarea unui dispozitiv pentru o anumită funcție. De exemplu, o mașină virtuală care este pregătită pentru a rula un server web.
modificări asupra sistemului - pentru Chef, Puppet, Ansible și SaltStack sistemul este mutabil, adică orice modificare are loc direct în sistemul existent. De exemplu: se instalează o nouă versiune de Java sau un patch de sistem de operare. Acest lucru, în timp, duce la un fenomen numit configuration drift, deoarece fiecare server aplică schimbările într-o anumită ordine. (Se mai poate întâmpla, de asemenea, ca unele patchuri să nu se aplice corect pe un sistem și pe altul, da). Terraform tratează sistemele ca imutabile, recreându-le de la zero.
limbaj - Chef și Ansible sunt procedurale: este specificat fiecare pas care trebuie rulat pentru a se ajunge la rezultatul dorit, ceea ce înseamnă că pentru a avea imaginea de ansamblu, dezvoltatorul va trebui să citească tot codul, spre deosebire de Terraform, CloudFormation, SaltStack și Puppet care sunt declarative, specificându-se doar rezultatul final.
locația stării sistemului - în Terraform, locația stării sistemului nu este legată de un server ca și în cazul Chef, Puppet sau SaltStack.
comunitate - folosind github, am aflat că Terraform are 253 commituri în ultima lună și 24k stele github, ansible are 127 commit-uri și 45k stele, chef 226 commit-uri și 6k stele, iar puppet 21commituri și 6k stele.
Instalarea este facilă, se descarcă arhiva conținând un singur executabil de pe site-ul https://www.terraform.io/downloads.html. După despachetare, se adaugă în PATH și se verifică folosind comanda:
$ terraform -version
Terraform se poate, de asemenea, instala folosind Homebrew în OS X:
$ brew tap hashicorp/tap
$ brew install hashicorp/tap/terraform
$ terraform -version
sau folosind Chocolatey din Windows:
$ choco install terraform
$ terraform -version
De asemenea, se poate instala, pe Linux/Debian, folosind repository-urile:
Terraform Cloud este o aplicație care ajută echipele să lucreze împreună oferind un mediu de lucru de unde dezvoltatorii pot avea acces la starea infrastructurii, datele secrete ( precum chei de conectare la cloud), pot aproba deployuri de infrastructură și au acces la informații despre deployuri, modulele folosite pe lângă multe alte funcționalități.
Terraform Cloud rulează la adresa https://app.terraform.io și oferă atât conturi gratuite pentru echipe mici, cât și conturi plătite. Companiile mari însă, pot rula Terraform Cloud din intranet, de pe serverele proprii. Acest mod de lucru se numește Terraform Enterprise - instanța privată Terraform Cloud, fără limitări și suport SAML.
Vom enumera doar o parte dintre comenzile oferite de Terraform CLI.
Prima comandă folosită este cea pentru aflarea versiunii, și, mai apoi, cea pentru ajutor:
version - versiune Terraform
help - ajutor
Odată creată prima configurație Terraform se face inițializare, planificare și aplicarea planului. Uneori, se poate dori ștergerea completă a resurselor (de exemplu în cazul unui demo).
init - inițializează un director de lucru în care Terraform salvează fișierele de configurare; la rulare, face update dacă există deja directorul.
plan - folosit pentru planificarea execuției; dacă se folosește argumentul -out va salva în fișier planul generat care va putea fi folosit de apply mai târziu - un plan conține resursele care trebuie adăugate, modificate sau șterse.
apply - folosit pentru a aplica un plan asupra țintei - de exemplu, crearea unei mașini virtuale în AWS;
destroy - distruge infrastructura creată de Terraform.
Managementul stării sistemului se poate realiza prin comenzile de mai jos, cea mai uzuală fiind import, în cazul în care există deja resurse adăugate.
import - folosit pentru a importa o singură resursă dată ca ID din cloud în Terraform.
state (cu subcomenzi: list, mv, pull, push, rm, show) - folosită pentru managementul stării, listare, mutare, import local, modificare stare remote sau afișare atribute a unei singure resurse din stare.
taint - marchează o resursă căreia i se face management prin Terraform ca modificată, forțând recrearea ei la rularea comenzii apply - nu va modifica infrastructura, dar modică starea cunoscută de Terraform.
untaint - marchează o resursă căreia i se face management prin Terraform ca nemodificată, și la fel cataint nu va modifica infrastructura, doar starea;
Alte comenzi:
output - extrage valorile variabilelor de ieșire din starea sistemului;
providers - afișează informații despre provideri ;
fmt - formatarea scripturilor Terraform;
get -downloadează și updatează modulele .
graph - generează o reprezentare vizuală a resurselor și legăturilor dintre acestea în format DOT, care pot fi deschise folosind GraphViz; ca alternativă, se pot folosi servicii oferite de SmartDraw [19], cloudwiz [20] doar pentru AWS sau hava.io [21] (suportă AWS, Azure și GCP), toate trei creând diagrame de arhitectură în mod automat sau AWS NetworkManager pentru a vizualiza topologia de rețea; Azure oferă un serviciu asemănător folosind Network Watcher [23] iar în GCP se poate folosi Network Topology [24] .
show - afișează starea sistemului în format human-readable .
echo
"1 + 5" | terraform console .
Starea sistemului este necesară pentru buna funcționare a Terraform, deoarece în acest mod Terraform știe care sunt resursele cărora trebuie să le facă management, fără să fie nevoit să inspecteze toate resursele din cloud la fiecare pornire. Starea sistemului, ca implementare, poate fi văzută ca o bază de date în care Terraform salvează datele legate de infrastructură (sistem). De exemplu, pentru a mapa o resursă reală AWS id i-1234567890abcdef0 către "aws_instance" "apache_server", salvează această informație în starea sistemului. Această mapare este necesară și pentru îmbunătățirea performanței Terraform, deoarece în starea sistemului Terraform salvează și valorile unor atribute.
Este o practică bună ca starea sistemului să fie salvată într-o locație remote, accesibilă de către toți dezvoltatorii. Dacă acea locație ar fi, de exemplu git, atunci fiecare branch ar conține o altă configurație a sistemului, ceea ce ar însemna că doar unele branchuri conțin starea reală - de aceea, nu este o practică bună. Locul pentru starea sistemului trebuie să fie accesibil, unic, ușor de recunoscut ca fiind legat de un anumit proiect și cu posibilitatea de a fi blocat atunci când se fac modificări. Dacă starea sistemului conține date sensibile, ca parole pentru mașini virtuale sau baze de date, va trebui securizată. De aceea, Terraform Cloud oferă posibilitatea de a cripta, at rest și în tranzit (TLS), datele. Dacă se folosește AWS S3, starea sistemului poate fi criptată at rest și protejată prin IAM policies și prin legarea accesării, atât autorizate cât și a încercărilor neautorizate. Starea sistemului remote este salvată în ceea ce Terraform numește backend.
Lista de mai jos oferă câteva soluții remote de a salva starea sistemului:
artifactory - suport pentru salvarea stării în Artifactory;
azurerm - starea este salvată ca Blob cu o anumită cheie într-un Blob Container în Blob Storage Account;
consul - se folosește Consul KV(Key/Value) store;
cos - salvare ca obiect într-un bucket în Tencent Cloud Object Storage (COS);
etcd - salvare în etcd 2.x la o anumită locație [24];
etcdv3 - se folosește etcd KV store[24];
gcs - salvat ca obiect într-un bucket în Google Cloud Storage (GCS);
http - salvează starea folosind un client REST;
kubernetes - folosește Kubernetes secret care suportă locking;
manta - salvează starea într-un artefact în Manta [25];
oss - salvare în bucket în Stores Alibaba Cloud OSS;
pg - salvare în baza de date Postgres (>9.5) ;
s3 - salvează în bucket în Amazon S3;
swift - salvare ca artefact în Swift [26] ;
Terraform oferă suport pentru toți furnizorii de cloud mari și o serie de servicii IaaS, SaaS și PaaS. Dintre furnizorii de cloud și serviciile suportate amintim: Alibaba Cloud, AWS, Azure, Azure DevOps, Azure Stack, Google Cloud Platform, Oracle Cloud Infrastructure, Oracle Cloud Platform, Oracle Public Cloud, VMware Cloud, VMware NSX-T, vCloud Director, VMware vRA7 și VMware vSphere .
Provisionerii oferă posibilitate de a efectua anumite acțiuni pentru a pregăti infrastructura. De exemplu, instalarea unor dependențe software. Dintre provisioneri amintim:
file - lucrul cu fișiere;
local-exec - executare locală;
remote-exec - executare acțiune remote ;
chef - configurare Chef Client pe serverul remote ;
habitat - instalează Habitat;
puppet - instalează agentul Puppet ;
În exemplul de mai jos, vom instala ngix și adauga agentul local la un cluster Consul:
resource „aws_instance” „web” {
provisioner „remote-exec” {
inline = [
„yum install -y nginx”,
„consul join ${aws_instance.web.private_ip}”,
]
}
}
Aceste acțiuni pot fi obținute folosind și data block prin crearea unui template numit templates/user_data.tpl folosind AWS user-data scripts[34]:
#!/bin/bash
yum install -y nginx
consul join 10.10.10.130
Şi adăugând referința în Terraform [32][33]:
resource „aws_instance” „web” {
user_data
„${data.template_file.install_dep.rendered}”
}
data „template_file” „user_data” {
template = „${file(„templates/user_data.tpl”)}”
}
Terraform suportă modularizarea în sensul în care fiecare modul definește:
variabile de intrare - folosite în interiorul modulului pentru a seta anumite proprietăți ale resurselor cum ar fi nume utilizator, regiune, adresa IP etc.
resurse - resursele care vor fi create/modificate (provisioning) și parametrii lor;
Rolul modulelor, ca în orice limbaj de programare, este de a fi refolosit. De exemplu: Definirea unui modul cu reguli de securitate la nivel de corporație, care mai apoi este folosită de către fiecare proiect, este o practică des întâlnită,.
Lista tuturor modulelor și a providerilor oferiți de Terraform se află în Terraform Registry.
Modulele în Terraform sunt definite în propriul director, conținând de obicei:
variables.tf - pentru variabile de intrare;
variabile locale - conținute în blocul locals ;
main.tf - pentru resurse;
În exemplul de mai jos se creează o variabilă care va conține regiunea dorită:
variable "region" {
default = "us-west-2"
}
Se poate accesa folosind prefixul var:
storage = var.region
Putem, de asemenea, defini liste
variable "users" {
type = list
default = ["root", "user1", "user2"]
}
Sau structuri asociative tip map:
variable „plans” {
type = map
default = {
„5USD” = „1xCPU-1GB”
„10USD” = „1xCPU-2GB”
„20USD” = „2xCPU-4GB”
}
}
Variabilele locale se definesc în modulul care le va utiliza, folosind blocul locals. De asemenea, se pot folosi funcții. În exemplu de mai jos, concat pentru a concatena două string-uri: [28]
locals {
instance_ids = concat(aws_instance.blue.*.id,
aws_instance.green.*.id)
}
Se folosesc prefixându-le cu local, ca în exemplul de mai jos:
resource "aws_instance" "example" {
tags = local.common_tags
}
output "public_ip" {
value = upcloud_server.server_name.network_interface[0].ip_address
}
Modulele pot, de asemenea, să folosească alte module folosind un module block . În exemplul de mai jos, se folosește modulul hashicorp/consul/aws (conține mai multe resurse pentru a face deployment la Hashicorp Consul în AWS [27]):
module „consul” {
source = „hashicorp/consul/aws”
version = „0.0.5”
servers = 3
}
Se pot defini surse de date externe - în exemplul de mai jos [32] vom încărca căută cel mai recent AMI cu tagul web:
data „aws_ami” „web” {
filter {
name = „state”
values = [„available”]
}
filter {
name = „tag:Component”
values = [„web”]
}
most_recent = true
}
Terraform oferă o mulțime de funcții gen concat sau abs. Lista completă poate fi accesată la [30]. Mai jos, se regăsesc categoriile de funcții: numerice, stringuri, colecții, encoding, sistem de fișiere, data și ora, hash și criptografie, adrese IP și conversii tip.
Sunt argumente speciale folosite pentru definirea resurselor.
depends_on - valorificat pentru definirea dependențelor între module;
count - pentru a defini câte resurse de un anumit tip să genereze;
for_each - pentru a crea resurse de același tip cu unul sau mai multe atribute diferite în funcție de lista/mapul folosit în for_each ;
provider - pentru a defini care provider să fie folosit - de exemplu, AWS .
Terraform oferă posibilitate setării nivelelor de logare, folosind variabile de sistem TF_LOG la una dintre valorile TRACE, DEBUG, INFO, WARN sau ERROR. Există, de asemenea, posibilitatea de a modifica locația logurilor folosind TF_LOG_PATH precum:
export TF_LOG_PATH=./terraform.log
Pentru a migra un mediu deja creat este mai ușor folosind unelte de tipul https://github.com/dtan4/terraforming, care oferă, de exemplu, suport pentru migrare AWS la Terraform; Terraform acordă suport doar pentru migrarea unei resurse o dată.
Terraform poate fi folosit în CI/CD, existând suport pentru CircleCI și GitHub Actions. Executând pașii din tutorialul oferit de HashiCorp [11] s-a reușit în numai 15 minute, automatizarea creării unui server web rulând pe AWS EC2, după cum se poate observa mai jos:
De asemenea, se poate face management direct din Terraform Cloud.
În acest articol am definit ce înseamnă IaC, am identificat unelte IaC printre care și Terraform și, nu în ultimul rând, am analizat cum poate fi folosit Terraform pentru a crea infrastructură în cloud, utilizând AWS .
[1] https://learn.hashicorp.com/tutorials/terraform/github-actions
[2] https://registry.terraform.io/providers/hashicorp/aws/latest/docs
[3] https://learn.hashicorp.com/tutorials/terraform/infrastructure-as-code?in=terraform/aws-get-started
[4] https://learn.hashicorp.com/tutorials/terraform/eks?in=terraform/kubernetes
[5] https://www.toptal.com/devops/terraform-aws-cloud-iac
[6] https://www.codemotion.com/magazine/dev-hub/cloud-manager/terraform-aws/
[7] https://www.terraform.io/docs/providers/index.html
[9] https://trends.google.com/trends/explore?date=all&q=terraform,ansible,puppet
[10] https://learn.hashicorp.com/tutorials/terraform/cloud-destroy?in=terraform/cloud-get-started
[11] https://learn.hashicorp.com/tutorials/terraform/github-actions?in=terraform/automation
[12] https://logz.io/devops-pulse-2020/
[13] https://logz.io/blog/the-devops-pulse-2016-results/
[14] https://www.chef.io/
[15] https://puppet.com/
[17] https://www.saltstack.com/
[18] https://www.terraform.io/downloads.html
[19] https://www.smartdraw.com/whats-new/aws-integration.htm
[20] https://cloudviz.io/
[22] https://cloud.google.com/network-intelligence-center/docs/network-topology/
[23] https://docs.microsoft.com/en-us/azure/network-watcher/view-network-topology
[24] https://etcd.io/
[25] https://www.joyent.com/triton/object-storage
[26] https://docs.openstack.org/swift/latest/
[27] https://github.com/hashicorp/terraform-aws-consul/tree/master/examples/example-with-custom-asg-role
[28] https://www.terraform.io/docs/configuration/locals.html
[29] https://www.terraform.io/docs/configuration/meta-arguments/for_each.html
[30] https://www.terraform.io/docs/configuration/functions.html
[31] https://www.terraform.io/docs/provisioners/remote-exec.html
[32] https://www.terraform.io/docs/configuration/data-sources.html
[33] https://registry.terraform.io/providers/hashicorp/template/latest/docs
[34] https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html
de Ovidiu Mățan
de Mihai Talpoș