Get Rewarded! We will reward you with up to €50 credit on your account for every tutorial that you write and we publish!

Erstellen von eigenen Betriebsystem-Images mit HashiCorp Packer

profile picture
Author
Henrik Gerdes
Published
2025-02-27
Time to read
9 minutes reading time

About the author- Software Engineer that works with various cloud services.

Einführung

Hetzner Cloud bietet eine Reihe verschiedener Betriebssystem-Images, aus denen man bei der Erstellung einer neuen virtuellen Maschine wählen kann. Diese Images bilden die Basis, um eigene Dienste aufzubauen, sind aber selten bereits für die jeweiligen Anforderungen konfiguriert. Vielleicht möchtest du eine Datenbank oder einen Webserver installieren, einen Wartungsbenutzer hinzufügen oder einfach die Systemeinstellungen optimieren, um die Leistung und Sicherheit zu verbessern. Es gibt zwar Tools, die dies automatisch für jede neue VM erledigen, aber dies kostet Zeit und ist nicht immer reproduzierbar. Wäre es nicht einfacher, ein eigenes, gut funktionierendes, persönliches konfiguriertes Betriebssystem-Image zu haben, das für jede neue VM verwendet werden kann?

Dieses kann mit HashiCorp Packer erreicht werden.

Dieser Artikel zeigt, wie du mit Packer eigene Images für deine Hetzner-VMs mit Infrastructure as Code (IaC) erstellen kannst. Es ermöglicht sogar die Erstellung von VMs mit Betriebssystemen, die nicht offiziell von Hetzner unterstützt werden.

Voraussetzungen

Um diesem Tutorial zu folgen, benötigst du:

Schritt 1 - Installieren von Packer

Besuche Hashicorp Packer und installiere Packer entsprechend deines Betriebssystems.
Auf einem Debian-basierten Linux, muss folgendes ausgeführt werden:

wget -O - https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install packer

Führe packer --version aus, um die Version anzuzeigen und zu prüfen, ob die Installation erfolgreich war.

Schritt 2 - Erstellen eines benutzerdefinierten Images

Erstelle einen Projektordner mit mkdir my-hetzner-img und wechsel in das Verzeichnis.
Ähnlich wie Terraform verwendet Packer Provider, um mit dem benötigten Build-System zu kommunizieren. Die Verwendung verschiedener Provider ermöglicht es Packer, eine große Auswahl an verschiedenen Zielsystemen anzubieten. Es ermöglicht Nutzern, Software- oder Betriebssystempakete für alle wichtigen Cloud-Anbieter, On-Prem-Systeme und sogar Docker-Images zu erstellen.
Um einen Provider zu verwenden, erstelle eine Datei namens provider.pkr.hcl und füge die Provider-Konfiguration hinzu:

# packer.pkr.hcl
packer {
  required_plugins {
    hcloud = {
      source  = "github.com/hetznercloud/hcloud"
      version = ">= 1.6.0"
    }
  }
}

Packer verwendet die HashiCorp Konfigurationssprache (HCL), um den gewünschten Zustand zu deklarieren, genau wie Terraform.

Um ein personalisiertes, benutzerdefiniertes Image zu erstellen, benötigt Packer eine Basis, von der aus es startet. Diese wird als "source" bezeichnet:

# custom-img-v1.pkr.hcl
source "hcloud" "base-amd64" {
  image         = "debian-12"
  location      = "nbg1"
  server_type   = "cx22"
  ssh_keys      = []
  user_data     = ""
  ssh_username  = "root"
  snapshot_name = "custom-img"
  snapshot_labels = {
    base    = "debian-12",
    version = "v1.0.0",
    name    = "custom-img"
  }
}

Damit wird Packer mitgeteilt, wo es beginnen soll. Im Fall von Hetzner muss Packer das Basis-Image, den Servertyp und den Standort der für die Erstellung verwendet werden soll, kennen. Außerdem werden der Name des Snapshots, welches erstellt wird, und die Tags, die auf dieses angewendet werden sollen, angegeben.

Der nächste Schritt besteht darin, die eigenen Anpassungen vorzunehmen. Damit ist der eigentliche Build-Schritt gemeint.

# custom-img-v1.pkr.hcl
build {
  sources = ["source.hcloud.base-amd64"]
  provisioner "shell" {
    inline = [
      "apt-get update",
      "apt-get install -y wget fail2ban cowsay",
      "/usr/games/cowsay 'Hi Hetzner Cloud' > /etc/motd",
    ]
    env = {
      BUILDER = "packer"
    }
  }
}

Man verwendet die zuvor angegebene Quelle und gibt dann z.B den shell-Parameter an. Jede Zeile in inline wird dann auf dem Server ausgeführt. In diesem Fall wird fail2ban installiert, um den Server abzusichern und es wird eine kleine Meldung in die motd-Datei einfügt.

