Best Practices für die Verwendung von Terraform

Dieses Dokument enthält Richtlinien und Empfehlungen für eine effektive Entwicklung mit Terraform für mehrere Teammitglieder und Arbeitsstreams.

Dieser Leitfaden ist keine Einführung in Terraform. Eine Einführung in die Verwendung von Terraform mit Google Cloud finden Sie unter Erste Schritte mit Terraform.

Allgemeine Stil- und Strukturrichtlinien

Die folgenden Empfehlungen decken den grundlegenden Stil und die Struktur Ihrer Terraform-Konfigurationen ab. Die Empfehlungen gelten für wiederverwendbare Terraform-Module und Stammkonfigurationen.

Einer Standardmodulstruktur folgen

  • Terraform-Module müssen der Standardmodulstruktur entsprechen.
  • Starten Sie jedes Modul mit einer main.tf-Datei, in der sich Ressourcen standardmäßig befinden.
  • Fügen Sie in jedem Modul eine README.md-Datei im Markdown-Format ein. Fügen Sie in der Datei README.md eine grundlegende Dokumentation zum Modul ein.
  • Platzieren Sie Beispiele in einem examples/-Ordner mit einem separaten Unterverzeichnis für jedes Beispiel. Fügen Sie für jedes Beispiel eine detaillierte README.md-Datei ein.
  • Erstellen Sie logische Gruppierungen von Ressourcen mit eigenen Dateien und beschreibenden Namen wie network.tf, instances.tf oder loadbalancer.tf.
    • Vermeiden Sie es, jeder Ressource eine eigene Datei zuzuweisen. Gruppieren Sie die Ressourcen nach ihrem gemeinsamen Zweck. Kombinieren Sie beispielsweise google_dns_managed_zone und google_dns_record_set in dns.tf.
  • Fügen Sie in das Stammverzeichnis des Moduls nur Terraform-Dateien (*.tf) und Repository-Metadatendateien ein, z. B. README.md und CHANGELOG.md.
  • Fügen Sie zusätzliche Dokumentation in ein Unterverzeichnis docs/ ein.

Namenskonvention verwenden

  • Benennen Sie alle Konfigurationsobjekte mit Unterstrichen, um mehrere Wörter zu trennen. Diese Vorgehensweise gewährleistet die Konsistenz mit der Namenskonvention für Ressourcentypen, Datenquellentypen und andere vordefinierte Werte. Diese Konvention gilt nicht für Namensargumente.

    Empfohlen:

    resource "google_compute_instance" "web_server" {
      name = "web-server"
    }
    

    Nicht empfohlen:

    resource "google_compute_instance" "web-server" {
      name = "web-server"
    }
    
  • Um Verweise auf eine Ressource zu vereinfachen, die die einzige ihres Typs ist (z. B. ein einzelner Load-Balancer für ein ganzes Modul), nennen Sie die Ressource main.

    • Es ist zusätzliche mentale Arbeit erforderlich, um sich some_google_resource.my_special_resource.id statt some_google_resource.main.id zu merken.
  • Wenn Sie Ressourcen desselben Typs voneinander unterscheiden möchten, z. B. primary und secondary, geben Sie aussagekräftige Ressourcennamen an.

  • Ressourcennamen sollten einmalig sein.

  • Wiederholen Sie den Ressourcentyp nicht im Ressourcennamen. Beispiel:

    Empfohlen:

    resource "google_compute_global_address" "main" { ... }
    

    Nicht empfohlen:

    resource "google_compute_global_address" "main_global_address" { … }
    

Variablen mit Bedacht verwenden

  • Deklarieren Sie alle Variablen in variables.tf.
  • Geben Sie den Variablen aussagekräftigen Namen, die für ihre Nutzung oder ihren Zweck relevant sind:
    • Eingaben, lokale Variablen und Ausgaben, die numerische Werte wie Laufwerksgrößen oder RAM-Größe darstellen, müssen mit Einheiten benannt werden (z. B. ram_size_gb). Google Cloud APIs haben keine Standardeinheiten. Durch die Benennung von Variablen mit Einheiten wird die erwartete Eingabeeinheit für Konfigurationsadministratoren klar.
    • Verwenden Sie für Speichereinheiten binäre Einheitenpräfixe (Potenzen von 1.024) – kibi, mebi, gibi. Verwenden Sie für alle anderen Maßeinheiten dezimale Einheitenpräfixe (Potenzen von 1.000) – kilo, mega, giga. Diese Nutzung entspricht der Nutzung in Google Cloud.
    • Um die bedingte Logik zu vereinfachen, geben Sie booleschen Variablen positive Namen, z. B. enable_external_access.
  • Variablen müssen Beschreibungen haben. Beschreibungen sind automatisch in der automatisch generierten Dokumentation eines veröffentlichten Moduls enthalten. Beschreibungen bieten für neue Entwickler zusätzlichen Kontext, den beschreibende Namen nicht angeben können.
  • Geben Sie für Variablen definierte Typen an.
  • Geben Sie gegebenenfalls Standardwerte an:
    • Geben Sie für Variablen mit umgebungsunabhängigen Werten (z. B. Laufwerksgröße) Standardwerte an.
    • Geben Sie für Variablen mit umgebungsspezifischen Werten wie project_id keine Standardwerte an. Dadurch muss das aufrufende Modul aussagekräftige Werte bereitstellen.
  • Verwenden Sie leere Standardwerte für Variablen (z. B. leere Strings oder Listen) nur dann, wenn das Leerlassen der Variable eine gültige Einstellung ist, die von den zugrunde liegenden APIs nicht abgelehnt wird.
  • Gehen Sie mit Bedacht vor, wenn Sie Variablen verwenden. Parametrisieren Sie nur Werte, die für jede Instanz oder Umgebung variieren müssen. Wenn Sie entscheiden, ob eine Variable bereitgestellt werden soll, sollten Sie einen konkreten Anwendungsfall haben, um diese Variable zu ändern. Wenn die Wahrscheinlichkeit, dass eine Variable erforderlich ist, nur gering ist, stellt Sie sie nicht bereit.
    • Das Hinzufügen einer Variablen mit einem Standardwert ist abwärtskompatibel.
    • Das Entfernen einer Variablen ist nicht abwärtskompatibel.
    • Wenn ein Literal an mehreren Stellen wiederverwendet wird, können Sie einen lokalen Wert verwenden, ohne ihn als Variable bereitzustellen.

