terraforming Hetzner, Pt 1
Tue 24 May 2022Die erste automatisierte VM
Erster Teil der terraform–Serie: Basics und eine einzelne VM.
Zum Zeitpunkt der Drucklegung benutze ich terraform in Version 1.2.0. Alles weitere installieren wir im Laufe des Wegs. :)
Vorbereitungen
Neben terraform und einem geeigneten Rechner um es auszuführen (btw, I use arch) brauchen wir einen Hetzner Cloud–Account. Die Kosten zum Experimentieren halten sich im Rahmen, ich rechne mit ca. 30–50 cent für einen Nachmittag/Abend. Wenn Du Maschinen dauerhaft laufen lassen willst kommt da natürlich mehr zusammen.
Ich vermute dass es nicht viel Aufwand ist die terraform templates auf andere Provider umzubauen — habe das aber noch nicht getestet.
Im Hetzner Cloud Account müssen wir ein Projekt anlegen, und in diesem Projekt ein API–Token, dies muss Lese– und Schreibberechtigung haben. Den Token erstmal irgendwo zwischenspeichern (wo niemand sonst drankommt).
Provider installieren und Zugang einrichten
Ich werde den Post über immer meine Quelldateien einbetten und darunter beschreiben was drin steht. Alles zusammen findest Du ganz unten als Git–Repo verlinkt.
terraform.tf:
terraform {
required_providers {
hcloud = {
source = "hetznercloud/hcloud"
version = "1.33.2"
}
}
required_version = ">= 1.1"
}
Zuerst legen wir fest das wir den hcloud–Provider nutzen werden, und setzen die
Version fest. Da sich über die Zeit durchaus Parameter verändern ist nicht gegeben
das die Templates so mit einer zukünftigen Providerversion funktionieren.
required_version
bezieht sich auf terraform selbst.
provider.tf:
provider "hcloud" {
token = var.hcloud_token
}
Zum Login brauchen wir das oben generiert API–Token. Das könnte hier direkt stehen (statt var...
), oder jedes mal eingetippt werden, dann wäre die Konfiguraton so fertig.
Ich will es aber nicht ins gitrepo einchecken, daher ists eine Variable.
variables.tf:
variable "hcloud_token" {
sensitive = true
# default = <defined in secret.auto.tfvars>
}
Auch hier könnte der Token als default stehen, aber auch die variables.tf will ich git haben, also...
secret.auto.tfvars:
hcloud_token = "hunter2"
Hier steht endlich der Token. tfvars–Dateien überschreiben schon gesetzte Variablen, endet der Dateiname auf .auto.tfvars werden sie automatisch geladen. Die Datei kommt ins .gitignore, und der Token bleibt sicher.
Jetzt ist das Projekt soweit das wir den Provider installieren können: terraform init
Theoretisch funktioniert jetzt auch terraform apply
schon, tut aber noch nichts...
SSH Public Key und Firewall–Regeln
ssh.tf:
resource "hcloud_ssh_key" "default" {
name = "terraform"
public_key = file("../ssh-terraform-hetzner.pub")
}
Upload eines SSH–Keys. In terraform wird der hier angelegte Key default
heissen, in der Hetzner Console "terraform". Vergiss nicht die Datei vorher zu erzeugen oder den Pfad zu ändern ;)
firewall.tf:
resource "hcloud_firewall" "single-firewall" {
name = "single-firewall"
rule {
direction = "in"
protocol = "icmp"
source_ips = [
"0.0.0.0/0",
"::/0"
]
}
rule {
direction = "in"
protocol = "tcp"
port = "22"
source_ips = [
"0.0.0.0/0",
"::/0"
]
}
rule {
direction = "in"
protocol = "tcp"
port = "80"
source_ips = [
"0.0.0.0/0",
"::/0"
]
}
}
Vorbereitung der Firewall. Eingehender Traffic wird blockiert, ausser ICMP (denn ohne icmp kein Internet), Port 22 für SSH und Port 80 für Web. Auch wenn die Beispiel–VM keinen Webserver haben wird…
Der eigentliche Server
Nun kommen wir endlich zum eigentlichen Server. Dazu gibts erstmal ein paar neue Variablen:
variables.tf:
variable "location" {
default = "nbg1"
}
variable "server_type" {
default = "cx11"
}
variable "os_type" {
default = "debian-11"
}
Es wird der kleinste VM–Typ in Nürnberg, mit Debian 11. Andere gültige Werte findest Du in der Dokumentation von Hetzner.
server.tf:
resource "hcloud_server" "single-server1" {
name = "single-server1"
image = var.os_type
server_type = var.server_type
location = var.location
labels = {
type = "single"
}
ssh_keys = [hcloud_ssh_key.default.id]
firewall_ids = [hcloud_firewall.single-firewall.id]
user_data = templatefile("user-data.yaml.tpl",
{ssh_pubkey = file("../ssh-terraform-hetzner.pub")})
}
Der eigentliche Server, heißt im terraform single-server1
, in der Hetzner Console auch, einige Werte kommen aus den Variablen. Das Label wird nicht weiter benutzt und ist eigentlich egal, das wird im nächsten Artikel interessant. Wir referenzieren die vorher angelete Firewall und den SSH–Key für den root–Account. Spannend sind die letzten beiden Zeilen, damit binden wir ein Template in cloud-init ein, um den Server nach dem Boot etwas weiter einzurichten.
user-data.yaml.tpl:
#cloud-config
users:
- name: "ansible"
groups: ["sudo"]
sudo: "ALL=(ALL) NOPASSWD:ALL"
shell: "/bin/bash"
ssh_authorized_keys:
- "${ssh_pubkey}"
package_update: true
package_upgrade: true
Per cloud-init legen wir einen Nutzer ansible an, der per sudo alle Befehle ausführen darf, und über die Variable ssh_pubkey
aus terraform heraus den public key bekommt.
Wichtig: Die erste Zeile muss dieser schusselige Kommentar sein, sonst sucht ihr eine Stunde lang warum das Skript nicht ausgeführt wird. :)
Und noch ein Hinweis: Das Skript läuft los nachdem terraform schon fertig ist. Es kann also ein paar Sekunden dauern bis der ansible–Nutzer da ist.
Last but not least, output.tf:
output "test_ip" {
description = "Test VM IP"
value = hcloud_server.single-server1.ipv6_address
}
output "test_ipv4" {
description = "Test VM legacy IP"
value = hcloud_server.single-server1.ipv4_address
}
Die outputs geben einfach nur am Ende des terraform–Laufs ein paar Daten aus. Hier sehen wir bequem die IP der neu angelegten VM.
Sind alle Dateien angelegt und ausgefüllt kannst Du mit terraform plan
anschauen was passieren wird, und mit terraform apply
die VM erstellen. terraform destroy
löscht alles wieder. Nicht vergessen, sonst läuft die Kostenuhr weiter. ;)
Die ganzen Dateien liegen auch in meinem git–Repository
Im nächsten Artikel fügen wir mehrere VMs, ein internes Netzwerk und Loadbalancer hinzu.