Methoden zum Testen von Terraform


Bicycle

Infrastructure as Code (IaC) hat die Art und Weise, wie wir Cloud-Ressourcen verwalten, revolutioniert, und Terraform hat sich zu einem führenden Tool in diesem Bereich entwickelt. Um die Zuverlässigkeit und Stabilität von Terraform-Konfigurationen zu gewährleisten, ist das Testen entscheidend. In diesem Blog werden wir verschiedene Ansätze für das Testen in Terraform untersuchen und Ihnen dabei helfen, Best Practices für ein sicheres Infrastrukturmanagement anzuwenden.

Warum Terraform Code testen?

Das Testen von Terraform Code ist wichtig für:

  • Vermeidung von Fehlkonfigurationen: Abfangen von Fehlern, bevor sie die Produktion beeinträchtigen.
  • Konsistenz sicherstellen: Sicherstellen, dass sich die Infrastruktur in verschiedenen Umgebungen wie erwartet verhält.
  • Erleichterung der Zusammenarbeit: Verbesserung der Codequalität in Teams durch automatisierte Tests.
  • Compliance und Sicherheit: Sicherstellen, dass die Infrastruktur die Compliance-Standards erfüllt.

Arten von Tests in Terraform

Terraform bietet verschiedene Methoden zum Testen der Infrastruktur, von Unit-Tests bis hin zu Acceptance-Tests. Sehen wir uns jeden Ansatz an.

1. Unit-Tests für Terraform-Plugins

Unit-Tests sind eine grundlegende Praxis in der Softwareentwicklung und das gilt auch für Terraform-Plugins. Unit-Tests werden verwendet, um die Korrektheit von kleinen, isolierten Codeeinheiten zu überprüfen, wie z.B. Hilfsfunktionen oder Methoden, die API-Antworten in Terraform-freundliche Datenstrukturen umwandeln. Diese Tests erfordern in der Regel keine Netzwerkverbindungen und konzentrieren sich auf die Überprüfung einzelner Teile der Logik in Isolation.

Beispiel: Verflachung von AWS-Sicherheitsgruppenregeln

Ein häufiger Anwendungsfall in Terraform-Plugins ist die Verflachung komplexer Datenstrukturen, die von APIs zurückgegeben werden, in einfachere Formate, die im Terraform-Status gespeichert werden können. AWS-Sicherheitsgruppenregeln werden beispielsweise oft in einer verschachtelten Struktur geliefert, und eine Flattener-Funktion wird benötigt, um diese Daten in ein Format umzuwandeln, das Terraform verwalten kann. Im Folgenden ist ein Beispiel für einen Unit-Test zu sehen, der eine Flattening-Funktion für AWS-Sicherheitsgruppenregeln verifiziert:

 1func TestFlattenSecurityGroups(t *testing.T) {
 2    cases := []struct {
 3        ownerId  *string
 4        pairs    []*ec2.UserIdGroupPair
 5        expected []*GroupIdentifier
 6    }{
 7        // Testfälle hier...
 8    }
 9    for _, c := range cases {
10        out := flattenSecurityGroups(c.pairs, c.ownerId)
11        if !reflect.DeepEqual(out, c.expected) {
12            t.Fatalf("Error matching output and expected: %#v vs %#v", out, c.expected)
13        }
14    }
15}

Dieser Testfall prüft, ob die Flattening-Funktion verschiedene Varianten von AWS-Sicherheitsgruppenregeln korrekt umwandelt, und stellt sicher, dass die Logik wie erwartet funktioniert.

2. Acceptance-Tests für Terraform Plugins

Acceptance-Tests sind ein wichtiger Teil des Testens von Terraform Plugins. Diese Tests wurden entwickelt, um das tatsächliche Verhalten von Terraform bei der Interaktion mit der realen Infrastruktur zu überprüfen. Das Ziel von Acceptance-Tests ist es, sicherzustellen, dass Terraform Ressourcen korrekt erstellen, verändern und zerstören kann und dass die Statusdatei die Ressourcenkonfiguration korrekt wiedergibt.