Ausgaben bereitstellen

  • Organisieren Sie alle Ausgaben in einer outputs.tf-Datei.
  • Geben Sie aussagekräftige Beschreibungen für alle Ausgaben an.
  • Dokumentieren Sie Ausgabebeschreibungen in der Datei README.md. Mithilfe von Tools wie terraform-docs können Sie Beschreibungen bei einen Commit automatisch generieren.
  • Geben Sie alle nützlichen Werte aus, auf die Stammmodule möglicherweise verweisen müssen oder die sie gemeinsam verwenden müssen. Besonders für Open-Source- oder stark genutzte Module sollten sie alle Ausgaben bereitstellen, die potenziell genutzt werden können.
  • Übergeben Sie Ausgaben nicht direkt über Eingabevariablen, da sie sonst nicht ordnungsgemäß der Abhängigkeitsgrafik hinzugefügt werden. Achten Sie darauf, dass Ausgaben auf Attribute aus Ressourcen verweisen, damit implizite Abhängigkeiten erstellt werden. Anstatt direkt auf eine Eingabevariable für eine Instanz zu verweisen, übergeben Sie das Attribut wie hier gezeigt:

    Empfohlen:

    output "name" {
      description = "Name of instance"
      value       = google_compute_instance.main.name
    }
    

    Nicht empfohlen:

    output "name" {
      description = "Name of instance"
      value       = var.name
    }
    

Datenquellen verwenden

  • Fügen Sie Datenquellen neben den Ressourcen hinzu, die darauf verweisen. Wenn Sie beispielsweise ein Image abrufen, das beim Starten einer Instanz verwendet werden soll, platzieren Sie es neben der Instanz, anstatt Datenressourcen in einer eigenen Datei zu erfassen.
  • Wenn die Anzahl der Datenquellen groß wird, sollten Sie sie in eine dedizierte data.tf-Datei verschieben.
  • Verwenden Sie die Interpolation von Variablen oder Ressourcen, um Daten relativ zur aktuellen Umgebung abzurufen.

Verwendung benutzerdefinierter Skripts einschränken

  • Verwenden Sie Skripts nur, wenn es notwendig ist. Der Zustand von Ressourcen, die über Skripts erstellt werden, wird von Terraform nicht berücksichtigt oder verwaltet.
    • Vermeiden Sie nach Möglichkeit benutzerdefinierte Skripts. Verwenden Sie sie nur, wenn Terraform-Ressourcen das gewünschte Verhalten nicht unterstützen.
    • Alle verwendeten benutzerdefinierten Skripts müssen einen klar dokumentierten Grund für ihre Existenz und idealerweise Einstellungsplan haben.
  • Terraform kann benutzerdefinierte Skripts über Bereitsteller aufrufen, einschließlich des local-exec-Bereitstellers.
  • Fügen Sie benutzerdefinierte Skripts, die Terraform aufruft, in ein scripts/-Verzeichnis ein.

Hilfsskripts in ein separates Verzeichnis einschließen

  • Organisieren Sie Hilfsskripts, die nicht von Terraform aufgerufen werden, in einem helpers/-Verzeichnis.
  • Dokumentieren Sie Hilfsskripts in der Datei README.md mit Erläuterungen und Beispielaufrufen.
  • Wenn Hilfsskripts Argumente akzeptieren, stellen Sie eine Argumentprüfung und eine --help-Ausgabe bereit.

Statische Dateien in ein separates Verzeichnis einfügen

  • Statische Dateien, auf die Terraform verweist, die es aber nicht ausführt (z. B. auf Compute Engine-Instanzen geladene Startskripts), müssen in einem files/-Verzeichnis organisiert werden.
  • Platzieren Sie umfangreiche HereDocs in externen Dateien, die von ihrer HCL-Datei getrennt sind. Verweisen Sie mit der Funktion file() auf sie.
  • Verwenden Sie für Dateien, die mit der Terraform-Funktion templatefile gelesen werden, die Dateierweiterung .tftpl.
    • Vorlagen müssen in einem templates/-Verzeichnis abgelegt werden.

Zustandsorientierte Ressourcen schützen

Achten Sie bei zustandsorientierten Ressourcen wie Datenbanken darauf, dass der Löschschutz aktiviert ist. Beispiele:

resource "google_sql_database_instance" "main" {
  name = "primary-instance"
  settings {
    tier = "D0"
  }

  lifecycle {
    prevent_destroy = true
  }
}

Integrierte Formatierung verwenden

Alle Terraform-Dateien müssen den Standards von terraform fmt entsprechen.

Komplexität von Ausdrücken beschränken

  • Beschränken Sie die Komplexität einzelner interpolierter Ausdrücke. Wenn viele Funktionen in einem einzelnen Ausdruck benötigt werden, können Sie ihn mithilfe von lokalen Werten in mehrere Ausdrücke aufteilen.
  • Verwenden Sie nie mehr als eine ternäre Operation in einer einzigen Zeile. Verwenden Sie stattdessen mehrere lokale Werte, um die Logik zu erstellen.

count für bedingte Werte verwenden

Wenn Sie eine Ressource bedingt instanziieren möchten, verwenden Sie das Metaargument count. Beispiel:

variable "readers" {
  description = "..."
  type        = list
  default     = []
}

resource "resource_type" "reference_name" {
  // Do not create this resource if the list of readers is empty.
  count = length(var.readers) == 0 ? 0 : 1
  ...
}

Seien Sie sparsam, wenn Sie benutzerdefinierte Variablen verwenden, um die Variable count für Ressourcen festzulegen. Wenn ein Ressourcenattribut für eine solche Variable (wie project_id) angegeben wird und diese Ressource noch nicht vorhanden ist, kann Terraform keinen Plan generieren. Stattdessen meldet Terraform den Fehler value of count cannot be computed. Verwenden Sie in solchen Fällen eine separate enable_x-Variable, um die bedingte Logik zu berechnen.

