Terraform war bereits häufiger Thema in unserem Blog. Die "infrastructure as code" Lösung von dem Softwareunternehmen Hashi Corp ist in der openFORCE regelmäßig im Einsatz. Die Kernfunktionen von Terraform sind, dank starkem Marketing, mittlerweile weitgehend bekannt:

  • Infrastruktur als Code
  • Installierbare Module
  • Planen und Vorhersehen von Veränderungen
  • Abhängigkeitsgraphen
  • Management von Zuständen
  • Eigene Registry mit 500+ Providern

Diese Punkte sind der Dokumentation aufgeführt und lassen schon die Frage aufkommen: Was ist da dran?

Immerhin ist es nicht unüblich, im Bereich Technologie jede neue Lösung als den heiligen Gral zu verkaufen, während sich die Branche regelmäßig selbst erneuert und kaum Tools und Lösungen eine Halbwertszeit von 5 Jahren überdauern.

Im Rahmen unserer openINNOVATION Days im Februar 2021 sind wir daher einer der Funktionen, die wir bisher selber nicht großartig genutzt haben, auf den Grund gegangen. Wir haben uns den unterschiedlichen Providern gewidmet, die als Kernelement von Terraform dafür sorgen, dass Infrastruktur von allen großen Cloud-Providern via der HCL orchestriert werden kann.

In diesem Artikel nehmen wir konkret die Unterschiede zweier Provider im Bereich grundlegendes Networking als Beispiel.

Die Ausgangssituation (Hetzner)

Die openFORCE setzt aktuell auf Hetzner als Cloud Provider und nutzt den Terraform Provider der Firma zum automatischen Aufbau einer Cloud Infrastruktur mit einem Nomad Cluster, mehrerer Consul Server, eines Reverse Proxies, einem Bastion Host und mehreren weiteren Servern. Für die Provisionierung der einzelnen Server wird Ansible verwendet. Der Fokus in der Darstellung liegt in diesem Fall auf den Servern aus dem folgenden Bild:

Es gilt also, dass genau 2 Schnittstellen nach außen existieren. Das wird im Hetzner Provider trivial handgehabt. Es wird lediglich ein Netzwerk angelegt in welchem alle Server eine statische IP-Adresse erhalten. Während der Installation wird dann eigenständig nach dem erstellen der Server via Ansible eine Firewall aufgesetzt. Das Firewalling findet also auf der Ebene jedes einzelnen Servers statt. Damit für diese Installation auf die Server zugegriffen werden kann, werden auf jedem Server die in der Hetzner Cloud hinterlegten ssh-keys der Administratoren auf den Servern hinterlegt.

Ein Server könnte exemplarisch so erstellt werden:

resource "hcloud_server" "example_server" {
  name = "${var.prefix}example_server"
  location = var.location
  image = "debian-10"
  server_type = "cx11"
  ssh_keys = data.hcloud_ssh_keys.all_keys.ssh_keys.*.name
  network {
    network_id = hcloud_network.cloud_net.id
    ip         = "10.0.2.5"
  }
  connection {
    private_key = file(var.private_key_file)
    host = self.ipv4_address
  }
  provisioner "remote-exec" {
    connection {
      type        = "ssh"
      private_key = file(var.private_key_file)
      user        = "root" 
    }
    inline = ["apt install python -y"]
  }

  provisioner "local-exec" {
    command = "ansible-playbook -i '${self.ipv4_address},' -u root --private-key ${var.private_key_file} playbooks/site.yml"
  }
}

Im Anschluss ist er nicht mehr via dem Internet, sondern nur noch via dem in der Firewall konfigurierten Adressbereich erreichbar. Der Bastion host wird konfiguriert, um nur noch via ssh erreichbar zu sein, sodass er als Einstiegspunkt fungiert, während der Proxy-Server entsprechende HTTP(S) Ports für die Kommunikation zulässt.

Die andere Seite (GCP)

So weit so gut. Doch was macht die Google Cloud Platform an dieser Stelle anders?