Das Test-Framework von Terraform unterstützt eine Vielzahl von Testmustern, die es einfach machen, Acceptance-Tests zu schreiben und auszuführen. Acceptance-Tests können in jeder Umgebung ausgeführt werden, die in der Lage ist, Go-Tests auszuführen, z. B. auf einer lokalen Workstation-Befehlszeile oder einem Continuous-Integration-Runner, wie z. B. GitHub Actions.

Beispiel: Grundlegender Test zur Überprüfung von Attributen

Der grundlegendste Akzeptanztest prüft, ob eine Ressource erstellt werden kann und ob ihre Attribute korrekt in der Terraform-Statusdatei gespeichert sind. Dieser Test stellt sicher, dass Terraform eine Konfiguration ohne Fehler anwenden kann und dass die entfernte Ressource mit Terraform Status übereinstimmt.

 1package example
 2// example.Widget stellt einen konkreten Go-Typ dar, der eine API-Ressource repräsentiert
 3func TestAccExampleWidget_basic(t *testing.T) {
 4    var widget example.Widget
 5    // es wird einen zufälligen Namen für jeden Widget-Testlauf erzeugen, um
 6    // Kollisionen durch mehrere gleichzeitige Tests zu vermeiden.
 7    // Das acctest-Paket enthält viele Hilfsprogramme wie RandStringFromCharSet
 8    // Siehe https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/helper/acctest
 9    rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
10    resource.Test(t, resource.TestCase{
11        PreCheck:     func() { testAccPreCheck(t) },
12        Providers:    testAccProviders,
13        CheckDestroy: testAccCheckExampleResourceDestroy,
14        Steps: []resource.TestStep{
15            {
16                // Es wird eine dynamische Konfiguration mit dem zufälligen Namen von oben verwenden
17                Config: testAccExampleResource(rName),
18                // Representiert einen einfachen Test, der sowohl remote als auch lokale Werte überprüft
19                ConfigStateChecks: []statecheck.StateCheck{
20                    // benutzerdefinierte State-Überprüfung - Abfrage der API zum Abrufen des Widget-Objekts
21                    stateCheckExampleResourceExists("example_widget.foo", &widget),
22                    // benutzerdefinierte State-Überprüfung - Überprüfung von Remote-Werten
23                    stateCheckExampleWidgetValues(widget, rName),
24                    //  eingebaute State-Überprüfung - Überprüfung lokaler (State-)Werte
25                    statecheck.ExpectKnownValue("example_widget.foo", tfjsonpath.New("active"), knownvalue.Bool(true)),
26                    statecheck.ExpectKnownValue("example_widget.foo", tfjsonpath.New("name"), knownvalue.StringExact(rName)),
27                },
28            },
29        },
30    })
31}
32// testAccExampleResource gibt eine Konfiguration für ein Beispiel-Widget mit dem angegebenen Namen zurück
33func testAccExampleResource(name string) string {
34    return fmt.Sprintf(`
35resource "example_widget" "foo" {
36  active = true
37  name = "%s"
38}`, name)
39}

Dieser Test prüft, ob eine Widget-Ressource mit den richtigen Attributen erstellt wird und ob diese Attribute sowohl in der Terraform- Statusdatei als auch in der Remote-API korrekt gespeichert sind.

3. Regressionstests für Terraform Plugins

Regressionstests sind essentiell, um sicherzustellen, dass Bugfixes oder Updates für Terraform Plugins keine neuen Probleme einführen. Wenn ein Fehler gemeldet wird, sollte ein Regressionstest folgen, der sicherstellt, dass das Problem behoben wird, ohne die bestehende Funktionalität zu zerstören.

Regressionstests sind besonders nützlich, um zu überprüfen, ob Codeänderungen, die einen Fehler beheben sollen, nicht unbeabsichtigt andere Teile der Codebasis beeinflussen. Diese Tests sollten eindeutig benannt werden, um das behobene Problem widerzuspiegeln, und sollten neben der Fehlerbehebung hinzugefügt werden, um eine umfassende Überprüfung zu gewährleisten.

Ein Beispiel für Regressionstests ist hier zu sehen.

4. Terraforms natives Test-Framework

