ADMIN

2023

03

2023-02-28T12:00:00

Hybrid Cloud

SCHWERPUNKT

081

Hybrid Cloud

Cloud

Images mit HashiCorp Packer erzeugen

Verlässliche Konstruktion

von Thorsten Scherf

Veröffentlicht in Ausgabe 03/2023 - SCHWERPUNKT

Das quelloffene Packer erzeugt Maschinen-Images für unterschiedliche Plattformen und Umgebungen mittels einer einzelnen Konfiguration. Damit lassen sich identische Systeme aufsetzen, unabhängig vom zugrundeliegenden Cloudanbieter. Wie Sie diese goldenen Images für das Deployment von Systemen in hybriden Cloudumgebungen anlegen und einsetzen, zeigt dieser Workshop.

Die Zeiten, in denen das Deployment von einem System mehrere Stunden oder gar Tage in Anspruch genommen hat, sind lange vorbei. Agile Arbeitsprozesse haben in den meisten Unternehmen Einzug gehalten, was letztendlich auch Auswirkungen auf den Provisionierungsprozess von neuen Systemen hat. Dabei zeichnen sich agile Workflows ja bekanntlich dadurch aus, dass komplexe Probleme in kleine Teilbereiche aufgebrochen werden, um Lösungen für die einzelnen Problembereiche möglichst effizient umzusetzen. Geschwindigkeit spielt bei den einzelnen Prozessen üblicherweise eine große Rolle und insofern ist die Automatisierung ein wichtiges Thema.
Schauen wir uns nun den Bereich System-Deployment an, so besteht das zu lösende Problem darin, eine Vielzahl von Systemen in möglichst kurzer Zeit zur Verfügung zu stellen – unabhängig davon, in welcher Umgebung sich die einzelnen Systeme befinden. Natürlich müssen diese Systeme eine einheitliche Konfiguration besitzen. Das gilt nicht nur für den Software-Stack selbst, sondern auch für Compliance-Anforderungen, die natürlich auf sämtlichen Systemen zu erfüllen sind. Eine Einbindung in bereits vorhandene CI/CD-Umgebungen gilt dabei als vorteilhaft.
Im Idealfall existieren also identische Images für das Deployment, unabhängig davon, in welcher Umgebung ein System letztendlich installiert werden soll. Und genau das ist oft das große Problem: Jede Cloudumgebung ist unterschiedlich und stellt andere Anforderungen an ein System-Image. Denken Sie allein an die zahlreichen Image-Formate. Schön wäre es, wenn Sie den kompletten Prozess automatisieren könnten, anstatt die Images immer wieder manuell zu erzeugen. Und genau hier kommt nun das Tool Packer [1] von HashiCorp zum Einsatz.
Die Zeiten, in denen das Deployment von einem System mehrere Stunden oder gar Tage in Anspruch genommen hat, sind lange vorbei. Agile Arbeitsprozesse haben in den meisten Unternehmen Einzug gehalten, was letztendlich auch Auswirkungen auf den Provisionierungsprozess von neuen Systemen hat. Dabei zeichnen sich agile Workflows ja bekanntlich dadurch aus, dass komplexe Probleme in kleine Teilbereiche aufgebrochen werden, um Lösungen für die einzelnen Problembereiche möglichst effizient umzusetzen. Geschwindigkeit spielt bei den einzelnen Prozessen üblicherweise eine große Rolle und insofern ist die Automatisierung ein wichtiges Thema.
Schauen wir uns nun den Bereich System-Deployment an, so besteht das zu lösende Problem darin, eine Vielzahl von Systemen in möglichst kurzer Zeit zur Verfügung zu stellen – unabhängig davon, in welcher Umgebung sich die einzelnen Systeme befinden. Natürlich müssen diese Systeme eine einheitliche Konfiguration besitzen. Das gilt nicht nur für den Software-Stack selbst, sondern auch für Compliance-Anforderungen, die natürlich auf sämtlichen Systemen zu erfüllen sind. Eine Einbindung in bereits vorhandene CI/CD-Umgebungen gilt dabei als vorteilhaft.
Im Idealfall existieren also identische Images für das Deployment, unabhängig davon, in welcher Umgebung ein System letztendlich installiert werden soll. Und genau das ist oft das große Problem: Jede Cloudumgebung ist unterschiedlich und stellt andere Anforderungen an ein System-Image. Denken Sie allein an die zahlreichen Image-Formate. Schön wäre es, wenn Sie den kompletten Prozess automatisieren könnten, anstatt die Images immer wieder manuell zu erzeugen. Und genau hier kommt nun das Tool Packer [1] von HashiCorp zum Einsatz.
Provisionierung mit einheitlicher Konfiguration
Mit Packer ist es ein Leichtes, fertig provisionierte Images zu erzeugen, um damit dann in kürzester Zeit neue Maschinen zu starten. Durch den modularen Aufbau des Tools können Sie mithilfe einer einzelnen Konfigurationsdatei mehrere Images für unterschiedliche Plattformen, beispielsweise AWS, Google Cloud, Azure oder VMware, generieren. Dank dieser einheitlichen Konfiguration lassen sich mögliche Fehler leicht identifizieren und frühzeitig beheben. Zur initialen Provisionierung der Images kann Packer auf bekannte Konfigurations-Managementtools wie Puppet, Chef oder Ansible zurückgreifen. Insofern lassen sich bereits etablierte und verifizierte Konfigurationen weiterhin verwenden.
Die in Go geschriebene Software wurde im Jahr 2013 ursprünglich von Mitchell Hashimoto entwickelt und ist mittlerweile, neben Terraform und einigen weiteren Tools, ein Kernbestandteil der HashiCorp Cloud Platform (HCP) [2]. Für den Zugriff ist eine kostenpflichtige Subskription notwendig [3]. Das Open-Source-Tool steht allerdings auch für den lokalen Einsatz zur Verfügung. Dies ist gerade dann interessant, wenn Sie unabhängig von Hashicorp als Cloudanbieter tätig sein möchten oder primär mit lokalen Virtualisierungslösungen wie beispielsweise Vagrant oder VMware arbeiten.
Unter [4] stehen fertige Pakete für den Einsatz unter macOS, Windows, Linux und einigen Unix-Derivaten bereit. Alternativ besteht oftmals auch die Möglichkeit, Packer über die Software-Repositories der einzelnen Linux-Distributionen zur direkten Installation zu beziehen. Alle Beispiele in diesem Artikel basieren auf einem lokalen Packer-Setup unter macOS.
Packer-Komponenten
Bevor wir mit Packer die gewünschten Images erzeugen, werfen wir einen Blick auf die einzelnen Komponenten (Bild 1). Die Konfiguration all dieser Bestandteile erfolgt innerhalb einer Template-Datei, die entweder auf JSON oder HCL, einer JSON-ähnlichen Beschreibungssprache, basiert. Das Herzstück von Packer stellen sicherlich die beiden Komponenten Builder und Provisioner dar. Mit einem Builder legen Sie die Plattform, für die Sie ein neues Image erzeugen möchten, fest. Lautet die Anforderung beispielsweise, ein neues Amazon Machine Image (AMI) sowie eine Vagrant Box zu erstellen, sind hierfür zwei unterschiedliche Builder notwendig. Ein Packer-Builder-Objekt enthält dabei die komplette Logik, um mit der gewünschten Virtualisierungsplattform zu kommunizieren.
Um ein individuelles Image nach den eigenen Anforderungen zu erzeugen, ist natürlich die gewünschte Software mit den passenden Konfigurationen zu installieren. Diese Aufgabe übernimmt bei Packer ein sogenannter Provisioner. Diesem können Sie beispielsweise ein Shell-Skript übergeben, das dann innerhalb einer initialen Systeminstanz läuft, um hieraus im Anschluss ein individuelles Image zu generieren.
Wie Packer mit einem solchen System, also beispielsweise einer AWS-Instanz, kommuniziert, wird über einen sogenannten Communicator definiert. In den allermeisten Fällen kommt hierfür eine Secure-Shell (SSH) zum Einsatz. Mit WinRM steht allerdings auch ein Communicator zur Verfügung, der mittels des Windows-Remote-Management-Protokolls Zugriff auf Windows-Systeme erhält. Ein solcher Communicator lädt das zuvor angesprochene Shell-Skript in das gewünschte System.
Des Weiteren hilft ein sogenannter Post-Processor dabei, ein Image nach Abschluss sämtlicher Anpassungen final zu bearbeiten. Ein typisches Beispiel für ein Container-Image wäre das Hinzufügen eines Tags und der Upload des neuen Images in die gewünschte Container-Registry.
Bild 1: Packer stellt eine Vielzahl an Komponenten zur Verfügung, die dabei helfen, ein goldenes Image für unterschiedliche Plattformen zu erzeugen.
Vorlagen bauen
Um die einzelnen Packer-Komponenten nun zu nutzen, ist eine Template-Datei notwendig. Seit Version 1.7.0 verwendet Packer hierfür die HCL2-Beschreibungssprache, wie es auch bei anderen HashiCorp-Produkten wie Terraform der Fall ist. Auch wenn aktuelle Packer-Versionen noch JSON als Beschreibungssprache verstehen, ist es doch empfehlenswert, HCL2 zu verwenden, da sich einige neuere Features der Software nicht über JSON abbilden lassen. Ein Upgrade einer Template-Datei von JSON auf HCL2 ist problemlos mit dem folgenden Kommando möglich:
packer hcl2_upgrade all-fedora.json -output-file all-fedora.pkr.hcl
Nützlich ist die Option "-with-annotations". Diese sorgt dafür, dass das Tool zusätzliche Kommentare mit in das erzeugte Template schreibt. Dies hilft dabei, die Konfigurationsanweisungen aus der Vorlage lesbarer zu machen, sodass auch ein späteres Troubleshooting dieser Datei leichtfällt.
Docker- und Vagrant-Image erzeugen
Im Folgenden möchten wir Ihnen ein Beispiel (Listing 1) zeigen, wie Sie mit einem Packer-Template individuelle Images für die Docker-Container-Engine sowie das Virtualisierungstool Vagrant erzeugen (Bild 3). Hierbei kommen die unterschiedlichen Packer-Komponenten zum Einsatz, sodass Sie leicht nachvollziehen können, wie Sie diese auch für das Erzeugen von Images für andere Plattformen verwenden können.
Listing 1: Packer-Template
variable "docker_image" {       type = string       default = "fedora:latest" } variable "fedora_version" {       type = string       default = "fedora-latest" } packer {       required_plugins {            docker = {                  version = ">= 0.0.7"                  source = "github.com/hashicorp/docker" }            vagrant = {                  version = ">= 1.0.2"                  source = "github.com/hashicorp/vagrant"                  }       } } source "docker" "fedora" {       image = var.docker_image       commit = true } source "vagrant" "fedora" {       add_force = true       communicator = "ssh"       provider = "virtualbox"       source_path = "fedora/37-cloud-base" } build {       name = "tscherf-packer"       sources = [            "source.docker.fedora",            "source.vagrant.fedora" ]       provisioner "shell" {                  script = "scripts/setup.sh" }       post-processor "docker-tag" {            repository = "tscherf-packer"            tags = [var.fedora_version]            only = ["docker.fedora"]       } }
Listing 1 beginnt mit einem Block, in dem Sie die beiden Variablen "docker_image" und "fedora_version" definieren. Beide Variablen verfügen jeweils über eine Standardzuweisung. Diese können Sie beim Aufruf von packer build jedoch einfach überschreiben. Somit lässt sich das gleiche Template verwenden, unabhängig davon, welches Docker-Image Sie als Grundlage für das Golden-Image verwenden. Auf die im Header des Templates definierten Variablen können Sie dann bei der Konfiguration der einzelnen Packer-Komponenten zurückgreifen.
Packer-Einstellungen definieren
Weiter geht es in Listing 1 mit einem packer-Block. Dieser kann grundlegende Einstellungen enthalten, die Packer selbst betreffen – also beispielsweise, welche Version der Software vorliegen muss oder welche Plug-ins Packer für die Bearbeitung eines Templates benötigt. In diesem Beispiel kommen Erweiterungen für die Container-Engine Docker und das Virtualisierungstool HashiCorp Vagrant zum Einsatz. Die jeweiligen Plug-ins lädt Packer dann von der angegebenen URL und speichert diese lokal ab. Der genaue Speicherort ist in der Variablen "PACKER_HOME_DIR" angegeben [5].
Erzeugen Sie zu Testzwecken einmal die Datei "podman.pkr.hcl" (siehe Listing 2) und rufen Sie danach packer init auf. Sie sehen nun, wie Packer das Plug-in für die Container-Engine Podman [6] von GitHub herunterlädt und wo genau Packer dieses lokal abspeichert. Auf einem mac­OS-System sieht dies wie folgt aus:
packer init podman.pkr.hcl
 