for_each für iterierte Ressourcen verwenden

Wenn Sie mehrere Kopien einer Ressource basierend auf einer Eingaberessource erstellen möchten, verwenden Sie das Metaargument for_each.

Module in einer Registry veröffentlichen

  • Wiederverwendbare Module: Veröffentlichen Sie wiederverwendbare Module in einer Modul-Registry.

  • Open-Source-Module: Veröffentlichen Sie Open-Source-Module in einer Terraform Registry.

  • Private Module: Veröffentlichen Sie private Module in einer privaten Registry.

Wiederverwendbare Module

Verwenden Sie für Module, die wiederverwendet werden sollen, zusätzlich zu den vorherigen Richtlinien die folgenden Richtlinien.

Erforderliche APIs in Modulen aktivieren

Terraform-Module können alle erforderlichen Dienste mithilfe der Ressource google_project_service oder des Moduls project_services aktivieren. Die Einbindung der API-Aktivierung erleichtert die Demonstration.

  • Wenn die API-Aktivierung in einem Modul enthalten ist, muss die API-Aktivierung deaktivierbar sein. Stellen Sie dazu eine Variable enable_apis bereit, die standardmäßig auf true gesetzt ist.
  • Wenn die API-Aktivierung in einem Modul enthalten ist, muss die API-Aktivierung disable_services_on_destroy auf false gesetzt sein, da dieses Attribut Probleme verursachen kann, wenn Sie mit mehreren Instanzen des Moduls arbeiten.

    Beispiel:

    module "project-services" {
      source  = "terraform-google-modules/project-factory/google//modules/project_services"
      version = "~> 12.0"
    
      project_id  = var.project_id
      enable_apis = var.enable_apis
    
      activate_apis = [
        "compute.googleapis.com",
        "pubsub.googleapis.com",
      ]
      disable_services_on_destroy = false
    }
    

Inhaberdatei hinzufügen

Fügen Sie für alle freigegebenen Module eine OWNERS-Datei (oder CODEOWNERS auf GitHub) ein, die dokumentiert, wer für das Modul verantwortlich ist. Bevor eine Pull-Anfrage zusammengeführt wird, sollte sie von einem Inhaber genehmigt werden.

Getaggte Versionen veröffentlichen

Manchmal sind für Module funktionsgefährdende Änderungen erforderlich und Sie müssen die Auswirkungen den Nutzern mitteilen, damit diese ihre Konfigurationen an eine bestimmte Version binden können.

Achten Sie darauf, dass freigegebene Module SemVer v2.0.0 folgen, wenn neue Versionen getaggt oder veröffentlicht werden.

Verwenden Sie beim Verweis auf ein Modul eine Versionseinschränkung, um eine Bindung an die Hauptversion vorzunehmen. Beispiele:

module "gke" {
  source  = "terraform-google-modules/kubernetes-engine/google"
  version = "~> 20.0"
}

Keine Anbieter oder Back-Ends konfigurieren

Freigegebene Module dürfen keine Anbieter oder Back-Ends konfigurieren. Stattdessen müssen Anbieter und Back-Ends in Stammmodulen konfiguriert werden.

Definieren Sie für freigegebene Module die mindestens erforderlichen Anbieterversionen so in einem required_providers-Block:

terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = ">= 4.0.0"
    }
  }
}

Sofern nicht anders angegeben, sollten Sie davon ausgehen, dass neue Anbieterversionen funktionieren.

Labels als Variable bereitstellen

Ermöglichen Sie flexible Labels für Ressourcen über die Modulschnittstelle. Sie können eine labels-Variable mit einer leeren Zuordnung als Standardwert so bereitstellen:

variable "labels" {
  description = "A map of labels to apply to contained resources."
  default     = {}
  type        = "map"
}

Ausgaben für alle Ressourcen bereitstellen

Anhand von Variablen und Ausgaben können Sie Abhängigkeiten zwischen Modulen und Ressourcen ableiten. Ohne Ausgaben können Nutzer Ihr Modul in Bezug auf seine Terraform-Konfigurationen nicht ordnungsgemäß sortieren.

Fügen Sie für jede in einem freigegebenen Modul definierte Ressource mindestens eine Ausgabe ein, die auf die Ressource verweist.

Inline-Untermodule für komplexe Logik verwenden

  • Mit Inline-Modulen können Sie komplexe Terraform-Module in kleinere Einheiten organisieren und gemeinsame Ressourcen deduplizieren.
  • Platzieren Sie Inline-Module in modules/$modulename.
  • Behandeln Sie Inline-Module als private Module, die nicht von externen Modulen verwendet werden sollen, es sei denn, in der Dokumentation des freigegebenen Moduls ist dies anders angegeben.
  • Terraform verfolgt keine refaktorierten Ressourcen. Wenn Sie mit mehreren Ressourcen im obersten Modul beginnen und sie dann in Untermodule übertragen, versucht Terraform, alle refaktorierten Ressourcen neu zu erstellen. Verwenden Sie bei der Refaktorierung moved-Blöcke, um dieses Verhalten zu umgehen.
  • Durch interne Module definierte Ausgaben werden nicht automatisch bereitgestellt. Wenn Sie Ausgaben von internen Modulen freigeben möchten, exportieren Sie sie noch einmal.

Terraform-Stammmodule

Stammkonfigurationen (Stammmodule) sind die Arbeitsverzeichnisse, von denen aus Sie die Terraform-Befehlszeile ausführen. Achten Sie darauf, dass Stammkonfigurationen die folgenden Standards (und gegebenenfalls die vorherigen Terraform-Richtlinien) einhalten. Explizite Empfehlungen für Stammmodule haben Vorrang vor den allgemeinen Richtlinien.

Anzahl der Ressourcen pro Stammmodul minimieren

