Herausforderungen bei Terraform.

Herausforderungen bei Terraform.
Wir werden einige Fallstricke beleuchten, einschließlich derjenigen, die mit Schleifen, if-Ausdrücken und Bereitstellungsmethoden sowie allgemeineren Problemen im Zusammenhang mit Terraform verbunden sind:

  • Die Parameter count und for_each haben Einschränkungen;
  • Einschränkungen bei Bereitstellungen mit null Ausfallzeit;
  • Selbst ein guter Plan kann scheitern;
  • Refactoring kann seine eigenen Tücken haben;
  • Verzögerte Konsistenz stimmt… mit Verzögerung überein.

Die Parameter count und for_each haben Einschränkungen

In den Beispielen dieses Kapitels werden die Parameter count und for_each aktiv in Schleifen und bedingter Logik verwendet. Sie funktionieren gut, haben jedoch zwei wichtige Einschränkungen, die bekannt sein sollten.

  • In count und for_each können keine Ausgabewerte von Ressourcen referenziert werden.
  • count und for_each können nicht in der Konfiguration eines Moduls verwendet werden.

In count und for_each können keine Ausgabewerte von Ressourcen referenziert werden.

Stellen Sie sich vor, Sie müssen mehrere EC2-Server bereitstellen, und aus irgendeinem Grund möchten Sie ASG nicht verwenden. Ihr Code könnte folgendermaßen aussehen:

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

Sehen wir uns diese nacheinander an.

Da der Parameter count einen statischen Wert hat, wird dieser Code problemlos funktionieren: Wenn Sie den Befehl apply ausführen, werden drei EC2-Server erstellt. Aber was, wenn Sie einen Server in jeder Availability Zone (AZ) innerhalb der aktuellen AWS-Region bereitstellen möchten? Sie können Ihren Code so gestalten, dass er die Liste der Zonen aus der Datenquelle aws_availability_zones lädt und dann „zyklisch“ durch jede von ihnen geht und darin einen EC2-Server erstellt, indem Sie den Parameter count und den Zugriff auf das Array über den Index verwenden:

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" {}

Dieser Code wird ebenfalls hervorragend funktionieren, da der Parameter count problemlos auf Datenquellen verweisen kann. Aber was passiert, wenn die Anzahl der Server, die Sie erstellen müssen, vom Output einer Ressource abhängt? Am einfachsten lässt sich dies mit der Ressource random_integer veranschaulichen, die, wie der Name schon sagt, eine zufällige ganze Zahl zurückgibt:

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

Dieser Code generiert eine Zufallszahl zwischen 1 und 3. Sehen wir uns an, was passiert, wenn wir den Output result dieser Ressource im Parameter count der Ressource aws_instance verwenden:

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

Wenn Sie für diesen Code terraform plan ausführen, erhalten Sie den folgenden Fehler:

Error: Ungültiges count-Argument

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

Der Wert "count" hängt von Ressourceneigenschaften ab, die bis zur Anwendung nicht bestimmt werden können, sodass Terraform nicht vorhersagen kann, wie viele Instanzen erstellt werden. Um dies zu umgehen, verwenden Sie das -target Argument, um zunächst nur die Ressourcen anzuwenden, von denen count abhängt.

Terraform erfordert, dass count und for_each in der Planungsphase berechnet werden, bevor Ressourcen erstellt oder geändert werden. Das bedeutet, dass count und for_each auf Literale, Variablen, Datenquellen und sogar Ressourcenlisten verweisen können (vorausgesetzt, ihre Länge kann während der Planung bestimmt werden), aber nicht auf berechnete Ausgabevariablen von Ressourcen.

count und for_each können nicht in der Modulkonfiguration verwendet werden.

Es kann irgendwann der Wunsch aufkommen, den Parameter count in die Modulkonfigurationen aufzunehmen:

Modul "count_example" {
     Quelle = "../../../../module/services/webserver-cluster"

     Anzahl = 3

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

Dieser Code versucht, die Anzahl innerhalb des Moduls zu verwenden, um drei Exemplare der Ressource webserver-cluster zu erstellen. Oder vielleicht möchten Sie die Modulverbindung abhängig von einer Booleschen Bedingung optional machen, indem Sie dem Parameter count den Wert 0 zuweisen. Ein solcher Code scheint durchaus sinnvoll, jedoch erhalten Sie bei der Ausführung von terraform plan diesen Fehler:

Fehler: Reservierter Argumentname im Modulblock

   in main.tf Zeile 13, im Modul "count_example":
   13: Anzahl = 3

Der Name "count" ist für die Verwendung in einer zukünftigen Version von Terraform reserviert.

Leider wird die Verwendung von count oder for_each in der Ressource Modul zum Zeitpunkt der Veröffentlichung von Terraform 0.12.6 nicht unterstützt. Laut den Veröffentlichungsnotizen zu Terraform 0.12 (http://bit.ly/3257bv4) plant HashiCorp, diese Funktion in Zukunft hinzuzufügen. Daher kann es je nach Zeitpunkt, an dem Sie dieses Buch lesen, bereits verfügbar sein. Um sicherzugehen, lesen Sie das Änderungsprotokoll von Terraform hier.

Einschränkungen für Bereitstellungen mit null Ausfallzeiten

Die Verwendung des Blocks create_before_destroy in Kombination mit ASG ist eine hervorragende Lösung, um Bereitstellungen mit null Ausfallzeiten zu organisieren, mit einer Ausnahme: die Auto-Scaling-Regeln werden dabei nicht unterstützt. Genauer gesagt setzt dies die Größe der ASG bei jeder Bereitstellung auf min_size zurück, was problematisch werden kann, wenn Sie Auto-Scaling-Regeln verwendet haben, um die Anzahl der aktiven Server zu erhöhen.

Zum Beispiel enthält das Modul webserver-cluster ein paar Ressourcen aws_autoscaling_schedule, die um 9 Uhr die Anzahl der Server im Cluster von zwei auf zehn erhöhen. Wenn die Bereitstellung beispielsweise um 11 Uhr erfolgt, startet die neue ASG-Gruppe nicht mit zehn, sondern nur mit zwei Servern und bleibt bis 9 Uhr am nächsten Tag in diesem Zustand.

Diese Einschränkung kann auf mehreren Wegen umgangen werden.

  • Ändern Sie den Parameter recurrence in aws_autoscaling_schedule von 0 9 * * * ("um 9 Uhr morgens starten") auf etwas wie 0-59 9-17 * * * ("jede Minute von 9 Uhr morgens bis 5 Uhr nachmittags starten"). Wenn bereits zehn Server im ASG vorhanden sind, ändert die erneute Ausführung dieser Autoskalierungsregel nichts, was wir wollen. Aber wenn die ASG-Gruppe ganz neu bereitgestellt ist, stellt diese Regel sicher, dass innerhalb einer Minute die Anzahl ihrer Server auf zehn steigt. Dies ist kein besonders eleganter Ansatz, und große Sprünge von zehn auf zwei Server und zurück können ebenfalls Probleme für die Benutzer verursachen.
  • Erstellen Sie ein benutzerdefiniertes Skript, das die AWS-API verwendet, um die Anzahl der aktiven Server im ASG zu bestimmen, rufen Sie es über eine externe Datenquelle auf (siehe Punkt „Externe Datenquelle“ auf S. 249) und weisen Sie dem Parameter desired_capacity der ASG-Gruppe den Wert zu, der von diesem Skript zurückgegeben wird. Somit wird jede neue Instanz des ASG immer mit der gleichen Kapazität gestartet, wie in unserem Terraform-Code, was die Wartung erschwert.

Natürlich sollte Terraform idealerweise Unterstützung für Zero-Downtime-Deployments integriert haben, aber stand Mai 2019 plante das HashiCorp-Team nicht, diese Funktionalität hinzuzufügen (Details — hier).

Ein korrekter Plan kann misslungen sein

Manchmal ergibt der Befehl plan einen durchaus plausiblen Deploy-Plan, jedoch gibt der Befehl apply einen Fehler zurück. Versuchen Sie zum Beispiel, eine Ressource aws_iam_user mit demselben Namen hinzuzufügen, den Sie zuvor in Kapitel 2 für den von Ihnen erstellten IAM-Benutzer verwendet haben:

resource "aws_iam_user" "existing_user" {
   # Geben Sie hier den Namen des bereits bestehenden IAM-Benutzers ein,
   # um die Verwendung des terraform import-Befehls zu üben
   name = "yevgeniy.brikman"
}

Jetzt, wenn Sie den Befehl plan ausführen, wird Terraform auf den ersten Blick einen durchaus vernünftigen Deploy-Plan ausgeben:

Terraform wird die folgenden Aktionen durchführen:

   # aws_iam_user.existing_user wird erstellt
   + resource "aws_iam_user" "existing_user" {
         + arn                  = (nach dem Anwenden bekannt)
         + force_destroy   = false
         + id                    = (nach dem Anwenden bekannt)
         + name               = "yevgeniy.brikman"
         + path                 = "/"
         + unique_id         = (nach dem Anwenden bekannt)
      }

Plan: 1 hinzuzufügen, 0 zu ändern, 0 zu zerstören.

Wenn Sie den Befehl apply ausführen, wird der folgende Fehler angezeigt:

Fehler: Fehler beim Erstellen des IAM-Benutzers yevgeniy.brikman: EntityAlreadyExists:
Benutzer mit dem Namen yevgeniy.brikman existiert bereits.

   in main.tf Zeile 10, in Ressource "aws_iam_user" "existing_user":
   10: Ressource "aws_iam_user" "existing_user" {

Das Problem ist natürlich, dass ein IAM-Benutzer mit diesem Namen bereits existiert. Und das kann nicht nur bei IAM-Benutzern passieren, sondern praktisch bei jedem Ressourcentyp. Möglicherweise hat jemand diese Ressource manuell erstellt oder über die Befehlszeile, aber wie auch immer, das Identitätsübereinstimmung führt zu Konflikten. Diese Fehlermeldung hat zahlreiche Varianten, die häufig Neulinge in Terraform überraschen.

Der entscheidende Punkt ist, dass der Befehl terraform plan nur die Ressourcen berücksichtigt, die in der Terraform-Zustandsdatei angegeben sind. Wenn Ressourcen auf andere Weise erstellt werden (zum Beispiel manuell durch Klicken auf die AWS-Konsole), gelangen sie nicht in die Zustandsdatei und Terraform berücksichtigt sie daher nicht beim Ausführen des Befehls plan. Infolgedessen wird ein auf den ersten Blick korrekter Plan fehlschlagen.

Daraus lassen sich zwei Lektionen ziehen.

  • Wenn Sie bereits mit Terraform arbeiten, sollten Sie nichts anderes verwenden. Wenn ein Teil Ihrer Infrastruktur mit Terraform verwaltet wird, sollten Sie diesen nicht mehr manuell ändern. Andernfalls riskieren Sie nicht nur merkwürdige Terraform-Fehler, sondern untergraben auch viele Vorteile von IaC, da der Code nicht mehr eine präzise Darstellung Ihrer Infrastruktur ist.
  • Wenn Sie bereits über eine Infrastruktur verfügen, verwenden Sie den Befehl import. Wenn Sie Terraform mit einer bestehenden Infrastruktur verwenden, kann diese mit dem Befehl terraform import in die Statusdatei hinzugefügt werden. So weiß Terraform, welche Infrastruktur verwaltet werden soll. Der Befehl import akzeptiert zwei Argumente. Das erste ist die Adresse des Ressourcen in Ihren Konfigurationsdateien. Hier gilt der gleiche Syntax wie bei Ressourcenverweisen: _. (wie aws_iam_user.existing_user). Das zweite Argument ist die ID der zu importierenden Ressource. Nehmen wir an, als ID der Ressource aws_iam_user dient der Benutzername (z. B. yevgeniy.brikman), während die ID der Ressource aws_instance die EC2-Server-ID (z. B. i-190e22e5) ist. Wie man eine Ressource importiert, wird normalerweise in der Dokumentation am Ende ihrer Seite angegeben.

    Unten ist der Befehl import dargestellt, der es ermöglicht, die Ressource aws_iam_user, die Sie zusammen mit dem IAM-Benutzer in Ihrer Terraform-Konfiguration aus Kapitel 2 hinzugefügt haben, zu synchronisieren (ersetzen Sie natürlich yevgeniy.brikman durch Ihren Benutzernamen):

    $ terraform import aws_iam_user.existing_user yevgeniy.brikman

    Terraform wird die AWS-API aufrufen, um Ihren IAM-Benutzer zu finden und eine Verbindung zwischen ihm und der Ressource aws_iam_user.existing_user in Ihrer Terraform-Konfiguration im Statusfile zu erstellen. Ab diesem Zeitpunkt wird Terraform beim Ausführen des Befehls plan wissen, dass der IAM-Benutzer bereits existiert und nicht versuchen, ihn erneut zu erstellen.

    Es ist wichtig zu beachten, dass das manuelle Schreiben von Code und das Importieren jeder Ressource nacheinander mühsam sein kann, wenn Sie bereits viele Ressourcen haben, die Sie in Terraform importieren möchten. Daher sollten Sie ein Tool wie Terraforming (http://terraforming.dtan4.net/) in Betracht ziehen, das automatisch Code und Zustand aus Ihrem AWS-Konto importieren kann.

    Refactoring kann seine Tücken haben.

    Refactoring — eine gängige Praxis in der Programmierung, bei der die interne Struktur des Codes geändert wird, während das äußere Verhalten unverändert bleibt. Dies ist notwendig, um den Code verständlicher, ordentlicher und einfacher wartbar zu machen. Refactoring ist eine unverzichtbare Technik, die regelmäßig angewendet werden sollte. Aber wenn es um Terraform oder jedes andere IaC-Werkzeug geht, ist Vorsicht geboten, was genau mit "äußeres Verhalten" eines Codeabschnitts gemeint ist, da sonst unvorhergesehene Probleme auftreten können.

    Ein typisches Beispiel für Refactoring ist die Ersetzung von Variablen- oder Funktionsnamen durch verständlichere. Viele IDEs haben integrierte Unterstützung für Refactoring und können Variablen und Funktionen im gesamten Projekt automatisch umbenennen. In allgemeinen Programmiersprachen ist dies eine triviale Prozedur, über die man sich keine Gedanken machen muss. Bei Terraform ist jedoch äußerste Vorsicht geboten, da es sonst zu Betriebsunterbrechungen kommen kann.

    Zum Beispiel hat das Modul webserver-cluster eine Eingangsvariable cluster_name:

    variable "cluster_name" {
       description = "Der Name, der für alle Cluster-Ressourcen verwendet werden soll"
       type          = string
    }

    Stellen Sie sich vor, Sie haben dieses Modul verwendet, um einen Microservice mit dem Namen foo bereitzustellen. Später möchten Sie Ihren Service in bar umbenennen. Diese Änderung mag trivial erscheinen, kann jedoch in der Realität zu Unterbrechungen führen.

    Das Problem ist, dass das Modul webserver-cluster die Variable cluster_name in einer Vielzahl von Ressourcen verwendet, einschließlich des Parameters name von zwei Sicherheitsgruppen und 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]
    }

    Wenn Sie den Parameter name in einer Ressource ändern, entfernt Terraform die alte Version dieser Ressource und erstellt eine neue. Wenn jedoch diese Ressource ein ALB ist, haben Sie während des Zeitraums zwischen der Löschung und der Bereitstellung der neuen Version keinen Mechanismus, um den Datenverkehr zu Ihrem Webserver umzuleiten. Ebenso wird, wenn eine Sicherheitsgruppe gelöscht wird, Ihr Server jeglichen Netzwerkverkehr ablehnen, bis eine neue Gruppe erstellt wird.

    Eine weitere Art des Refactorings, die Sie interessieren könnte, ist die Änderung der Terraform-ID. Nehmen wir als Beispiel die Ressource aws_security_group im Modul webserver-cluster:

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

    Die ID dieser Ressource nennt sich instance. Stellen Sie sich vor, dass Sie während des Refactorings beschlossen haben, sie auf einen verständlicheren Namen, Ihrer Meinung nach cluster_instance, zu ändern:

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

    Was wird letztendlich geschehen? Richtig: ein Ausfall.

    Terraform verknüpft die ID jeder Ressource mit der ID des Cloud-Anbieters. Zum Beispiel wird iam_user mit der IAM-Benutzer-ID in AWS verknüpft, während aws_instance mit der ID eines AWS EC2-Servers verbunden ist. Wenn Sie die ID der Ressource ändern (sagen wir, von instance auf cluster_instance, wie im Fall von aws_security_group), wird Terraform dies als Löschung der alten Ressource und Hinzufügung einer neuen interpretieren. Wenn Sie diese Änderungen anwenden, wird Terraform die alte Sicherheitsgruppe löschen und eine neue erstellen, während Ihre Server jeglichen Netzwerkverkehr ablehnen.

    Hier sind vier wichtige Lektionen, die Sie aus dieser Diskussion ziehen sollten.

    • Verwenden Sie immer den Befehl plan. Damit können Sie all diese Probleme identifizieren. Überprüfen Sie die Ausgabe sorgfältig und achten Sie auf Situationen, in denen Terraform plant, Ressourcen zu löschen, die wahrscheinlich nicht entfernt werden sollten.
    • Erstellen Sie, bevor Sie löschen. Wenn Sie eine Ressource ersetzen möchten, überlegen Sie gut, ob es notwendig ist, eine Ersatzressource vor dem Löschen des Originals zu erstellen. Wenn die Antwort ja ist, kann create_before_destroy dabei helfen. Das gleiche Ergebnis kann auch manuell erzielt werden, indem Sie zuerst die neue Ressource zur Konfiguration hinzufügen und den Befehl apply ausführen, und dann die alte Ressource aus der Konfiguration entfernen und den Befehl apply erneut ausführen.
    • Änderungen an Identifikatoren erfordern eine Aktualisierung des Zustands. Wenn Sie einen mit der Ressource verbundenen Identifikator ändern möchten (zum Beispiel aws_security_group von instance auf cluster_instance umbenennen), ohne die Ressource zu löschen und eine neue Version zu erstellen, muss die Terraform-Zustandsdatei entsprechend aktualisiert werden. Machen Sie dies niemals manuell — verwenden Sie stattdessen den Befehl terraform state. Beim Umbenennen von Identifikatoren sollten Sie den Befehl terraform state mv verwenden, der folgende Syntax hat:
      terraform state mv

      ORIGINAL_REFERENCE ist der Ausdruck, der auf die Ressource in ihrem aktuellen Zustand verweist, und NEW_REFERENCE ist der Ort, an den Sie sie verschieben möchten. Zum Beispiel, wenn Sie die Gruppe aws_security_group von instance auf cluster_instance umbenennen, müssen Sie den folgenden Befehl ausführen:

      $ terraform state mv 
         aws_security_group.instance 
         aws_security_group.cluster_instance

      Damit teilen Sie Terraform mit, dass der Zustand, der zuvor zu aws_security_group.instance gehörte, nun mit aws_security_group.cluster_instance verbunden sein sollte. Wenn nach dem Umbenennen und Ausführen dieses Befehls terraform plan keine Änderungen anzeigt, haben Sie alles richtig gemacht.

    • Einige Parameter können nicht geändert werden. Die Parameter vieler Ressourcen sind unveränderlich. Wenn Sie versuchen, sie zu ändern, entfernt Terraform die alte Ressource und erstellt stattdessen eine neue. Auf der Seite jeder Ressource wird normalerweise angegeben, was passiert, wenn bestimmte Parameter geändert werden, also vergessen Sie nicht, die Dokumentation zu konsultieren. Verwenden Sie immer den Befehl plan und erwägen Sie die Angemessenheit der Strategie create_before_destroy.

    Verzögerte Konsistenz wird abgestimmt… mit Verzögerung

    Die APIs bestimmter Cloud-Anbieter wie AWS sind asynchron und weisen verzögerte Konsistenz auf. Asynchronität bedeutet, dass die Schnittstelle sofort eine Antwort zurückgeben kann, ohne auf den Abschluss der angeforderten Aktion zu warten. Verzögerte Konsistenz bedeutet, dass es Zeit in Anspruch nehmen kann, bis die Änderungen im gesamten System verteilt sind; währenddessen können Ihre Antworten inkonsistent sein und von der Replik des Datenquellenservers abhängen, die auf Ihre API-Anfragen reagiert.

    Stellen Sie sich vor, Sie führen einen API-Aufruf an AWS durch, um einen EC2-Server zu erstellen. Die API gibt nahezu sofort eine "erfolgreiche" Antwort (201 Created) zurück, ohne auf die tatsächliche Erstellung des Servers zu warten. Wenn Sie sofort versuchen, sich mit diesem Server zu verbinden, wird das höchstwahrscheinlich nicht funktionieren, da AWS in diesem Moment noch die Ressourcen initialisiert oder der Server möglicherweise noch nicht gestartet ist. Darüber hinaus kann es zu einem Fehler (404 Not Found) kommen, wenn Sie einen weiteren Aufruf tätigen, um Informationen über diesen Server abzurufen. Die Informationen über diesen EC2-Server könnten sich noch innerhalb von AWS verbreiten, sodass es einige Sekunden dauern kann, bis sie überall verfügbar sind.

    Jedes Mal, wenn Sie eine asynchrone API mit verzögerter Konsistenz verwenden, müssen Sie Ihre Anfrage regelmäßig wiederholen, bis die Aktion abgeschlossen ist und sich im System verbreitet hat. Leider bietet das AWS SDK hierfür keine guten Werkzeuge, und das Terraform-Projekt hatte früher mit einer Vielzahl von Fehlern wie 6813 (https://github.com/hashicorp/terraform/issues/6813) zu kämpfen:

    $ terraform apply
    aws_subnet.private-persistence.2: InvalidSubnetID.NotFound:
    Die Subnetz-ID 'subnet-xxxxxxx' existiert nicht

    Mit anderen Worten, Sie erstellen eine Ressource (zum Beispiel ein Subnetz) und versuchen dann, Informationen darüber abzurufen (wie die ID des gerade erstellten Subnetzes), doch Terraform kann diese nicht finden. Die meisten dieser Fehler (einschließlich 6813) wurden bereits behoben, treten jedoch gelegentlich immer noch auf, insbesondere wenn Terraform Unterstützung für einen neuen Ressourcentyp hinzufügt. Das ist frustrierend, verursacht jedoch in den meisten Fällen keinen Schaden. Bei wiederholter Ausführung von terraform apply sollte alles funktionieren, da die Informationen bis dahin im System verbreitet sind.

    Dieser Abschnitt stammt aus dem Buch von Evgeny Brikman „Terraform: Infrastruktur als Code“.

Quelle: habr.com

Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen 🔥 Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen | ProHoster