Installed plugin github.com/polpetta/podman v0.1.0 in "/usr/local/bin/github.com/polpetta/podman/packer-plugin-podman_v0.1.0_x5.0_darwin_amd64"
Listing 2: Plug-in für Podman laden
packer {       required_plugins {            podman = {                  version = ">= 0.1.0"                  source = "github.com/Polpetta/podman"             }       } }
Auf einem Linux-System hingegen landet das Plug-in in einem anderen Ordner:
packer init podman.pkr.hcl
 
Installed plugin github.com/polpetta/podman v0.1.0 in "/home/tscherf/ .packer.d/plugins/github.com/polpetta/podman/packer-plugin-podman_v0.1.0_x5.0_linux_amd64"
Welche Erweiterungen Packer unterstützt und wozu Sie dies im Detail einsetzen können, ist ausführlich in der Dokumentation beschrieben [7].
Bild 2: Am Ende des Packer-Workflows erhalten Sie ein oder mehrere individuell angepasste System-Images für das Deployment auf unterschiedlichen Plattformen.
Builder mit Plug-ins verknüpfen
Nun folgt in Listing 1 ein source-Block. Dieser macht eigentlich nichts anderes, als eine Instanz der jeweils eingesetzten Plug-ins zu erzeugen und diese mit einem Namen zu versehen. Diese Instanz kommt dann später während des Build-Vorgangs zum Einsatz, um ein neues Image zu erzeugen.
Im Template aus Listing 1 sind zwei Sources definiert. Diese bestehen aus einem sogenannten Builder-Type und einem Namen. Der Builder-Type gibt an, welches Plug-in zum Einsatz kommen soll, um ein bestimmtes Image zu erzeugen. Die Erweiterung selbst kennt dann die ganzen Details der eingesetzten Virtualisierungsumgebung. Der Name dient als Referenz, die später während des Builds verwendet wird, um auf genau dieses Plug-in zurückzugreifen.
Ein source-Block kann viele weitere Details enthalten – beispielsweise, wie der Builder mit einem Container oder virtuellen System kommunizieren kann, um dort die gewünschten Anpassungen vorzunehmen, bevor auf Basis dieses Systems ein neues Image erzeugt wird. Hierfür kommt dann der bereits im Vorfeld angesprochene Communicator zum Einsatz. Manche Plug-ins verwenden Default-Einstellungen, sodass ein Communicator nicht zwingend zu konfigurieren ist, andere Plug-ins hingegen schreiben dies explizit vor. Die Dokumentation der einzelnen Erweiterungen hilft dabei, einen besseren Überblick zu bekommen, welche Konfigurationsmöglichkeiten zur Verfügung stehen.
Unser Beispiel greift nun auch auf eine der zuvor definierten Variablen zurück. Wird diese nicht explizit beim Erzeugen des Images überschrieben, verwendet Packer den Default-Wert "fedora:latest", der für die Variable "docker_image" im Template hinterlegt ist. Für den zweiten source-Block fehlt diese Variable. Somit würde Packer also immer das Fedora-37-Cloudimage als Basis verwenden, um hierauf basierend ein neues Vagrant-Image zu erzeugen. Natürlich steht es Ihnen frei, auch hier wieder eine Variable zu verwenden. Das Beispiel soll an dieser Stelle lediglich die Möglichkeiten aufzeigen, die Ihnen ein Packer-Template bietet.
Images anpassen
Bisher verfügt Packer lediglich über die Information, welche Virtualisierungstechnik und welche Basis-Images zum Einsatz kommen sollen. Diese Angaben finden nun im build-Block Verwendung, um ein individuelles Image für die unterschiedlichen Plattformen, in diesem Beispiel also Docker und Vagrant, zu erzeugen.
Hierzu werden zunächst die beiden Sources "docker.fedora" und "vagrant.fedora" referenziert, bevor es dann mithilfe eines Provisioners an die individuelle Anpassung des Basis-Images geht. In diesem Beispiel verwenden wir ein Shell-Skript, Sie können jedoch an dieser Stelle auch bereits vorhandene Konfigurations-Managementtools einbinden. Das folgende kurze Beispiel zeigt, wie Sie mit dem Packer-Ansible-Plug-in ein bestimmtes Playbook aufrufen, um die gewünschten Anpassungen durchzuführen:
provisioner "ansible" {
      playbook_file = "./my-packer- playbook.yml"
    }
