Terraforme valkuilen

Terraforme valkuilen
Laten we een paar valkuilen benadrukken, waaronder die met betrekking tot loops, if-statements en implementatietechnieken, evenals meer algemene problemen die Terraform in het algemeen beïnvloeden:

  • de parameters count en for_each hebben beperkingen;
  • beperk implementaties zonder downtime;
  • zelfs een goed plan kan mislukken;
  • refactoring kan zijn valkuilen hebben;
  • uitgestelde coherentie is consistent... met uitstel.

De parameters count en for_each hebben beperkingen

De voorbeelden in dit hoofdstuk maken uitgebreid gebruik van de count-parameter en de for_each-expressie in lussen en voorwaardelijke logica. Ze presteren goed, maar ze hebben twee belangrijke beperkingen waar u rekening mee moet houden.

  • Count en for_each kunnen niet verwijzen naar bronuitvoervariabelen.
  • count en for_each kunnen niet worden gebruikt in de moduleconfiguratie.

count en for_each kunnen niet verwijzen naar bronuitvoervariabelen

Stel je voor dat je meerdere EC2-servers moet inzetten en om de een of andere reden ASG niet wilt gebruiken. Uw code zou er als volgt uit kunnen zien:

resource "aws_instance" "example_1" {
   count             = 3
   ami                = "ami-0c55b159cbfafe1f0"
   instance_type = "t2.micro"
}

Laten we ze een voor een bekijken.

Omdat de count-parameter is ingesteld op een statische waarde, werkt deze code zonder problemen: wanneer u de apply-opdracht uitvoert, worden er drie EC2-servers gemaakt. Maar wat als u één server in elke Availability Zone (AZ) binnen uw huidige AWS-regio zou willen inzetten? U kunt uw code een lijst met zones laten laden uit de gegevensbron aws_availability_zones en vervolgens elke zone doorlopen en er een EC2-server in maken met behulp van de count-parameter en toegang tot de array-index:

resource "aws_instance" "example_2" {
   count                   = length(data.aws_availability_zones.all.names)
   availability_zone   = data.aws_availability_zones.all.names[count.index]
   ami                     = "ami-0c55b159cbfafe1f0"
   instance_type       = "t2.micro"
}

data "aws_availability_zones" "all" {}

Deze code werkt ook prima, omdat de count-parameter zonder problemen naar gegevensbronnen kan verwijzen. Maar wat gebeurt er als het aantal servers dat u moet creëren afhankelijk is van de output van een bepaalde bron? Om dit aan te tonen is de eenvoudigste manier om de resource random_integer te gebruiken, die, zoals de naam al doet vermoeden, een willekeurig geheel getal retourneert:

resource "random_integer" "num_instances" {
  min = 1
  max = 3
}

Deze code genereert een willekeurig getal tussen 1 en 3. Laten we eens kijken wat er gebeurt als we de uitvoer van deze bron proberen te gebruiken in de count-parameter van de aws_instance bron:

resource "aws_instance" "example_3" {
   count             = random_integer.num_instances.result
   ami                = "ami-0c55b159cbfafe1f0"
   instance_type = "t2.micro"
}

Als u terraform plan op deze code uitvoert, krijgt u de volgende foutmelding:

Error: Invalid count argument

   on main.tf line 30, in resource "aws_instance" "example_3":
   30: count = random_integer.num_instances.result

The "count" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the count depends on.

Terraform vereist dat het aantal en for_each worden berekend tijdens de planningsfase, voordat er resources worden gemaakt of gewijzigd. Dit betekent dat count en for_each kunnen verwijzen naar letterlijke waarden, variabelen, gegevensbronnen en zelfs bronnenlijsten (zolang hun lengte tijdens de planning kan worden bepaald), maar niet naar berekende bronuitvoervariabelen.

count en for_each kunnen niet worden gebruikt in de moduleconfiguratie

Op een dag komt u misschien in de verleiding om een ​​telparameter aan uw moduleconfiguratie toe te voegen:

module "count_example" {
     source = "../../../../modules/services/webserver-cluster"

     count = 3

     cluster_name = "terraform-up-and-running-example"
     server_port = 8080
     instance_type = "t2.micro"
}

