Aus Linux-Magazin 12/2019

Infrastrukturmanagement in Multiclouds mit Terraform

© welcomia, 123RF

Beim Cloud-Konfigurationsmanagement sind Tools wie Puppet, Chef oder Ansible heute Standard. In Multi-Cloud-Szenarien bedarf aber auch das Verwalten der darunterliegenden Infrastruktur der Automatisierung. Terraform ist dafür das ideale Werkzeug.

Als Hybrid-Cloud bezeichnet man die Kombination aus einer oder mehreren Public und Private Clouds, also die Zusammenarbeit einer Cloud-Lösung im eigenen Rechenzentrum mit einer Cloud eines externen Anbieters (Abbildung 1). Eine Multicloud besteht hingegen aus mehreren Clouds unterschiedlicher Anbieter (Abbildung 2) oder auch nur aus verschiedenen Cloud-Lösungen, die mittels APIs über VPNs untereinander Daten austauschen.

Abbildung 1: Bei einer hybriden Cloud arbeitet immer eine private mit einer oder mehreren öffentlichen Clouds zusammen.

Abbildung 1: Bei einer hybriden Cloud arbeitet immer eine private mit einer oder mehreren öffentlichen Clouds zusammen.


Abbildung 2: Eine Multicloud besteht aus Private und/oder Public Clouds verschiedener Anbieter, die über VPNs verbunden sind.

Abbildung 2: Eine Multicloud besteht aus Private und/oder Public Clouds verschiedener Anbieter, die über VPNs verbunden sind.

Damit ergibt sich eine Landschaft unterschiedlicher virtueller Ressourcen, die zum Teil Drittanbieter bereitstellen und betreuen. In der Regel betrifft das die Hardware, oft auch das regelmäßige Update von Software oder die Datensicherung. Die Motivationen für eine Multicloud sind vielfältig. So bietet kein Anbieter alle Features, der Anwender muss sich unter Umständen die benötigten Komponenten bei mehreren Providern zusammensuchen. Auch unterschiedliche Preismodelle können in diesem Zusammenhang eine Rolle spielen.

Ein weiteres Motiv besteht darin, sich seine Unabhängigkeit zu bewahren und kurzfristig den Anbieter wechseln zu können. Allerdings gilt es in einem solchen Fall, größere Mengen von Daten zu bewegen. Unangenehm kommt hinzu, dass der Anwender dann eventuell unterschiedliche Formate, etwa von VMs, erst konvertieren muss. Selbst bei weit ausgebauter Automatisierung – egal, ob mit Puppet, Salt, Ansible oder Chef – müssen am Ende immer noch die Nutzdaten übertragen werden, also der Content der betriebenen Anwendungen.

Die Konfiguration der Software in einer VM oder einem Container übernehmen die schon erwähnten Automatisierungswerkzeuge. Für die darunterliegende Infrastruktur steht Terraform bereit, das die Firma HashiCorp [1] entwickelt und unter der Mozilla Public License 2.0 [2] bereitstellt. Es eignet sich besonders für heterogene Hybrid- oder Multi-Cloud-Umgebungen. Terraform verwaltet virtuelle Maschinen auf unterschiedlichsten Cloud-Plattformen, die Netzinfrastruktur vom Subnetz über Router bis hin zum Load-Balancer oder der Firewall und alles, was die gewählte Cloud-Lösung darüber hinaus zu bieten hat.

Infrastruktur als Code

Der zugrundeliegende Prozess, der unterschiedliche Ressourcen in Deklarationen beschreibt, nennt sich Infrastructure as Code. Auf Basis dieser Beschreibung kümmert sich Terraform dann um das Management der benötigten Ressourcen, erstellt oder löscht sie und kümmert sich um Statusänderungen. Abhängigkeiten berücksichtigt es dabei implizit. Terraform deklariert Ressourcen im eigenen HCL-Format (HashiCorp Language), einem erweiterten JSON, das zusätzlich Dokumentation ermöglicht und die Lesbarkeit vereinfacht. Auch natives JSON wird unterstützt, ist jedoch vor allem Neulingen nicht zu empfehlen.