Auch hier empfehlen wir wieder einen Blick in die Dokumentation [8] für einen Überblick darüber, welche Plug-ins für die Provisionierung zur Verfügung stehen und wie Sie diese an die eigenen Anforderungen anpassen.
Docker-Tags vergeben
Die letzte Anweisung im build-Block des Templates betrifft schließlich den post-processor. An dieser Stelle haben Sie vielfältige Möglichkeiten, das neue Image anzupassen. Welche dies im Detail sind, hängt natürlich von den eingesetzten Plug-ins ab. Listing 1 verwendet den Docker-spezifischen "post-processor docker-tag", um das neue Docker-Image mit einem Tag zu versehen.
An dieser Stelle kommt auch die zuvor definierte Variable "fedora_version" zum Einsatz, sodass Sie die Möglichkeit haben, mit diesem Template Images für unterschiedliche Fedora-Versionen zu erzeugen. Sie können dabei beliebig viele post-processor-Anweisungen in einer Vorlage verwenden. Für welches Image die Anweisungen gelten sollen, bestimmen Sie dann mittels der Anweisung "only = <source>".
Bild 3: Nach dem Aufruf von packer build erhalten Sie in diesem Beispiel jeweils ein neues Image für den Einsatz mit der Docker-Engine sowie dem Virtualisierungstool Vagrant.
Neue Images erzeugen
Nach diesem Streifzug durch ein beispielhaftes Packer-Template ist es an der Zeit, neue Images zu erzeugen. Hierzu ist, wenig überraschend, das Kommando packer build aufzurufen. Bevor es aber soweit ist, sollten Sie sicherstellen, dass Packer sämtliche Plug-ins zur Verfügung hat, die Sie im Template benutzen. Hierfür rufen Sie das Kommando packer init auf. Sollten Erweiterungen fehlen, lädt Packer diese von der angegebenen URL herunter und speichert diese lokal ab:
packer init ansible-pkr.hcl
Installed plugin github.com/hashicorp/ansible v1.0.3 in "/home/tscherf/.packer.d/plugins/github.com/hashicorp/ansible/packer-plugin-ansible_ v1.0.3_x5.0_linux_amd64"
Ferner sollten Sie darauf achten, dass Ihre Template-Dateien korrekt formatiert sind. Hierfür nutzen Sie das Kommando packer fmt. Sollte beispielsweise die Einrückung der Anweisungen in einigen Vorlagen nicht korrekt sein, korrigiert das Kommando diesen Umstand. Wenden Sie den Befehl wie im folgenden Beispiel auf mehrere Dateien im aktuellen Ordner an, erhalten Sie eine Information darüber, welche Dateien modifiziert wurden:
packer fmt .
 