Es ist wichtig, dass eine einzelne Stammkonfiguration nicht zu groß wird, weil zu viele Ressourcen im selben Verzeichnis und im selben Zustand gespeichert sind. Alle Ressourcen in einer bestimmten Stammkonfiguration werden bei jeder Ausführung von Terraform aktualisiert. Dies kann zu einer langsamen Ausführung führen, wenn zu viele Ressourcen in einem einzigen Zustand enthalten sind. Eine allgemeine Regel lautet: Fügen Sie nicht mehr als 100 Ressourcen (und idealerweise nur wenige Dutzend) in einem einzigen Zustand hinzu.

Separate Verzeichnisse für jede Anwendung verwenden

Um Anwendungen und Projekte unabhängig voneinander zu verwalten, platzieren Sie Ressourcen für jede Anwendung und jedes Projekt in ihren eigenen Terraform-Verzeichnissen. Ein Dienst kann eine bestimmte Anwendung oder einen gemeinsamen Dienst wie freigegebene Netzwerke darstellen. Verschachteln Sie den gesamten Terraform-Code für einen bestimmten Dienst in einem einzigen Verzeichnis (einschließlich Unterverzeichnissen).

Anwendungen in umgebungsspezifische Unterverzeichnisse aufteilen

Teilen Sie beim Bereitstellen von Diensten in Google Cloud die Terraform-Konfiguration für den Dienst in zwei oberste Verzeichnisse auf: ein modules-Verzeichnis, das die tatsächliche Konfiguration des Dienstes enthält, und ein environments-Verzeichnis, das die Stammkonfigurationen für jede Umgebung enthält.

-- SERVICE-DIRECTORY/
   -- OWNERS
   -- modules/
      -- <service-name>/
         -- main.tf
         -- variables.tf
         -- outputs.tf
         -- provider.tf
         -- README
      -- ...other…
   -- environments/
      -- dev/
         -- backend.tf
         -- main.tf

      -- qa/
         -- backend.tf
         -- main.tf

      -- prod/
         -- backend.tf
         -- main.tf

Umgebungsverzeichnisse verwenden

Verweisen Sie auf Module, um Code umgebungsübergreifend freizugeben. In der Regel kann dies ein Dienstmodul sein, das die gemeinsame Terraform-Basiskonfiguration für den Dienst enthält. In Dienstmodulen sollten Sie allgemeine Eingaben hartcodieren und nur umgebungsspezifische Eingaben als Variablen erforderlich machen.

Dieses Umgebungsverzeichnis muss die folgenden Dateien enthalten:

  • Eine backend.tf-Datei, die den Terraform-Backend-Zustandsspeicherort angibt (normalerweise Cloud Storage)
  • Eine main.tf-Datei, die das Dienstmodul instanziiert

Jedes Umgebungsverzeichnis (dev, qa, prod) entspricht einem Terraform-Standardarbeitsbereich und stellt eine Version des Dienstes für diese Umgebung bereit. Diese Arbeitsbereiche isolieren umgebungsspezifische Ressourcen in ihren eigenen Kontexten. Verwenden Sie nur den Standardarbeitsbereich.

Mehrere CLI-Arbeitsbereiche innerhalb einer Umgebung werden aus folgenden Gründen nicht empfohlen:

  • Es kann schwierig sein, die Konfiguration in jedem Arbeitsbereich zu prüfen.
  • Ein einzelnes freigegebenes Backend für mehrere Arbeitsbereiche wird nicht empfohlen, da das freigegebene Backend für die Umgebungstrennung zu einem Single Point of Failure wird.
  • Die Wiederverwendung von Code ist zwar möglich, aber der Code ist schwieriger zu lesen und muss basierend auf der aktuellen Arbeitsbereichsvariable (z. B. terraform.workspace == "foo" ? this : that) geändert werden.

Hier finden Sie weitere Informationen:

Ausgaben über den Remotezustand bereitstellen

Achten Sie darauf, dass Sie nützliche Ausgaben von Modulinstanzen aus einem Stammmodul zur Verfügung stellen.

Das folgende Code-Snippet leitet beispielsweise die Projekt-ID-Ausgabe der Projekt-Factory-Modulinstanz als Ausgabe des Stammmoduls weiter.

# Project root module
terraform {
  backend "gcs" {
    bucket  = "BUCKET"
  }
}

module "project" {
  source  = "terraform-google-modules/project-factory/google"
  ...
}

output "project_id" {
  value       = module.project.project_id
  description = "The ID of the created project"
}

Andere Terraform-Umgebungen und -Anwendungen können nur auf Ausgaben auf Stammmodulebene verweisen.

Mithilfe des Remotezustands können Sie auf die Ausgaben des Stammmoduls verweisen. Wenn Sie die Verwendung durch andere abhängige Anwendungen für die Konfiguration zulassen möchten, exportieren Sie Informationen, die sich auf die Endpunkte eines Dienstes beziehen, in den Remotezustand.

# Networks root module
data "terraform_remote_state" "network_project" {
  backend = "gcs"

  config = {
    bucket = "BUCKET"
  }
}

module "vpc" {
  source  = "terraform-google-modules/network/google"
  version = "~> 9.0"

  project_id   = data.terraform_remote_state.network_project.outputs.project_id
  network_name = "vpc-1"
  ...
}

Manchmal ist es sinnvoll, das gesamte untergeordnete Modul so zu exportieren, wenn eine freigegebenes Dienstmodul aus Umgebungsverzeichnissen aufgerufen wird:

output "service" {
  value       = module.service
  description = "The service module outputs"
}

Bindung an Nebenanbieterversionen vornehmen

Deklarieren Sie in Stammmodulen jeden Anbieter und nehmen Sie eine Bindung an eine Nebenversion vor. Dies ermöglicht ein automatisches Upgrade auf neue Patchreleases unter Beibehaltung eines soliden Ziels. Aus Konsistenzgründen sollten Sie die Versionsdatei versions.tf nennen.

terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 4.0.0"
    }
  }
}

Variablen in einer tfvars-Datei speichern

Geben Sie für Stammmodule Variablen mithilfe einer .tfvars-Variablendatei an. Nennen Sie Variablendateien aus Konsistenzgründen terraform.tfvars.

Geben Sie Variablen nicht mit den alternativen Befehlszeilenoptionen var-files oder var='key=val' an. Befehlszeilenoptionen sind sitzungsspezifisch und werden leicht vergessen. Die Verwendung einer Standardvariablendatei ist vorhersehbarer.

