Nachdem Terraform auf dem Rechner installiert ist, mit dem wir unsere ersten Cloud-Ressourcen bereitstellen wollen, ist das erste Beispiel in Kapitel 2 die Bereitstellung einer VM. Einer Linux-basierten VM. Alle Beispiele aus dem Buch von Yevgeniy findet ihr in diesem GitHub-Repo.

Wie machen wir das in Azure?

Die erste VM in Azure bereitstellen

Ein wesentlicher Unterschied zu AWS ist, dass wir in Azure Ressourcen immer in einer Resource Group (RG) bereitstellen – daher ist das Skript in Terraform nicht so einfach wie in AWS.

    resource "azurerm_resource_group" "RG" {
        name = "myFirstTerraform-RG"
        location = "westeurope"
}

Mit der bereitgestellten Resource Group können wir die VM darin platzieren. Das ist aber nicht alles, was wir für eine VM in Azure brauchen. Schauen wir uns die Dokumentation des Azure-Providers bei HashiCorp für die “azurerm_virtual_machine” an. Beim Lesen der Einführung sehen wir, dass es einen weiteren Ressourcentyp gibt, der sich direkt auf Linux-VMs fokussiert - “azurerm_linux_virtual_machine”.

In der Dokumentation finden wir folgende erforderliche Konfigurationsargumente für eine Linux-VM:

  • admin_username
  • location
  • name
  • network_interface_ids
  • os_disk
  • resource_group_name
  • size

und eines davon:

  • admin_password
    (wenn diese Option verwendet wird, muss disable_password_authentication auf false gesetzt werden)
  • admin_ssh_key

Verglichen mit einem einfachen Beispiel aus Kapitel 2 des Buches scheint Azure komplexer zu sein. Für mich sieht es so aus, als ob man in AWS ein Template nutzen kann und AWS jede benötigte Konfiguration mit Standardwerten ergänzt (siehe Seite 43). Der Azure Resource Manager benötigt die Konfiguration, die vom Admin bzw. dem Skript definiert wird.

Also zurück zu unserem Skript.

Wir haben bereits die RG darin. Jetzt müssen wir ein Netzwerk bereitstellen:

resource "azurerm_virtual_network" "vnet" {
  name                = "myFirstVnet"
  address_space       = ["10.0.0.0/16"]
  location            = "myFirstTerraform-RG"
  resource_group_name = "westeurope"
}

Im Buch wird das Konzept von “DRY” später erklärt, aber ich denke, es ist gut, jetzt schon daran zu denken. “DRY” bedeutet “don’t repeat yourself” (Wiederhole dich nicht). Es wäre keine gute Idee, die Location und den Resource-Group-Namen durch die vorher definierten Werte direkt einzugeben. Es ist besser, auf die Werte zu verweisen.

_{resource}.{name}.{config}_

Wenn wir den Namen der RG referenzieren wollen, müssen wir verwenden:

azurerm_resource_group.myFirstTerraform-RG.name

und das Gleiche mit der Location aus der Definition der RG:

azurerm_resource_group.myFirstTerraform-RG.location

Im Code sieht das dann so aus:

resource "azurerm_virtual_network" "vnet" {
  name                = "myFirstVnet"
  address_space       = ["10.0.0.0/16"]
  location            = azurerm_resource_group.myFirstTerraform-RG.name
  resource_group_name = azurerm_resource_group.myFirstTerraform-RG.location
}

Wichtig zu verstehen ist, dass Terraform nun in der Lage ist, Abhängigkeiten zwischen den Ressourcen zu erkennen und die Reihenfolge der Bereitstellung zu steuern (siehe Kapitel 2 im Buch, Seite 51). Die nächsten Schritte wären, alle benötigten Ressourcen inklusive Referenzen sowie die VM selbst hinzuzufügen.

Das vollständige Skript sieht so aus:

## Die erste VM in Azure bereitstellen
## basierend auf einem Ubuntu-Image

## Provider definieren
provider "azurerm" {
  version = "~>2.0.0"
  features {}
}

## Resource Group definieren
resource "azurerm_resource_group" "myFirstTerraform" {
  name     = "myFirstTerraform-RG"
  location = "West Europe"
}

## Netzwerk definieren
resource "azurerm_virtual_network" "myFirstTerraform" {
  name                = "myFirstTerraform-vNet"
  address_space       = ["10.0.0.0/16"]
  location            = azurerm_resource_group.myFirstTerraform.location
  resource_group_name = azurerm_resource_group.myFirstTerraform.name
}

## Subnetz definieren
resource "azurerm_subnet" "myFirstTerraform" {
  name                 = "internal"
  resource_group_name  = azurerm_resource_group.myFirstTerraform.name
  virtual_network_name = azurerm_virtual_network.myFirstTerraform.name
  address_prefix       = "10.0.2.0/24"
}

## Öffentliche IP für SSH-Zugang zur VM
resource "azurerm_public_ip" "myFirstTerraform" {
  name                = "myFirstTerraform-pip"
  location            = azurerm_resource_group.myFirstTerraform.location
  resource_group_name = azurerm_resource_group.myFirstTerraform.name
  allocation_method   = "Dynamic"
}

## Netzwerkinterface definieren
resource "azurerm_network_interface" "myFirstTerraform" {
  name                = "myFirstTerraform-nic"
  location            = azurerm_resource_group.myFirstTerraform.location
  resource_group_name = azurerm_resource_group.myFirstTerraform.name

  ip_configuration {
    name                          = "internal"
    subnet_id                     = azurerm_subnet.myFirstTerraform.id
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id          = azurerm_public_ip.myFirstTerraform.id
  }
}

## VM definieren
resource "azurerm_linux_virtual_machine" "myFirstTerraform" {
  name                = "myFirstTerraform-vm"
  resource_group_name = azurerm_resource_group.myFirstTerraform.name
  location            = azurerm_resource_group.myFirstTerraform.location
  size                = "Standard_B2s"
  computer_name = "myFirstLinuxVM"
  admin_username = "adminuser"
  admin_password = "Password1234!"
  disable_password_authentication = false
  network_interface_ids = [
    azurerm_network_interface.myFirstTerraform.id,
  ]

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

  source_image_reference {
    publisher = "Canonical"
    offer     = "UbuntuServer"
    sku       = "16.04-LTS"
    version   = "latest"
  }
}

ACHTUNG Wir haben keine Firewall vor der VM definiert. In Azure benötigen wir z.B. eine Netzwerksicherheitsgruppe (NSG), die dem Netzwerkinterface zugewiesen werden sollte.

Die NSG im Skript:

resource "azurerm_network_security_group" "myFirstTerraform" {
    name                = "myFirstTerraform-NSG"
    location            = azurerm_resource_group.myFirstTerraform.location
    resource_group_name = azurerm_resource_group.myFirstTerraform.name

    security_rule {
        name                       = "SSH"
        priority                   = 1001
        direction                  = "Inbound"
        access                     = "Allow"
        protocol                   = "Tcp"
        source_port_range          = "*"
        destination_port_range     = "22"
        source_address_prefix      = "*"
        destination_address_prefix = "*"
    }
}

Und die Zuordnung zum Netzwerkinterface:

resource "azurerm_network_interface_security_group_association" "myFirstTerraform" {
    network_interface_id      = azurerm_network_interface.myFirstTerraform.id
    network_security_group_id = azurerm_network_security_group.myFirstTerraform.id
}

Sobald ich das gesamte Kapitel 2 abgeschlossen habe, werde ich ein GitHub-Repo mit allen Informationen bereitstellen.