Deze code probeert count binnen een module te gebruiken om drie kopieën van de webserver-clusterbron te maken. Of misschien wil je het verbinden van een module optioneel maken op basis van een of andere Booleaanse voorwaarde door de count-parameter in te stellen op 0. Dit lijkt misschien een redelijke code, maar je krijgt deze foutmelding als je Terraform Plan uitvoert:

Error: Reserved argument name in module block

   on main.tf line 13, in module "count_example":
   13: count = 3

The name "count" is reserved for use in a future version of Terraform.

Helaas wordt vanaf Terraform 0.12.6 het gebruik van count of for_each in een moduleresource niet ondersteund. Volgens de release-opmerkingen van Terraform 0.12 (http://bit.ly/3257bv4) is HashiCorp van plan deze mogelijkheid in de toekomst toe te voegen, dus afhankelijk van wanneer je dit boek leest, kan het al beschikbaar zijn. Om het zeker te weten, lees hier de Terraform-changelog.

Beperkingen van implementaties zonder downtime

Het gebruik van het create_before_destroy-blok in combinatie met ASG is een geweldige oplossing voor het creëren van zero-downtime-implementaties, met uitzondering van één voorbehoud: regels voor automatisch schalen worden niet ondersteund. Of om preciezer te zijn: hierdoor wordt de ASG-grootte bij elke implementatie teruggezet naar min_size, wat een probleem zou kunnen zijn als u regels voor automatisch schalen zou gebruiken om het aantal actieve servers te vergroten.

De webserver-clustermodule bevat bijvoorbeeld een paar aws_autoscaling_schedule-bronnen, waardoor om 9 uur het aantal servers in het cluster toeneemt van twee naar tien. Als je bijvoorbeeld om 11 uur implementeert, zal de nieuwe ASG opstarten met slechts twee servers in plaats van tien, en dat zal zo blijven tot 9 uur de volgende dag.

Deze beperking kan op verschillende manieren worden omzeild.

  • Wijzig de herhalingsparameter in aws_autoscaling_schedule van 0 9 * * * (“uitgevoerd om 9 uur”) in iets als 0-59 9-17 * * * (“uitgevoerd elke minuut van 9 uur tot 5 uur”). Als ASG al tien servers heeft, zal het opnieuw uitvoeren van deze automatische schalingsregel niets veranderen, en dat is wat we willen. Maar als de ASG nog maar onlangs is ingezet, zorgt deze regel ervoor dat het aantal servers binnen maximaal een minuut tien zal bereiken. Dit is geen geheel elegante aanpak, en grote sprongen van tien naar twee servers en terug kunnen ook problemen voor gebruikers veroorzaken.
  • Maak een aangepast script dat de AWS API gebruikt om het aantal actieve servers in de ASG te bepalen, roep dit aan met behulp van een externe gegevensbron (zie "Externe gegevensbron" op pagina 249) en stel de parameter wanted_capacity van de ASG in op de waarde die wordt geretourneerd door het script. Op deze manier zal elke nieuwe ASG-instantie altijd op dezelfde capaciteit draaien als de bestaande Terraform-code, wat het onderhoud ervan moeilijker maakt.

Natuurlijk zou Terraform idealiter ingebouwde ondersteuning hebben voor implementaties zonder downtime, maar vanaf mei 2019 had het HashiCorp-team geen plannen om deze functionaliteit toe te voegen (details - hier).

Het kan zijn dat het juiste plan niet succesvol wordt uitgevoerd

Soms levert de opdracht plan een perfect correct implementatieplan op, maar retourneert de opdracht apply een fout. Probeer bijvoorbeeld de bron aws_iam_user toe te voegen met dezelfde naam die u gebruikte voor de IAM-gebruiker die u eerder in hoofdstuk 2 hebt gemaakt:

resource "aws_iam_user" "existing_user" {
   # Подставьте сюда имя уже существующего пользователя IAM,
   # чтобы попрактиковаться в использовании команды terraform import
   name = "yevgeniy.brikman"
}

Als u nu de opdracht plan uitvoert, zal Terraform een ​​ogenschijnlijk redelijk implementatieplan uitvoeren:

Terraform will perform the following actions:

   # aws_iam_user.existing_user will be created
   + resource "aws_iam_user" "existing_user" {
         + arn                  = (known after apply)
         + force_destroy   = false
         + id                    = (known after apply)
         + name               = "yevgeniy.brikman"
         + path                 = "/"
         + unique_id         = (known after apply)
      }

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

Als u de opdracht apply uitvoert, krijgt u de volgende foutmelding:

Error: Error creating IAM User yevgeniy.brikman: EntityAlreadyExists:
User with name yevgeniy.brikman already exists.

   on main.tf line 10, in resource "aws_iam_user" "existing_user":
   10: resource "aws_iam_user" "existing_user" {

Het probleem is natuurlijk dat er al een IAM-gebruiker met die naam bestaat. En dit kan niet alleen bij IAM-gebruikers gebeuren, maar bij vrijwel elke bron. Het is mogelijk dat iemand deze bron handmatig heeft gemaakt of via de opdrachtregel, maar hoe dan ook leidt het matchen van ID's tot conflicten. Er zijn veel variaties op deze fout die nieuwkomers bij Terraform vaak verrassen.

Het belangrijkste punt is dat de opdracht terraform plan alleen rekening houdt met de bronnen die zijn opgegeven in het Terraform-statusbestand. Als bronnen op een andere manier worden aangemaakt (bijvoorbeeld handmatig door in de AWS-console te klikken), komen ze niet in het statusbestand terecht en zal Terraform er dus geen rekening mee houden bij het uitvoeren van het plan-commando. Als gevolg hiervan zal een plan dat op het eerste gezicht correct lijkt, niet succesvol blijken te zijn.

Hieruit kunnen twee lessen worden getrokken.

  • Als u al met Terraform werkt, gebruik dan niets anders. Als een deel van uw infrastructuur wordt beheerd met Terraform, kunt u dit niet meer handmatig wijzigen. Anders riskeert u niet alleen rare Terraform-fouten, maar doet u ook veel van de voordelen van IaC teniet, aangezien de code niet langer een nauwkeurige weergave van uw infrastructuur zal zijn.
  • Als u al over enige infrastructuur beschikt, gebruikt u de importopdracht. Als u Terraform begint te gebruiken met een bestaande infrastructuur, kunt u het aan het statusbestand toevoegen met behulp van de opdracht terraform import. Zo weet Terraform welke infrastructuur beheerd moet worden. De importopdracht heeft twee argumenten nodig. De eerste is het bronadres in uw configuratiebestanden. De syntaxis is hier hetzelfde als voor bronkoppelingen: _. (zoals aws_iam_user.existing_user). Het tweede argument is de ID van de te importeren bron. Laten we zeggen dat de bron-ID aws_iam_user de gebruikersnaam is (bijvoorbeeld yevgeniy.brikman) en de bron-ID aws_instance de EC2-server-ID is (zoals i-190e22e5). Hoe u een bron importeert, wordt meestal aangegeven in de documentatie onderaan de pagina.

    Hieronder staat een importopdracht die de aws_iam_user-bron synchroniseert die u aan uw Terraform-configuratie hebt toegevoegd, samen met de IAM-gebruiker in hoofdstuk 2 (waarbij u uiteraard uw naam vervangt door yevgeniy.brikman):

    $ terraform import aws_iam_user.existing_user yevgeniy.brikman

    Terraform zal de AWS API aanroepen om uw IAM-gebruiker te vinden en een statusbestandskoppeling te maken tussen deze en de aws_iam_user.existing_user bron in uw Terraform-configuratie. Vanaf nu weet Terraform, wanneer u de opdracht plan uitvoert, dat de IAM-gebruiker al bestaat en zal hij niet proberen deze opnieuw aan te maken.

    Het is de moeite waard om op te merken dat als u al veel bronnen heeft die u in Terraform wilt importeren, het handmatig schrijven van de code en het importeren ervan allemaal een hele klus kan zijn. Het is dus de moeite waard om naar een tool als Terraforming (http://terraforming.dtan4.net/) te kijken, die automatisch code en status uit uw AWS-account kan importeren.

    Refactoring kan zijn valkuilen hebben

    Refactoring is een gebruikelijke praktijk bij het programmeren waarbij u de interne structuur van de code wijzigt terwijl u het externe gedrag ongewijzigd laat. Dit is om de code duidelijker, netter en gemakkelijker te onderhouden te maken. Refactoring is een onmisbare techniek die regelmatig moet worden toegepast. Maar als het om Terraform of een ander IaC-tool gaat, moet je uiterst voorzichtig zijn met wat je bedoelt met het ‘externe gedrag’ van een stukje code, anders ontstaan ​​er onverwachte problemen.

    Een veel voorkomende vorm van refactoring is bijvoorbeeld het vervangen van de namen van variabelen of functies door begrijpelijker namen. Veel IDE's hebben ingebouwde ondersteuning voor refactoring en kunnen gedurende het hele project automatisch variabelen en functies hernoemen. In algemene programmeertalen is dit een triviale procedure waar je misschien niet aan denkt, maar in Terraform moet je hier uiterst voorzichtig mee zijn, anders kun je last krijgen van storingen.

    De webserver-clustermodule heeft bijvoorbeeld een invoervariabele clusternaam:

    variable "cluster_name" {
       description = "The name to use for all the cluster resources"
       type          = string
    }

    Stel je voor dat je deze module bent gaan gebruiken om een ​​microservice met de naam foo te implementeren. Later wilt u uw service hernoemen naar bar. Deze wijziging lijkt misschien triviaal, maar kan in werkelijkheid verstoringen van de dienstverlening veroorzaken.

    Feit is dat de webserver-clustermodule de clusternaamvariabele gebruikt in een aantal bronnen, waaronder de naamparameter van twee beveiligingsgroepen en de ALB:

    resource "aws_lb" "example" {
       name                    = var.cluster_name
       load_balancer_type = "application"
       subnets = data.aws_subnet_ids.default.ids
       security_groups      = [aws_security_group.alb.id]
    }

    Als u de naamparameter van een bron wijzigt, zal Terraform de oude versie van die bron verwijderen en in plaats daarvan een nieuwe maken. Maar als die bron een ALB is, beschikt u tussen het verwijderen ervan en het downloaden van een nieuwe versie niet over een mechanisme om verkeer naar uw webserver om te leiden. Op dezelfde manier zullen uw servers, als een beveiligingsgroep wordt verwijderd, al het netwerkverkeer weigeren totdat er een nieuwe groep wordt gemaakt.

    Een ander type refactoring waarin u mogelijk geïnteresseerd bent, is het wijzigen van de Terraform-ID. Laten we de bron aws_security_group in de webserver-clustermodule als voorbeeld nemen:

    resource "aws_security_group" "instance" {
      # (...)
    }

    De identificatie van deze bron wordt instance genoemd. Stel je voor dat je tijdens het refactoren hebt besloten om het te veranderen in een begrijpelijker (naar jouw mening) naam cluster_instance:

    resource "aws_security_group" "cluster_instance" {
       # (...)
    }

    Wat zal er uiteindelijk gebeuren? Dat klopt: een verstoring.

    Terraform koppelt elke resource-ID aan de cloudprovider-ID. iam_user is bijvoorbeeld gekoppeld aan de AWS IAM-gebruikers-ID en aws_instance is gekoppeld aan de AWS EC2-server-ID. Als u de bron-ID wijzigt (bijvoorbeeld van instance in cluster_instance, zoals het geval is met aws_security_group), zal het in Terraform lijken alsof u de oude bron hebt verwijderd en een nieuwe hebt toegevoegd. Als u deze wijzigingen toepast, verwijdert Terraform de oude beveiligingsgroep en maakt een nieuwe aan, terwijl uw servers al het netwerkverkeer beginnen te weigeren.

    Hier zijn vier belangrijke lessen die u uit deze discussie kunt leren.

    • Gebruik altijd het plancommando. Het kan al deze haken en ogen onthullen. Controleer de uitvoer zorgvuldig en let op situaties waarin Terraform van plan is bronnen te verwijderen die hoogstwaarschijnlijk niet mogen worden verwijderd.
    • Maak aan voordat u verwijdert. Als u een hulpbron wilt vervangen, denk er dan goed over na of u een vervanging moet maken voordat u het origineel verwijdert. Als het antwoord ja is, kan create_before_destroy helpen. Hetzelfde resultaat kan handmatig worden bereikt door twee stappen uit te voeren: voeg eerst een nieuwe bron toe aan de configuratie en voer de opdracht apply uit, en verwijder vervolgens de oude bron uit de configuratie en gebruik de opdracht apply opnieuw.
    • Het wijzigen van ID's vereist een veranderende status. Als u de ID die aan een bron is gekoppeld wilt wijzigen (bijvoorbeeld de naam aws_security_group wilt wijzigen van instance naar cluster_instance) zonder de bron te verwijderen en er een nieuwe versie van te maken, moet u het Terraform-statusbestand dienovereenkomstig bijwerken. Doe dit nooit handmatig; gebruik in plaats daarvan de opdracht terraform state. Bij het hernoemen van ID's moet u de opdracht terraform state mv uitvoeren, die de volgende syntaxis heeft:
      terraform state mv <ORIGINAL_REFERENCE> <NEW_REFERENCE>

      ORIGINAL_REFERENCE is een expressie die verwijst naar de bron in zijn huidige vorm, en NEW_REFERENCE is waar u deze naartoe wilt verplaatsen. Als u bijvoorbeeld de naam aws_security_group wijzigt van instance naar cluster_instance, moet u de volgende opdracht uitvoeren:

      $ terraform state mv 
         aws_security_group.instance 
         aws_security_group.cluster_instance

      Dit vertelt Terraform dat de status die eerder was gekoppeld aan aws_security_group.instance nu moet worden gekoppeld aan aws_security_group.cluster_instance. Als terraform plan na het hernoemen en uitvoeren van dit commando geen wijzigingen vertoont, dan heb je alles correct gedaan.

    • Sommige instellingen kunnen niet worden gewijzigd. De parameters van veel bronnen zijn onveranderlijk. Als u ze probeert te wijzigen, zal Terraform de oude bron verwijderen en in plaats daarvan een nieuwe maken. Op elke bronpagina wordt doorgaans aangegeven wat er gebeurt als u een bepaalde instelling wijzigt, dus zorg ervoor dat u de documentatie raadpleegt. Gebruik altijd de opdracht plan en overweeg de strategie create_before_destroy te gebruiken.

    Uitgestelde consistentie is consistent... met uitstel

    De API's van sommige cloudproviders, zoals AWS, zijn asynchroon en hebben een vertraagde consistentie. Asynchronie betekent dat de interface onmiddellijk een antwoord kan retourneren zonder te wachten tot de gevraagde actie is voltooid. Vertraagde consistentie betekent dat het enige tijd kan duren voordat veranderingen zich door het hele systeem verspreiden; terwijl dit gebeurt, kunnen uw reacties inconsistent zijn en afhankelijk zijn van welke gegevensbronreplica reageert op uw API-aanroepen.

    Stel je voor dat je bijvoorbeeld een API-aanroep doet naar AWS met het verzoek een EC2-server te maken. De API retourneert vrijwel onmiddellijk een “succesvol” antwoord (201 aangemaakt), zonder te wachten tot de server zelf is aangemaakt. Als u er meteen verbinding mee probeert te maken, zal dit vrijwel zeker mislukken omdat AWS op dat moment nog steeds bronnen aan het initialiseren is of, als alternatief, de server nog niet is opgestart. Als u bovendien nog een keer belt om informatie over deze server te krijgen, kunt u een foutmelding krijgen (404 Not Found). Het punt is dat de informatie over deze EC2-server mogelijk nog steeds door AWS wordt verspreid voordat deze overal beschikbaar wordt, je zult een paar seconden moeten wachten.

    Telkens wanneer u een asynchrone API met luie consistentie gebruikt, moet u uw verzoek periodiek opnieuw proberen totdat de actie is voltooid en door het systeem wordt doorgegeven. Helaas biedt de AWS SDK hiervoor geen goede tools, en het Terraform-project had vroeger last van veel bugs zoals 6813 (https://github.com/hashicorp/terraform/issues/6813):

    $ terraform apply
    aws_subnet.private-persistence.2: InvalidSubnetID.NotFound:
    The subnet ID 'subnet-xxxxxxx' does not exist

    Met andere woorden, u maakt een bron aan (zoals een subnet) en probeert er vervolgens informatie over te verkrijgen (zoals de ID van het nieuw gemaakte subnet), maar Terraform kan deze niet vinden. De meeste van deze bugs (waaronder 6813) zijn opgelost, maar ze duiken nog steeds af en toe op, vooral wanneer Terraform ondersteuning toevoegt voor een nieuw resourcetype. Dit is vervelend, maar veroorzaakt in de meeste gevallen geen schade. Wanneer u terraform apply opnieuw uitvoert, zou alles moeten werken, aangezien de informatie zich tegen die tijd al door het systeem zal hebben verspreid.

    Dit fragment komt uit het boek van Evgeniy Brikman "Terraform: infrastructuur op codeniveau".

Bron: www.habr.com

Voeg een reactie