ADMIN

2024

11

2024-10-30T12:00:00

Cloudmanagement

SCHWERPUNKT

080

Cloudinfrastruktur

Terraform

Aufbau einer Landing Zone mit Terraform

Im Anflug

von Dr. Guido Söldner

Veröffentlicht in Ausgabe 11/2024 - SCHWERPUNKT

Der Einsatz einer Landing Zone gehört zu den Best Practices und zum Startpunkt jeder Cloudinfrastruktur. Dabei empfiehlt es sich, einen Infrastructure-as-Code-Ansatz für den Aufbau der Cloudumgebung zu wählen und damit die Bereitstellung von Ressourcen zu ermöglichen. Unser Workshop zeigt, was eine Landing Zone erfordert, erklärt deren Vorteile und zeigt den Aufbau einer Landing Zone für die Google-Cloud mittels Terraform.

Eine Landing Zone (LZ) zielt darauf ab, die Einrichtung der Cloudumgebung zu optimieren und zu standardisieren. Durch das Befolgen von Best Practices hilft eine Landing Zone, Wiederverwendung und Standardisierung sicherzustellen und die Einhaltung vereinbarter Richtlinien zu erzwingen. Dabei erfolgt der Betrieb mittels Infrastructure-as-Code (IaC).
Gründe für eine Landing Zone
Die Vorteile der Bereitstellung einer Landing Zone sind vielfältig und für Firmen, die ihre Cloudumgebungen effektiv und sicher verwalten möchten, von entscheidender Bedeutung. Für den Aufbau einer Landing Zone spricht zunächst einmal die Standardisierung. Eine LZ bietet einen standardisierten Ansatz zur Einrichtung und Konfiguration von Cloudumgebungen. Dies stellt sicher, dass alle Deployments denselben Verfahren, Konfigurationen und Sicherheitsstandards folgen, um zu einer konsistenten Infrastruktur in der gesamten Organisation zu gelangen und unnötige Komplexität zu reduzieren. Durch einheitliche Richtlinien hilft eine LZ sicherzustellen, dass alle Cloudressourcen gängigen Sicherheitsanforderungen und regulatorischen Standards entsprechen. Mit einem solchen präventiven Ansatz zur Sicherheit reduziert sich das Risiko für Schwachstellen und Sicherheitsverletzungen erheblich.
Aber auch Effizienz und Skalierbarkeit spielen eine Rolle, denn über eine Landing Zone können Sie die Bereitstellung von Cloudressourcen automatisieren und somit einfacher skalieren. Eine derartige Automatisierung beschleunigt nicht nur den Bereitstellungsprozess, sondern verringert auch die Wahrscheinlichkeit menschlicher Fehler und erhöht damit Zuverlässigkeit und Effizienz. Somit helfen LZs, unnötige Kosten zu vermeiden, indem sie sicherstellen, dass Ressourcen effizient zugewiesen und genutzt werden. Governance und standardisierte Tagging-Methoden erleichtern es, Cloudausgaben in verschiedenen Abteilungen oder Projekten zu verfolgen und zu verwalten.
Eine Landing Zone (LZ) zielt darauf ab, die Einrichtung der Cloudumgebung zu optimieren und zu standardisieren. Durch das Befolgen von Best Practices hilft eine Landing Zone, Wiederverwendung und Standardisierung sicherzustellen und die Einhaltung vereinbarter Richtlinien zu erzwingen. Dabei erfolgt der Betrieb mittels Infrastructure-as-Code (IaC).
Gründe für eine Landing Zone
Die Vorteile der Bereitstellung einer Landing Zone sind vielfältig und für Firmen, die ihre Cloudumgebungen effektiv und sicher verwalten möchten, von entscheidender Bedeutung. Für den Aufbau einer Landing Zone spricht zunächst einmal die Standardisierung. Eine LZ bietet einen standardisierten Ansatz zur Einrichtung und Konfiguration von Cloudumgebungen. Dies stellt sicher, dass alle Deployments denselben Verfahren, Konfigurationen und Sicherheitsstandards folgen, um zu einer konsistenten Infrastruktur in der gesamten Organisation zu gelangen und unnötige Komplexität zu reduzieren. Durch einheitliche Richtlinien hilft eine LZ sicherzustellen, dass alle Cloudressourcen gängigen Sicherheitsanforderungen und regulatorischen Standards entsprechen. Mit einem solchen präventiven Ansatz zur Sicherheit reduziert sich das Risiko für Schwachstellen und Sicherheitsverletzungen erheblich.
Aber auch Effizienz und Skalierbarkeit spielen eine Rolle, denn über eine Landing Zone können Sie die Bereitstellung von Cloudressourcen automatisieren und somit einfacher skalieren. Eine derartige Automatisierung beschleunigt nicht nur den Bereitstellungsprozess, sondern verringert auch die Wahrscheinlichkeit menschlicher Fehler und erhöht damit Zuverlässigkeit und Effizienz. Somit helfen LZs, unnötige Kosten zu vermeiden, indem sie sicherstellen, dass Ressourcen effizient zugewiesen und genutzt werden. Governance und standardisierte Tagging-Methoden erleichtern es, Cloudausgaben in verschiedenen Abteilungen oder Projekten zu verfolgen und zu verwalten.
Wichtig ist auch die Automatisierung, die die Zeit für die Bereitstellung neuer Anwendungen oder Dienste verringert. Diese schnellere Bereitstellungsfähigkeit kann eine geringe Time-to-Market und damit einen Wettbewerbsvorteil bieten.
Beispiel für eine Landing Zone
Wie bereits besprochen sollten Sie eine Landing Zone mittels IaC erstellen. Eine typische Landing Zone zeigen wir in Bild 1. Bei deren Aufbau legt der IT-Verantwortliche zuerst eine Google-Organisation an. Innerhalb dieser erzeugt er anschließend eine Ordnerstruktur samt der Projekte. Initial ist es erforderlich, eine gewisse Anzahl an Projekten anzulegen, darunter jeweils ein Netzwerk-, ein Logging- und ein Monitoringprojekt für die Landing Zone. Dazu gesellen sich ein Projekt für Audit-Logs und bei Bedarf ein Ort zum Speichern von Firewall-Logs. Herzstück jeder LZ ist aber das Netzwerk mit einer lokalen Anbindung.
Bild 1: Die Architektur einer typischen Landing Zone in der Google-Cloud.
Bevor Sie jedoch damit loslegen, mit Terraform eine Landing Zone oder zumindest Teile davon zu automatisieren, sollten Sie sich Gedanken über den Aufbau einer Continuous Integration (CI) Pipeline machen. Diese kann auf unterschiedliche Art und Weise erfolgen und in der Praxis finden sich oft Ansätze auf Basis von Terraform Cloud, Azure DevOps oder GitHub Actions. Für unseren Workshop setzen wir auf GitLab CI. GitLab selbst bringt schon in seiner freien Edition Unterstützung für Terraform in Form einer Terraform Registry mit und erlaubt, Terraform State in GitLab zu verwalten. In Kombination mit dem ausgeklügelten Rollen- und Rechtekonzept und der Tatsache, dass Sie GitLab auch lokal betreiben können plus dem flexiblen GitLab Runner ist GitLab ausgezeichnet für Terraform geeignet.
Aufbau einer GitLab CI Pipeline
Für den Betrieb einer GitLab CI Pipeline müssen Sie zuerst einen GitLab Runner bereitstellen. Diesen können Sie auf allen gängigen Betriebssystemen, als Container oder in Kubernetes betreiben. Wir zeigen, wie Sie den GitLab Runner auf Linux installieren. Zuerst laden Sie sich das entsprechende Binary herunter:
sudo curl -L --output /usr/local/ bin/gitlab-runner https://s3.dualstack.us-east-1.amazonaws.com/gitlab-runner-downloads/latest/binaries/gitlab-runner-linux-amd64
Anschließend machen Sie das Binary wie folgt ausführbar:
sudo chmod +x /usr/local/bin/gitlab-runner
Nun erzeugen Sie einen GitLab-CI-Benutzer:
sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash
Abschließend können Sie mit der Installation beginnen:
sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
sudo gitlab-runner start
Um einen GitLab Runner zu registrieren, benötigen Sie ein Authentication-Token. Sie erhalten es, indem Sie in der Admin Area auf "CI/CD" klicken, dann "Runners" auswählen und anschließend oben rechts "New instance runner" ansteuern. Nachdem Sie ein Tag definiert haben, wählen Sie "Create Runner" und kopieren sich anschließend das Kommando zum Registrieren eines Runners (), wie in Bild 2 zu sehen.
Bild 2: Beim Registrieren eines GitLab-Runners erhalten Sie einen Codeschnipsel, den es in die Zwischenablage zu kopieren gilt.
Nun folgt die Definition einer Terraform-Pipeline für GitLab CI. Verwenden Sie GitLab-SaaS, tragen Sie direkt die Definition aus Listing 1 in die gitlab-ci.yaml-Datei ein (in der lokalen Version müssen Sie vorher noch die entsprechenden GitLab Component auf die eigene Instanz spiegeln).
Listing 1: Definition für die gitlab-ci.yaml-Datei
include:
  - component: $CI_SERVER_FQDN/components/opentofu/full-pipeline@<VERSION>
    inputs:
      # The version must currently be
