Warum Terraform für vSphere und das Deployment virtueller Maschinen?
Der Code ist die Dokumentation! In diesem Fall trifft das vollkommen zu – jedenfalls wenn es um den Aufbau der Maschine und die ursprünglichen Spezifikationen geht. Wir hinterlegen in einer Terraform Datei den kompletten Bauplan unserer Maschine. Wie viel RAM, CPU, Festplattenkapazität und welches Image benötigen wir? Das alles hinterlegen wir einfach im Code. Außerdem werden unsere virtuellen Maschinen zu einem „Wegwerfartikel“. Natürlich nur, sofern die Maschine stateless ist, keine Datenbank vorzeigt und wir ein Configuration Management wie z.B. Ansible beherrschen. Bevor wir also lange an einer fehlerhaften Maschine troubleshooting Betreiben, erstellen wir diese einfach neu. Abhängig von der Serverumgebung benötigt Terraform im Schnitt 2 Minuten für das Deployment einer Maschine.
Golden Image in der Content Library – ein Segen
Wenn wir über Linux und Windows Deployments sprechen, sollten wir für Terraform unter vSphere auch ein vorbereitetes Virtual Machine Template bereithalten. Bei größeren Umgebungen mit mehreren Datacenter/Standorten eignet sich dazu die Content Library von VMware. Wir Synchronisieren dann die Templates über alle Standorte hinweg – praktisch. Das Template sollte direkt für unser Configuration Management vorbereitet sein.
Und was bedeutet das jetzt?
Wir können innerhalb weniger Minuten komplett individualisierte und zugleich standardisierte Server aufbauen. Die Basis wird immer durch das gleiche Virtual Machine Template gebildet. Konkret: Wir führen Terraform aus und lassen im Anschluss das Configuration Management laufen. Danach haben wir beispielsweise sofort unseren fertigen Webserver, Appserver, Reverse Proxy etc.
Ein eigenes Terraform vSphere Modul schreiben
Die ganze Konfiguration für ein Deployment unter vSphere kann natürlich über ein einziges Terraform File durchgeführt werden. Diese Vorgehensweise ist jedoch eher ungünstig, wenn wir große Umgebungen betreuen möchten. Hier sollte direkt ein passendes Terrafom Modul geschrieben werden.
Ich werde im folgenden Beispiel das reine Server Deployment Modul beschreiben, es müssen dazu Datendefinitionen und Variablen deklariert werden. Vielleicht werde ich dazu nochmal einen weiteren Blogpost verfassen.
Wir beginnen mit unserer server.tf und hinterlegen fürs Erste eine resource definition.
resource "vsphere_virtual_machine" "server" {
count = var.guest_id == "ubuntu64Guest" ? 1 : 0
name = var.virtual_machine_name
datastore_id = data.vsphere_datastore.datastore.id
resource_pool_id = data.vsphere_compute_cluster.cluster.resource_pool_id
num_cpus = var.cpus
memory = var.memory
guest_id = var.guest_id
tags = [data.vsphere_tag.tag.id]
scsi_type = var.scsi_type
„count“ bestimmt, ob wir einen Windows Server oder einen Linux Server erstellen. Die „guest_id“ muss dann im späteren Deployment angegeben werden.
Wir sollten jetzt zwingend ein „lifecycle“ Statement verfassen, sonst löschen wir vielleicht unbeabsichtigt produktive Maschinen.
lifecycle {
prevent_destroy = true
ignore_changes = all
}
Als nächstes bauen wir noch das Netzwerkinterface für unsere Maschine ein.
network_interface {
network_id = data.vsphere_network.network.id
}
Es kann gut sein, dass unser Server mehr als eine Festplatte halten soll. Daher können wir in Terraform eine Schleife deklarieren, um es uns im späteren Deployment möglichst einfach zu machen.
dynamic "disk" {
for_each = [for s in var.disks: {
label = s.label
unit_number = s.unit_number
size = s.size
}]
content {
label = disk.value.label
unit_number = disk.value.unit_number
size = disk.value.size
}
}
Jetzt kommt der wichtige Teil. Mit dem „clone“ Block legen wir das Template und die Optionen der virtuellen Maschine fest.
clone {
template_uuid = data.vsphere_content_library_item.content_library_item.id
customize {
linux_options {
host_name = var.host_name
domain = var.domain
}
network_interface {
ipv4_address = var.ipv4_address
ipv4_netmask = var.ipv4_netmask
}
ipv4_gateway = var.ipv4_gateway
dns_server_list = var.dns_server_list
}
}
Mit der „template_uuid“ Datendefinition holen wir uns die uuid des gewünschten Content Library Objekts, um dieses für unser Deployment zu verwenden.
Hier nochmal der komplette Code unserer server.tf
resource "vsphere_virtual_machine" "server" {
count = var.guest_id == "ubuntu64Guest" ? 1 : 0
name = var.virtual_machine_name
datastore_id = data.vsphere_datastore.datastore.id
resource_pool_id = data.vsphere_compute_cluster.cluster.resource_pool_id
num_cpus = var.cpus
memory = var.memory
guest_id = var.guest_id
tags = [data.vsphere_tag.tag.id]
scsi_type = var.scsi_type
lifecycle {
prevent_destroy = true
ignore_changes = all
}
network_interface {
network_id = data.vsphere_network.network.id
}
dynamic "disk" {
for_each = [for s in var.disks: {
label = s.label
unit_number = s.unit_number
size = s.size
}]
content {
label = disk.value.label
unit_number = disk.value.unit_number
size = disk.value.size
}
}
clone {
template_uuid = data.vsphere_content_library_item.content_library_item.id
customize {
linux_options {
host_name = var.host_name
domain = var.domain
}
network_interface {
ipv4_address = var.ipv4_address
ipv4_netmask = var.ipv4_netmask
}
ipv4_gateway = var.ipv4_gateway
dns_server_list = var.dns_server_list
}
}
}
resource "vsphere_virtual_machine" "windows_server" {
count = var.guest_id == "windows2019srv_64Guest" ? 1 : 0
name = var.virtual_machine_name
datastore_id = data.vsphere_datastore.datastore.id
firmware = "efi"
resource_pool_id = data.vsphere_compute_cluster.cluster.resource_pool_id
num_cpus = var.cpus
memory = var.memory
guest_id = var.guest_id
tags = [data.vsphere_tag.tag.id]
scsi_type = "lsilogic-sas"
lifecycle {
prevent_destroy = true
ignore_changes = all
}
network_interface {
network_id = data.vsphere_network.network.id
}
dynamic "disk" {
for_each = [for s in var.disks: {
label = s.label
unit_number = s.unit_number
size = s.size
}]
content {
label = disk.value.label
unit_number = disk.value.unit_number
size = disk.value.size
}
}
clone {
template_uuid = data.vsphere_content_library_item.content_library_item.id
customize {
windows_options {
computer_name = var.host_name
}
network_interface {
ipv4_address = var.ipv4_address
ipv4_netmask = var.ipv4_netmask
}
ipv4_gateway = var.ipv4_gateway
dns_server_list = var.dns_server_list
}
}
}
👍👍👍👍👍
Sehr gut beschrieben. Der Artikel war sehr informativ und hat mir dabei geholfen, das Konzept besser zu verstehen und zu verinnerlichen.