Hinweis: Wenn du packer build . ausführst, wird Packer automatisch einen neuen Server und einen neuen Snapshot in deinem Hetzner Cloud Projekt erstellen. Beide sind kostenpflichtig. Der Server wird automatisch gelöscht, sobald entweder der Snapshot erstellt wurde oder die Erstellung fehlgeschlagen ist.

Um das Image zu erstellen, führe die folgenden Befehle aus:

# Set your Hetzner API Token
export HCLOUD_TOKEN="XXX"
# Initialize the project - only needed once
packer init .
# Build
packer build .

Jetzt baut Packer den angegebenen Server mit allen Anweisungen und überträgt alle Ausgaben auf das aktuelle Terminal. Überprüfe, ob das neue Image in der Cloud Console verfügbar ist:

image-overview-1.png.png

Schritt 3 - Konfigurierbare Images

Wenn man alle Befehle in die HCL einträgt, kann es schnell unübersichtlich werden. Es ist auch nicht sinnvoll, den Code für jede Variante eines Images, das du erstellen möchtest, zu kopieren und einzufügen. Um die Dinge allgemeiner zu gestalten, kann Packer Variablen verwenden:

# custom-img-v2.pkr.hcl
variable "base_image" {
  type    = string
  default = "debian-12"
}
variable "output_name" {
  type    = string
  default = "snapshot"
}
variable "version" {
  type    = string
  default = "v1.0.0"
}
variable "user_data_path" {
  type    = string
  default = "cloud-init-default.yml"
}

source "hcloud" "base-amd64" {
  image         = var.base_image
  location      = "nbg1"
  server_type   = "cx22"
  ssh_keys      = []
  user_data     = file(var.user_data_path)
  ssh_username  = "root"
  snapshot_name = "${var.output_name}-${var.version}"
  snapshot_labels = {
    base    = var.base_image,
    version = var.version,
    name    = "${var.output_name}-${var.version}"
  }
}

build {
  sources = ["source.hcloud.base-amd64"]
  provisioner "shell" {
    scripts = [
      "os-setup.sh",
    ]
    env = {
      BUILDER = "packer"
    }
  }
}

Erstelle folgende Dateien:

cloud-init-default.yml
#cloud-config

ssh_pwauth: false
disable_root_opts: no-port-forwarding,no-agent-forwarding,no-X11-forwarding

# Install base packages
package_update: true
package_upgrade: true
packages:
  - gnupg
  - curl
  - jq
  - unzip
  - apparmor
  - aptitude
  - lsb-release
  - ca-certificates
  - apt-transport-https
  - unattended-upgrades
  - apparmor-profiles-extra
  - logrotate
  - wget
os-setup.sh
#!/bin/bash
set -e -o pipefail

echo "waiting for cloud-init to finish..."
if ! cloud-init status --wait; then
    echo "Note: cloud-init failed or was already completed."
fi

echo "Installing packages..."
apt-get update
apt-get install --yes --no-install-recommends wget fail2ban

echo "cleanup..."
cloud-init clean --machine-id --seed --logs

Jetzt können wir das Basis-Image zur Laufzeit angeben und überschreiben, indem wir diesen Befehl verwenden:

packer build -var base_image=ubuntu-24.04 -var version=v1.1.0 .

Diese Version verwendet auch den scrips-Parameter, anstatt alle Befehle direkt anzugeben. Dies ermöglicht viel komplexere Setups. Außerdem wird auch cloud-init verwendet, um einige Einstellungen deklarativ zu konfigurieren.

Hetzner unterstützt auch Arm64-basierte Server, die ein hervorragendes Preis-Leistungs-Verhältnis und eine beeindruckende Effizienz bieten. Aufgrund der anderen Architektur von Arm muss die Software für diese Architektur kompiliert werden und alle Konfigurationen müssen auch für diese angewendet werden. Glücklicherweise erlaubt Packer die Wiederverwendung beliebiger Build-Skripte für Arm.

Füge im Code einfach eine weitere "source" hinzu und aktualisiere den Build-Schritt, um diese Quelle einzubeziehen:

# custom-img-v2.pkr.hcl
source "hcloud" "base-arm64" {
  image         = var.base_image
  location      = "nbg1"
  server_type   = "cax11"
  ssh_keys      = []
  user_data     = file(var.user_data_path)
  ssh_username  = "root"
  snapshot_name = "${var.output_name}-${var.version}"
  snapshot_labels = {
    base    = var.base_image,
    version = var.version,
    name    = "${var.output_name}-${var.version}"
  }
}
...
build {
-  sources = ["source.hcloud.base-amd64"]
+  sources = ["source.hcloud.base-amd64", "source.hcloud.base-arm64"]
  provisioner "shell" {
...

In dieser Version werden nun automatisch Images für Arm64-basierte Server erstellt.

Schritt 4 - Tipps und fortgeschrittene Verwendung:

Über die Festplattengröße:
Du hast vielleicht bemerkt, dass der obige Code die kleinsten auf Hetzner verfügbaren Instanzen verwendet. Dies dient nicht nur der Kostenreduzierung, sondern ermöglicht auch, dass der erstellte Snapshot für jede VM eingesetzt werden kann. Aufgrund der Art und Weise, wie Festplatten bei Hetzner verwaltet werden, muss eine neue VM über eine Festplatte verfügen, die mindestens so groß ist wie diejenige, von der der Snapshot erstellt wurde. Die Verwendung eines Servers mit acht Kernen könnte etwas schneller sein als die Verwendung eines kleineren Servers, aber das würde die Server, die mit diesem Image bereitgestellt werden können, auf VMs mit mindestens 240 GB Festplatten beschränken.

Über Tags:
Tags sind Metadaten, die einem Snapshot hinzufügt werden können. Sie sind nützlich, um Informationen über die Herkunft des Abbilds und dessen Verwendungszweck bereitzustellen. Außerdem sind Tags die einzige Möglichkeit, einen vorhandenen Snapshot in Terraform auszuwählen, um eine neue VM bereitzustellen. Sie sollten definitiv verwendet werden.

Über cloud-init:
Du kannst cloud-init verwenden, um einige frühe Einstellungen beim Erstellen von Images mit Packer vorzunehmen. Du kannst cloud-init aber auch verwenden, um ein benutzerdefiniertes Image für eine neue VM zu verwenden. Standardmäßig wird cloud-init nur einmal ausgeführt, daher muss es zurückgesetzt werden.
Füge die folgenden Zeilen in das Bash-Skript ein, um auf die Fertigstellung von cloud-init zu warten und um es abschließend zurück zu setzen.

# os-setup.sh
#!/bin/bash
set -e -o pipefail

echo "Waiting for cloud-init to finish..."
if ! cloud-init status --wait; then
    echo "Note: cloud-init failed or was already completed."
fi

echo "Installing packages..."
apt-get update
apt-get install --yes --no-install-recommends wget fail2ban

# My setup...

echo "Cleanup..."
cloud-init clean --machine-id --seed --logs

Dies ermöglicht es den Benutzern, cloud-init ein zweites Mal auszuführen.

Verschiedene Betriebssysteme:
Du kannst auch andere Betriebssysteme erstellen und booten und mit Packer eigene Images für diese erstellen. Durch Booten in den Hetzner-rescue Modus kann man die gesamte Disk einer VM überschreiben:

build {
  sources = ["source.hcloud.myvm"]
  provisioner "shell" {
    inline = [
      "apt-get install -y wget",
      "wget https://github.com/siderolabs/talos/releases/download/v1.7.7/hcloud-amd64.raw.xz",
      "xz -d -c hcloud-amd64.raw.xz | dd of=/dev/sda && sync",
    ]
  }
}

Mein persönlicher Anwendungsfall für Packer ist die Erstellung vorkonfigurierter Kubernetes-Images mit fein abgestimmten Systemparametern und vorgeladenen Container-Images. Der horizontale Autoscaler kann diese Images verwenden, um neue Worker Nodes schnell und zuverlässig bereitzustellen. Aber es gibt noch viele andere Anwendungsfälle.

image-overview-2.png.png

Weitere Informationen zur Konfiguration werden in der Packer Dokumentation und in der Dokumentation zum Hetzner Packer-Builder bereitgestellt.
Der gesamte Code, der in diesem Artikel verwendet wurde, ist auf GitHub zu finden.

Ergebnis

Dieser Artikel gab eine umfassende Anleitung zur Installation und Verwendung von HashiCorp Packer. Er lieferte ein Basisprojekt, wie man Packer verwendet, um benutzerdefinierte Betriebssystem-Images für Hetzner Cloud VMs zu erstellen und dabei Infrastructure as Code Praktiken zu nutzen. Es wurde auch auf die fortgeschrittene Verwendung von Variablen und cloud-init eingegangen. Dieser Ansatz ermöglicht die Erstellung von maßgeschneiderten, vorkonfigurierten Betriebssystem-Images und bietet einen effizienteren und schlankeren Prozess für die Bereitstellung von VMs mit benutzerdefinierten Einstellungen und Anwendungen.

License: MIT
Want to contribute?

Get Rewarded: Get up to €50 in credit! Be a part of the community and contribute. Do it for the money. Do it for the bragging rights. And do it to teach others!

Report Issue
Try Hetzner Cloud

Get €20/$20 free credit!

Valid until: 31 December 2025 Valid for: 3 months and only for new customers
Get started
Want to contribute?

Get Rewarded: Get up to €50 credit on your account for every tutorial you write and we publish!

Find out more