Die Deklaration der benötigten Ressourcen mittels HCL in gewöhnlichen Textdateien bietet gegenüber der Konfiguration mithilfe einer Webkonsole oder mit Werkzeugen des Herstellers einige entscheidende Vorteile. Zum einen lässt sich so auf einfache Weise die gesamte Konfiguration teilen und mithilfe eines Versionskontrollsystems wie Git nachvollziehbar und sicher speichern. Zum anderen kann man aus der gespeicherten Konfiguration jederzeit schnell und vollautomatisch eine reproduzierbare Arbeitsumgebung aufbauen. Die große Stärke von Terraform liegt darin, dass es eine Vielzahl von Cloud- und Infrastrukturanbietern unterstützt und so die große Lücke schließt, die fehlende Standards verursacht haben.

Provider

Die Verwaltung in einer Multi- oder Hybrid-Cloud bewerkstelligt Terraform über sogenannte Provider. Als Provider bezeichnet man die grundlegende Abstraktionsschicht zur Steuerung und zur Authentifizierung gegenüber der API der zu verwaltenden Lösung. Solche Provider stellt HashiCorp für die Clouds aller großen Anbieter standardmäßig bereit [3]. Aber auch für das nicht proprietäre OpenStack, das kleinere, regionale Anbieter gern einsetzen und das auch für die eigene Private Cloud infrage kommt, gibt es einen Provider.

Idealerweise legt der Systemadministrator seine Provider in einer Datei »provider.tf« im eigenen Projektverzeichnis ab. Wie Listing 1 demonstriert, unterscheiden sich die Attribute der Ressourcen aufgrund der unterschiedlichen Strukturen in der jeweiligen Implementation. OpenStack setzt beispielsweise immer ein Projekt (»tenant_name«) voraus, Amazon Web Service (AWS) hingegen arbeitet benutzerbezogen.

Listing 1

provider.tf

provider "openstack" {
  user_name = var.openstack_username
  tenant_name = var.openstack_project
  password = var.openstack_password
  auth_url = "https://cloud.openstack.de:5000/v3/"
  region = "de-nuremberg-1"
}
provider "aws" {
  access_key = var.aws_access_key
  secret_key = var.aws_access_key
  region = "us-east-1"
}

Die Unterschiede bei den Ressourcentypen und auch bei den angebotenen Features der Anbieter sind noch viel ausgeprägter als die Unterschiede zwischen den Providern in Listing 1. Auch wer Terraform verwendet, muss hier mit einem erhöhten Aufwand bei der Codeerzeugung für die eigene Infrastruktur rechnen. Das verhindert eine vollständige Abstraktion; stattdessen muss man die unterschiedlichen Ressourcentypen pro Provider individuell konfigurieren. Das ließe sich dadurch reduzieren, dass sich der Anwender für Anbieter entscheidet, die auf dieselbe Cloud-Implementierung setzen. In diesem Fall wird lediglich ein weiterer Provider benötigt (Listing 2).

Listing 2

Provider OpenStack

provider "openstack" {
  alias = "provider-2"
  user_name = var.openstack_username
  tenant_name = var.openstack_project
  password = var.openstack_password
  auth_url = "https://cloud.openstack.de:5000/v3/"
  region = "de-nuremberg-2"
}

Genauso verfährt, wer zwar mit denselben Anbietern arbeitet, aber unterschiedliche Standorte für Ressourcen vorsieht. Diese Beziehung definiert der Wert des Attributs »region«. Ressourcen ordnet man durch Setzen des Metaparameters (existiert für jeden Ressourcentyp) »provider« dem entsprechenden Provider zu. Fehlt bei einer Ressource die Angabe des Providers, verwendet Terraform den Default-Provider, also denjenigen ohne Angabe eines Aliases.