Das native Testing Framework von Terraform ermöglicht die Validierung der Modulkonfiguration durch den Build einer ephemeren Infrastruktur. Diese Tests laufen unabhängig von den regulären Plan- oder Apply-Workflows. Anstatt die bestehende Infrastruktur zu verändern, erstellen die Tests temporäre Ressourcen, die Terraform nur während der Testphase verwaltet. Dieser Ansatz stellt sicher, dass Tests nicht in bestehenden Statusdateien eingreifen und bietet eine sichere Umgebung, um Änderungen zu validieren.

Die Syntax für Terraform-Tests ist relativ einfach. Es werden .tftest.hcl-Dateien für die Definition von Testfällen und optionale Hilfsmodule für die Verwaltung von testspezifischen Ressourcen verwendet. Sehen wir uns nun an, wie man diese Tests Schritt für Schritt implementiert.

Beispiel: Bei AWS gehostete Website

Die Verzeichnisstruktur für native Terraform-Tests sieht etwa so aus:

 1.
 2├── LICENSE
 3├── README.md
 4├── main.tf
 5├── outputs.tf
 6├── terraform.tf
 7├── tests
 8│   ├── setup
 9│   │   ├── main.tf
10│   └── website.tftest.hcl
11├── variables.tf
12└── www
13    ├── error.html
14    └── index.html

Die Datei main.tf definiert die zu testende Infrastruktur (z. B. ein S3-Bucket). Der Ordner tests enthält die Testkonfiguration.

Terraform-Tests bestehen aus zwei Komponenten:

  • Testdateien: Diese Dateien enden mit der Erweiterung .tftest.hcl und enthalten die eigentliche Testlogik.
  • Helfer-Module: Diese sind optional und helfen dabei, testspezifische Ressourcen oder Datenquellen zu erstellen.

Das Verzeichnis tests enthält die Testkonfigurationsdatei website.tftest.hcl, die zwei Hauptblöcke enthält:

  • Run-Blöcke: Jeder Run-Block wendet einen bestimmten Terraform-Befehl an (z.B. apply oder plan) und führt Assertions für den resultierenden Zustand durch.
  • Assert-Blöcke: Innerhalb eines Run-Blocks prüfen Assert-Blöcke Bedingungen. Diese Bedingungen müssen als true bewertet werden, damit der Test erfolgreich ist.

Hier ist ein Beispiel für die Datei website.tftest.hcl:

 1run "setup_tests" {
 2  module {
 3    source = "./tests/setup"
 4  }
 5}
 6run "create_bucket" {
 7  command = apply
 8  variables {
 9    bucket_name = "${run.setup_tests.bucket_prefix}-aws-s3-website-test"
10  }
11  assert {
12    condition     = aws_s3_bucket.s3_bucket.bucket == "${run.setup_tests.bucket_prefix}-aws-s3-website-test"
13    error_message = "Invalid bucket name"
14  }
15  assert {
16    condition     = aws_s3_object.index.etag == filemd5("./www/index.html")
17    error_message = "Invalid eTag for index.html"
18  }
19  assert {
20    condition     = aws_s3_object.error.etag == filemd5("./www/error.html")
21    error_message = "Invalid eTag for error.html"
22  }
23}

In dieser Konfiguration:

  • run setup_tests erzeugt einen zufälligen Bucket-Namen mit Hilfe des Helper-Moduls.
  • run create_bucket prüft, ob der Bucketname und die hochgeladenen Dateien mit den erwarteten Werten übereinstimmen.

Ausführen der Tests

Um die Tests auszuführen, muss Terraform initialisiert und alle benötigten Provider installiert werden:

1$ terraform init

Als nächstes werden die Tests durchgeführt:

1$ terraform test

Wenn dies erfolgreich war, erhalten wir eine Ausgabe wie diese:

1Success! 2 passed, 0 failed.

Mocking-Ressourcen zum Testen

Um das Testen zu beschleunigen und den Overhead bei der Erstellung echter Ressourcen zu vermeiden, ermöglicht Terraform das Mocking. Mocking simuliert Ressourcen und Datenquellen, so dass diese nicht mehr bereitgestellt werden müssen.

Ein Beispiel für eine reale Ressource, die wir mocken wollen, wäre:

1resource "aws_instance" "backend_api" {
2  ami           = data.aws_ami.ubuntu.id
3  instance_type = "t3.micro"
4  tags = {
5    Name = "backend"
6  }
7}