.terraform.lock.hcl-Datei einchecken

Bei Root-Modulen sollte die Abhängigkeitssperre .terraform.lock.hcl in die Versionsverwaltung eingecheckt werden. Damit können Sie die Änderungen der Anbieterauswahl für eine bestimmte Konfiguration verfolgen und prüfen.

Konfigurationsübergreifende Kommunikation

Ein häufiges Problem bei der Verwendung von Terraform besteht darin, Informationen über verschiedene Terraform-Konfigurationen hinweg freizugeben (gegebenenfalls von verschiedenen Teams verwaltet). Im Allgemeinen können Informationen zwischen Konfigurationen geteilt werden, ohne dass sie in einem einzigen Konfigurationsverzeichnis (oder gar in einem einzigen Repository) gespeichert werden müssen.

Zum Freigeben von Informationen zwischen verschiedenen Terraform-Konfigurationen wird empfohlen, den Remotezustand zu verwenden, um auf andere Stammmodule zu verweisen. Cloud Storage oder Terraform Enterprise sind die bevorzugten Zustands-Backends.

Verwenden Sie zum Abfragen von Ressourcen, die nicht von Terraform verwaltet werden, Datenquellen des Google-Anbieters. Das Compute Engine-Standarddienstkonto kann beispielsweise mit einer Datenquelle abgerufen werden. Verwenden Sie keine Datenquellen, um Ressourcen abzufragen, die von einer anderen Terraform-Konfiguration verwaltet werden. Dies kann implizite Abhängigkeiten von Ressourcennamen und -strukturen verursachen, die von normalen Terraform-Vorgängen unbeabsichtigt gestört werden können.

Mit Google Cloud-Ressourcen arbeiten

Best Practices für die Bereitstellung von Google Cloud-Ressourcen mit Terraform sind in die von Google verwalteten Module des Cloud Foundation Toolkit eingebunden. In diesem Abschnitt werden einige dieser Best Practices wiederholt.

VM-Images erstellen

Im Allgemeinen empfehlen wir das Erstellen von VM-Images mit einem Tool wie Packer. Terraform muss dann nur Maschinen mit den vorgefertigten Images starten.

Wenn keine vorgefertigten Images verfügbar sind, kann Terraform neue virtuelle Maschinen mit einem provisioner-Block an ein Konfigurationsverwaltungstool übergeben. Wir empfehlen, diese Methode zu vermeiden und sie nur als letztes Mittel zu verwenden. Wenn Sie den mit der Instanz verknüpften alten Zustand bereinigen möchten, sollten Bereitsteller, die eine Bereinigungslogik benötigen, einen provisioner-Block mit when = destroy verwenden.

Terraform sollte VM-Konfigurationsinformationen für die Konfigurationsverwaltung mit Instanzmetadaten bereitstellen.

Identity and Access Management verwalten

Bei der Bereitstellung von IAM-Verknüpfungen mit Terraform sind mehrere verschiedene Ressourcen verfügbar:

  • google_*_iam_policy (z. B. google_project_iam_policy)
  • google_*_iam_binding (z. B. google_project_iam_binding)
  • google_*_iam_member (z. B. google_project_iam_member)

google_*_iam_policy und google_*_iam_binding erstellen autoritative IAM-Verknüpfungen, wobei die Terraform-Ressourcen als einzige „Source of Truth“ dafür dienen, welche Berechtigungen entsprechenden Ressource zugewiesen werden können.

Wenn sich die Berechtigungen außerhalb von Terraform ändern, überschreibt Terraform bei der nächsten Ausführung alle Berechtigungen, um die in Ihrer Konfiguration definierte Richtlinie darzustellen. Dies kann für Ressourcen sinnvoll sein, die vollständig mit einer bestimmten Terraform-Konfiguration verwaltet werden. Dies bedeutet jedoch, dass automatisch von Google Cloud verwaltete Rollen entfernt werden, was die Funktionalität einiger Dienste beeinträchtigen kann.

Um dies zu verhindern, empfehlen wir entweder die direkte Verwendung von google_*_iam_member-Ressourcen oder das IAM-Modul von Google.

Versionsverwaltung

Speichern Sie wie bei anderen Codetypen Infrastrukturcode in der Versionsverwaltung, um den Verlauf beizubehalten und einfache Rollbacks zu ermöglichen.

Standardverzweigungsstrategie verwenden

Verwenden Sie für alle Repositories, die Terraform-Code enthalten, standardmäßig die folgende Strategie:

  • Der Zweig main ist der primäre Entwicklungszweig und stellt den neuesten genehmigten Code dar. Der main-Zweig ist geschützt.
  • Die Entwicklung erfolgt in Feature- und Fehlerkorrekturzweigen, die vom Zweig main ausgehen.
    • Nennen Sie Featurezweige feature/$feature_name.
    • Nennen Sie Fehlerkorrekturzweige fix/$bugfix_name.
  • Wenn ein Feature oder eine Fehlerkorrektur abgeschlossen ist, führen Sie sie über eine Pull-Anfrage wieder mit dem Zweig main zusammen.
  • Führen Sie zur Vermeidung von Zusammenführungskonflikten ein Rebasing von Zweigen durch, bevor Sie sie zusammenführen.

Umgebungszweige für Stammkonfigurationen verwenden

Für Repositories mit Stammkonfigurationen, die direkt in Google Cloud bereitgestellt werden, ist eine sichere Roll-out-Strategie erforderlich. Wir empfehlen, für jede Umgebung einen separaten Zweig zu erstellen. Dadurch können Änderungen an der Terraform-Konfiguration durch Zusammenführen von Änderungen zwischen den verschiedenen Zweige hochgestuft werden.

Separater Zweig für jede Umgebung

Umfassende Sichtbarkeit zulassen

Sorgen Sie dafür, dass Terraform-Quellcode und -Repositories in Entwicklungsorganisationen sowie für Infrastrukturinhaber (z. B. SREs) und Infrastruktur-Stakeholder (z. B. Entwickler) umfassend sichtbar und zugänglich sind. Dadurch erhalten die Stakeholder in der Infrastruktur ein besseres Verständnis der Infrastruktur, von der sie abhängen.

