Portfolio |

Getting Started with Packer and Azure

Packer ist eine Open-Source-Anwendung von HashiCorp, um Maschine Images automatisiert zu erstellen. Wir stellen das Tool vor.

Einleitung

Packer ist eine Open-Source-Anwendung von HashiCorp, um Maschine Images automatisiert zu erstellen. Damit werden bisher zumeist zeitaufwendige manuelle Arbeiten – beispielsweise an einem Golden Image – deutlich nachvollziehbarer und vor allem zuverlässiger.

Installation

Die Installation erfolgt unkompliziert über die offizielle Packer-Website. Dafür steht der Download des Binary Files für die Plattformen macOS, Windows, Linux sowie FreeBSD, OpenBSD und Solaris bereit. In der jeweiligen Kommandozeile sollte anschließend die Eingabe packer die verfügbaren Befehle anbieten. Wir verwenden in unserem Beispiel die Version 1.7.4.

Templates

Anhand eines Templates werden wir die grundlegenden Funktionsweisen von Packer demonstrieren. Dazu wird folgender Code unter dem Dateinamen azure-ubuntu.pkr.hcl gespeichert.

Zunächst werden die Variablen subscription_id, client_id sowie client_secret definiert, die wir für die Authentifizierung zu Azure benötigen. Diese können vorerst ignoriert werden, da wir sie uns im nächsten Schritt genauer ansehen.

variable "subscription_id" {
  type    = string
}

variable "client_id" {
  type    = string
}

variable "client_secret" {
  type    = string
  sensitive  = true
}

Anschließend folgt der Block packer, in dem das für uns notwendige Plugin azure in einer bestimmten Version näher spezifiziert wird.

packer {
  required_plugins {
    azure = {
      version = ">= 1.0.0"
      source  = "github.com/hashicorp/azure"
    }
  }
}

Der Block source legt das Builder Plugin fest. Innerhalb unseres Beispiels verwenden wir azure-arm, welches eine Virtuelle Maschine (VM) in einer selbständig angelegten Ressource Group provisioniert und uns das daraus gewünschte Abbild erzeugt. Nicht mehr benötigte Artefakte werden anschließend durch Packer gelöscht. Hierfür werden Location, Größe der zu verwendenden VM sowie Details zum Betriebssystem angegeben. Wir nutzen hier Azure Managed Images als Alternative zu VHD. Dadurch ist zwingend die Angabe des managed_image_resource_group_name erforderlich, worin das Image abgelegt werden soll. Diese Ressource Group wird nicht durch Packer angelegt, muss also vor der Ausführung bereits existieren.

Unter managed_image_name erfolgt die Benamung des zu erzeugenden Images. Wichtig ist zu beachten, dass dieser Name einzigartig sein muss, da Packer in seiner Standardeinstellung keine Datei überschreibt. Anschließend werden azure_tags zur besseren Nachvollziehbarkeit angelegt. Der beschriebene Block kann für sogenannte Parallel Builds auch mehrfach in verschiedenen Ausführungen auftreten.

source "azure-arm" "example" {
  subscription_id = "${var.subscription_id}"
  client_id       = "${var.client_id}"
  client_secret   = "${var.client_secret}"

  location                          = "West Europe"
  managed_image_name                = "packerimage"
  managed_image_resource_group_name = "packerdemo"

  os_type         = "Linux"
  image_publisher = "Canonical"
  image_offer     = "UbuntuServer"
  image_sku       = "16_04-lts-gen2"
  vm_size         = "Standard_DC2s_v2"

  azure_tags = {
    dept = "engineering"
  }
}

Der letzte Block build definiert, was nach dem Start der VM ausgeführt werden soll. Momentan wird nur auf die Quelle sources.azure-arm.example referenziert, damit die durch uns definierten Werte übernommen werden.

build {
  sources = ["sources.azure-arm.example"]
}

Variablen

Um die oben definierten Variablen zu initialisieren, legen wir nun im selben Verzeichnis eine Datei namens azure.auto.pkrvars.hcl mit dem untenstehenden Inhalt an. Packer lädt automatisch jede Datei mit der Dateiendung auto.pkrvars.hcl. Ansonsten kann die Flag --vars-file= in der Kommandozeile gesetzt werden. Die Beispielwerte müssen entsprechend ersetzt werden. Ein Service Principal kann dazu wie folgt in Azure angelegt werden. Dieser benötigt Zugriffrechte auf die genannten Ressource Groups.

