Terraform, GitLab und Azure
In diesem Post geht es um Terraform, GitLab und Azure, und das Zusammenspiel. Ich habe Terraform Skripte erstellt, und diese per git nach GitLab hochgeladen. Bei jedem Hochladen wollte ich, dass eine Pipeline startet, die die Ressourcen in Azure dann erstellt. Ich beschreibe, wie das geht.
Terraform
Zunächst habe ich ein einfaches Projekt erstellt mit einem Terraform-Skript main.tf.
Darin ist der Provider enthalten. In diesem Fall Azure von Hashicorp.
terraform { required_providers { azurerm = { source = "hashicorp/azurerm" version = "=3.0.0" } } } provider "azurerm" { features {} }
Um das Passwort und den Admin der zu erstellenden VM nicht im Klartext zu haben, habe ich Variablen erstellt.
variable "azure_vm_user_name" { type = string } variable "azure_vm_user_password" { type = string }
Anschließend müssen die Ressourcen codiert werden. Dazu zunächst eine Ressourcegroup erstellen.
resource "azurerm_resource_group" "pkrg" { name = "${terraform.workspace}-rg" location = "West Europe" }
Im Anschluss werden Netzwerk relevante Ressourcen angelegt.
resource "azurerm_virtual_network" "pknetwork" { name = "${terraform.workspace}-network" address_space = ["10.0.0.0/16"] location = azurerm_resource_group.pkrg.location resource_group_name = azurerm_resource_group.pkrg.name } resource "azurerm_subnet" "pksubnet" { name = "${terraform.workspace}-subnet" resource_group_name = azurerm_resource_group.pkrg.name virtual_network_name = azurerm_virtual_network.pknetwork.name address_prefixes = ["10.0.2.0/24"] } resource "azurerm_network_interface" "pkinterface" { count = 4 name = "${terraform.workspace}-nic${count.index}" location = azurerm_resource_group.pkrg.location resource_group_name = azurerm_resource_group.pkrg.name ip_configuration { name = "internal" subnet_id = azurerm_subnet.pksubnet.id private_ip_address_allocation = "Dynamic" } }
Als letztes werden die VMs erstellt.
count
gibt an, wie oft die Ressource angelegt werden soll.
${terraform.workspace}
ist ein dynamischer Wert, der per Default
default
ist. Es ist ratsam, pro Umgebung einen Workspace anzulegen. Man kann diese Variable auch nutzen, um Tags zu setzen.
${count.index}
ist die laufende Variable zu
count
resource "azurerm_windows_virtual_machine" "createVMs" { count = 4 name = "${terraform.workspace}-vm-${count.index}" resource_group_name = azurerm_resource_group.pkrg.name location = azurerm_resource_group.pkrg.location size = "Standard_B1s" admin_username = var.azure_vm_user_name admin_password = var.azure_vm_user_password network_interface_ids = [element(azurerm_network_interface.pkinterface.*.id, count.index)] os_disk { caching = "ReadWrite" storage_account_type = "Standard_LRS" } source_image_reference { publisher = "MicrosoftWindowsServer" offer = "WindowsServer" sku = "2016-Datacenter" version = "latest" } tags = { Environment = "${terraform.workspace}" } }
GitLab
Damit GitLab ein Projekt erkennt, muss im Hauptverzeichnis eine .gitlab-ci.yml erstellt werden. Hier wird die Pipeline mit den Stages definiert. Der Code ist einfach gehalten und ist nicht optimiert. Er dient lediglich dazu, zu zeigen, wie man relativ schnell eine Pipeline zum Ausrollen der Infrastruktur auf Azure automatisieren kann.
Der
cache
Block ist dafür da, dass GitLab Dateien aus einem vorherigen Schritt nicht mehrmals erzeugen muss, womit der Prozess insgesamt schneller wird. Der
before_script
Block wird vor jedem
script
Block ausgeführt. Das ist hier sicher nicht optimal, aber der Einfachheit geschuldet. Mit
auto-approve
verhindert man die manuelle Eingabe von 'yes' beim Ausführen von
terrafom apply
.
image: name: hashicorp/terraform:1.3.6 entrypoint: [""] stages: - infra:plan - infra:apply cache: key: tf-cache paths: - ${TF_ROOT}/.terraform before_script: - terraform --version - Set-Item -Path env:TF_VAR_azure_vm_user_name -Value $TF_VAR_azure_vm_user_name - Set-Item -Path env:TF_VAR_azure_vm_user_password -Value $TF_VAR_azure_vm_user_password - az login --service-principal -u $TF_VAR_azure_principal_client_id -p $TF_VAR_azure_principal_password --tenant $TF_VAR_azure_tenant_id plan_infra: stage: infra:plan script: - terraform init - terraform workspace new dev - terraform workspace select dev - terraform plan -out=tfplan artifacts: paths: - tfplan untracked: false when: on_success expire_in: "1 days" apply_infra: stage: infra:apply script: - terraform init - terraform workspace new dev - terraform workspace select dev - terraform apply -auto-approve "tfplan" dependencies: - plan_infra when: manual
Secrets (Variablen)
Um sensible Variablen wie Passwörter aus GitLab nach Terraform durchzureichen, muss man diese als Umgebungsvariablen definieren. Wieso Windows mag sich der ein oder andere fragen. Das liegt daran, dass GitLab lediglich 400 Requests kostenfrei erlaubt, wenn man seine Kreditkarten hinterlegt. Da ich meine Kreditkartendaten nicht hinterlegen wollte, habe ich einen lokalen GitLab Runner unter Windows erstellt.
GitLab Variablen werden im Projekt unter Settings > CI/CD angelegt. Da ich direkt auf nach master deploye, musste ich bei den Variablen den Flag Protected deaktivieren.
In der .gitlab-ci.yml muss man diese Variablen dann als Umgebunsvariable deklarieren:
- Set-Item -Path env:TF_VAR_azure_vm_user_name -Value $TF_VAR_azure_vm_user_name - Set-Item -Path env:TF_VAR_azure_vm_user_password -Value $TF_VAR_azure_vm_user_password
Im dritten Schritt muss man die Definition der Variablen in Terraform anlegen.
variable "azure_vm_user_name" { type = string } variable "azure_vm_user_password" { type = string }
Im letzten Schritt kann man in Terraform im Ressource Block auf die Variable zugreifen.
admin_username = var.azure_vm_user_name admin_password = var.azure_vm_user_password
Azure
Es ist nicht ratsam, die Admin-Credentials für die Verbindung zwischen einem Automaten mit Azure zu nutzen. Bei mir hat das auch erst gar nicht geklappt, so dass ich zunächst einen Service Principal mit PowerShell erzeugen musste. Die im Anschluss angezeigten Werte habe ich dann als GitLab-Variablen gespeichert.
az ad sp create-for-rbac --name <service_principal_name> --role Contributor --scopes /subscriptions/<subscription_id>
Das Verbinden mit Azure aus der Pipeline funktioniert dann wie folgt:
az login --service-principal -u $TF_VAR_azure_principal_client_id -p $TF_VAR_azure_principal_password --tenant $TF_VAR_azure_tenant_id
Issues
Der Weg (in der IT) ist immer steinig. Ich kann mich kaum an eine initiale Erstellung einer Umgebung erinnern, die reibungsfrei funktioniert hätte. Meist stößt man auf Restriktionen und Abhängigkeiten, die man erst lösen muss, ehe man über weite Umwege zur eigentlichen Entwicklung oder Problemstellung kommt.
Der lokale GitLab Runner hat mich einige Zeit gekostet, da Windows beim Registrieren des Runners rumgezickt hat. Aber was soll ich sagen, bei Windows hilft es, den Rechner durchzustarten.
Das Verbinden zwischen Terraform und Azure hat mich auch einiges an Recherche gekostet, bis ich den ServicePrincipal angelegt und
az login
angepasst habe.
Leider habe ich wenig Quellen darüber gefunden, wie man GitLab-Variablen nach Terraform durchreichen kann. Die wenigen Quellen haben dann auch nicht funktioniert. Bis ich erkannt habe, dass zum einen natürlich die export-Funktion auf dem Windows-Runner nicht existiert, und zum anderen die Syntax sich etwas geändert hat, und man export auch nicht mit set ersetzen kann.