Ermutigen Sie die Infrastruktur-Stakeholder, im Rahmen des Änderungsanfrageprozesses Zusammenführungsanfragen zu senden.

Niemals Commit von Secrets durchführen

Führen Sie niemals einen Commit von Secrets an die Versionsverwaltung durch, auch nicht an die Terraform-Konfiguration. Laden Sie sie stattdessen in ein System wie Secret Manager hoch und verweisen Sie mithilfe von Datenquellen darauf.

Beachten Sie, dass solche vertraulichen Werte möglicherweise in der Zustandsdatei landen und auch als Ausgaben bereitgestellt werden können.

Repositories anhand von Teamgrenzen organisieren

Obwohl Sie separate Verzeichnisse verwenden können, um logische Grenzen zwischen Ressourcen zu verwalten, bestimmen Organisationsgrenzen und Logistik die Repository-Struktur. Verwenden Sie im Allgemeinen das Designprinzip, dass Konfigurationen mit unterschiedlichen Genehmigungs- und Verwaltungsanforderungen in verschiedene Versionsverwaltungs-Repositories unterteilt werden. Zur Veranschaulichung dieses Prinzips hier einige mögliche Repository-Konfigurationen:

  • Ein zentrales Repository: In diesem Modell wird der gesamte Terraform-Code zentral von einem einzelnen Plattformteam verwaltet. Dieses Modell funktioniert am besten, wenn ein dediziertes Infrastrukturteam für die gesamte Cloud-Verwaltung verantwortlich ist und alle von anderen Teams angeforderten Änderungen genehmigt.

  • Team-Repositories: In diesem Modell ist jedes Team für sein eigenes Terraform-Repository verantwortlich, in dem es alles im Zusammenhang mit der Infrastruktur verwaltet, die ihm gehört. Das Sicherheitsteam kann beispielsweise ein Repository haben, in dem alle Sicherheitseinstellungen verwaltet werden, und die Anwendungsteams haben jeweils ein eigenes Terraform-Repository zur Bereitstellung und Verwaltung ihrer Anwendung.

    Die Organisation von Repositories gemäß Teamgrenzen ist für die meisten Unternehmensszenarien die beste Struktur.

  • Entkoppelte Repositories: In diesem Modell wird jede logische Terraform-Komponente in ein eigenes Repository eingeteilt. Netzwerke können beispielsweise ein dediziertes Repository haben und es kann ein separates Projekt-Factory-Repository für die Erstellung und Verwaltung von Projekten geben. Dies funktioniert am besten in stark dezentralisierten Umgebungen, in denen Verantwortlichkeiten häufig zwischen Teams wechseln.

Beispiel für Repository-Struktur

Sie können diese Prinzipien kombinieren, um die Terraform-Konfiguration auf verschiedene Repository-Typen aufzuteilen:

  • Grundlegend
  • Anwendungs- und teamspezifisch
Grundlegendes Repository

Ein grundlegendes Repository, das wichtige zentrale Komponenten wie Ordner oder Organisations-IAM enthält. Dieses Repository kann vom zentralen Cloud-Team verwaltet werden.

  • Fügen Sie in diesem Repository für jede Hauptkomponente ein Verzeichnis ein (z. B. Ordner, Netzwerke usw.).
  • Fügen Sie in die Komponentenverzeichnisse einen separaten Ordner für jede Umgebung ein, was den zuvor genannten Verzeichnisstrukturrichtlinien entspricht.

Grundlegende Repository-Struktur

Anwendungs- und teamspezifische Repositories

Stellen Sie anwendungs- und teamspezifische Repositories für jedes Team separat bereit, um seine spezielle anwendungsspezifische Terraform-Konfiguration zu verwalten.

Anwendungs- und teamspezifische Repository-Struktur

Vorgänge

Für die Sicherheit Ihrer Infrastruktur ist ein stabiler und sicherer Prozess zum Anwenden von Terraform-Updates erforderlich.

Immer zuerst planen

Generieren Sie immer zuerst einen Plan für Terraform-Ausführungen. Speichern Sie den Plan in einer Ausgabedatei. Führen Sie den Plan aus, nachdem er von einem Infrastrukturinhaber genehmigt wurde. Selbst wenn Entwickler lokal Prototypen für Änderungen erstellen, sollten sie einen Plan generieren und die hinzuzufügenden, zu ändernden und zu löschenden Ressourcen prüfen, bevor sie den Plan anwenden.

Automatisierte Pipeline implementieren

Führen Sie für einen konsistenten Ausführungskontext Terraform über automatisierte Tools aus. Wenn ein Build-System (wie Jenkins) bereits verwendet und weit verbreitet ist, können Sie es mit den Befehlen terraform plan und terraform apply automatisch ausführen. Wenn kein vorhandenes System verfügbar ist, verwenden Sie entweder Cloud Build oder Terraform Cloud.

Dienstkonto-Anmeldedaten für Continuous Integration verwenden

Wenn Terraform von einem Computer in einer CI/CD-Pipeline ausgeführt wird, sollte es die Dienstkonto-Anmeldedaten von dem Dienst übernehmen, der die Pipeline ausführt. Führen Sie nach Möglichkeit CI-Pipelines in Google Cloud aus, da Cloud Build, Google Kubernetes Engine oder Compute Engine Anmeldedaten einfügen, ohne Dienstkontoschlüssel herunterzuladen.

Für Pipelines, die außerhalb von Google Cloud ausgeführt werden, sollten Sie die Identitätsföderation von Arbeitslasten verwenden, um Anmeldedaten abzurufen, ohne Dienstkontoschlüssel herunterzuladen.

Vorhandene Ressourcen nicht importieren

Vermeiden Sie nach Möglichkeit den Import vorhandener Ressourcen (mit terraform import), da es sonst schwierig sein kann, die Herkunft und Konfiguration von manuell erstellten Ressourcen vollständig zu verstehen. Erstellen Sie stattdessen mit Terraform neue Ressourcen und löschen Sie die alten Ressourcen.