client_id       = "YOUR_CLIENT_ID"
client_secret   = "YOUR_CLIENT_SECRET"
subscription_id = "YOUR_SUBSCRIPTION_ID"

Ausführung

Da nun die Vorbereitungen abgeschlossen sind, können wir Packer ausführen. Dafür nutzen wir als erstes den Befehl packer init ., der uns das angegebene Plugin (azure) herunterlädt. Mit dem Befehl packer fmt . kann eine konsistente Formatierung gewährleistet werden; packer validate . prüft hingegen die Konfiguration auf mögliche syntaktische Fehler. Sofern keine Ausgabe beim Ausführen der Befehle erfolgt, wurden auch keine Veränderungen vorgenommen. Durch packer build . kann das Image erstellt werden. Das Image sollte nach erfolgreichem Durchlauf in Azure zur Verfügung stehen.

Provisioners

Der wirkliche Mehrwert von Packer erfolgt durch sogenannte Provisioners. Durch Provisioners können moderne Automatisierungstools wie Ansible, Chef und Puppet, aber auch herkömmliche Werkzeuge wie Bash oder Powershell, in den Build-Prozess einfließen.

Wir entscheiden uns, den Webserver NGINX innerhalb des Images zu installieren. Dazu müssen folgende provisioner Blöcke in die bestehende Datei azure-ubuntu.pkr.hcl innerhalb des build Blocks unter der Zuordnung von source eingefügt werden.

provisioner "shell" {
    execute_command = "chmod +x {{ .Path }}; {{ .Vars }} sudo -E sh '{{ .Path }}'"
    inline          = ["apt-get update", "apt-get upgrade -y", "apt-get -y install nginx"]
    inline_shebang  = "/bin/sh -x"
  }

  provisioner "file" {
    destination = "/tmp/"
    source      = "./index.nginx.html"
  }

  provisioner "shell" {
    execute_command = "chmod +x {{ .Path }}; {{ .Vars }} sudo -E sh '{{ .Path }}'"
    inline = [
      "mv /tmp/index.nginx.html /var/www/html/",
      "chown root:root /var/www/html/index.nginx.html",
      "chmod 0644 /var/www/html/index.nginx.html"
    ]
    inline_shebang = "/bin/sh -x"
  }

  provisioner "shell" {
    execute_command = "chmod +x {{ .Path }}; {{ .Vars }} sudo -E sh '{{ .Path }}'"
    inline = [
      "/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync"
    ]
    inline_shebang = "/bin/sh -x"
  }

Wie dem Ablauf zu entnehmen ist, wird zudem die Index-Datei angepasst. Das Dokument muss dafür im gleichen Verzeichnis mit dem folgenden oder ähnlichen Inhalt unter dem Namen index.nginx.html angelegt werden.

<!doctype html>
<html>
  <head>
    <title>Hier kommt der Titel hin</title>
  </head>
  <body>
    <p>Das ist ein Text</p>
  </body>
</html>

Ergänzend sei erwähnt, dass der letzte provisioner Block der ordnungsgemäßen Verbindung der VM zu Azure dient – weitere Informationen sind unter Azure-Linux-VM-Agent – Übersicht – Azure Virtual Machines | Microsoft Docs zu entnehmen.

Da wir den Dateinamen des zu erstellenden Images nicht jedes Mal manuell anpassen wollen, fügen wir zu den bereits definierten Variablen zwei weitere hinzu. Zum einen die Input-Variable azure_prefix und zum anderen die lokale Variable timestamp.

variable "azure_prefix" {
  type    = string
  default = "azure-nginx"
}

locals {
  timestamp = regex_replace(timestamp(), "[- TZ:]", "")
}

Jetzt müssen wir im oberen Abschnitt nur noch den Wert "packerimage" für managed_image_name durch "${var.azure_prefix}-${local.timestamp}" ersetzen. Zukünftig wird hiermit ein einzigartiger Name für das jeweilige Image generiert. Wir können nun packer build . erneut ausführen.

Ausblick

Packer ist nicht für das weitere Management von VMs vorgesehen, beschränkt sich also nur auf den Build-Prozess des Images. Nach einem Deployment, zum Beispiel via HashiCorp Terraform, kann nun der Inhalt der erstellten Beispiel-Website angezeigt werden.

Zurzeit kann packer init als optionaler Schritt angesehen werden. Auch das Verwenden von JSON anstelle von HCL stellt momentan kein Problem dar. In der kommenden Version 2.0. wird HCL (wie hier verwendet) und packer init zwingend bei der Verwendung von Packer vorausgesetzt.