Die verwendeten Variablen, wie »var.openstack_projekt« in den Provider-Definitionen, definiert man gemeinhin in einer gesonderten Datei »varibales.tf« (Listing 3) oder gibt sie beim Aufruf von Terraform mit an. Da es sich hierbei um sicherheitskritische Informationen handelt, sind hier die Environment-Variablen der Shell (Listing 4) zu bevorzugen.

Listing 3

variables.tf

variable "openstack_user" {}
variable "openstack_password" {}
variable "openstack_project" { default = "0815-openstack-4e0f9" }
variable "aws_access_key" {}
variable "aws_secret_key" {}

Listing 4

Environment-Variablen

export TF_VAR_openstack_user="lbetz"
export TF_VAR_openstack_password="XXX"
export TF_VAR_aws_access_key="YYY"
export TF_VAR_aws_secret_key="ZZZ"

Damit dem Anwender stets eine verlässliche und funktionierende Infrastruktur zur Verfügung steht, trennt Terraform das Ermitteln der notwendigen Operationen von der tatsächlichen Umsetzung. Dadurch lassen sich Konfigurationsbeschreibungen ohne Risiko vorab evaluieren und verifizieren. Das vermeidet ein versehentliches Löschen oder Verändern oder minimiert zumindest die Folgen.

Die drei Phasen des Lebenszyklus

Die in HCL beschriebene Infrastruktur durchläuft in ihrem Lebenszyklus immer die drei Schritte »terraform plan«, »terraform apply« und »terraform destroy«.

Im ersten Schritt löst Terraform beschriebene Ressourcen auf und bringt sie mittels graphentheoretischer Algorithmen in die richtige Reihenfolge. Die Konfiguration, die der Admin in willkürlicher Reihenfolge notiert hat, sortiert die Software nach deren Abhängigkeiten und ermittelt gleichzeitig den optimalen Ausführungsweg. Beispielsweise gilt es, zuerst ein Netzwerksegment zu erstellen, bevor sich einer VM eine IP-Adresse zuordnen lässt, die wiederum für die DNS-Konfiguration nötig ist. Der Admin erfährt also sofort, was das Abarbeiten des Plans erfordert und welche Ressourcen in welcher Reihenfolge angelegt werden.

Die Phase »terraform apply« setzt den vorher erstellten Plan um und legt die jeweiligen Ressourcen an beziehungsweise löscht oder modifiziert sie. Vor jeder Ausführung von »plan« oder »apply« ermittelt Terraform den aktuellen Zustand des Zielsystems, um notwendige Änderungen mit dem aktuellen Status abzugleichen. Diesen Status speichert es in eine lokale Statusdatei »terraform.tfstate« im Projektverzeichnis. Terraform benötigt sie, um Cloud-Ressourcen mit den lokalen Definitionen zu vergleichen und das Mapping von beschriebenen auf reale Ressourcen zu generieren [4]. Beim Arbeiten im Team empfiehlt es sich, die Statusdatei an zentraler Stelle abzulegen.

Zu guter Letzt löscht terraform destroy die in Terraform verwalteten (also die im Statefile enthaltenen) Ressourcen aus den Zielsystemen.

Ein Beispiel

Im Folgenden sollen Webserver als Beispiel dienen, die ein Nutzer sowohl bei Amazon hostet als auch bei einem Anbieter, der OpenStack einsetzt. Dazu konfiguriert er die Provider so, wie in Listing 1, 2 und 4 gezeigt. Da der Systemadministrator Zugang zu den Maschinen benötigt, deklariert er je eine Ressource für den öffentlichen Schlüssel des Admins. Er muss zwei dieser Ressourcen anlegen, da jeder Provider seine eigenen Ressourcentypen mitbringt und auch die Attribute unterschiedliche Namen tragen. Die Online-Referenz [5] beschreibt die unterschiedlichen Ressourcentypen umfassend.