Verwenden Sie in Fällen, in denen das Löschen alter Ressourcen erheblichen Aufwand verursachen würde, den Befehl terraform import mit expliziter Genehmigung. Nachdem eine Ressource in Terraform importiert wurde, verwalten Sie sie ausschließlich mit Terraform.

Google bietet ein Tool, mit dem Sie Ihre Google Cloud-Ressourcen in den Terraform-Zustand importieren können. Weitere Informationen finden Sie unter Google Cloud-Ressourcen in den Terraform-Zustand importieren.

Terraform-Zustand nicht manuell ändern

Die Terraform-Zustandsdatei ist für die Zuordnung zwischen der Terraform-Konfiguration und den Google Cloud-Ressourcen von entscheidender Bedeutung. Ihre Beschädigung kann zu erheblichen Infrastrukturproblemen führen. Wenn Änderungen am Terraform-Zustand erforderlich sind, verwenden Sie den Befehl terraform state.

Versionsbindungen regelmäßig prüfen

Das Binden an Versionen gewährleistet Stabilität, verhindert jedoch, dass Fehlerkorrekturen und andere Verbesserungen in Ihre Konfiguration integriert werden. Prüfen Sie daher regelmäßig Versionsbindungen für Terraform, Terraform-Anbieter und Module.

Verwenden Sie ein Tool wie Dependabot, um diesen Prozess zu automatisieren.

Bei der lokalen Ausführung die Standardanmeldedaten für Anwendungen verwenden

Wenn Entwickler die Terraform-Konfiguration lokal iterieren, sollten sie sich authentifizieren. Dazu führen sie gcloud auth application-default login aus, um Standardanmeldedaten für Anwendungen zu generieren. Laden Sie keine Dienstkontoschlüssel herunter, da heruntergeladene Schlüssel schwieriger zu verwalten und zu sichern sind.

Aliasse auf Terraform festlegen

Sie können dem Befehls-Shell-Profil Aliasse hinzufügen, um die lokale Entwicklung zu vereinfachen:

  • alias tf="terraform"
  • alias terrafrom="terraform"

Sicherheit

Terraform erfordert einen vertraulichen Zugriff auf Ihre Cloud-Infrastruktur. Wenn Sie diese Best Practices für die Sicherheit befolgen, können Sie die damit verbundenen Risiken minimieren und die allgemeine Cloud-Sicherheit verbessern.

Remotezustand verwenden

Für Google Cloud-Kunden empfehlen wir die Verwendung des Cloud Storage-Zustands-Back-Ends. Dieser Ansatz sperrt den Zustand, um die Zusammenarbeit als Team zu ermöglichen. Außerdem werden der Zustand und alle potenziell vertraulichen Informationen von der Versionsverwaltung getrennt.

Achten Sie darauf, dass nur das Build-System und Administratoren mit umfangreichen Berechtigungen auf den Bucket zugreifen können, der für den Remotezustand verwendet wird.

Um zu vermeiden, dass der Entwicklungszustand versehentlich an die Versionsverwaltung übergeben wird, verwenden Sie gitignore für Terraform-Zustandsdateien.

Zustand verschlüsseln

Obwohl Google Cloud-Buckets im Ruhezustand verschlüsselt werden, können Sie vom Kunden bereitgestellte Verschlüsselungsschlüssel verwenden, um den Schutz zu erhöhen. Verwenden Sie dazu die Umgebungsvariable GOOGLE_ENCRYPTION_KEY. Auch wenn in der Zustandsdatei keine Secrets enthalten sein sollten, sollten Sie den Zustand als zusätzliche Verteidigungsmaßnahme immer verschlüsseln.

Secrets nicht im Zustand speichern

Es gibt viele Ressourcen und Datenanbieter in Terraform, die Secret-Werte als Klartext in der Zustandsdatei speichern. Vermeiden Sie nach Möglichkeit das Speichern von Secrets im Zustand. Im Folgenden finden Sie einige Beispiele von Anbietern, die Secrets als Klartext speichern:

Vertrauliche Ausgaben markieren

Anstatt vertrauliche Werte manuell zu verschlüsseln, nutzen Sie die integrierte Terraform-Unterstützung für die Verwaltung vertraulicher Zustände. Achten Sie beim Exportieren vertraulicher Werte für die Ausgabe darauf, dass die Werte als vertraulich markiert sind.

Aufgabentrennung sicherstellen

Wenn Sie Terraform nicht von einem automatisierten System aus ausführen können, auf das keine Nutzer Zugriff haben, müssen Sie eine Aufgabentrennung vornehmen, indem Sie Berechtigungen und Verzeichnisse trennen. Ein Netzwerkprojekt entspricht beispielsweise einem Netzwerk-Terraform-Dienstkonto oder einem Nutzer, dessen Zugriff auf dieses Projekt beschränkt ist.

Prüfungen vor dem Anwenden ausführen

Wenn Sie Terraform in einer automatisierten Pipeline ausführen, verwenden Sie ein Tool wie gcloud terraform vet, um die Planausgabe auf Richtlinien zu prüfen, bevor sie angewendet wird. Auf diese Weise können Sie Sicherheitsregressionen erkennen, bevor sie auftreten.

Kontinuierliche Audits ausführen

Führen Sie nach der Ausführung des Befehls terraform apply automatisierte Sicherheitsprüfungen aus. Diese Prüfungen sorgen dafür, dass die Infrastruktur nicht in einen unsicheren Zustand versetzt wird. Für diesen Diagnosetyp stehen die folgenden Tools zur Verfügung:

Test