specified explicitly as an input,
      # to find the correctly associated
images. # This can be removed
      # once https://gitlab.com/gitlab-
org/gitlab/-/issues/438275 is solved.
      version: <VERSION>
      opentofu_version: <OPENTOFU_VERSION>
stages: [validate, test, build, deploy, cleanup]
Terraform-Module nutzen und selber schreiben
Der Aufbau einer Landing Zone sollte möglichst komplett automatisiert sein. Leider ist der Aufwand, eigene Terraform-Module für die LZ zu schreiben, recht aufwendig. Aus diesem Grund bietet Google bereits fertige Terraform-Module an. Es gibt auch Firmen wie etwa CloudGems aus Frankfurt, die neben Modulen auch kommerziellen Support anbieten.
Eine Landing Zone benötigt in der Regel Konnektivität zur lokalen Umgebung und muss Applikationen eine Netzwerkanbindung bieten. In der Google-Cloud erledigen Sie dies mithilfe eines Shared VPCs, das sich innerhalb eines Host-Projekts befindet. Mit "Project Modules" aus der Google Terraform Cloud Foundation Fabric lässt sich dies leicht abbilden. Den Aufruf der Module zeigt Listing 2.
Listing 2: Module in der Google-Cloud aufrufen
module "host-project" {
  source     = "./fabric/modules/project"
  billing_account = var.billing_account_id
  name      = "host"
  parent     = var.folder_id
  prefix     = var.prefix
  shared_vpc_host_config = {
    enabled = true
  }
}
module "service-project" {
  source     = "./fabric/modules/project"
  billing_account = var.billing_account_id
  name      = "service"
  parent     = var.folder_id
  prefix     = var.prefix
  services = [
    "container.googleapis.com",
    "run.googleapis.com"
  ]
  shared_vpc_service_config = {
  host_project =
     module.host-project.project_id
    service_agent_iam = {
      "roles/compute.networkUser" = [
        "cloudservices", "container-engine"
      ]
      "roles/vpcaccess.user" = [
        "cloudrun"
      ]
      "roles/container.hostServiceAgentUser" = [
        "container-engine"
      ]
    }
  }
}
Der erste Modulaufruf erzeugt ein Host-Projekt für ein Shared VPC. Als Input benötigen Sie als Erstes die ID des Billing-Accounts. Mit der Eigenschaft "name" hinterlegen Sie den Namen des Projekts. Der "parent"-Wert definiert, an welcher Stelle der Organisationshierarchie das Projekt angelegt werden soll. Soll dies auf Organisationsebene erfolgen, benötigen Sie die Organisations-ID. Falls Sie Projekt unterhalb eines Ordners erzeugen möchten, ist die Ordner-ID anzugeben. Die entsprechenden Werte erfahren Sie, indem Sie in der grafischen Benutzeroberfläche oben links den Projekt-Picker wählen und dort in der nach einem Klick angezeigten Tabelle den entsprechenden Wert suchen. Darüber hinaus ist es möglich, ein Präfix für den Objektnamen zu definieren. Zu guter Letzt aktivieren Sie das VPC.
Die Erzeugung eines Service-Projekts gelingt ähnlich. Dabei gilt es folgende Werte beim Modulaufruf zu setzen: Mit der Liste "services" definieren Sie die Google-APIs, die im Projekt aktiv sind – in unserem Beispiel sind das GKE (container.googleapis.com) und Cloud Run. Damit sich ein Service-Projekt mit einem Host-Projekt verbindet, ist die Map "shared_vpc_ service_config" notwendig. Darin definieren Sie zuerst das nutzende Host-Projekt und anschließend legen Sie die Berechtigungen fest, die Service-Projekte auf dem Host-Projekt erhalten. Dies ist bei Shared VPC von Bedeutung, da sich ohne Berechtigungsvergabe die entsprechenden Dienste in den Service-Projekten nicht nutzen lassen.
Es ist Ihnen auch möglich, im Rahmen der Projektanlage Security- und Compliance-Regeln festzulegen, sofern erforderlich. Typische Beispiele dafür sind beispielsweise der Ausschluss öffentlicher IP-Adressen oder die Begrenzung von Regionen. So ist es möglich, den Projektaufruf um folgende Einstellung zu erweitern:
factories_config = {
    org_policies =
      "configs/org-policies/"
}
Ist dies geschehen, legen Sie im referenzierten Ordner entsprechende YAML-Dateien ab, wie in Listing 3 zu sehen.
Listing 3: YAML-Dateien in Ordner ablegen
compute.trustedImageProjects:
  rules:
  - allow:
      values:
      - projects/my-project
