Die meisten Entwicklungsprojekte umfassen eine Vielzahl von Umgebungen: Produktion, Entwicklung, QA, Staging und dazu die lokalen Umgebungen aller Entwickler:innen. Diese Umgebungen so synchron zu halten, dass Ihr Projekt überall gleich läuft (oder überhaupt läuft), kann eine ziemliche Herausforderung sein.
Es gibt viele Gründe für Inkompatibilitäten, doch mit Docker lassen sich die meisten davon beseitigen. In diesem Artikel sehen wir uns Docker, Anwendungscontainer und die Frage an, wie Ihnen diese Werkzeuge dabei helfen, Ihre Anwendung überall dort auszuführen, wo Sie Docker installieren können, und so Kompatibilitätsprobleme zu vermeiden.
Was ist ein Anwendungscontainer?
Ein Anwendungscontainer ist ein schlankes, eigenständiges Paket, das eine Anwendung und all ihre Abhängigkeiten enthält, darunter Bibliotheken, Laufzeitumgebungen und Konfigurationsdateien. Ein Container enthält alles, was die Anwendung benötigt, um in einer isolierten Umgebung effizient zu laufen. Container sind so konzipiert, dass sie auf jedem kompatiblen Betriebssystem und jeder kompatiblen Hardware laufen: Sie abstrahieren Unterschiede zwischen Betriebssystem-Distributionen und teilen sich den Kernel des Host-Betriebssystems. So können mehrere isolierte Anwendungen oder Dienste auf einem einzigen Host laufen, ohne dass für jede Anwendung ein vollständiges Betriebssystem nötig ist.
Anwendungscontainer unterscheiden sich von klassischer Virtualisierung in Bezug auf Ressourcenzuweisung, Betriebssystem, Isolation und Skalierbarkeit. Container teilen sich Ressourcen wie CPU, Arbeitsspeicher und Speicherplatz auf Betriebssystemebene und verbrauchen damit weniger Ressourcen als virtuelle Maschinen (VMs), die für jede Instanz ein vollständiges Betriebssystem benötigen. Dadurch starten Container in Sekunden, während VMs Minuten brauchen können. Diese Eigenschaft macht Container außerdem besser skalierbar als VMs. In einem Punkt haben VMs allerdings die Nase vorn: Sie bieten eine deutlich stärkere Isolation zwischen den Instanzen als Container, weil jede VM ihr eigenes Betriebssystem ausführt.
Darum lohnt sich die Containerisierung von Anwendungen
Die Containerisierung von Anwendungen bringt zahlreiche Vorteile mit sich, die einige Herausforderungen in Softwareentwicklung, Bereitstellung und Betrieb leichter beherrschbar machen. Zu den wichtigsten Gründen, eine Anwendung zu containerisieren, zählen:
- Portabilität:Die Containerisierung schafft eine konsistente Laufzeitumgebung, indem sie eine Anwendung zusammen mit ihren Abhängigkeiten und ihrer Konfiguration verpackt. Das sorgt für gleichbleibendes Verhalten, egal ob der Container auf dem Rechner einer Entwicklerin läuft oder auf einer Cloud-Plattform.
- Skalierbarkeit: Container ermöglichen eine schnelle Bereitstellung, einfaches Patching und flexible Skalierung von Anwendungen. Container-Orchestratoren können intelligent skalieren und genau so viele Anwendungsinstanzen betreiben, wie für die aktuelle Last nötig sind, immer unter Berücksichtigung der verfügbaren Ressourcen.
- Produktivität: Container bieten eine vorhersehbare, konsistente Umgebung, ganz gleich, wo sie laufen. Statt jede Umgebung manuell einzurichten, genügt Entwickler:innen meist ein einziger Befehl, um einen Container zu starten, der die Umgebung dann selbst erstellt und hochfährt.
- Flexibilität: Dank ihrer Effizienz lässt sich eine Anwendung mit Containern leicht in kleinere, einzeln bereitstellbare Microservices aufteilen, ohne den Ressourcen-Overhead mehrerer physischer Server oder auch nur mehrerer VMs.
Einführung in Docker
Docker wurde 2013 als Open-Source-Plattform veröffentlicht, die das Erstellen, Ausführen, Verwalten und Verteilen von Anwendungen mithilfe von Containerisierung vereinfacht. Für viele Entwicklungsteams ist Docker ein grundlegendes Tool in ihren Workflows für Softwareentwicklung und Bereitstellung, denn es erlaubt Entwickler:innen, Anwendungen samt Abhängigkeiten in schlanke, portable Container zu packen.
Werfen wir einen kurzen Blick auf einige zentrale Komponenten von Docker, um zu verstehen, wie das Ganze funktioniert.
Dockerfile
Ein Dockerfile ist eine Textdatei mit den Anweisungen zum Erstellen eines Docker-Images. Sie legt fest, welches Docker-Image als Basis dient, wo sich der Quellcode der Anwendung befindet, der mit dem Image gebündelt wird, und welche Bibliotheken, Pakete und sonstigen Abhängigkeiten für den Betrieb der Anwendung erforderlich sind. Docker liest diese Datei und führt die darin enthaltenen Anweisungen aus, um das Docker-Image zu erstellen.
Docker-Images
Ein Docker-Image ist ein schreibgeschütztes Template, das den Quellcode und die Abhängigkeiten der Anwendung enthält und als Grundlage für Docker-Container dient. Docker-Images werden mit einem Dockerfile erstellt und lassen sich versionieren, in Registries speichern und mit anderen teilen. Docker-Images nutzen eine mehrschichtige Architektur, bei der jede Schicht eine Änderung am Image darstellt. Da die Schichtung direkt an die einzelnen Befehle in einem Dockerfile gekoppelt ist, kann jede Anweisung eine neue Schicht erzeugen
Docker-Container
Ein Docker-Container ist eine laufende Instanz eines Docker-Images. Wie andere Container laufen sie in isolierten Umgebungen auf einem Hostsystem, sind schlank, starten schnell und verhalten sich konsistent, unabhängig davon, auf welchem Hostsystem sie ausgeführt werden.
Anwendungen mit Docker containerisieren: Schritt für Schritt
Viele Arten von Anwendungen lassen sich containerisieren, darunter Frontend- und Backend-Anwendungen. Im Folgenden sehen wir uns an, wie sich eine einfache Python-Flask-Webanwendung containerisieren lässt. Es gibt zwar noch viele weitere Sprachen und Anwendungen, die sich mit Docker paketieren lassen, für unsere Beispiele verwenden wir jedoch Python Flask.
Voraussetzungen für die Containerisierung mit Docker
Da bei der Containerisierung einer Anwendung sämtlicher Code und alle Abhängigkeiten im Container verpackt werden, gibt es kaum Voraussetzungen, abgesehen von diesen:
- Docker muss installiert sein. Installationspakete für Linux, Mac oder Windows finden Sie hier.
- Legen Sie einen Projektordner für alle Dateien der Anwendung an. In unserem Beispiel unten verwenden wir nur drei Dateien.
- Vielleicht möchten Sie die verwendeten Programmiersprachen und Bibliotheken lokal installieren, um Ihre Anwendung vor der Containerisierung zu testen. Zwingend nötig ist das allerdings nicht, da Sie diese ohnehin im Anwendungscontainer installieren und Docker sich um diesen Teil kümmert.
Der Anwendungscode und seine Abhängigkeiten
Für unser Beispiel erstellen wir eine einfache Python-Flask-App. So wird der Projektordner aussehen:
my_flask_app/
- app.py
- requirements.txtUnsere Anwendung besteht aus zwei Dateien: app.py mit dem Code der Webanwendung und requirements.txt, einer Datei, über die Python Abhängigkeiten installiert. Hier der Inhalt von app.py, mit einigen Kommentaren, die erklären, was die einzelnen Teile bewirken:
# Import the Python Flask module
from flask import Flask
# Flask constructor uses the name of the current module
app = Flask(__name__)
# The route() function is a decorator,
# It binds a URL to an associated function
@app.route('/')
def hello_world():
return 'Hello World'
# If the file is run directly
# run the Flask application
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')
Die Datei requirements.txt ist denkbar schlicht. Sie führt lediglich Flask als Abhängigkeit auf:
Flask==2.3.2
Schritt-für-Schritt-Anleitung zur Containerisierung einer Anwendung
Jetzt, da wir eine Anwendung zum Containerisieren haben, besteht der nächste Schritt darin, ein Dockerfile mit den Anweisungen für die Erstellung des Docker-Images anzulegen.
Das Dockerfile erstellen
Erstellen Sie zunächst ein leeres Dockerfile im Projekt. Diese Datei hat keine Dateiendung. Ihr Projektordner sollte nun so aussehen:
my_flask_app/
- app.py
- Dockerfile
- requirements.txtUnd das ist der Inhalt des Dockerfile:
FROM python:3.10 COPY . /app WORKDIR /app RUN pip install -r requirements.txt EXPOSE 5000 CMD ["python3", "app.py"]
Das bedeutet all das im Einzelnen:
- FROM: Legt fest, welchen Container wir als Basis-Container verwenden. Eine große Auswahl an Basis-Containern für den Einstieg finden Sie auf Docker Hub.
- COPY: Beim Erstellen des Containers werden die Dateien unseres Projekts, dargestellt durch den Punkt, in einen Ordner /app innerhalb des Containers kopiert.
- WORKDIR: Teilt Docker mit, wo der Befehl am Ende unserer Datei ausgeführt werden soll, nämlich in dem Ordner, in den wir unser Projekt kopiert haben.
- RUN: Die Anweisung RUN gibt Docker einen Befehl vor, der beim Erstellen des Containers ausgeführt wird. Dieser Befehl läuft während der Image-Erstellung, nicht beim Start des Containers. Hier weisen wir Docker an, die Anforderungen aus unserer Datei requirements.txt zu installieren.
- EXPOSE: Teilt Docker mit, auf welchem Port der Container lauschen wird, also auf dem Port, auf dem unsere Flask-Anwendung läuft.
- CMD: Teilt Docker mit, welche Befehle beim Start des Containers ausgeführt werden und die Flask-App starten. Die CMD-Anweisung liefert Standardwerte für die Ausführung eines Containers und lässt sich beim Docker-Run überschreiben.
Das Docker-Image erstellen
Nachdem wir das Dockerfile angelegt haben, können wir das Docker-Image erstellen, indem wir diesen Befehl in unserem Projektverzeichnis ausführen:
docker build -t flask-app .
In diesem Befehl steht -t für Tag und versieht das Image mit einem Namen, flask-app ist der Name, den wir dem Image geben, und der . legt den Build-Kontext fest, also das aktuelle Verzeichnis.
Sobald Sie den Befehl ausführen, erstellt Docker Ihr Image und Sie sehen eine Ausgabe wie diese:
Den Docker-Container ausführen
Jetzt, da das Docker-Image erstellt ist, können Sie den Container mit dem folgenden Befehl im Projektverzeichnis starten:
docker run -it -p 5000:5000 flask-app
Im obigen Befehl weist -it Docker an, den Container im interaktiven Modus auszuführen. Sie können dann mit der Shell des Containers interagieren, und es wird ein TTY zugewiesen, das Ihnen eine textbasierte Konsole gibt. Beim Ausführen der Flask-Anwendung ist -it allerdings nicht immer nötig, da es sich nicht um ein Kommandozeilenwerkzeug handelt, das ein interaktives Terminal erfordert. Der Schalter -p legt das Port-Mapping des Containers fest. Die erste 5000 ist der Port auf dem Host-Rechner, den Sie auf den Container-Port abbilden möchten. Die zweite 5000 ist der Container-Port. Und flask-app ist der Name des Images, das wir gerade erstellt haben und ausführen wollen.
Die Ausgabe im Terminal nach dem Ausführen des Befehls sieht in etwa so aus:
Und wenn Sie http://localhost:5000/ aufrufen, sehen Sie die ganz einfache Flask-Anwendung.
Best Practices für die Containerisierung mit Docker
Die Containerisierung einer Anwendung mit Docker bietet zwar unzählige Vorteile für Entwicklung und Bereitstellung, um sie wirklich auszuschöpfen, sollten Sie jedoch einige Best Practices beachten.
Hier sind einige wichtige Regeln:
- Ein minimales Basis-Image verwenden: Wählen Sie das schlankeste praktikable Basis-Image, zum Beispiel Alpine Linux, um die Größe Ihres finalen Images zu reduzieren, den Ressourcenverbrauch zu senken und Builds zu beschleunigen.
- Mehrstufige Builds nutzen: Bauen und kompilieren Sie in einer Stufe und kopieren Sie dann nur die benötigten Artefakte in die finale Stufe, um die Größe des finalen Images zu verringern.
- Schichten minimieren: Reduzieren Sie die Anzahl der Schichten in Ihrem Image, indem Sie Befehle in einem einzigen RUN-Befehl zusammenfassen, denn jede Schicht verursacht zusätzlichen Overhead.
- Unnötige Dateien entfernen: Löschen Sie temporäre Dateien, Caches und Build-Artefakte, nachdem Sie Abhängigkeiten installiert oder die Anwendung gebaut haben, um die Image-Größe zu verringern.
- Zustandslose Container betreiben: Speichern Sie dauerhafte Daten außerhalb des Containers in Datenbanken, Objektspeichern oder einem anderen externen Speichersystem, damit Sie den Container herunterfahren können, ohne Daten zu verlieren.
- Images taggen: Tags helfen dabei, die Versionen Ihrer Images zu verwalten. Eindeutige Tags für Bereitstellungen ermöglichen es Ihnen, einen Produktions-Cluster schnell auf viele Nodes zu skalieren.
- Container patchen: Das Patchen und Aktualisieren Ihrer Container ist ein proaktiver Weg, die Sicherheit zu erhöhen und Bedrohungen zu reduzieren.
Sie suchen einen besseren Weg, Ihre Apps bereitzustellen? Sehen Sie sich an, wie Sie eine Anwendung mit Docker containerisieren.
Docker: ein unverzichtbares Tool für moderne Entwicklung
Die Containerisierung mit Docker hat die Anwendungsentwicklung revolutioniert. Containerisierte Anwendungen bringen erhebliche Vorteile mit sich, darunter Portabilität, Skalierbarkeit, Produktivität, Effizienz und Flexibilität. Docker hat diesen Prozess einfach gemacht, indem es einen standardisierten und leicht nutzbaren Ansatz für die Containerisierung bietet.
Wer auf Docker setzt und dabei die Best Practices für die Containerisierung beachtet, kann Entwicklung, Bereitstellung und Verwaltung moderner Anwendungen deutlich verbessern. Durch die konsistente Laufzeitumgebung können Entwickler:innen Anwendungen einmal entwickeln und überall ausführen, was die Abläufe über verschiedene Umgebungen hinweg vereinfacht. Die Skalierbarkeit von Containern ermöglicht eine schnelle Bereitstellung und Skalierung von Anwendungen in kleineren, einzeln bereitstellbaren Microservices.
Als führende Containerisierungs-Plattform vereinfacht Docker das Erstellen, Ausführen, Verwalten und Verteilen containerisierter Anwendungen und ist damit ein unverzichtbares Tool für die moderne Softwareentwicklung.