Das Testen von Terraform-Modulen und -Konfigurationen folgt manchmal anderen Mustern und Konventionen als das Testen von Anwendungscode. Beim Testen von Anwendungscode geht es hauptsächlich darum, die Geschäftslogik von Anwendungen zu testen. Für den vollständigen Test von Infrastrukturcode müssen jedoch echte Cloud-Ressourcen bereitgestellt werden, um das Risiko von Produktionsausfällen zu minimieren. Beim Ausführen von Terraform-Tests sind einige Überlegungen zu beachten:

  • Durch das Ausführen eines Terraform-Tests wird eine echte Infrastruktur erstellt, geändert und gelöscht, sodass Ihre Tests möglicherweise zeitaufwendig und teuer sind.
  • Sie können für eine reine End-to-End-Architektur nicht nur Unittests ausführen. Der beste Ansatz besteht darin, Ihre Architektur in Module aufzuteilen und diese einzeln zu testen. Zu den Vorteilen dieses Ansatzes gehören eine schnellere iterative Entwicklung aufgrund einer schnelleren Testlaufzeit, geringere Kosten für jeden Test und eine geringere Wahrscheinlichkeit von fehlgeschlagenen Tests aufgrund von Faktoren, die außerhalb Ihrer Kontrolle liegen.
  • Vermeiden Sie nach Möglichkeit die Wiederverwendung des Zustands. Es kann Situationen geben, in denen Sie Tests mit Konfigurationen durchführen, die Daten mit anderen Konfigurationen teilen. Idealerweise sollte jedoch jeder Test unabhängig sein und den Zustand nicht testübergreifend wiederverwenden.

Zuerst kostengünstigere Testmethoden verwenden

Es gibt mehrere Methoden, mit denen Sie Terraform testen können. In aufsteigender Reihenfolge der Kosten, Laufzeit und Tiefe beinhalten sie Folgendes:

  • Statische Analyse: Testen der Syntax und Struktur Ihrer Konfiguration ohne Bereitstellung von Ressourcen und mit Tools wie Compilern, Linting und Probeläufen. Verwenden Sie dazu terraform validate.
  • Testen der Modulintegration: Testen Sie, ob die Module ordnungsgemäß funktionieren, indem Sie einzelne Module isoliert testen. Integrationstests für Module umfassen die Bereitstellung des Moduls in einer Testumgebung und die Überprüfung, ob erwartete Ressourcen erstellt werden. Es gibt mehrere Test-Frameworks, die das Schreiben von Tests erleichtern:
  • End-to-End-Tests: Wenn Sie den Ansatz für Integrationstests auf eine gesamte Umgebung erweitern, können Sie überprüfen, ob mehrere Module zusammenarbeiten. Bei diesem Ansatz stellen Sie alle Module, aus denen die Architektur besteht, in einer neuen Testumgebung bereit. Idealerweise ist die Testumgebung Ihrer Produktionsumgebung so ähnlich wie möglich. Dies ist kostspielig, bietet jedoch die größte Sicherheit, dass Änderungen Ihre Produktionsumgebung nicht beeinträchtigen.

Klein anfangen

Achten Sie darauf, dass Ihre Tests iterativ aufeinander aufbauen. Erwägen Sie, zuerst kleinere Tests auszuführen und dann mit einem Fail-Fast-Ansatz zu komplexeren Tests überzugehen.

Projekt-IDs und Ressourcennamen zufällig festlegen

Um Namenskonflikte zu vermeiden, sollten Sie darauf achten, dass Ihre Konfigurationen innerhalb jedes Projekts eine global eindeutige Projekt-ID und nicht überlappende Ressourcennamen haben. Verwenden Sie dazu Namespaces für Ihre Ressourcen. Terraform hat dafür einen integrierten zufälligen Anbieter.

Separate Umgebung zum Testen verwenden

Während des Tests werden viele Ressourcen erstellt und gelöscht. Achten Sie darauf, dass die Umgebung von Entwicklungs- oder Produktionsprojekten isoliert ist, um versehentliches Löschen während der Ressourcenbereinigung zu vermeiden. Die beste Vorgehensweise besteht darin, für jeden Test ein neues Projekt oder einen neuen Ordner zu erstellen. Um Konfigurationsfehler zu vermeiden, sollten Sie Dienstkonten speziell für jede Testausführung erstellen.

Alle Ressourcen bereinigen

Wenn Sie Infrastrukturcode testen, stellen Sie tatsächliche Ressourcen bereit. Implementieren Sie einen Bereinigungsschritt, um Gebühren zu vermeiden.

Verwenden Sie den Befehl terraform destroy, um alle von einer bestimmten Konfiguration verwalteten Remote-Objekte zu löschen. Einige Test-Frameworks haben einen integrierten Bereinigungsschritt für Sie. Wenn Sie beispielsweise Terratest verwenden, fügen Sie Ihrem Test defer terraform.Destroy(t, terraformOptions) hinzu. Wenn Sie Kitchen-Terraform verwenden, löschen Sie Ihren Arbeitsbereich mit terraform kitchen delete WORKSPACE_NAME.

Führen Sie nach Ausführung des Befehls terraform destroy auch weitere Bereinigungsvorgänge aus, um alle Ressourcen zu entfernen, die Terraform nicht löschen konnte. Löschen Sie dazu alle für die Testausführung verwendeten Projekte oder verwenden Sie ein Tool wie das project_cleanup-Modul.

Testlaufzeit optimieren

Gehen Sie so vor, um die Testausführungszeit zu optimieren:

  • Tests parallel ausführen. Einige Test-Frameworks unterstützen die gleichzeitige Ausführung mehrerer Terraform-Tests.
    • Mit Terratest ist dies beispielsweise durch Hinzufügen von t.Parallel() nach der Definition der Testfunktion möglich.
  • In Phasen testen. Teilen Sie Ihre Tests in unabhängige Konfigurationen auf, die separat getestet werden können. Bei diesem Ansatz müssen Sie nicht mehr alle Phasen eines Tests durchlaufen und beschleunigen den iterativen Entwicklungszyklus.
    • Teilen Sie in Kitchen-Terraform beispielsweise Tests in separate Suites auf. Führen Sie beim Iterieren jede Suite unabhängig aus.
    • Analog können Sie mit Terratest jede Phase des Tests mit stage(t, STAGE_NAME, CORRESPONDING_TESTFUNCTION) zusammenfassen. Legen Sie Umgebungsvariablen fest, die angeben, welche Tests ausgeführt werden sollen. Beispiel: SKIPSTAGE_NAME="true"
    • Das Blueprint-Test-Framework unterstützt die gestaffelte Ausführung.

Nächste Schritte