• Roman Maire

Docker Images erstellen mit Packer

Wie in einem früheren Blog-Post beschrieben, erstellen wir Test- und Schulungsumgebungen auf einer speziell dafür erstellten Plattform. Die Kernkomponente davon ist Docker. Wir ersparen uns so die manuellen Installationsschritte der Atlassian Applikationen. Wie die Images erstellt werden möchte ich in diesem Post ein wenig erläutern.


Docker

Bei Docker handelt es sich um eine Software zum Verwalten von Linux-Containern. Das ist eine Technologie, die ein wenig an virtuelle Maschinen erinnert, aber wesentlich weniger schwergewichtig ist. Bei einer virtuellen Maschine wird ein kompletter Computer in Software abgebildet. Speicher wird ihr fest zugeteilt, und Festplattenplatz wird exklusiv reserviert. Das heißt, auch nicht benötigte Ressourcen sind besetzt. Docker hingegen isoliert nur den Prozess, der darin läuft, von allen anderen Prozessen des Host-Systems. Ein Docker-Container hingegen benutzt nach Möglichkeit die Ressourcen des Hosts. Man kann z.B. Node.js in ein Docker-Image installieren ohne, dass der Host davon betroffen ist. Alle Dateien, die auf dem Host installiert sind, können in dem Image weiterverwendet werden. So enthält ein Image z.B. keinen Linux-Kernel, es wird immer der vom Host-System verwendet. Auch kann man in einem Container beliebig Dateien schreiben und verändern, der Host ist davon nicht betroffen, solange das nicht explizit konfiguriert wird.


Packer

Docker erstellt einen laufenden Container aus einem sogenannten Image. Das sind Archive, die die Dateien enthalten, die der laufende Container benutzen soll. Um diese zu erstellen, benutzt man meistens ein Docker-File. Darin kann man z.B. Shell Befehle ausführen, und Dateien in das Ziel-Image kopieren. Das ist zwar praktisch, wir wollten aber auch Synergien mit anderen Tools nutzen:

  • Vagrant ist schon im Einsatz, der Build Prozess sollte auch dafür funktionieren.

  • Der Build Prozess sollte auch für die VM Images unseres Cloud-Providers funktionieren.

Da wir schon mehrere Hashicorp-Tools im Einsatz haben, fiel die Entscheidung auf Packer.


Build-File

Der Build-Prozess mit Packer wird mit einer Datei im JSON-Format konfiguriert. Am Beispiel von Jira soll die Erstellung eines Images demonstriert werden. Die Datei besteht aus mehreren Sektionen. Die erste, die man anschauen muss, ist Builders:

"builders": [
    {
        "communicator": "ssh",
        "source_path": "bento/ubuntu-18.04",
        "provider": "virtualbox",
        "add_force": true,
        "type": "vagrant"
    },
    {
        "type": "docker",
        "image": "ubuntu:18.04",
        "commit": true,
        "changes": [
            "WORKDIR /tmp",
            "USER jirauser",
            "ENV HOSTNAME jira",
            "Volume /var/atlassian/jira",
            "EXPOSE 8080",
            "ONBUILD RUN date",
        "CMD [\"/usr/local/startup.sh\"]"
    ]
  }]

Hier sieht man, dass zwei Builds konfiguriert sind. Einmal für Vagrant (Virtualbox) und einmal für Docker. Bei Virtualbox passiert nicht viel, man konfiguriert ein Basisimage. In Docker müssen ein paar Sachen mehr konfiguriert werden.

Die wichtigsten Parameter:

  • image: Bei Docker geht man (fast) immer von einem Basisimage aus, auf das man aufbaut. Man findet sie auf Docker Hub.

  • CMD: Der Befehl, der beim Start des Images ausgeführt wird.

  • ENV: Man kann beim Build Umgebungsvariablen verwenden. Der ENV-Parameter kann mehrfach verwendet werden.

  • Volume: Ordner, die vom laufenden Container auf den Host verbunden werden. Dateien, die dort hinein geschrieben werden überstehen auch einen Container-Neustart, alle anderen nicht.

  • EXPOSE: Gibt die Netzwerk-Ports an, die der Container benötigt.

Der nächste wichtige Block sind die Provisioner. Damit kann man automatisieren, wie und mit was das Image erstellt und konfiguriert wird:


"provisioners": [{
            "type": "file",
            "source": "resources/",
            "destination": "/tmp"
        },
        {
            "type": "file",
            "source": "scripts/jira/",
            "destination": "/tmp"
        },
        {
            "type": "file",
            "source": "scripts/jira/startup.sh",
            "destination": "/usr/local/"
        },
        {
            "type": "shell",
            "inline": "chmod u+x /usr/local/startup.sh"
        },
        {
            "type": "shell",
            "inline": "chmod u+x /tmp/*.sh"
        },
        {
            "type": "shell",
            "scripts": [
                "scripts/jira/docker-base.sh",
                "scripts/common/java.sh"
            ]
        },
        {
            "type": "shell",
            "inline": "bash /tmp/jira.sh {{user `version`}} {{user `software_memory_min`}} {{user `software_memory_max`}}"
        }
    ]

Der wichtigsten Provisioner sind:

  • file: Kopiert Dateien vom Host in den Container. Kann auch ganze Verzeichnisse kopieren.

  • shell: Führt Shell Scripts oder Shell Befehle aus. Man kann entweder Dateien angeben (scripts) oder direkt ein oder mehrere Befehle (inline).

Hier tauchen auch zum ersten Mal Variablen auf. Man kann sie aber überall in der Datei verwenden.

{
    "type": "shell",
    "inline": "bash /tmp/jira.sh {{user `version`}} {{user `software_memory_min`}} {{user `software_memory_max`}}"
}

Packer ersetzt ein Konstrukt wie {{user `version`}} durch den Wert, der in der Variable version konfiguriert ist.

Variablen können auf mehrere Arten gesetzt werden. Beim Start von Packer als Kommandozeilenargument, in einer weiteren Konfigurationsdatei, oder im variables Block:

"variables": {
    "compression_level": "6",
    "software_memory_max": "2048m",
    "software_memory_min": "1024m"
}

Damit kann man Defaultwerte für die Variablen vorgeben. Sie können aber auch mit den anderen beiden Varianten überschrieben werden.

Der letzte Block sind die post-processors. Die Provisioners sind unabhängig von den Buildern. Viele Aktionen, die man gerne automatisieren möchte, sind aber abhängig vom Builder. Z.B. benutzt man für Docker und Vagrant verschiedene Repositories, Docker Hub und Vagrant Cloud. Auch kann man mit dem Virtualbox-Builder das Image kompimieren, bei Docker nicht. Beispiel Vagrant Builder:

"post-processors": [{
    "type": "vagrant",
    "compression_level": "{{user `compression_level`}}",
    "keep_input_artifact": true,
    "output": "zuara-base-{{user `version`}}-{{.Provider}}.box",
    "only": ["vagrant"]
},
{
    "type": "vagrant-cloud",
    "box_tag": "mairer/zuara-base",
    "access_token": "{{user `cloud_token`}}",
    "version": "{{user `version`}}",
    "only": ["vagrant"]
}]

Hier sind zwei Post Processors konfiguriert, einmal zum Nachbearbeiten des Images (vagrant) und einmal zum Hochladen des Images (vagrant-cloud). Auch hier können Variablen verwendet werden. Mit der Option “only”: [“vagrant”]sagt man Packer, dass der Post-Processor nur beim Vagrant-Build ausgeführt werden soll. Ein Beispiel, um nach dem Docker-Build das Image zu taggen:

{
    "type": "docker-tag",
    "repository": "zuara/jira",
    "tag": "{{user `version`}}"
    "only": ["docker"]
}

Build

Ein Build kann jetzt mit folgendem Befehl gestartet werden:

packer build --only=docker -var 'version=7.13.3'buidfile.json

Dieser Befehl erstellt das Docker Image. Der Vagrant-Build kann entsprechend mit folgendem Befehl ausgeführt werden:

packer build --only=vagrant -var 'version=7.13.3'buidfile.json

Somit können wir beliebige Jira-Versionen für verschiedene Virtualisierungs-Techniken mit kleinstem Aufwand erstellen.




Haben Sie Fragen oder Anregungen zum diesem Blog-Beitrag? Dürfen wir Sie unterstützen? Schreiben Sie uns auf hallo@zuara.ch oder rufen Sie uns an: 031 302 60 00.
0 Ansichten0 Kommentare

Aktuelle Beiträge

Alle ansehen