compute.vmExternalIpAccess:
  rules:
  - deny:
      all: true
iam.allowedPolicyMemberDomains:
  rules:
  - allow:
      values:
      - C0xxxxxxx
      - C0yyyyyyy
Netzwerke definieren
Nun können Sie sich daranmachen, die notwendige Netze im Hubhost-Projekt zu erzeugen. Dies gelingt mit dem Aufruf des net-vpc-Moduls aus der Google Cloud Fabric Foundation, wie in Listing 4 zu sehen. Dazu definieren Sie zuerst die Projekt-ID des Google-Projekts, in dem die Subnetze entstehen sollen. Auch für das VPC ist dann ein Name erforderlich. Anschließend geben Sie die Subnetz-Bereiche an, sekundäre IP-Bereiche sind insbesondere für den Einsatz von GKE-Clustern relevant. So erhalten alle Pods und Kubernetes-Dienste IP-Adressen aus den angegeben Adressbereichen. Auch hier dürfen Sie nicht vergessen, den Diensten Zugriff auf die Subnetze zu gewähren. Zum Schluss definieren Sie, welche Service-Projekte an das Shared VPC angeschlossen werden.
Listing 4: Netze über net-vpc-Module anlegen
module "vpc-host" {
  source   = "./fabric/modules/net-vpc"
  project_id = var.project_id
  name   = "my-host-network"
  subnets = [
    {
      ip_cidr_range = "10.0.0.0/24"
      name     = "subnet-1"
      region    = "europe-west1"
      secondary_ip_ranges = {
        pods   = "172.16.0.0/20"
        services = "192.168.0.0/24"
      }
      iam = {
        "roles/compute.networkUser" = [
          "serviceAccount:${var.service_
              account.email}"
        ]
        "roles/compute.securityAdmin" = [
          "serviceAccount:${var.service_
              account.email}"
        ]
      }
    }
  ]
  shared_vpc_host = true
  shared_vpc_service_projects = [
    module.service-project.project_id
  ]
}
Nachdem das Netzwerk in der Google Cloud eingerichtet ist, kümmern Sie sich um Konnektivität zu Ihrer lokalen Infrastruktur. Auch hier bietet sich wieder ein fertiges Modul aus der Google-Kollektion an – diesmal zum Aufbau eines hochverfügbaren VPN (Listing 5).
Listing 5: Aufbau eines hochverfügbaren VPN
module "vpn_ha" {
  source   = "./fabric/modules/net-vpn-ha"
  project_id = var.project_id
  region   = var.region
  network  = var.vpc.self_link
  name   = "mynet-to-onprem"
  peer_gateways = {
    default = {
      external = {
        redundancy_type = "SINGLE_IP_
          INTERNALLY_REDUNDANT"
        interfaces   = ["8.8.8.8"]
        # on-prem router ip address
      }
    }
  }
  router_config = { asn = 64514 }
  tunnels = {
    remote-0 = {
      bgp_peer = {
        address = "169.254.1.1"
        asn   = 64513
        # BFD is optional
        bfd = {
          min_receive_interval    = 1000
          min_transmit_interval   = 1000
          multiplier         = 5
          session_initialization_mode =
                      "ACTIVE"
        }
        # MD5 Authentication is optional
        md5_authentication_key = {
          name = "foo"
          key = "bar"
        }
      }
      bgp_session_range = "169.254.1.2/30"
      peer_external_gateway_interface = 0
      shared_secret     = "mySecret"
      vpn_gateway_interface     = 0
    }
    remote-1 = {
      bgp_peer = {
        address = "169.254.2.1"
        asn   = 64513
        # BFD is optional
        bfd = {
          min_receive_interval    = 1000
          min_transmit_interval   = 1000
          multiplier         = 5
          session_initialization_mode =
                       "ACTIVE"
        }
        # MD5 Authentication is optional
        md5_authentication_key = {
          name = "foo"
          key = "bar"
        }
      }
      bgp_session_range       = "169.254.2.2/30"
      peer_external_gateway_interface = 0
      shared_secret       = "mySecret"
      vpn_gateway_interface     = 1
    }
  }
}
Dabei müssen Sie beachten, dass die Parameter "project_id", "region" und "network" identifizieren, in welchem VPC das VPN entstehen soll. Der Name gibt an, wie das VPN benannt wird, und mit der Eigenschaft "peer_gateways" bringen Sie Google bei, welche Gegenstellen sich mit dem VPN verbinden. Im Element "router_config" hinterlegen Sie die ASN-Nummer.
Die Tunnel selbst konfigurieren Sie mithilfe des tunnels-Blocks. Damit das VPN hochverfügbar ist, benötigen Sie zwei Tunnel und für jeden Tunnel lässt sich optional Bidirectional Foward Detection (BFD) und MD4-Authentifizierung einrichten. Als verpflichtende Element geben Sie darüber hinaus die "BGP Session Range", ein Secret, den Index des VPN-Gateway-Interfaces sowie das Gateway der Gegenstelle an.
DNS-Zonen einrichten
Die Konfiguration von DNS und zugehörigen DNS-Zonen gehört zu den Grundlagen jeder Landing Zone. Ein einfaches Setup definiert eine DNS-Zone für ein Shared VPC, die die angeschlossenen Service-Projekte nutzen können. Darüber hinaus ist es möglich, DNS-Fowarding-Zonen in der Google-Cloud einzurichten und damit DNS-Abfragen Richtung on-premises weiterzuleiten.
Zum Konfigurieren von DNS-Zonen und deren Einträgen kommt das DNS-Modul aus der Cloud Foundation Fabric zum Einsatz. Das Erzeugen einer privaten DNS-Zone zeigt Listing 6. Damit entsteht eine Private-Zone für die Domäne "test. example" mit zwei Record-Sets. Mit der Eigenschaft "client_networks" definieren Sie die VPC-Netzwerke, für die die Zone gelten soll. Berechtigungen vergeben Sie mit der Übergabe einer entsprechenden Map-Variable an die iam-Eigenschaft.
Listing 6: Erzeugen einer privaten DNS-Zone
module "private-dns" {
  source   = "./fabric/modules/dns"
  project_id = var.project_id
  name   = "test-example"
  zone_config = {
    domain = "test.example."
    private = {
      client_networks = [var.vpc.self_link]
    }
  }
  recordsets = {
    "A localhost" = { records = ["127.0.0.1"] }
    "A myhost"  = { ttl = 600, records =
         ["10.0.0.120"] }
  }
  iam = {
    "roles/dns.admin" = ["group:${var.group_email}"]
  }
}
Eine zusätzliche Fowarding-Zone, die DNS-Abfragen zur lokalen Infrastruktur weiterleitet, erzeugen Sie mit dem folgenden Code-block, den Sie in Listing 5 einbauen. Darin können Sie innerhalb der fowarding-Konfiguration die IP-Adressen der entsprechenden Name-Server angeben.
module "private-dns" {
  source   = "./fabric/modules/dns"
  project_id = var.project_id
  name = "test-example"
  zone_config = {
    domain = "test.example."
    forwarding = {
      client_networks = [var.vpc.
         self_link]
      forwarders = { "10.0.1.1" =
        null, "1.2.3.4" = "private" }
    }
  }
}
Einrichten von Logging
Zu jeder Landing Zone gehört eine Strategie zur Sicherung von Audit-, System- oder Applikationslogs. Ein kostengünstiger Weg ist, Google Cloud Storage als Speicherort zu wählen und mittels Google Big Query Analysen darauf auszuführen. Wie bei unseren anderen Beispielen können Sie auch hier mit einem fertigen Modul arbeiten. Dies rufen Sie folgt auf:
module "bucket-organization" {
  source   = "./fabric/modules/logging-bucket"
  parent_type = "organization"
  parent = "organizations/012345"
  id   = "mybucket"
  log_analytics = {
    enable = true
    dataset_link_id = "log"
  }
}
Der Aufruf ist im Großen und Ganzen selbsterklärend. Mit den Variablen "parent_type" und "parent" konfigurieren Sie, dass das Bucket auf Organisationsebene zu erstellen ist. Mit dem Schalter "log_ analytics" aktivieren Sie anschließend die Log-Funktionalität.
Fazit
Der Aufbau einer Landing Zone ist wichtig, weil IT-Verantwortliche damit Governance, Sicherheit und Compliance besser gewährleisten. Das Einrichten einer LZ sollte aber nicht manuell erfolgen. Mit Terraform steht ein Tool zur Verfügung, das den Vorgang automatisiert und den Betreib der LZ vereinfacht. Unterstützung kommt auch von Cloudprovidern, die mittlerweile fertige Terraform-Module anbieten, mit deren Hilfe das Etablieren einer Landing Zone deutlich einfacher ist.
(jp)