Update, November 17, 2016: vi tog den här blogginlägget serien, expanderade den och gjorde den till en bok som heter Terraform: Up & Running!
Uppdatering, 8 juli 2019: vi har uppdaterat denna blogginläggsserie för Terraform 0.12 och släppt 2: a upplagan av Terraform: Up & Running!
detta är del 1 i den omfattande Guide to Terraform-serien. I introduktionen till serien diskuterade vi varför varje företag borde använda infrastructure-as-code (IAC). I det här inlägget kommer vi att diskutera varför vi valde Terraform som vårt IAC-verktyg.
om du söker på Internet efter ”infrastruktur-som-kod” är det ganska lätt att komma med en lista över de mest populära verktygen:
- kock
- marionett
- Ansible
- SaltStack
- CloudFormation
- Terraform
vad som inte är lätt är att ta reda på vilken av dessa du ska använda. Alla dessa verktyg kan användas för att hantera infrastruktur som kod. Alla är öppen källkod, stöds av stora grupper av bidragsgivare, och arbetar med många olika molnleverantörer (med det anmärkningsvärda undantaget CloudFormation, som är stängd källa och endast AWS). Alla erbjuder företagsstöd. Alla är väl dokumenterade, både när det gäller officiell dokumentation och samhällsresurser som blogginlägg och StackOverflow-frågor. Så hur bestämmer du dig?
det som gör detta ännu svårare är att de flesta jämförelser du hittar online mellan dessa verktyg gör lite mer än att lista de allmänna egenskaperna för varje verktyg och få det att låta som om du kan vara lika framgångsrik med någon av dem. Och även om det är tekniskt sant, är det inte till hjälp. Det är lite som att berätta för en programmeringsnybörjare att du kan vara lika framgångsrik att bygga en webbplats med PHP, C eller Assembly — ett uttalande som är tekniskt sant, men en som utelämnar en stor mängd information som skulle vara otroligt användbar för att fatta ett bra beslut.
i det här inlägget kommer vi att dyka in i några mycket specifika skäl till varför vi valde Terraform över de andra IAC-verktygen. Som med alla tekniska beslut är det en fråga om avvägningar och prioriteringar, och även om dina specifika prioriteringar kan vara annorlunda än våra, hoppas vi att dela vår tankeprocess hjälper dig att fatta ditt eget beslut. Här är de viktigaste avvägningarna vi ansåg:
- Configuration Management vs Provisioning
- Mutable Infrastructure vs Immutable Infrastructure
- procedurell vs deklarativ
- Master vs Masterless
- Agent vs Agentless
- stor gemenskap vs liten gemenskap
- Mogen vs Cutting Edge
- använda flera verktyg tillsammans
kock, marionett, Ansible och SaltStack är alla konfigurationshanteringsverktyg, som används för innebär att de är utformade för att installera och hantera programvara på befintliga servrar. CloudFormation och Terraform är provisioneringsverktyg, vilket innebär att de är utformade för att tillhandahålla servrarna själva (liksom resten av din infrastruktur, som lastbalanserare, databaser, nätverkskonfiguration etc.), vilket ger jobbet att konfigurera dessa servrar till andra verktyg. Dessa två kategorier utesluter inte varandra, eftersom de flesta konfigurationshanteringsverktyg kan göra en viss grad av provisionering och de flesta provisioneringsverktyg kan göra en viss grad av konfigurationshantering. Men fokus på konfigurationshantering eller provisionering innebär att vissa av verktygen kommer att passa bättre för vissa typer av uppgifter.
i synnerhet har vi funnit att om du använder Docker eller Packer är de allra flesta av dina konfigurationshanteringsbehov redan omhändertagna. Med Docker och Packer kan du skapa bilder (t.ex. behållare eller virtuella maskinbilder) som har all programvara som din server behöver redan installerat och konfigurerat. När du har en sådan bild är allt du behöver en server för att köra den. Och om allt du behöver göra är att tillhandahålla en massa servrar, kommer ett provisioneringsverktyg som Terraform vanligtvis att passa bättre än ett konfigurationshanteringsverktyg (här är ett exempel på hur man använder Terraform för att distribuera Docker på AWS).
Mutable Infrastructure vs Immutable Infrastructure
Konfigurationshanteringsverktyg som kock, marionett, Ansible och SaltStack är vanligtvis standard för ett muterbart infrastrukturparadigm. Om du till exempel ber Chef att installera en ny version av OpenSSL körs programuppdateringen på dina befintliga servrar och ändringarna kommer att ske på plats. Med tiden, när du tillämpar fler och fler uppdateringar, bygger varje server upp en unik historik över förändringar. Detta leder ofta till ett fenomen som kallas konfigurationsdrift, där varje server blir något annorlunda än alla andra, vilket leder till subtila konfigurationsfel som är svåra att diagnostisera och nästan omöjliga att reproducera.
Om du använder ett provisioneringsverktyg som Terraform för att distribuera maskinbilder skapade av Docker eller Packer, är varje ”förändring” faktiskt en distribution av en ny server (precis som varje ”förändring” till en variabel i funktionell programmering returnerar faktiskt en ny variabel). Om du till exempel vill distribuera en ny version av OpenSSL skulle du skapa en ny bild med Packer eller Docker med den nya versionen av OpenSSL som redan är installerad, distribuera den bilden över en uppsättning helt nya servrar och sedan undeploy de gamla servrarna. Detta tillvägagångssätt minskar sannolikheten för konfigurationsdriftfel, gör det lättare att veta exakt vilken programvara som körs på en server och låter dig trivialt distribuera någon tidigare version av programvaran när som helst. Naturligtvis är det möjligt att tvinga konfigurationshanteringsverktyg att göra oföränderliga distributioner också, men det är inte det idiomatiska tillvägagångssättet för dessa verktyg, medan det är ett naturligt sätt att använda provisioneringsverktyg.
procedur vs deklarativ
kock och Ansible uppmuntra en procedur stil där du skriver kod som anger, steg för steg, hur man ska uppnå vissa önskade slut tillstånd. Terraform, CloudFormation, SaltStack och marionett uppmuntrar alla till en mer deklarativ stil där du skriver kod som anger ditt önskade sluttillstånd, och IAC-verktyget själv ansvarar för att räkna ut hur du uppnår det tillståndet.låt oss till exempel säga att du ville distribuera 10 servrar (”EC2 Instances” i AWS lingo) för att köra v1 i en app. Här är ett förenklat exempel på en Ansible mall som gör detta med en procedur tillvägagångssätt:
- ec2:
count: 10
image: ami-v1
instance_type: t2.micro
och här är ett förenklat exempel på en Terraform mall som gör samma sak med hjälp av en deklarativ metod:
resource "aws_instance" "example" {
count = 10
ami = "ami-v1"
instance_type = "t2.micro"
}
Nu på ytan, dessa två metoder kan se liknande ut, och när du först utföra dem med Ansible eller Terraform, de kommer att ge liknande resultat. Det intressanta är vad som händer när du vill göra en förändring.Tänk dig till exempel att trafiken har gått upp och du vill öka antalet servrar till 15. Med Ansible är procedurkoden du skrev tidigare inte längre användbar; om du bara uppdaterat antalet servrar till 15 och omdirigera den koden skulle den distribuera 15 nya servrar, vilket ger dig 25 totalt! Så istället måste du vara medveten om vad som redan är utplacerat och skriva ett helt nytt procedurskript för att lägga till de 5 nya servrarna:
- ec2:
count: 5
image: ami-v1
instance_type: t2.micro
med deklarativ kod, eftersom allt du gör är att deklarera det slutläge du vill ha, och Terraform räknar ut hur du kommer till det slutläget, kommer Terraform också att vara medveten om vilken stat som helst som den skapade tidigare. För att distribuera ytterligare 5 servrar behöver du därför bara gå tillbaka till samma Terraform-Mall och uppdatera räkningen från 10 till 15:
resource "aws_instance" "example" {
count = 15
ami = "ami-v1"
instance_type = "t2.micro"
}
Om du körde den här mallen skulle Terraform inse att den redan hade skapat 10 servrar och därför att allt det behövde göra var att skapa 5 nya servrar. Innan du kör den här mallen kan du faktiskt använda Terraforms plan
kommando för att förhandsgranska vilka ändringar det skulle göra:
$ terraform plan+ aws_instance.example.11
ami: "ami-v1"
instance_type: "t2.micro"+ aws_instance.example.12
ami: "ami-v1"
instance_type: "t2.micro"+ aws_instance.example.13
ami: "ami-v1"
instance_type: "t2.micro"+ aws_instance.example.14
ami: "ami-v1"
instance_type: "t2.micro"+ aws_instance.example.15
ami: "ami-v1"
instance_type: "t2.micro"Plan: 5 to add, 0 to change, 0 to destroy.
vad händer nu när du vill distribuera v2 tjänsten? Med procedurmetoden är båda dina tidigare Ansible-mallar återigen inte användbara, så du måste skriva ännu en mall för att spåra de 10 servrarna du distribuerade tidigare (eller var det 15 nu?) och uppdatera var och en noggrant till den nya versionen. Med terraforms deklarativa tillvägagångssätt går du tillbaka till exakt samma mall igen och ändrar helt enkelt ami-versionsnumret till v2:
resource "aws_instance" "example" {
count = 15
ami = "ami-v2"
instance_type = "t2.micro"
}
uppenbarligen förenklas ovanstående exempel. Ansible tillåter dig att använda taggar för att söka efter befintliga EC2-instanser innan du distribuerar nya (t. ex. använder instance_tags
och count_tag
parametrar), men att manuellt räkna ut den här typen av logik för varje enskild resurs du hanterar med Ansible, baserat på varje resurs tidigare historia, kan vara överraskande komplicerat (t. ex. hitta befintliga instanser inte bara av taggen, men också bildversion, tillgänglighet zon, etc). Detta belyser två stora problem med procedurella IAC-verktyg:
- när man hanterar procedurkod är infrastrukturens tillstånd inte helt fångat i koden. Att läsa igenom de tre Ansible mallarna vi skapade ovan räcker inte för att veta vad som distribueras. Du måste också veta i vilken ordning vi tillämpade dessa mallar. Hade vi tillämpat dem i en annan ordning kan vi sluta med annan infrastruktur, och det är inte något du kan se i själva kodbasen. Med andra ord, att resonera om en Ansible eller kock kodbas, du måste veta hela historien om varje förändring som någonsin har hänt.
- återanvändbarheten för procedurkoden är i sig begränsad eftersom du måste manuellt ta hänsyn till kodbasens nuvarande tillstånd. Eftersom det tillståndet ständigt förändras kan kod som du använde för en vecka sedan inte längre vara användbar eftersom den var utformad för att ändra ett tillstånd för din infrastruktur som inte längre finns. Som ett resultat tenderar procedurkodsbaser att växa stora och komplicerade över tiden.
å andra sidan, med den typ av deklarativ metod som används i Terraform, representerar koden alltid det senaste tillståndet för din Infrastruktur. I ett ögonblick kan du berätta vad som för närvarande distribueras och hur det är konfigurerat, utan att behöva oroa dig för historia eller timing. Detta gör det också enkelt att skapa återanvändbar kod, eftersom du inte behöver manuellt redogöra för världens nuvarande tillstånd. Istället fokuserar du bara på att beskriva ditt önskade tillstånd, och Terraform räknar ut hur man automatiskt kommer från ett tillstånd till det andra. Som ett resultat tenderar Terraform-kodbaser att vara små och lätta att förstå.
naturligtvis finns det nackdelar med deklarativa språk också. Utan tillgång till ett fullständigt programmeringsspråk är din uttrycksfulla kraft begränsad. Till exempel är vissa typer av infrastrukturförändringar, till exempel en rullande driftsättning utan driftstopp, svåra att uttrycka i rent deklarativa termer. På samma sätt, utan förmågan att göra ”logik” (t.ex. if-uttalanden, loopar) kan det vara svårt att skapa Generisk, återanvändbar kod (särskilt i CloudFormation). Lyckligtvis ger Terraform ett antal kraftfulla primitiver, såsom ingångsvariabler, utgångsvariabler, moduler, create_before_destroy
och count
, som gör det möjligt att skapa ren, konfigurerbar, modulär kod även på ett deklarativt språk. Vi diskuterar dessa verktyg mer i Del 4, Hur man skapar återanvändbar infrastruktur med Terraform-moduler och Del 5, Terraform tips & tricks: loopar, if-uttalanden och fallgropar.
Master Versus Masterless
som standard kräver Chef, Puppet och SaltStack alla att du kör en masterserver för att lagra tillståndet för din infrastruktur och distribuera uppdateringar. Varje gång du vill uppdatera något i din Infrastruktur använder du en klient (t.ex. ett kommandoradsverktyg) för att utfärda nya kommandon till huvudservern, och huvudservern skjuter antingen uppdateringarna ut till alla andra servrar, eller de servrarna drar de senaste uppdateringarna ner från huvudservern regelbundet.
en huvudserver erbjuder några fördelar. För det första är det en enda central plats där du kan se och hantera statusen för din Infrastruktur. Många konfigurationshanteringsverktyg tillhandahåller även ett webbgränssnitt (t.ex. Chef Console, Puppet Enterprise Console) för master server för att göra det lättare att se vad som händer. För det andra kan vissa huvudservrar köras kontinuerligt i bakgrunden och genomdriva din konfiguration. På det sättet, om någon gör en manuell ändring på en server, kan huvudservern återställa den ändringen för att förhindra konfigurationsdrift.
men att behöva köra en huvudserver har några allvarliga nackdelar:
- Extra Infrastruktur: du måste distribuera en extra server, eller till och med ett kluster av extra servrar (för hög tillgänglighet och skalbarhet), bara för att köra master.
- Underhåll: du måste underhålla, uppgradera, säkerhetskopiera, övervaka och skala huvudservern(erna).
- säkerhet: du måste tillhandahålla ett sätt för klienten att kommunicera med huvudservern (- erna) och ett sätt för huvudservern (- erna) att kommunicera med alla andra servrar, vilket vanligtvis innebär att öppna extra portar och konfigurera extra autentiseringssystem, som alla ökar din yta till angripare.
Chef, Puppet och SaltStack har olika nivåer av stöd för masterless-lägen där du bara kör deras agentprogramvara på var och en av dina servrar, vanligtvis på ett periodiskt schema (t.ex. ett cron-jobb som körs var 5: e minut) och använder det för att dra ner de senaste uppdateringarna från versionskontroll (snarare än från en huvudserver). Detta minskar antalet rörliga delar avsevärt, men som diskuteras i nästa avsnitt lämnar detta fortfarande ett antal obesvarade
– frågor, särskilt om hur man tillhandahåller servrarna och installerar agentprogramvaran på dem i första hand.
Ansible, CloudFormation, Heat och Terraform är alla masterless som standard. Eller för att vara mer exakt kan vissa av dem lita på en huvudserver, men det är redan en del av den Infrastruktur du använder och inte en extra bit du måste hantera. Terraform kommunicerar till exempel med molnleverantörer med hjälp av molnleverantörens API: er, så på något sätt är API-servrarna huvudservrar, förutom att de inte kräver någon extra infrastruktur eller några extra autentiseringsmekanismer (dvs använd bara dina API-nycklar). Ansible fungerar genom att ansluta direkt till varje server via SSH, så igen behöver du inte köra någon extra infrastruktur eller hantera extra autentiseringsmekanismer (dvs använd bara dina SSH-nycklar).
Agent kontra Agentless
Chef, Puppet och SaltStack kräver alla att du installerar agent-programvara (t.ex. Chef Client, Puppet Agent, Salt Minion) på varje server du vill konfigurera. Agenten körs vanligtvis i bakgrunden på varje server och ansvarar för att
installerar de senaste uppdateringarna för konfigurationshantering.
detta har några nackdelar:
- Bootstrapping: Hur tillhandahåller du dina servrar och installerar agentprogramvaran på dem i första hand? Vissa konfigurationshanteringsverktyg sparkar burken på vägen, förutsatt att någon extern process tar hand om detta för dem (t.ex. använder du först Terraform för att distribuera en massa servrar med en VM-bild som redan har agenten installerad); andra konfigurationshanteringsverktyg har en speciell bootstrapping-process där du kör engångskommandon för att tillhandahålla servrarna med molnleverantörens API: er och installera agentens programvara på dessa servrar över SSH.
- underhåll: Du måste noggrant uppdatera agentens programvara regelbundet, var noga med att hålla den synkroniserad med huvudservern om det finns en. Du måste också övervaka agentens programvara och starta om den om den kraschar.
- säkerhet: Om agent-programvaran drar ner konfigurationen från en huvudserver (eller någon annan server om du inte använder en master) måste du öppna utgående portar på varje server. Om huvudservern trycker på konfigurationen till agenten måste du öppna inkommande portar på varje server. I båda fallen måste du ta reda på hur du autentiserar agenten till servern den pratar med. Allt detta ökar din yta till angripare.återigen har Chef, Puppet och SaltStack olika nivåer av stöd för agentlösa lägen (t.ex. salt-ssh), men dessa känns ofta som om de klibbades på som en eftertanke och stöder inte alltid hela funktionsuppsättningen för konfigurationshanteringsverktyget. Det är därför i naturen, standard eller idiomatisk konfiguration för Kock, marionett, och SaltStack innehåller nästan alltid en agent, och oftast en mästare också.
alla dessa extra rörliga delar introducerar ett stort antal nya fellägen i din Infrastruktur. Varje gång du får en felrapport klockan 3 måste du ta reda på om det är ett fel i din programkod eller din IAC-kod eller konfigurationshanteringsklienten eller huvudservern eller hur klienten pratar med huvudservern eller hur andra servrar pratar med huvudservern eller…
Ansible, CloudFormation, Heat och Terraform kräver inte att du installerar några extra agenter. Eller, för att vara mer exakt, kräver vissa av dem agenter, men dessa är vanligtvis redan installerade som en del av den Infrastruktur du använder. Till exempel tar AWS, Azure, Google Cloud och alla andra molnleverantörer hand om att installera, hantera och autentisera agentprogramvara på var och en av sina fysiska servrar. Som användare av Terraform behöver du inte oroa dig för något av det: du utfärdar bara kommandon och molnleverantörens agenter kör dem åt dig på alla dina servrar. Med Ansible måste dina servrar köra SSH-demonen,vilket är vanligt att köra på de flesta servrar ändå.
stor gemenskap vs liten gemenskap
När du väljer en teknik väljer du också en gemenskap. I många fall kan ekosystemet kring projektet ha en större inverkan på din upplevelse än den inneboende kvaliteten på själva tekniken. Gemenskapen bestämmer hur många personer som bidrar till projektet, hur många plug-ins, integrationer och tillägg som finns tillgängliga, hur lätt det är att hitta hjälp online (t.ex. blogginlägg, frågor om StackOverflow) och hur lätt det är att anställa någon som hjälper dig (t. ex. en anställd, konsult eller supportföretag).
det är svårt att göra en exakt jämförelse mellan samhällen, men du kan upptäcka några trender genom att söka online. Tabellen nedan visar en jämförelse av populära IAC-verktyg, med data som jag samlade in under maj 2019, inklusive Om IAC-verktyget är öppen källkod eller stängd källa, vilka molnleverantörer det stöder, det totala antalet bidragsgivare och stjärnor på GitHub, hur många åtaganden och aktiva problem det fanns över en månadsperiod från mitten av April till mitten av maj, hur många open source-bibliotek som finns tillgängliga för verktyget, antalet frågor som anges för det verktyget på StackOverflow och antalet jobb som nämner verktyget på Indeed.com.