Beispiel für das Mocking einer EC2-Instanz:

1override_resource {
2  target = aws_instance.backend_api
3}
4run "check_backend_api" {
5  assert {
6    condition     = aws_instance.backend_api.tags.Name == "backend"
7    error_message = "Invalid name tag"
8  }
9}

Dadurch wird eine EC2-Instanz simuliert, anstatt sie zu erstellen, so dass wir Konfigurationen schneller testen können.

Verwendung der Tests in HCP Terraform

HCP Terraform ist eng mit dem Testen von Modulen verknüpft und bietet Entwicklern und Unternehmen, die Terraform verwenden, mehrere Vorteile. So können wir die von uns erstellten Module in eine private Registry hochladen, die viele Funktionalitäten bietet:

  • Wenn ein Modul branch-based publishing verwendet und Tests enthält, führt HCP Terraform diese Tests automatisch aus:
    • Bei jedem Push in den konfigurierten Zweig
    • bei allen Pull-Requests gegen diesen Zweig
  • Entwickler können Remote-Tests in HCP Terraform mit dem Terraform CLI lokal auslösen, ohne Änderungen in die Versionskontrolle zu übertragen
1$ terraform test -cloud-run=app.terraform.io/ORG/s3-website-tests/aws
  • Mit HCP Terraform können Umgebungsvariablen für Tests konfiguriert werden, einschließlich sensibler Daten wie Anmeldedaten von Cloud-Anbietern.
  • Diese Konfiguration erfolgt über die HCP Terraform-Schnittstelle, wodurch die Sicherheit erhöht wird, da die Anmeldedaten nicht in die Quellcodekontrolle gelangen.

Vorteile der Verwendung von HCP Terraform

  • Kontinuierliche Validierung: Stellt sicher, dass Moduländerungen keine bahnbrechenden Änderungen mit sich bringen
  • Sichere Testumgebung:
    • Verwendet konfigurierte Umgebungsvariablen für Tests, einschließlich sensibler Daten
    • Eliminiert die Notwendigkeit, Cloud-Anmeldeinformationen auf lokalen Maschinen zu speichern
  • Integration der Versionskontrolle: Führt automatisch Tests bei Push- und Pull-Anfragen durch, um Probleme frühzeitig im Entwicklungsprozess zu erkennen
  • Zentrale Testhistorie: HCP Terraform bietet eine Historie aller Testläufe, die eine einfache Nachverfolgung und Überprüfung der Testergebnisse im Laufe der Zeit ermöglicht
  • Detaillierte Testberichte:
    • Zeigt den Status jedes Testschritts an
    • Detaillierte Ausgabe für fehlgeschlagene Tests zur Unterstützung bei der Fehlersuche
  • Ferngesteuertes Testen: Ermöglicht das Ausführen von Tests in der HCP Terraform-Umgebung von der lokalen CLI aus und gewährleistet so konsistente Testumgebungen
  • Verbesserte Zusammenarbeit: Teammitglieder können Testergebnisse direkt in HCP Terraform anzeigen und analysieren, was die Zusammenarbeit und Problemlösung erleichtert

Schlussfolgerung

Das Testen mit Terraform ist ein wichtiger Prozess, um sicherzustellen, dass der Code für die Infrastruktur korrekt ist, bevor er in der Produktion eingesetzt wird. Durch die Verwendung von Tests können wir unsere Konfigurationen mit minimalen Auswirkungen auf unsere bestehenden Ressourcen validieren. In diesem Artikel wurde das Wesentliche behandelt, von der Einrichtung von Tests bis hin zu deren Veröffentlichung und der Remote-Ausführung von Tests in HCP Terraform.

Denken Sie bei der Weiterentwicklung Ihrer Terraform-Konfigurationen daran, die Tests auf dem neuesten Stand zu halten, um die Vorhersagbarkeit der Infrastruktur aufrechtzuerhalten und das Einführen von Breaking-Changes.

Zurück Unsere Trainings entdecken

Wir sind für Sie da

Sie interessieren sich für unsere Trainings oder haben einfach eine Frage, die beantwortet werden muss? Sie können uns jederzeit kontaktieren! Wir werden unser Bestes tun, um alle Ihre Fragen zu beantworten.

Hier kontaktieren