Lassen Sie uns einige Fallstricke hervorheben, einschließlich derer, die mit Schleifen, if-Ausdrücken und Bereitstellungsmethoden sowie mit allgemeineren Problemen zusammenhängen, die Terraform insgesamt betreffen:
- Die Parameter count und for_each haben Einschränkungen;
- Einschränkungen bei der Bereitstellung mit null Ausfallzeit;
- Selbst ein guter Plan kann misslingen;
- Refactoring kann seine eigenen Tücken haben;
- Verzögerte Konsistenz stimmt... mit Aufschub überein.
Die Parameter count und for_each haben Einschränkungen
In den Beispielen dieses Kapitels werden der Parameter count und der Ausdruck for_each aktiv in Schleifen und bedingter Logik verwendet. Sie funktionieren gut, aber es gibt zwei wichtige Einschränkungen, die man beachten sollte.
- In count und for_each dürfen keine Ausgabevariablen von Ressourcen referenziert werden.
- count und for_each dürfen nicht in der Konfigurationsdatei eines Moduls verwendet werden.
In count und for_each dürfen keine Ausgabevariablen von Ressourcen referenziert werden.
Stellen Sie sich vor, Sie müssen mehrere EC2-Server bereitstellen und aus irgendwelchen Gründen möchten Sie kein ASG verwenden. Ihr Code könnte so aussehen:
resource "aws_instance" "example_1" {
count = 3
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
Gesichtserkennung ist eine Technologie, die zur Identifizierung von Personen ohne deren Wissen oder Zustimmung eingesetzt werden kann. Sie beruht auf der Verbreitung von Überwachungskameras, die leistungsfähiger und kompakter werden, sowie auf Technologien des maschinellen Lernens, die in der Lage sind, aufgezeichnetes Material mit Bildern aus einer Datenbank vorhandener Fotos abzugleichen.
Da der Parameter count einen statischen Wert zugewiesen bekommen hat, wird dieser Code problemlos funktionieren: Wenn Sie den Befehl apply ausführen, werden drei EC2-Server erstellt. Aber was passiert, wenn Sie in jeder Verfügbarkeitszone (Availability Zone oder AZ) in der aktuellen AWS-Region je einen Server 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" jede von ihnen durchläuft und in jeder einen EC2-Server erstellt, indem er den Parameter count und den Zugriff auf das Array über den Index verwendet:
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 gut funktionieren, da der Parameter count problemlos auf Datenquellen verweisen kann. Aber was passiert, wenn die Anzahl der Server, die Sie erstellen müssen, von der Ausgabe einer bestimmten Ressource abhängt? Um dies zu demonstrieren, ist es am einfachsten, die Ressource random_integer zu verwenden, die, wie der Name schon sagt, eine Zufallszahl zurückgibt:
resource "random_integer" "num_instances" {
min = 1
max = 3
}Dieser Code generiert eine zufällige Zahl von 1 bis 3. Lassen Sie uns sehen, was passiert, wenn wir versuchen, das Ergebnis result dieser Ressource im Parameter count der Ressource aws_instance zu verwenden:
resource "aws_instance" "example_3" {
count = random_integer.num_instances.result
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}Wenn Sie diesen Code mit 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 "count"-Wert hängt von Ressourceneigenschaften ab, die nicht bestimmt werden können, bis angewendet wird. Daher kann Terraform nicht vorhersagen, wie viele Instanzen erstellt werden. Um dies zu umgehen, verwenden Sie das Argument -target, 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 Listen von Ressourcen verweisen können (vorausgesetzt, ihre Länge kann während der Planung bestimmt werden), jedoch nicht auf berechnete Ausgabewerte von Ressourcen.
count und for_each können nicht in der Modulkonfiguration verwendet werden.
Irgendwann könnten Sie versucht sein, einen count-Parameter in Modulkonfigurationen hinzuzufügen:
module "count_example" {
source = "..\/..\/..\/..\/modules\/services\/webserver-cluster"
count = 3
cluster_name = "terraform-up-and-running-example"
server_port = 8080
instance_type = "t2.micro"
}Dieser Code versucht, count innerhalb eines Moduls zu verwenden, um drei Kopien 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 sieht sinnvoll aus, aber als Ergebnis der Ausführung von terraform plan erhalten Sie den folgenden Fehler:
Error: Reservierter Argumentname im Modulblock
auf main.tf Zeile 13, in module "count_example":
13: count = 3
Der Name "count" ist für die Verwendung in einer zukünftigen Version von Terraform reserviert.Leider wird beim Release von Terraform 0.12.6 die Verwendung von count oder for_each in der Ressource module nicht unterstützt. Laut den Veröffentlichungsnotizen von Terraform 0.12 (http://bit.ly/3257bv4) plant HashiCorp, diese Funktion in Zukunft hinzuzufügen. Daher kann es sein, dass sie bereits verfügbar ist, je nachdem, wann Sie dieses Buch lesen. Um sicher zu sein, .
Einschränkungen bei der Bereitstellung mit null Ausfallzeiten
Die Verwendung des Blocks create_before_destroy in Kombination mit ASG ist eine ausgezeichnete Lösung für die Organisation von Bereitstellungen mit null Ausfallzeiten, abgesehen von einem kleinen Nachteil: Die Skalierungsregeln werden dabei nicht unterstützt. Oder, um genauer zu sein, es setzt die Größe der ASG bei jeder Bereitstellung auf min_size zurück, was problematisch sein kann, wenn Sie Skalierungsregeln verwendet haben, um die Anzahl der laufenden 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, wird die neue ASG-Gruppe nicht mit zehn, sondern nur mit zwei Servern geladen und bleibt bis 9 Uhr des folgenden Tages in diesem Zustand.
Diese Einschränkung kann auf verschiedene Weise umgangen werden.
- Ändern Sie die Recurrence-Parameter im aws_autoscaling_schedule von 0 9 * * * ("um 9 Uhr ausführen") auf etwas wie 0-59 9-17 * * * ("jede Minute von 9 Uhr bis 17 Uhr ausführen"). Wenn bereits zehn Server in der ASG vorhanden sind, wird die erneute Ausführung dieser Skalierungsregel nichts ändern, was wir wollen. Aber wenn die ASG-Gruppe ganz neu bereitgestellt wurde, stellt diese Regel sicher, dass die Anzahl ihrer Server spätestens in einer Minute zehn erreicht. Das ist nicht unbedingt ein eleganter Ansatz, und große Sprünge von zehn auf zwei Server und zurück können ebenfalls Probleme für die Nutzer verursachen.
- Erstellen Sie ein benutzerdefiniertes Skript, das die AWS-API verwendet, um die Anzahl der aktiven Server in der ASG zu bestimmen, und rufen Sie es über eine externe Datenquelle auf (siehe "Externe Datenquelle" auf S. 249) und weisen Sie dem Parameter desired_capacity der ASG-Gruppe den Wert zu, den das Skript zurückgibt. So wird jeder neue ASG-Instanz immer mit der gleichen Kapazität gestartet, was die Wartung Ihres Terraform-Codes umständlicher macht.
Natürlich sollte Terraform idealerweise eine eingebaute Unterstützung für Bereitstellungen mit null Ausfallzeiten haben, aber Stand Mai 2019 hatte das HashiCorp-Team nicht vor, diese Funktionalität hinzuzufügen ().
Ein korrekter Plan kann möglicherweise nicht erfolgreich umgesetzt werden.
Manchmal ergibt die Ausführung des Befehls plan einen durchaus korrekten Bereitstellungsplan, jedoch gibt der Befehl apply einen Fehler zurück. Versuchen Sie zum Beispiel, die Ressource aws_iam_user mit dem gleichen Namen hinzuzufügen, den Sie zuvor für den von Ihnen in Kapitel 2 erstellten IAM-Benutzer verwendet haben:
resource "aws_iam_user" "existing_user" {
# Setzen Sie hier den Namen eines bereits bestehenden IAM-Benutzers ein,
# um die Verwendung des Befehls terraform import zu üben
name = "yevgeniy.brikman"
}Jetzt, wenn Sie den Befehl plan ausführen, wird Terraform auf den ersten Blick einen durchaus vernünftigen Bereitstellungsplan ausgeben:
Terraform wird die folgenden Aktionen durchführen:
# aws_iam_user.existing_user wird erstellt
+ resource "aws_iam_user" "existing_user" {
+ arn = (nach dem apply bekannt)
+ force_destroy = false
+ id = (nach dem apply bekannt)
+ name = "yevgeniy.brikman"
+ path = "/"
+ unique_id = (nach dem apply bekannt)
}
Plan: 1 hinzuzufügen, 0 zu ändern, 0 zu zerstören.Wenn Sie den Befehl apply ausführen, erhalten Sie den folgenden Fehler:
Error: Fehler beim Erstellen des IAM-Benutzers yevgeniy.brikman: EntityAlreadyExists:
Benutzer mit dem Namen yevgeniy.brikman existiert bereits.
auf main.tf Zeile 10, in resource "aws_iam_user" "existing_user":
10: resource "aws_iam_user" "existing_user" {Das Problem ist natürlich, dass ein IAM-Benutzer mit diesem Namen bereits existiert. Dies kann nicht nur bei IAM-Benutzern der Fall sein, sondern auch fast bei jedem anderen Resource. Möglicherweise hat jemand diese Ressource manuell oder über die Befehlszeile erstellt, aber unabhängig davon führt eine Übereinstimmung der Identifikatoren zu Konflikten. Diese Fehlermeldung hat viele Varianten, die oft Anfänger 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 einen Mausklick in der AWS-Konsole), finden sie nicht ihren Weg in die Zustandsdatei und daher berücksichtigt Terraform sie nicht bei der Ausführung des Befehls plan. Infolgedessen wird ein zunächst plausibler Plan schließlich fehlschlagen.
Daraus lassen sich zwei Lehren ziehen.
- Wenn Sie bereits mit Terraform begonnen haben, verwenden Sie nichts anderes. Wenn ein Teil Ihrer Infrastruktur über Terraform verwaltet wird, darf er nicht mehr manuell geändert werden. Andernfalls riskieren Sie, merkwürdige Terraform-Fehler zu bekommen, und schmälern auch viele Vorteile von IaC, da der Code nicht mehr genau die Darstellung Ihrer Infrastruktur ist.
- Wenn Sie bereits über eine Infrastruktur verfügen, verwenden Sie den Befehl import. Wenn Sie beginnen, Terraform mit einer bestehenden Infrastruktur zu verwenden, kann diese mit dem Befehl terraform import in die Zustandsdatei aufgenommen werden. So weiß Terraform, welche Infrastruktur verwaltet werden muss. Der Befehl import akzeptiert zwei Argumente. Das erste ist die Adresse der Ressource in Ihren Konfigurationsdateien. Hier gilt derselbe Syntax wie in Ressourcenverweisen: _. (zum Beispiel aws_iam_user.existing_user). Das zweite Argument ist die ID der Ressource, die importiert werden soll. Zum Beispiel ist der ID der Ressource aws_iam_user der Benutzername (zum Beispiel yevgeniy.brikman), und die ID der Ressource aws_instance ist die ID des EC2-Servers (zum Beispiel i-190e22e5). Wie man eine Ressource importiert, wird normalerweise in der Dokumentation am Ende ihrer Seite angegeben.
Im Folgenden sehen Sie den Importbefehl, mit dem die Ressource aws_iam_user synchronisiert wird, die Sie zusammen mit dem IAM-Benutzer im Kapitel 2 zu Ihrer Terraform-Konfiguration hinzugefügt haben (natürlich sollten Sie anstelle von yevgeniy.brikman Ihren eigenen Namen einsetzen):
$ terraform import aws_iam_user.existing_user yevgeniy.brikmanTerraform wird die AWS-API anrufen, um Ihren IAM-Benutzer zu finden und eine Verbindung zwischen ihm und der Ressource aws_iam_user.existing_user in Ihrer Terraform-Konfiguration in der Zustandsdatei herzustellen. Ab diesem Moment weiß Terraform bei der Ausführung des Befehls plan, dass der IAM-Benutzer bereits existiert, und wird nicht versuchen, ihn erneut zu erstellen.
Es ist zu beachten, dass, wenn Sie bereits viele Ressourcen haben, die Sie in Terraform importieren möchten, das manuelle Schreiben des Codes und das nacheinander Importieren jeder von ihnen mühsam sein kann. Daher sollten Sie auf ein Werkzeug wie Terraforming (http://terraforming.dtan4.net/) achten, das automatisch Code und Zustand aus Ihrem AWS-Konto importieren kann.
Refactoring kann seine Tücken haben
Refactoring ist eine gängige Praxis in der Programmierung, bei der Sie die interne Struktur des Codes ändern, während das äußere Verhalten unverändert bleibt. Dies ist erforderlich, um den Code verständlicher, ordentlicher und einfacher zu warten. Refactoring ist eine unerlässliche Methode, die regelmäßig angewendet werden sollte. Aber wenn es um Terraform oder ein anderes IaC-Tool geht, sollte man äußerst vorsichtig sein, was unter "äußeren Verhalten" eines Codeabschnitts zu verstehen ist, da sonst unvorhergesehene Probleme auftreten können.
Ein verbreitetes Beispiel für Refactoring ist die Ersetzung von Variablen- oder Funktionsnamen durch verständlichere. Viele IDEs bieten integrierte Unterstützung für Refactoring und können Variablen und Funktionen im gesamten Projekt automatisch umbenennen. In Programmiersprachen für allgemeine Zwecke ist dies ein triviales Verfahren, über das man nicht nachdenken muss, aber bei Terraform sollte man äußerst vorsichtig sein, sonst kann es zu Störungen kommen.
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 Dienst in bar umbenennen. Diese Änderung mag trivial erscheinen, kann aber tatsächlich Störungen verursachen.
Der Punkt ist, dass das Modul webserver-cluster die Variable cluster_name in einer Vielzahl von Ressourcen verwendet, einschließlich des Parameters name in zwei Sicherheitsgruppen und in 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 stattdessen eine neue. Wenn jedoch diese Ressource ALB ist, haben Sie in der Zeit zwischen ihrer Löschung und dem Laden der neuen Version keinen Mechanismus, um den Verkehr an Ihren Webserver umzuleiten. Genauso, wenn eine Sicherheitsgruppe gelöscht wird, beginnen Ihre Server, jeglichen Netzwerkverkehr abzulehnen, bis eine neue Gruppe erstellt wurde.
Eine weitere Art von Refactoring, 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 heißt instance. Stellen Sie sich vor, während des Refactorings entscheiden Sie sich, sie in einen verständlicheren Namen cluster_instance zu ändern:
resource "aws_security_group" "cluster_instance" { # (...) }Was wird letztendlich passieren? Richtig: Störungen.
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 des AWS EC2-Servers verbunden ist. Wenn Sie die ID einer Ressource ändern (sagen wir, von instance zu cluster_instance, wie im Fall von aws_security_group), wird Terraform dies so interpretieren, als hätten Sie die alte Ressource gelöscht und eine neue hinzugefügt. Wenn Sie diese Änderungen anwenden, entfernt Terraform die alte Sicherheitsgruppe und erstellt eine neue, während Ihre Server begonnen haben, jeglichen Netzwerkverkehr abzulehnen.
Hier sind vier grundlegende Lektionen, die Sie aus dieser Diskussion ziehen sollten.
- Verwenden Sie immer das Kommando plan. Damit können Sie all diese Probleme aufdecken. Überprüfen Sie sorgfältig die Ausgabe und achten Sie auf Situationen, in denen Terraform plant, Ressourcen zu löschen, die wahrscheinlich nicht gelöscht werden sollten.
- Erstellen Sie, bevor Sie löschen. Wenn Sie eine Ressource ersetzen möchten, denken Sie gründlich nach, ob die Ersetzung erstellt werden sollte, bevor das Original gelöscht wird. Wenn die Antwort ja ist, kann Ihnen create_before_destroy dabei helfen. Das gleiche Ergebnis können Sie manuell erreichen, indem Sie zwei Schritte ausführen: Zuerst fügen Sie die neue Ressource zur Konfiguration hinzu und führen den Befehl apply aus, dann entfernen Sie die alte Ressource aus der Konfiguration und verwenden den Befehl apply erneut.
- Die Änderung von Identifikatoren erfordert eine Änderung des Status. Wenn Sie die ID ändern möchten, die mit einer Ressource verknüpft ist (zum Beispiel, um aws_security_group von instance in cluster_instance umzubenennen), ohne die Ressource zu löschen und eine neue Version zu erstellen, müssen Sie die Terraform-Zustandsdatei entsprechend aktualisieren. Machen Sie dies niemals manuell — verwenden Sie stattdessen den Befehl terraform state. Beim Umbenennen von Identifikatoren führen Sie den Befehl terraform state mv aus, der folgenden Syntax hat:
terraform state mvORIGINAL_REFERENCE ist ein Ausdruck, der auf die Ressource in ihrem aktuellen Zustand verweist, und NEW_REFERENCE ist der Ort, an den Sie sie verschieben möchten. Beispielsweise müssen Sie beim Umbenennen der Gruppe aws_security_group von instance auf cluster_instance den folgenden Befehl ausführen:
$ terraform state mv aws_security_group.instance aws_security_group.cluster_instanceSo teilen Sie Terraform mit, dass der Zustand, der zuvor mit aws_security_group.instance verbunden war, jetzt mit aws_security_group.cluster_instance verknüpft sein soll. Wenn nach der Umbenennung und dem 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, wird Terraform die alte Ressource löschen und eine neue an ihrer Stelle erstellen. Auf der Seite jeder Ressource wird normalerweise angegeben, was passiert, wenn dieser oder jener Parameter geändert wird, daher sollten Sie die Dokumentation immer konsultieren. Verwenden Sie immer den Befehl plan und prüfen Sie die Umsetzbarkeit der Strategie create_before_destroy.
Die verzögerte Konsistenz stimmt… mit dem Aufschub überein
Die APIs einiger Cloud-Anbieter, wie AWS, sind asynchron und weisen eine 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 Änderungen im gesamten System verbreitet sind; während dieser Zeit können Ihre Antworten inkonsistent sein und davon abhängen, welche Datenquelle auf Ihre API-Anfragen reagiert.
Stellen Sie sich vor, Sie machen einen API-Anruf an AWS mit der Bitte, einen EC2-Server zu erstellen. Die API gibt fast sofort eine "erfolgreiche" Antwort (201 Created) zurück, ohne auf die tatsächliche Erstellung des Servers zu warten. Wenn Sie versuchen, sich sofort mit ihm zu verbinden, wird es sehr wahrscheinlich nicht funktionieren, da AWS in diesem Moment immer noch die Ressourcen initialisiert oder der Server möglicherweise noch nicht hochgefahren ist. Darüber hinaus kann es sein, dass, wenn Sie einen weiteren Anruf zur Abfrage von Informationen über diesen Server tätigen, ein Fehler (404 Not Found) zurückkommt. Der Grund dafür ist, dass die Informationen über diesen EC2-Server möglicherweise noch durch AWS verbreitet werden müssen, und es kann einige Sekunden dauern, bis sie überall verfügbar sind.
Bei jedem Einsatz einer asynchronen API mit verzögerter Konsistenz müssen Sie Ihre Anfrage in regelmäßigen Abständen 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 vielen 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 nichtMit anderen Worten, Sie erstellen eine Ressource (zum Beispiel ein Subnetz) und versuchen dann, Informationen darüber (wie die ID des gerade erstellten Subnetzes) zu erhalten, aber Terraform kann diese nicht finden. Die meisten dieser Fehler (einschließlich 6813) wurden bereits behoben, aber gelegentlich treten sie immer noch auf, insbesondere wenn Terraform Unterstützung für einen neuen Ressourcentyp hinzufügt. Das ist ärgerlich, verursacht jedoch in den meisten Fällen keinen Schaden. Bei der erneuten Ausführung von terraform apply sollte alles funktionieren, da bis dahin die Informationen bereits im System verbreitet sind.
Dieser Absatz stammt aus dem Buch von Jewgeni Brikman .
Quelle: habr.com