ansible.pkr.hcl
Passt nun alles, stoßen Sie im nächsten Schritt den Prozess zum Erzeugen der neuen Images an:
packer build all-fedora.pkr.hcl
In Bild 3 erkennen Sie die einzelnen Schritte, die Packer beim Abarbeiten des Templates durchläuft. Nachdem alle Anweisungen erfolgreich ausgeführt wurden, steht das neue Docker-Image lokal zur Verfügung. Möchten Sie das Image als Teil des Build-Vorgangs direkt in eine Docker-Registry laden, ist dies natürlich auch möglich. Hierfür steht der Packer "post-processor docker-push" zur Verfügung, den Sie bei Bedarf mit in das Template aufnehmen können.
Das Packer-Vagrant-Plug-in schreibt neue Image-Dateien in einen Unterordner, benannt analog zum Build-Namen – "fedora" in diesem Beispiel – mit einem vorangestellten "output-":
ls output-fedora
 
Vagrantfile package.box
Sie können den Speicherort des neuen Images im Template jedoch auch ändern. Hierfür stellt die Vagrant-Erweiterung die Anweisung "output_dir" zur Verfügung, die Sie dann im builder-Block einsetzen. Die neue Vagrant-Box können Sie dann wie gewohnt mit dem folgenden Befehl in die gewünschte Umgebung importieren:
vagrant box add --name packer-tscherf-fedora package.box
Und schließlich bestätigt der folgende Aufruf, dass der Import erfolgreich war:
vagrant box list
 
