Docker Image
Ein Docker-Image ist eine leichtgewichtige, ausführbare Softwareeinheit, die alles enthält, was benötigt wird, um eine Anwendung auszuführen. Dazu gehören der Anwendungscode, Bibliotheken, Abhängigkeiten, Umgebungsvariablen und Konfigurationsdateien. Docker-Images sind die Grundlage für Container, die die Ausführung von Anwendungen in isolierten Umgebungen ermöglichen.
Hauptmerkmale von Docker-Images:
Schichtenarchitektur
- Docker-Images bestehen aus mehreren Schichten (Layers). Jede Schicht repräsentiert eine Änderung oder einen Befehl, der in einem Dockerfile definiert ist. Diese Schichten sind schreibgeschützt und werden in einem Dateisystem gespeichert.
- Wenn ein Image aktualisiert wird, wird nur die betroffene Schicht neu erstellt, was die Effizienz erhöht und Speicherplatz spart.
Dockerfile
- Ein Docker-Image wird typischerweise aus einem Dockerfile erstellt, das eine
Reihe von Anweisungen enthält, die Docker mitteilen, wie das Image aufgebaut
werden soll. Ein Dockerfile kann Befehle wie
FROM,RUN,COPY,CMDund viele andere enthalten. - Beispiel eines einfachen Dockerfiles:
# Basis-Image
FROM python:3.9
# Arbeitsverzeichnis festlegen
WORKDIR /app
# Abhängigkeiten kopieren
COPY requirements.txt .
# Abhängigkeiten installieren
RUN pip install -r requirements.txt
# Anwendungscode kopieren
COPY . .
# Befehl zum Ausführen der Anwendung
CMD ["python", "app.py"]
Portabilität
Docker-Images sind plattformunabhängig und können auf jedem System ausgeführt werden, das Docker unterstützt. Dies ermöglicht eine konsistente Ausführung von Anwendungen in verschiedenen Umgebungen, sei es lokal, in der Cloud oder in einem Rechenzentrum.
Versionierung
Docker-Images können versioniert werden, indem Tags verwendet werden. Ein Tag ist ein Label, das einem Image zugewiesen wird, um eine bestimmte Version zu kennzeichnen. Zum Beispiel könnte ein Image mit dem Tag myapp:1.0 eine bestimmte Version der Anwendung darstellen.
Verteilung
Docker-Images können in einem Docker-Registry (z. B. Docker Hub) gespeichert und verteilt werden. Benutzer können Images von der Registry herunterladen oder eigene Images hochladen, um sie mit anderen zu teilen.
Verwendung von Docker-Images
- Erstellen von Containern: Um einen Container zu starten, wird ein Docker-Image verwendet. Der Befehl docker run erstellt einen neuen Container aus dem angegebenen Image.
- Microservices-Architektur: In einer Microservices-Architektur können verschiedene Teile einer Anwendung in separaten Containern ausgeführt werden, die jeweils aus unterschiedlichen Docker-Images erstellt werden. Dies fördert die Modularität und Skalierbarkeit.
Dockerfile
Wie bereits erwähnt, werden Docker Images mithilfe eines Dockerfile erstellt.
Ein Dockerfile ist eine Textdatei, die alle Anweisungen enthält, die Docker benötigt, um ein Docker-Image zu erstellen. Der Aufbau eines Dockerfiles folgt einer bestimmten Syntax und Struktur. Hier sind die grundlegenden Komponenten und Anweisungen, die in einem Dockerfile verwendet werden:
Befehle
-
FROM: Diese Anweisung gibt das Basis-Image an, auf dem das neue Image aufgebaut wird. Es ist die erste Zeile in den meisten Dockerfiles.
FROM ubuntu:20.04 -
LABEL: Mit dieser Anweisung können Metadaten zum Image hinzugefügt werden, wie z.B. der Autor oder die Version.
LABEL maintainer="deinname@example.com" -
RUN: Diese Anweisung führt Befehle während des Build-Prozesses aus, um Software zu installieren oder Konfigurationen vorzunehmen.
RUN apt-get update && apt-get install -y python3 -
COPY: Mit dieser Anweisung können Dateien oder Verzeichnisse vom Host in das Image kopiert werden.
COPY . /app -
ADD: Ähnlich wie COPY, aber mit zusätzlichen Funktionen, wie das Entpacken von Archiven oder das Herunterladen von Dateien von einer URL.
ADD myapp.tar.gz /app -
CMD: Diese Anweisung gibt den Standardbefehl an, der ausgeführt wird, wenn ein Container aus dem Image gestartet wird.
CMD ["python3", "app.py"] -
ENTRYPOINT: Diese Anweisung legt den Hauptbefehl fest, der beim Starten des Containers ausgeführt wird. Sie kann zusammen mit CMD verwendet werden, um Standardargumente bereitzustellen.
ENTRYPOINT ["python3"] -
ENV: Mit dieser Anweisung können Umgebungsvariablen im Container gesetzt werden. Diese Variablen können während der Laufzeit des Containers von Anwendungen oder Skripten verwendet werden, um Konfigurationen oder Einstellungen bereitzustellen.
ENV APP_ENV=production -
EXPOSE: Diese Anweisung dokumentiert, welche Ports der Container zur Verfügung stellt. Es hat keine Auswirkungen auf die Netzwerkkonfiguration, dient aber als Hinweis für Benutzer.
EXPOSE 80 -
VOLUME: Diese Anweisung erstellt ein Verzeichnis, das als Datenvolumen verwendet wird, um Daten zwischen Containern oder zwischen Host und Container zu teilen. Auf Volumes wird in der nächsten Woche ausführlicher eingegangen.
VOLUME ["/data"] -
WORKDIR: Diese Anweisung legt das Arbeitsverzeichnis für nachfolgende Anweisungen fest.
WORKDIR /app
Beispiel
Ein einfaches Beispiel für ein Dockerfile könnte so aussehen:
# Basis-Image
FROM python:3.8-slim
# Metadaten
LABEL maintainer="deinname@example.com"
LABEL version="1.0"
LABEL description="Ein einfaches Beispiel für ein Dockerfile"
# Arbeitsverzeichnis festlegen
WORKDIR /app
# Umgebungsvariablen setzen
ENV APP_ENV=production
ENV PORT=5000
# Abhängigkeiten kopieren und installieren
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Anwendungscode kopieren
COPY . .
# Exponiere den Port
EXPOSE $PORT
# Datenvolumen erstellen
VOLUME ["/app/data"]
# Hauptbefehl festlegen
ENTRYPOINT ["python3"]
CMD ["app.py"]
In diesem Beispiel wird ein Python-Image als Basis verwendet, Abhängigkeiten werden installiert, und schliesslich wird die Anwendung gestartet.
CMD vs. ENTRYPOINT
Der Hauptunterschied zwischen CMD und ENTRYPOINT in einem Dockerfile liegt
in ihrer Funktion und Verwendung:
CMD
- Zweck: CMD wird verwendet, um den Standardbefehl anzugeben, der ausgeführt wird, wenn ein Container aus dem Image gestartet wird. Es kann durch Argumente, die beim Starten des Containers übergeben werden, überschrieben werden.
- Flexibilität: CMD ist flexibler, da es leicht durch Benutzeranweisungen beim Starten des Containers ersetzt werden kann.
Beispiel:
CMD ["app.py"]
Wenn der Container gestartet wird, wird standardmässig app.py ausgeführt, aber der Benutzer kann dies durch Angabe eines anderen Befehls beim Starten des Containers überschreiben.
ENTRYPOINT
- Zweck: ENTRYPOINT legt den Hauptbefehl fest, der beim Starten des Containers ausgeführt wird. Es ist schwieriger, diesen Befehl zu überschreiben, da er als fester Bestandteil des Containers betrachtet wird.
- Verwendung: ENTRYPOINT wird häufig verwendet, um sicherzustellen, dass ein bestimmter Befehl immer ausgeführt wird, unabhängig von den übergebenen Argumenten.
Beispiel:
ENTRYPOINT ["python3"]
In diesem Fall wird beim Starten des Containers immer python3 ausgeführt, und der Benutzer kann zusätzliche Argumente (z.B. app.py) über CMD oder beim Starten des Containers hinzufügen.
In vielen Fällen werden CMD und ENTRYPOINT zusammen verwendet, um eine flexible und dennoch kontrollierte Ausführung von Containeranwendungen zu ermöglichen.
Image bilden
Um ein Docker-Image aus einem Dockerfile zu erstellen, folgen Sie diesen Schritten:
1. Dockerfile erstellen
Erstellen Sie eine Datei mit dem Namen Dockerfile (ohne Dateiendung) in einem Verzeichnis, das Ihren Anwendungscode und alle benötigten Dateien enthält. Fügen Sie die gewünschten Anweisungen (z.B. FROM, RUN, COPY, etc.) in das Dockerfile ein.
2. Terminal öffnen
Öffnen Sie ein Terminal oder eine Eingabeaufforderung (z.B. PowerShell oder Eingabeaufforderung) und navigieren Sie zu dem Verzeichnis, in dem sich Ihr Dockerfile befindet.
3. BuildKit aktivieren (falls erforderlich)
Stellen Sie sicher, dass BuildKit aktiviert ist, indem Sie die Umgebungsvariable
DOCKER_BUILDKIT=1 setzen. Dies kann in PowerShell oder der Eingabeaufforderung
erfolgen:
set DOCKER_BUILDKIT=1
4. Docker Buildx aktivieren (falls erforderlich)
Überprüfen Sie, ob Buildx verfügbar ist, indem Sie den folgenden Befehl ausführen:
docker buildx ls
Wenn kein Builder vorhanden ist, können Sie einen neuen Builder erstellen:
docker buildx create --name mybuilder
docker buildx use mybuilder
5. Docker-Image erstellen
Verwenden Sie den Befehl docker buildx build, um das Image zu erstellen. Der grundlegende Befehl hat die folgende Syntax:
docker buildx build -t <image-name>:<tag> .
<image-name>: Geben Sie einen Namen für Ihr Image an.<tag>: (Optional) Geben Sie eine Tag-Version an (z.B. latest oder v1.0).- Der Punkt
.am Ende gibt an, dass das Dockerfile im aktuellen Verzeichnis gefunden werden soll.
Beispiel:
docker buildx build -t myapp:latest .
6. Image überprüfen
Nach dem erfolgreichen Erstellen des Images können Sie die Liste der verfügbaren Docker-Images mit dem folgenden Befehl anzeigen:
docker images
7. Container aus dem Image starten
Um einen Container aus dem erstellten Image zu starten, verwenden Sie den Befehl
docker run:
docker run -d -p <host-port>:<container-port> <image-name>:<tag>
-d: Startet den Container im Hintergrund (detached mode).-p <host-port>:<container-port>: Bindet einen Port des Hosts an einen Port des Containers.
Beispiel:
docker run -d -p 5000:5000 myapp:latest
Zusammenfassung
- Erstellen Sie ein Dockerfile mit den gewünschten Anweisungen.
- Öffnen Sie ein Terminal und navigieren Sie zum Verzeichnis mit dem Dockerfile.
- Aktivieren Sie BuildKit mit
set DOCKER_BUILDKIT=1. - Überprüfen Sie Buildx mit
docker buildx lsund erstellen Sie einen neuen Builder, falls erforderlich. - Führen Sie docker
buildx build -t <image-name>:<tag> .aus, um das Image zu erstellen. - Überprüfen Sie das Image mit
docker images. - Starten Sie einen Container mit
docker run.
Mit diesen Schritten können Sie ein Docker-Image mit docker buildx build erstellen und einen Container basierend auf diesem Image ausführen.
Caching beim Erstellen von Docker-Images
Beim Erstellen von Docker-Images gibt es einen Prozess, der als "Caching" bezeichnet wird. Caching hilft, den Erstellungsprozess schneller und effizienter zu gestalten. Hier ist eine einfache Erklärung, wie das funktioniert.
Was ist Caching?
Caching bedeutet, dass bereits ausgeführte Schritte oder Ergebnisse gespeichert werden, damit sie nicht erneut berechnet oder heruntergeladen werden müssen. Wenn Sie ein Docker-Image erstellen, durchläuft Docker mehrere Schritte, die im Dockerfile definiert sind. Jeder Schritt in dieser Datei wird als "Layer" bezeichnet.
Wie funktioniert das Caching in Docker?
- Layer-Erstellung: Jedes Mal, wenn Sie ein Docker-Image erstellen, wird jeder Befehl im Dockerfile in einen eigenen Layer umgewandelt. Zum Beispiel, wenn Sie ein Basis-Image herunterladen oder eine Datei kopieren, wird dafür ein Layer erstellt.
- Speicherung der Layer: Wenn Sie das Image das nächste Mal erstellen, prüft Docker, ob es bereits einen Layer gibt, der dem aktuellen Befehl entspricht. Wenn ja, wird dieser Layer aus dem Cache verwendet, anstatt ihn erneut zu erstellen. Das spart Zeit und Ressourcen.
- Änderungen und Cache-Invalidierung: Wenn Sie eine Änderung im Dockerfile vornehmen, z. B. eine neue Datei hinzufügen oder einen Befehl ändern, wird der Cache für diesen Layer und alle nachfolgenden Layer ungültig. Docker muss diese Layer dann neu erstellen.
Vorteile von Caching
- Schnelligkeit: Durch das Caching werden die Erstellungszeiten erheblich verkürzt, da Docker nicht alles von Grund auf neu erstellen muss.
- Effizienz: Weniger Bandbreite und Speicherplatz werden benötigt, da bereits vorhandene Layer wiederverwendet werden.
- Ressourcenschonung: Caching reduziert die Belastung des Systems, da weniger Rechenleistung benötigt wird.
Fazit
Caching ist ein wichtiger Bestandteil des Docker-Image-Erstellungsprozesses. Es hilft, die Effizienz zu steigern und die Zeit zu verkürzen, die benötigt wird, um ein Image zu erstellen. Wenn Sie verstehen, wie Caching funktioniert, können Sie Ihre Docker-Images schneller und effektiver erstellen.
Multistage Builds
Multistage Builds in Dockerfiles sind eine Technik, die es ermöglicht, mehrere Schritte beim Erstellen eines Docker-Images zu kombinieren. Dadurch kann die Grösse des endgültigen Images reduziert und die Build-Zeit optimiert werden. Hier ist eine einfache Erklärung mit Beispielen:
Was sind Multistage Builds?
Stellen Sie sich vor, Sie möchten eine Anwendung bauen, die aus mehreren Teilen besteht. Normalerweise würden Sie alle Abhängigkeiten und Tools in ein einziges Docker-Image packen. Das kann jedoch dazu führen, dass das Image sehr gross wird, weil viele Dinge, die nur während des Builds benötigt werden, auch im endgültigen Image landen.
Mit Multistage Builds können Sie verschiedene "Bauphasen" in einem Dockerfile definieren. In jeder Phase können Sie unterschiedliche Basis-Images verwenden und nur die Teile, die Sie wirklich benötigen, in das endgültige Image kopieren.
Beispiel
Angenommen, Sie haben eine einfache Node.js-Anwendung. Hier ist ein einfaches Dockerfile mit Multistage Builds:
# Erste Phase: Build
FROM node:14 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Zweite Phase: Produktion
FROM nginx:alpine
COPY /app/dist /usr/share/nginx/html
Erklärung des Beispiels:
-
Erste Phase (Build):
- Wir verwenden das Node.js-Image (
node:14) als Basis. - Wir setzen das Arbeitsverzeichnis auf /app.
- Wir kopieren die package.json-Dateien und installieren die Abhängigkeiten.
- Dann kopieren wir den gesamten Code und führen den Build-Befehl aus, um die Anwendung zu erstellen.
- Wir verwenden das Node.js-Image (
-
Zweite Phase (Produktion):
- Hier verwenden wir ein leichtgewichtiges Nginx-Image (
nginx:alpine). - Wir kopieren nur die gebauten Dateien (/app/dist) aus der ersten Phase in das Nginx-Image.
- Das endgültige Image enthält also nur die notwendigen Dateien für die Ausführung der Anwendung, nicht die Build-Tools oder den Quellcode.
- Hier verwenden wir ein leichtgewichtiges Nginx-Image (
Vorteile von Multistage Builds:
- Kleinere Images: Nur die benötigten Dateien werden ins finale Image kopiert, was die Grösse reduziert.
- Schnelleres Deployment: Kleinere Images laden schneller.
- Sauberer Code: Sie können verschiedene Umgebungen (Build, Test, Produktion) klarer trennen.
Multistage Builds sind also eine sehr nützliche Technik, um Docker-Images effizienter zu gestalten!