Listing 5

Schlüsselpaare

resource "aws_key_pair" "public_key" {
  key_name   = "aws_public_key"
  public_key = file("~/.ssh/id_rsa.pub")
}
resource "openstack_compute_keypair_v2" "public_key" {
  name       = "openstack_public_key"
  public_key = file("~/.ssh/id_rsa.pub")
}

Die Attribute »key_name« beziehungsweise »name« geben die Namen an, mit denen später auf diese Ressourcen Bezug genommen wird. Für die interne Bezeichnung lässt sich hingegen in beiden Fällen mit »public_key« derselbe Name wählen. Beide Anbieter stellen einen Router und ein aus dem privaten IP-Adressbereich bestehendes Subnetz zur Verfügung. Für den Zugang aus dem Internet übersetzt der Router eine öffentliche IP-Adresse mittels Destination Network Address Translation (DNAT) in die private Adresse des jeweiligen Hosts. Damit überhaupt ein Login via SSH erfolgen kann, muss der Admin bei beiden Anbietern der Paketfilter für eingehenden Datenverkehr auf Port 22/TCP konfigurieren (Listing 6).

Listing 6

Paketfilter-Konfiguration

resource "aws_security_group" "allow_ssh" {
  name        = "aws_allow_ssh"
  description = "Allow SSH inbound traffic"
  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
resource "openstack_compute_secgroup_v2" "allow_ssh" {
  name        = "openstack_allow_ssh"
  description = "Allow SSH inbound traffic"
  rule {
    from_port   = 22
    to_port     = 22
    ip_protocol = "tcp"
    cidr        = "0.0.0.0/0"
  }
}

Für die internen Namen wie auch die referenzierenden Bezeichner gilt dasselbe wie bei den beiden Keypair-Ressourcen. Der CIDR-Block schränkt den Zugang nicht auf bestimmte Zieladressen ein, somit ist der Zugang zum SSH-Standard-Port für die beiden privaten Netze erlaubt.

Module

Die virtuellen Maschinen und vor allem deren Anzahl lassen sich gut über Terraform-Module verwalten. Ein Modul besteht aus einer oder mehreren Terraform-Dateien, die in einem gesonderten Verzeichnis liegen müssen. Die Ressourcen, die ein Modul verwaltet, lassen sich mehrfach nutzen. Dafür versieht der Admin das Modul einfach mit einem anderen Bezeichner. Welches Modul zu verwenden ist, gibt er über die Pfadangabe zum Modulverzeichnis mithilfe des Attributs »source« an (Listing 7).

Listing 7

main.tf

module "aws_web" {
  source              = "./aws_server"
  instance_count      = 2
  instance_ami        = "ami-0f9cf087c1f27d9b1"
  instance_key_name   = "aws_public_key"
  instance_sec_groups = [ "aws_allow_ssh" ]
}
module "openstack_web" {
  source              = "./openstack_server"
  instance_count      = 2
  instance_name       = "web"
  instance_image_name = "Centos 7"
  instance_key_name   = "openstack_public_key"
  instance_sec_groups = [ "openstack_allow_ssh" ]
}
output "puplic_ip" {
  value = concat(module.aws_web.public_ip, module.openstack_web.public_ip)
}

Die VM-Instanzen erfordern bei AWS und OpenStack getrennte Module. Alle Attribute außer »source« sind Parameter des Moduls, die der Anwender selbst bestimmen kann, vorausgesetzt, er schreibt das Modul selbst. Unter »registry.terraform.io« stehen aber auch eine Vielzahl vorgefertigter Module zum Herunterladen bereit, so auch die für die hier beschriebene Aufgabe.

Neben der Angabe, welches Basis-Image es auszurollen gilt, finden sich hier auch die Referenzen auf die oben deklarierten Objekte vom Typ Security Group und SSH-Key. Der Parameter »instance_count« gibt die Anzahl der zu provisionierenden VMs an, im Beispiel von Listing 7 jeweils zwei für beide Provider. Hier gibt es einen Unterschied zwischen AWS und OpenStack: Bei Letzterem darf der Admin den Hostnamen der VM setzen, bei diesem Modul mit dem Attribut »instance_name«. Freilich sollen nicht beide Instanzen »web« heißen; der Name dient lediglich als Basis und bekommt noch ein Suffix in Form einer Ziffer. Bei zwei Instanzen ergeben sich also die Namen »web0« und »web1«.

Da die Software die öffentlichen IP-Adressen dynamisch vergibt, definiert der Anwender hier einen Output, der mit »terraform output« die vergebenen Adressen ausgibt. Als Grundlage dient jeweils die Ausgabe der beiden Module für »public_ip«. Das Ergebnis ist jeweils eine Liste, die mit der Funktion »concat« zu einer gemeinsamen Liste zusammengefasst wird.

Listing 8 bis**10 zeigen die Dateien des AWS-Server-Moduls, Listing 11 bis**14 jene des OpenStack-Servers. Die Parameter eines Moduls definiert der Admin über Variablen. Fehlt eine Vorgabe, handelt es sich um einen Parameter, der beim Modulaufruf zu setzen ist.

Listing 8

aws_server/variables.tf

variable "instance_ami" {}
variable "instance_name" {}
variable "instance_key_name" {}
variable "instance_count" {
  default = 1
}
variable "instance_type" {
  default = "t2.micro"
}
variable "instance_sec_groups" {
  type    = "list"
  default = []
}

Bei AWS muss sich der Admin nur um die Instanz selbst kümmern, da standardmäßig eine öffentliche IP-Adresse zu jeder Instanz vergeben wird – Amazon spricht hier von ElasticIP. Der Metaparameter »count« implementiert eine For-Schleife über die gesamte Ressource. Dabei hängt Terraform den »count.index« als Laufparameter automatisch an den Ressourcennamen an, in Listing 9 beispielsweise »this[0]«, »this[1]« und so weiter.

Listing 9

aws_server/main.tf

resource "aws_instance" "this" {
  count           = var.instance_count
  ami             = var.instance_ami
  instance_type   = var.instance_type
  security_groups = var.instance_sec_groups
  key_name        = var.instance_key_name
}

Listing 10 enthält den Output des Moduls »aws_server«, der schon in Listing 7 Verwendung fand. Als alternative Schreibweise für »this[*]« kommt »this.*« zum Einsatz. Die Rückgabe besteht aus einer Liste aller Werte zum Attribut »public_ip« der Ressourcen »aws_instance«, die mittels »count« von diesem Modul erzeugt wurden.

Listing 10

aws_server/outputs.tf

output "public_ip" {
  value = "${aws_instance.this.*.public_ip}"
}

Der Anbieter der OpenStack-Cloud stellt zwar, wie oben erwähnt, ebenfalls einen Router mit DNAT und ein Default-Netz bereit, bietet allerdings nicht automatisch eine öffentliche IP (Floating IP im OpenStack-Jargon) zu jeder provisionierten Instanz. Das hat zur Folge, dass sich der Admin selbst um eine solche kümmern muss.

Listing 11 definiert analog zum AWS-Modul zunächst die Parameter des OpenStack-Moduls. Der Hauptteil des Moduls in der Datei »main.tf« (Listing 12) unterscheidet sich jedoch erheblich vom AWS-Pendant: So muss man hier der Instanz mitteilen, mit welchen Netz sie sich zu verbinden hat. Das Attribut »network« stellt unter Zuhilfenahme der ID des Netzes die Verknüpfung her.

Listing 11

openstack_server/variables.tf

variable "instance_name" {}
variable "instance_image_name" {}
variable "instance_key_name" {}
variable "instance_count" {
  default = 1
}
variable "instance_flavor_name" {
  default = "s1.small"
}
variable "instance_sec_groups" {
  type    = "list"
  default = []
}

Listing 12

openstack_server/main.tf

esource "openstack_compute_instance_v2" "this" {
  count             = var.instance_count
  name              = "${var.instance_name}${count.index}"
  image_name        = var.instance_image_name
  flavor_name       = var.instance_flavor_name
  key_pair          = var.instance_key_name
  security_groups   = var.instance_sec_groups
  network {
    uuid = data.openstack_networking_network_v2.this.id
  }
}
resource "openstack_networking_floatingip_v2" "this" {
  count = var.instance_count
  pool  = "public-network"
}
resource "openstack_compute_floatingip_associate_v2" "this" {
  count       = var.instance_count
  floating_ip = openstack_networking_floatingip_v2.this[count.index].address
  instance_id = openstack_compute_instance_v2.this[count.index].id
}

Da Terraform das Netz nicht selbst angelegt hat, ermittelt man die ID üblicherweise über eine Data-Ressource (Listing 13). Damit stellt man die Eigenschaften des Netzwerks mit dem Namen »default« fest und erzeugt ein Terraform-Data-Objekt, das diese Eigenschaften als Attribute enthält, darunter auch die eindeutige ID des Netzes »default«.

Listing 13

openstack_server/data.tf

data "openstack_networking_network_v2" "this" {
  name = "default"
}

Wie schon nach dem Namen »default« des privaten Netzes muss sich der Admin beim Anbieter auch nach dem Namen des öffentlichen Netzes erkundigen, im Allgemeinen über die von ihm zur Verfügung gestellte Konsole. Dieser Name ist im Attribut »pool« des Objekts vom Typ »openstack_networking_floatingip_v2« anzugeben (Listing 14). Dieses Objekt verknüpft der Admin dann mit dem der Instanz über ein weiteres Objekt des Typs »openstack_compute_floatingip_associate_v2«.

Listing 14

openstack_server/outputs.tf

output "public_ip" {
  value = openstack_networking_floatingip_v2.this.*.address
}

Alle drei Ressourcen werden jeweils durch »count« mit einer For-Schleife umgeben. Die zu einer Instanz gehörigen anderen Objekte erhalten damit alle denselben Index und lassen sich mit »count.index« eindeutig referenzieren.

Nun sollte einem abschließenden »terraform plan« beziehungsweise »apply« nichts mehr im Wege stehen. Vorher steht aber noch der obligatorische Aufruf »terraform init« zum Projektstart an. Er kann auch wiederholt anfallen, wenn der Admin dem Projekt einen neuen Provider oder ein neues Modul hinzufügt.

Zur Komplettierung des vorgestellten Lifecycles der Infrastruktur bietet die Firma HashiCorp noch diverse andere Produkte, darunter Packer [6], das ebenfalls unter der Mozilla-Lizenz 2.0 steht. Im Zusammenspiel mit Vagrant [7], einem Framework zum Provisionieren und Steuern virtueller Maschinen, lassen sich Images erstellen und testen, die Packer später automatisiert und regelmäßig erzeugt. Sie dienen dann in der Cloud als Images zum Provisionieren neuer Maschinen. Mit anderen Werkzeugen kann der Anwender diesen Prozess sogar vollständig automatisieren. Für diesen Bereich bietet HashiCorp mit Vault [8], Nomad [9] und Consul [10] entsprechende Lösungen an.

Fazit

Hat man sich erst einmal in Terraform eingearbeitet, verursacht es nur noch wenig Aufwand, damit komplette Infrastrukturen auf Knopfdruck auszurollen und im laufenden Betrieb zu managen. (jcb)

Der Autor

Der Mathematiker Lennart Betz arbeitet seit 2011 als Consultant und Trainer für die in Nürnberg ansässige Netways GmbH. Seine Hauptaufgabenfelder bei Kunden und als Trainer sind Systemüberwachung, Automatisierung und Konfigurationsmanagement.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 5 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Nach oben