Der grundlegende Unterschied besteht im Firewalling, da Google grundsätzlich eine Firewall anlegt, um Kommunikation bevor ein Server gestartet wird bereits zu verhindern:

# Firewall setup
resource "google_compute_firewall" "allow-internal" {
  name    = "${var.cloud_prefix}-fw-allow-internal"
  network = google_compute_network.cloud_net.id
  allow {
    protocol = "icmp"
  }
  allow {
    protocol = "tcp"
    ports    = ["0-65535"]
  }
  allow {
    protocol = "udp"
    ports    = ["0-65535"]
  }
  source_ranges = [
    google_compute_subnetwork.public_subnet.ip_cidr_range,
    google_compute_subnetwork.private_subnet.ip_cidr_range
  ]
}

resource "google_compute_firewall" "allow-http" {
  name    = "${var.cloud_prefix}-fw-allow-http"
  network = google_compute_network.cloud_net.name
  allow {
    protocol = "tcp"
    ports    = ["80","443"]
  }
  target_tags = ["http"] 
}

resource "google_compute_firewall" "allow-bastion" {
  name    = "${var.cloud_prefix}-fw-allow-bastion"
  network = google_compute_network.cloud_net.name
  allow {
    protocol = "tcp"
    ports    = ["22"]
  }
  target_tags = ["ssh"]
}

Diese praktische Variante ermöglicht e, die erstellten Server direkt von außen komplett unzugänglich zu machen. Somit kann ein Server, der nicht mit den definierten Tags (ssh,http) versehen ist nur via dem Bastion Host aufgerufen werden:

resource "google_compute_instance" "example_server" {
  name = "${var.cloud_prefix}example_server"
    tags          = ["http"]
  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-10"
    }
  }  
  machine_type = "custom-1-2048"
  metadata = { ssh-keys = "root:${file("~/.ssh/ssh_keys.pub")}"}
  network_interface{
    subnetwork = google_compute_subnetwork.private_subnet.name
    network_ip = "10.0.2.5"
    access_config {
      # Ephemeral IP
    }
  }
  provisioner "remote-exec" {
    connection {
      bastion_host= google_compute_instance.bastion.network_interface.0.access_config.0.nat_ip
      host = self.network_interface.0.network_ip
      private_key = file(var.private_key_file)
      user        = "root" 
    }
    inline = ["sleep 5"]
  }
  metadata_startup_script = "apt install python -y"
  provisioner "local-exec" {
    command = "ansible-playbook -i '${self.network_interface.0.network_ip},' -u root --private-key ${var.private_key_file} playbooks/site.yml"
  }
  depends_on = [ google_compute_subnetwork.private_subnet ]
}

Terraform ist nicht gleich Terraform

Dieser Unterschied ist nur ein Beispiel von vielen zwischen diesen beiden und etlichen weiteren Providern, die in der Terraform Registry bereitstellt werden. Denn auch wenn die praktische Deklaration von Infrastruktur und die programmatische Provision via Ansible uns das Leben einfacher machen gilt in der IAC Welt:

Jeder Cloud Provider kocht sein eigenes Süppchen

Deshalb sollte bei der Wahl des Cloud Providers nicht nur der Kostenfaktor, sondern auch die vorhandenen Funktionen, sowie die mitgelieferten Integrationen beachtet werden. Da jeder Anbieter etliche eigene Funktionen auf seiner Platform bereitstellt unterscheiden sich auch die Provider in Terraform teils grundlegend, daher helfen alle auf der Ebene von Terraform getroffenen Konfigurationen nur solange eine äquivalente Stanza bei einem neuen Provider vorhanden ist. Am Ende des Tages gilt also das, was immer für die Übertragbarkeit in der IT greift:

Konzepte sind übertragbar, Umsetzungen eher selten

Unsere Exepertise in der Umsetzung findet sich in allen unseren Lösungen wieder. Solltest du Unterstützungsbedarf bei der Umsetzung eines DevOps-Konzeptes benötigen, kontaktiere uns gerne! PS: Gerne bewerten wir auch bereits fertige Konzepte ;-)