packer-tscherf-fedora (virtualbox, 0)
Ein neues System auf Basis dieser Vagrant-Box starten Sie dann innerhalb der Vagrant-Umgebung mit
vagrant init
 
vagrant up
Dynamik mit Variablen
Bild 3 zeigt den Packer-Buildvorgang anhand der Default-Werte, die für die beiden Variablen "docker_image" und "fedora_version" innerhalb des Templates definiert wurden. Diese können Sie jedoch beim Aufruf des Befehls packer build überschreiben:
packer build -var docker_image=fedora:36 -var fedora_version=fedora-36 docker-fedora.pkr.hcl
In diesem Beispiel erzeugen Sie nun also ein neues Docker-Image auf Basis der Fedora-Version 36 anstatt der jeweils aktuellsten Version. Dies funktioniert natürlich auch für das Vagrant-Image, allerdings müssen Sie hierfür das Template anpassen und eine weitere Variable für das Vagrant-Plug-in hinzufügen.
Fazit
Vorbereitete Images sind bei regelmäßigen Deployments überaus hilfreich, um sich so Stunden an immer wiederkehrender Arbeit zu ersparen. Das quelloffene Packer ist ein mächtiges Werkzeug, mit dem Sie sehr schnell mehrere Images für unterschiedliche Cloudumgebungen und Virtualisierungsplattformen parallel erzeugen.
Somit eignet es sich hervorragend für den Einsatz in hybriden Cloudlandschaften. Eine Integration in die HashiCorp-eigene Cloud HCP ist dabei nicht notwendig. Packer ist auch als alleinstehendes Open-Source-Tool verfügbar, was den Einsatz in beliebigen Umgebungen erlaubt.
(dr)
Link-Codes
[1] Packer-Tool: https://www.packer.io/
[2] HashiCorp-Cloudplattform: https://cloud.hashicorp.com/
[3] HCP-Subskriptionen: https://cloud.hashicorp.com/pricing/