Portfolio |

Terraform – getting started

Zuletzt haben wir einen kleinen Einblick in das Unternehmen HashiCorp und dessen Toolchain – mit Fokus auf Terraform – gegeben. Mit diesem Artikel folgt nun ein ausführlicher “Getting Started”-Guide.

In unserem Video haben wir einen kleinen Einblick in das Unternehmen HashiCorp gegeben.

Bevor wir mit der eigentlichen Entwicklung starten fassen wir kurz zusammen, was Terraform ist und warum es sich so gut für die Bereitstellung von Infrastruktur eignet.

Terraform ist ein Open Source Infrastructure as Code-Tool der Firma HashiCorp, mit dem sich komplexe Infrastrukturumgebungen erstellen und verwalten lassen. Dabei hebt sich Terraform von anderen Tools dieser Art – Cloudformation [AWS], ARM-Templates [Azure] – mit seiner Plattformunabhängigkeit ab. Terraform ist aber nicht nur auf Cloud Provider und die dortigen Services begrenzt, sondern ermöglicht auch die Verwaltung und Provisionierung virtueller und physischer Infrastrukturkomponenten oder die Anbindung und Verwaltung von Umsystemen, wie beispielsweise git. Aktuell umfasst die Terraform Registry ca. 1.250 Provider, von denen einige von den Providern selbst gepflegt werden, unter anderem Azure, AWS oder GCP. Weitere Provider werden durch die Terraform-Community oder durch HashiCorp gepflegt, verifiziert und erweitert (https://registry.terraform.io/). Als Definitionssprache verwendet Terraform die “HashiCorp Configuration Language” (HCL), die JSON sehr ähnlich ist.

Unser Ziel

Ziel der Aufgabe in unserem “Getting Started”-Guide, soll die Provisionierung von Infrastruktur-Ressourcen sein. Um den Spagat zwischen einfachem Einstieg und Real Life-Szenario zu schaffen, provisionieren wir die Infrastruktur-Ressourcen mit Terraform in der Microsoft Azure Cloud. Für die Authentifizierung gegen Azure nutzen wir die Application Registration mit einem Client Secret (https://docs.microsoft.com/de-de/azure/active-directory/develop/quickstart-register-app). Doch zunächst wollen wir unsere Arbeitsfähigkeit herstellen und Terraform installieren.

Einfache Installation

Die Installation von Terraform gestaltet sich sehr simpel. Die einfachste Möglichkeit ist es, Terraform über einen Paketmanager zu installieren. Dies funktioniert sowohl für die gängigsten Linux-Versionen (Ubuntu, Debian, RHEL, CentOS, Fedora), als auch über Homebrew auf MacOS oder Chocolatey auf Microsoft Windows. Alternativ kann auch das für das Betriebssystem benötigte Binary heruntergeladen und im gewünschten Projektordner abgelegt werden. Ebenso kann das Binary auch global über eine “PATH environment variable” zugänglich gemacht werden. Eine genaue Anleitung finden Sie hier (https://learn.hashicorp.com/tutorials/terraform/install-cli?in=terraform/certification-associate-tutorials).

Die Erstellung eines Terraform Projektes

Um ein neues Terraform Projekt zu erstellen, genügt es einen entsprechenden Ordner anzulegen und dort ein main.tf-File zu erstellen, in dem der Code abgelegt wird. Bei Terraform wird der gesamte Code in einer main.tf-Datei abgelegt.

Schritt für Schritt

Nun gilt es, innerhalb der Konfigurationsdatei den gewünschten Provider zu definieren. So weiß Terraform, welche Provider-Libraries es zur Laufzeit laden und nutzen muss.

provider "azurerm" {
  version = "2.12.0"

  subscription_id = “12345”
  client_id       = “12345”
  client_secret   = “12345”
  tenant_id       = „12345“

  features {}
}

Die hier dargestellten Felder müssen mit den entsprechenden Werten aus der Application Registration befüllt werden. Der Einfachheit halber haben wir im Beispiel diese direkt in der main.tf eingepflegt. An dieser Stelle sei aber bereits gesagt, dass es ebenso möglich – und empfohlen – ist, diese Werte aus einer separaten Variablendatei oder einer Credential-Verwaltung, wie beispielsweise HashiCorp Vault, auszulesen.

Bei der Angabe des Providers ist die Version zu beachten. Terraform verwendet, wenn nicht näher spezifiziert, die „latest“-Version. Um Kompatibilitätsproblemen im späteren Verlauf vorzubeugen, empfehlen wir daher immer die Providerversionsnummer in der main.tf anzugeben.

Nachdem der Provider bestimmt wurde, können nun die Ressourcen definiert werden. Für die Erstellung einer einfachen virtuellen Maschine in Azure benötigen wir die folgenden zusätzlichen Ressourcen:

  • Ressourcengruppe (RG)
  • Virtuelles Netzwerk (VNET)
  • Subnetz (SNET)
  • Netzwerksicherheitsgruppe (NSG)
  • Netzwerkinterface (NIC)
  • Öffentliche IP-Adresse (PIP)

Beginnen wir mit der Erstellung einer Ressourcengruppe, um an dieser den Aufbau einer Ressource in Terraform zu betrachten.

Das Schlüsselwort resource verdeutlicht, dass im Anschluss eine Ressourcenbeschreibung folgt. Nun folgt die Art der resource, in diesem Fall eine azurerm_resource_group. Im Anschluss wird ein interner Name der Ressource festgelegt, mit welchem auf diese Ressource referenziert werden kann (example_rg).

Die Ressource hat zwei notwendige Argumente: den Namen der RG in Azure (name) und die Lokation (location).

resource "azurerm_resource_group" "example_rg" {
  name     = "RG_LINUX_VM"
  location = "West Europe"
}

Hier sehen wir den typischen Aufbau einer Terraform-Ressource. Die Anzahl der optionalen und notwendigen Argumente kann – je nach Ressourcen-Typ – variieren und in der Dokumentation der Ressource nachvollzogen werden.

resource "<Ressourcen Typ>" "<Referenzname der Ressource>" {
  <Argument1> = "<Wert 1>"
  <Argument2> = "<Wert 2>"
}

Im nächsten Schritt werden das virtuelle Netzwerk, das Subnetz, die Netzwerksicherheitsgruppe (NSG), das Netzwerkinterface, die Verknüpfung der NSG und der NIC sowie eine öffentliche IP-Adresse nach gleichem Schema definiert.

resource "azurerm_virtual_network" "vnet_linux" {
  name                = "vnet_terraform_linux"
  location            = azurerm_resource_group.example_rg.location
  resource_group_name = azurerm_resource_group.example_rg.name
  address_space       = ["10.0.2.0/16"]
  dns_servers         = ["10.0.0.4", "10.0.0.5"]
}

resource "azurerm_subnet" "linux_subnet" {
  name                 = "subnet_terraform_linux"
  resource_group_name  = azurerm_resource_group.example_rg.name
  virtual_network_name = azurerm_virtual_network.vnet_linux.name
  address_prefixes       = ["10.0.2.0/24"]
}

resource "azurerm_public_ip" "linux_public_ip" {
  name                    = "public_ip_terraform_linux"
  location                = azurerm_resource_group.example_rg.location
  resource_group_name     = azurerm_resource_group.example_rg.name
  allocation_method       = "Static"
  idle_timeout_in_minutes = 30
}

resource "azurerm_network_interface" "linux_nic" {
  name                = "nic_terraform_linux"
  location            = azurerm_resource_group.example_rg.location
  resource_group_name = azurerm_resource_group.example_rg.name

  ip_configuration {
    name                          = "private_ip_terraform_linux"
    subnet_id                     = azurerm_subnet.linux_subnet.id
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id          = azurerm_public_ip.linux_public_ip.id
    }
}

resource "azurerm_network_security_group" "linux_nsg" {
  name                = "nsg_terraform_linux"
  location            = azurerm_resource_group.example_rg.location
  resource_group_name = azurerm_resource_group.example_rg.name

   security_rule {
    name                       = "linux_SSH_access"
    priority                   = 120
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "*"
    source_port_range          = "*"
    destination_port_range     = "22"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }

}

resource "azurerm_network_interface_security_group_association" "assc_nsg_nic_linux" {
  network_interface_id      = azurerm_network_interface.linux_nic.id
  network_security_group_id = azurerm_network_security_group.linux_nsg.id
}

Nachdem alle notwendigen Ressourcen definiert wurden, erfolgt nun die Definition der virtuellen Maschine. Wie im Code gezeigt, ist es möglich, auf bereits definierte Infrastruktur und deren Attribute zu referenzieren. Wie unten zu sehen, kann ein lokales (ssh) Key-File auf der zu erstellenden Maschine ausgerollt werden.

resource "azurerm_linux_virtual_machine" "linux_vm" {
  name                = "vm_terraform_linux"
  resource_group_name = azurerm_resource_group.example_rg.name
  location            = azurerm_resource_group.example_rg.location
  size                = "Standard_B1s"
  admin_username      = "adminuser"
  network_interface_ids = [
    azurerm_network_interface.linux_nic.id,
  ]

  admin_ssh_key {
    username   = "adminuser"
    public_key = file("./.ssh/bastion-key-rsa.pub")
  }

  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }

  source_image_reference {
    publisher = "OpenLogic"
    offer     = "CentOS"
    sku       = "8_2"
    version   = "latest"
  }

}

Die Initialisierung

Nachdem alle Ressourcen definiert wurden, sollen diese nun provisioniert werden. Das geschieht über die Kommandozeile. Dafür muss zunächst das Projekt initialisiert werden, was wir über den folgenden Befehl erreichen:

Dabei wird ein Ordner mit dem für das Deployment notwendigen Azure-Plugin bereitgestellt.

Der Output sollte ähnlich dem unteren Bild sein.

Der nützliche Preflight Check

Um potenzielle Auswirkungen auf die bestehende und durch Terraform verwaltete Infrastruktur durch Änderungen an der Konfigurationsdatei nachvollziehen zu können, kann der nachfolgende Befehl genutzt werden:

Der daraus folgende Output stellt eine Übersicht über die Ressourcenveränderungen dar, d.h. welche Ressourcen bei einer Ausführung hinzugefügt, geändert und gelöscht werden. Auch Konfigurationsfehler können oft schon in diesem Schritt – und somit vor der Ausführung – identifiziert werden.

Beispielhaft das Ergebnis für die virtuelle Maschine im Bild unten.

Provisionierung

Nun kann die eigentliche Provisionierung erfolgen. Dies geschieht mittels des „apply“ Comands:

Nach einer Bestätigung mit “yes”, dass die Änderungen durchgeführt werden sollen, wird Terraform beginnen, die definierten Ressourcen in der Microsoft Azure-Umgebung zu erstellen. Die Reihenfolge, etwaige Abhängigkeiten der Ressourcen untereinander sowie eine Parallelisierung der Ressourcenerstellung übernimmt dabei vollständig Terraform. Wurden alle Ressourcen erfolgreich erstellt, erscheint eine Erfolgsmeldung und die öffentlich IP-Adresse der VM wird auf der Konsole ausgegeben.

Mit der Erstellung der Ressourcen wird im Arbeitsverzeichnis eine Datei namens “terraform.tfstate” erstellt. Diese dient Terraform als Single Point of Trust. Das bedeutet, sämtliche Änderungen in der Konfiguration werden gegen dieses Statefile validiert und auf den entsprechenden Umgebungen ausgeführt. Manuelle Änderungen an Ressourcen können von Terraform nicht wahrgenommen oder berücksichtigt werden und zu Fehlern bei der Provisionierung führen. Manuelle Änderungen am Statefile sollten unbedingt vermieden werden!

Schaut man nun in die Azure Umgebung, wurden die Ressourcen angelegt und die VM deployed.

Mittels ssh und des entsprechenden privaten Schlüssels ist es nun möglich, sich auf die erstellte Linux-VM zu verbinden.

Deprovisionierung

Um die erstellten Ressourcen wieder zu entfernen, gibt es zwei Möglichkeiten. Möchte man gezielt eine bestimmte Ressource löschen, genügt es bereits, diese aus der Konfigurationsdatei zu entfernen und mittels terraform apply die aktuelle Konfiguration auszurollen.

Für den Fall, dass alle erstellten Ressourcen gelöscht werden sollen, nutzt Terraform den Destroy-Befehl:

Damit werden alle im Statefile definierten Ressourcen entfernt.

Ein kurzes Fazit

Im oben gezeigten Beispiel konnte auf schnelle und einfache Weise eine virtuelle Maschine samt benötigter Azure-Ressourcen erstellt werden. Dies ist einer der großen Vorteile von Terraform: Der Einstieg ist simpel, die Dokumentation nachvollziehbar, die Community einladend und es sind schnell erste Erfolge zu erzielen. Mit wachsender Komplexität der benötigten Infrastrukturen steigt natürlich der Aufwand, auch innerhalb von Terraform-Projekten. Unterm Strich spart man jedoch durch die konsequente Anwendung von Infrastructre-as-Code gleich an mehreren Stellen sehr viel Zeit.

  1. Ressourcen müssen nicht „zusammengeklickt“ werden
  2. Die komplette Infrastruktur ist dokumentiert, Schatten-IT wird unterbunden
  3. Ressourcen können im Fehlerfall/Ausfall jederzeit neu deployed werden
  4. Die Infrastruktur kann gemäß Auslastung unbegrenzt hoch- und runterskaliert werden

Wichtig ist zu beachten, dass sich Terraform als reines Tool für die Pro- und Deprovisionierung von Infrastruktur versteht und nicht dafür genutzt werden sollte, um die erstellten Ressourcen im späteren Verlauf zu konfigurieren und beispielsweise Software darauf zu installieren.

Durch die deklarative Beschreibung wird der gewünschte Endzustand einer Umgebung beschrieben, nicht wie dieser zu erreichen ist. Dies macht es auch für fachfremde Nutzer einfach, den Code zu lesen und zu verstehen.

Herzlichen Glückwunsch, damit sind Sie am Ende unseres “Getting Started”-Guides mit Terraform angekommen und haben erfolgreich Terraform installiert, mittels Terraform eine VM auf Microsoft Azure automatisiert ausgerollt und wieder gelöscht. Wir hoffen, der Guide hat Ihnen gefallen und Sie konnten sich ein erstes Bild von der Arbeit mit Terraform machen.

Wir freuen uns, Sie in Zukunft in einem unserer Terraform-Webinare zu begrüßen und stehen Ihnen in der Zwischenzeit gerne beratend zur Seite.

Bei Fragen kontaktieren Sie uns gern per E-Mail.