Featured image of post Containerisierung mit Docker: Die ideale Grundlage für dein Homelab

Containerisierung mit Docker: Die ideale Grundlage für dein Homelab

Dieser Blogbeitrag führt dich in die Welt von Docker und Docker Compose ein und zeigt, wie sie zusammen mit Ansible deine IT-Infrastruktur revolutionieren können. Du erfährst, wie diese Tools konsistente, effiziente und skalierbare Bereitstellungen sowohl im Homelab als auch in der Hybrid Cloud ermöglichen. Anhand eines praktischen Beispiels demonstrieren wir, wie Dienste automatisiert bereitgestellt werden. Bereite dich darauf vor, die Grundlagen zu legen, um in zukünftigen Blogbeiträgen leistungsstarke Anwendungen wie den Caddy-Webserver zu integrieren!

Die Hybrid-Cloud-Serie für dein Homelab bietet eine umfassende Anleitung zur Einrichtung einer sicheren, flexiblen und skalierbaren Homelab-Umgebung. Jeder Beitrag behandelt einen spezifischen Schritt, von der Hardware-Auswahl über Automatisierung und Containerisierung bis hin zu Bereitstellung von Applikationen wie Nextcloud oder Home-Assistant.

  1. Hybrid Cloud: Die ideale Kombination aus Flexibilität, Sicherheit und Skalierbarkeit für dein Homelab
  2. Ansible in der Hybrid-Cloud: Der Weg zur effizienten Automatisierung
  3. Containerisierung mit Docker: Die ideale Grundlage für dein Homelab
  4. Caddy als Reverse Proxy: Sichere deine Hybrid-Cloud
  5. Authentik in der Hybrid-Cloud: Zentrale Authentifizierung leicht gemacht
  6. Sicherer Zugriff auf deine Hybrid-Cloud: Alles über SSH-Tunneling

Containerisierung mit Docker und Docker Compose: Die ideale Grundlage für dein Homelab

In der heutigen digitalen Welt sind Flexibilität und Effizienz entscheidend, sowohl für Unternehmen als auch für individuelle IT-Projekte. Docker und Docker Compose haben sich als unverzichtbare Werkzeuge etabliert, um Anwendungen schnell und konsistent in hybriden Cloud-Umgebungen bereitzustellen. Sie ermöglichen eine klare Trennung von Diensten und eine automatisierte Verwaltung, was nicht nur in professionellen Umgebungen, sondern auch in Homelabs von Vorteil ist.

In diesem Beitrag geben wir eine Übersicht über die Grundlagen und Vorteile von Docker und Docker Compose. Im letzten Blogbeitrag, Ansible in der Hybrid-Cloud: Der Weg zur effizienten Automatisierung, haben wir bereits die Basis geschaffen, indem wir Docker mithilfe von Ansible auf unseren Hosts bereitgestellt haben. Dies bildet die Grundlage für die hier vorgestellten Konzepte und Werkzeuge. Docker bietet die Möglichkeit, Anwendungen in portablen Containern zu isolieren, während Docker Compose die Orchestrierung komplexer Setups erheblich vereinfacht. Gemeinsam bilden sie das Rückgrat moderner Infrastruktur-Automatisierung.

Was ist Docker?

Docker ist eine Open-Source-Plattform, die Anwendungen und deren Abhängigkeiten in leichtgewichtige, isolierte Container verpackt. Container teilen sich denselben Kernel des Host-Betriebssystems, was sie ressourcenschonender und schneller macht als virtuelle Maschinen. Docker umfasst mehrere grundlegende Konzepte:

  • Images: Unveränderliche Vorlagen, die Software, Bibliotheken und Konfigurationen enthalten. Sie bilden die Grundlage für Container und können aus Vorlagen erstellt oder von Docker Hub heruntergeladen werden.
  • Container: Laufende Instanzen von Images, die isolierte Umgebungen für Anwendungen bieten. Sie können schnell erstellt, gestoppt und entfernt werden.
  • Volumes: Persistente Speicherorte für Daten, die von Containern genutzt werden können und unabhängig vom Lebenszyklus eines Containers bestehen bleiben.
  • Networks: Ermöglichen die Kommunikation zwischen Containern und externen Diensten in isolierten Umgebungen.

Vorteile von Docker im Vergleich zu anderen Tools

  1. Portabilität: Docker-Container laufen unverändert auf verschiedensten Plattformen, vom Laptop bis zur Cloud, wodurch Entwicklungs- und Produktionsumgebungen konsistent bleiben.
  2. Effizienz: Im Vergleich zu VMs benötigen Container weniger Ressourcen, da sie keinen eigenen Kernel starten müssen. Dies ermöglicht schnellere Starts und eine höhere Anwendungsdichte.
  3. Konsistenz: Herstellerbereitgestellte Images, etwa für Datenbanken oder Webserver, vereinfachen die Bereitstellung und reduzieren die Fehleranfälligkeit.
  4. Skalierbarkeit: Anwendungen können mit minimalem Aufwand horizontal skaliert werden, indem Container repliziert oder neue Instanzen gestartet werden.
  5. Einfache Integration: Docker lässt sich nahtlos in CI/CD-Pipelines einbinden und ist kompatibel mit Kubernetes, was die Verwaltung großer Anwendungen erleichtert.
  6. Sicherheit: Container bieten eine isolierte Umgebung, die potenzielle Sicherheitsrisiken minimiert, da Anwendungen voneinander abgeschirmt sind.

Docker Compose

Docker Compose baut auf Docker auf und bietet eine einfache Methode, mehrere Container-Anwendungen zu orchestrieren. Die Konfiguration erfolgt in einer YAML-Datei, die alle Dienste, Netzwerke und Volumes beschreibt.

  • Services: Beschreiben, wie Container gestartet werden, einschließlich Images, Ports und Umgebungsvariablen.
  • Volumes und Netzwerke: Ermöglichen persistente Datenhaltung und isolierte Kommunikation zwischen Containern.

Mit Docker Compose kannst du komplexe Setups schnell starten, stoppen und anpassen, was es besonders für hybride Cloud-Umgebungen attraktiv macht. Es bietet die Grundlage für einfache Test- und Entwicklungsumgebungen sowie für Produktions-Setups mit mehreren Diensten.

Grundlagen von Docker und Docker Compose

Docker ist eine zentrale Technologie, die moderne IT-Infrastrukturen revolutioniert hat. Es ermöglicht, Anwendungen und ihre Abhängigkeiten in leichtgewichtige, isolierte Umgebungen zu verpacken und so eine einfache Entwicklung, Bereitstellung und Skalierung zu gewährleisten. Im Kontext hybrider Cloud-Setups spielt Docker eine entscheidende Rolle, da es Flexibilität und Konsistenz über verschiedene Plattformen hinweg bietet. In den folgenden Abschnitten erklären wir, wie Docker funktioniert und welche Vorteile es bietet.

Aufbau und Erstellung eines Docker-Images

Docker-Images werden mithilfe einer sogenannten Dockerfile erstellt, die alle notwendigen Anweisungen enthält. Ein Image besteht aus mehreren Schichten (Layers), die Änderungen zwischen verschiedenen Befehlen speichern.

Beispiel eines erweiterten Dockerfiles:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Basis-Image definieren
FROM python:3.9-slim

# Metadaten zum Image hinzufügen
LABEL maintainer="example@example.com"
LABEL version="1.0"
LABEL description="Beispielanwendung mit Flask"

# Arbeitsverzeichnis im Container setzen
WORKDIR /app

# Abhängigkeiten kopieren und installieren
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

# Anwendungsdateien kopieren
COPY . ./

# Umgebungsvariable setzen
ENV FLASK_ENV=production

# Exponierte Ports definieren
EXPOSE 5000

# Startbefehl definieren
CMD ["flask", "run", "--host=0.0.0.0", "--port=5000"]

Um ein Image aus diesem Dockerfile zu erstellen, verwendet man den Befehl:

1
docker build -t my-flask-app .
  • -t my-flask-app: Vergibt einen Namen (Tag) für das erstellte Image.
  • .: Gibt an, dass das Dockerfile im aktuellen Verzeichnis liegt.

Docker-Images können auf Docker Hub oder in privaten Registries wie Amazon ECR gespeichert und bereitgestellt werden, um sie für andere Projekte oder Teams verfügbar zu machen.

Start und Verwaltung von Containern

Ein Container wird aus einem Image gestartet und repräsentiert eine laufende Instanz des Images. Mit dem Befehl docker run kann ein Container gestartet werden:

1
docker run -d -p 8080:5000 --name flask-container my-flask-app
  • -d: Startet den Container im Hintergrund (detached mode).
  • -p 8080:5000: Leitet den Port 8080 des Hosts auf den Port 5000 des Containers weiter.
  • --name flask-container: Vergibt einen Namen für den Container.

Weitere nützliche Parameter:

  • -e: Setzt Umgebungsvariablen, die im Container verfügbar sind (z. B. -e FLASK_ENV=development).
  • -v: Bindet Volumes ein, um Daten zwischen Host und Container zu teilen (z. B. -v /host/path:/container/path).
  • --rm: Entfernt den Container automatisch nach dem Stoppen.
  • --restart: Definiert Neustartverhalten (z. B. --restart=always für automatische Neustarts).
  • --network: Verbindet den Container mit einem benutzerdefinierten Netzwerk.

Schichtenprinzip und Optimierung von Dockerfiles

Jeder Befehl wie RUN, COPY oder ADD erzeugt eine neue Schicht (Layer) im Docker-Image. Diese Schichten machen Images effizient, da sie bei Änderungen nur inkrementell aktualisiert werden müssen. Allerdings können viele kleine Schichten die Größe des Images unnötig erhöhen.

Best Practices zur Optimierung:

  • Kombiniere Befehle in einer Zeile, um die Anzahl der Schichten zu reduzieren:
    1
    
    RUN apt-get update && apt-get install -y python3 && apt-get clean
    
  • Vermeide das Speichern von temporären Dateien in Schichten.
  • Nutze leichtgewichtige Basis-Images wie alpine, um die Gesamtgröße zu minimieren.

Docker Compose für Multi-Container-Setups

Docker Compose ist ein Werkzeug, das die Verwaltung und Orchestrierung mehrerer Container-Anwendungen vereinfacht. Es verwendet eine einzige docker-compose.yml-Datei, um die Konfiguration für alle Dienste, Netzwerke und Volumes zu definieren. Dies ermöglicht eine klar strukturierte und automatisierte Bereitstellung komplexer Systeme.

Dies sorgt für eine konsistente Umgebung, in der Dienste sowohl im Homelab als auch in der Hybrid Cloud zuverlässig bereitgestellt werden können. Darüber hinaus isoliert Docker Compose jeden Service in einem eigenen Netzwerk, was die Sicherheit und Übersichtlichkeit verbessert. Neue Dienste können leicht hinzugefügt werden, ohne die bestehende Struktur zu ändern. Schließlich ermöglicht Compose eine klare Trennung von Daten und Diensten durch Volumes, wodurch persistente Datenverwaltung vereinfacht wird.

  • Services: Dienste sind die Kernkomponenten einer Docker-Compose-Datei. Jeder Service beschreibt, wie ein Container gestartet wird, einschließlich:

    • Der Verwendung eines Images (z. B. von Docker Hub) oder des Buildens eines eigenen Images.
    • Der Port-Weiterleitung zwischen Host und Container.
    • Der Definition von Umgebungsvariablen, die im Container verfügbar sind.
    • Der Anbindung von Volumes für persistente Daten.
  • Volumes: Volumes dienen der Speicherung von Daten, die über den Lebenszyklus eines Containers hinaus bestehen bleiben sollen. Sie können benannt oder anonym sein und ermöglichen den Datenaustausch zwischen Containern.

  • Netzwerke: Netzwerke verbinden Dienste innerhalb eines Docker-Compose-Setups miteinander. Standardmäßig wird ein Netzwerk für alle Dienste erstellt, aber benutzerdefinierte Netzwerke erlauben eine granularere Kontrolle.

Beispiel einer Docker-Compose-Konfiguration

Ein typisches docker-compose.yml-File definiert mehrere Dienste, Netzwerke und Volumes. Dies ermöglicht eine einfache Orchestrierung komplexer Multi-Container-Anwendungen.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
version: '3.8'
services:
  web:
    build: .
    ports:
      - "5000:5000"
    environment:
      FLASK_ENV: development
    volumes:
      - .:/app
  db:
    image: postgres
    environment:
      POSTGRES_PASSWORD: example
    volumes:
      - db-data:/var/lib/postgresql/data
volumes:
  db-data:

In dieser Konfiguration:

  • Web-Service:

    • Baut ein Image basierend auf dem Dockerfile im aktuellen Verzeichnis (build: .).
    • Leitet den Port 5000 des Containers auf Port 5000 des Hosts weiter (ports).
    • Setzt die Umgebungsvariable FLASK_ENV auf development.
    • Bindet das Host-Verzeichnis (.) an das Verzeichnis /app im Container, wodurch Änderungen lokal in Echtzeit im Container verfügbar sind.
  • Datenbank-Service:

    • Nutzt das vorgefertigte postgres-Image von Docker Hub.
    • Setzt die Umgebungsvariable POSTGRES_PASSWORD für den Datenbankzugriff.
    • Speichert persistente Daten in einem benannten Volume (db-data).
  • Volumes:

    • db-data wird verwendet, um Daten der Datenbank auch nach dem Neustart des Containers zu speichern.

Dienste starten und verwalten

Mit dem Befehl docker-compose up können alle definierten Dienste gestartet werden:

1
docker-compose up -d
  • -d: Startet alle Dienste im Hintergrund (detached mode).

Compose übernimmt automatisch:

  • Das Erstellen und Verbinden der Netzwerke für die Dienste.
  • Das Mounten der definierten Volumes.
  • Die Weiterleitung der Ports.

Praktische Umsetzung: Zwei “Hello World”-Dienste mit Docker Compose

Docker und Docker Compose sind zentrale Werkzeuge, um Dienste effizient in Hybrid-Cloud-Umgebungen bereitzustellen. Mit Docker Compose lassen sich komplexe Multi-Container-Setups definieren und orchestrieren, wodurch Automatisierung und Konsistenz gewährleistet werden. Ansible ergänzt diese Tools ideal, indem es eine zentrale Verwaltung und Wiederholbarkeit der Bereitstellungsprozesse ermöglicht.

Ein wesentlicher Vorteil ist, dass viele Anwendungen bereits als vorgefertigte Docker-Images verfügbar sind, die regelmäßig gepflegt werden. Dadurch entfällt die Notwendigkeit, eigene Images zu erstellen. Stattdessen können Entwickler und Administratoren sich auf die Integration und Konfiguration der Dienste konzentrieren. Insbesondere in Hybrid-Cloud-Szenarien ermöglicht diese Kombination aus Docker und Ansible eine konsistente Bereitstellung von Diensten sowohl lokal als auch in der Cloud. Dienste können flexibel skaliert, aktualisiert oder migriert werden, ohne dass Änderungen an der grundlegenden Infrastruktur erforderlich sind.

Bereitstellung von Docker-Compose-Konfigurationen mit Ansible

In diesem Abschnitt demonstrieren wir, wie ein einfacher Dienst mithilfe von Ansible und Docker Compose bereitgestellt wird. Die Rolle hello-world dient dabei als Beispiel, um die grundlegende Struktur und Funktionsweise zu demonstrieren. Dieses Setup legt den Grundstein für das Verständnis der Konzepte, bevor wir in späteren Beiträgen auf komplexere Anwendungen und Workloads eingehen.

Wie im im Blog Beitrag Ansible in der Hybrid-Cloud: Der Weg zur effizienten Automatisierung bereits kennengelernt, beginnt die Ansible-Rolle mit ihrer Initialisierung. Nutze dazu das Kommando ansible-galaxy init, um eine neue Rolle mit allen notwendigen Verzeichnissen und Dateien zu erstellen:

1
ansible-galaxy init roles/docker

Dies erstellt die Basisstruktur für die Rolle docker im Verzeichnis roles/docker und den notwendigen Verzeichnissen und Dateien. In den nächsten Abschnitten werden die für diese Rolle notwendigen Dateien erstellt bzw. bearbeitet, um mittels Docker Compose ein “Hello World”-Beispiel zu deployen.

Variablen und Defaults der Rolle

Die Datei defaults/main.yml definiert Variablen, die in der gesamten Rolle verwendet werden. Ein Beispiel hierfür ist die Definition des Verzeichnisses, in dem der Dienst bereitgestellt wird:

1
helloworld_directory: "{{ services_directory }}/hello-world"

Die Variable services_directory wurde im letzten Beitrag als Teil des Inventories definiert und gibt das Hauptverzeichnis für alle Dienste vor. Mit der hier definierten Variable helloworld_directory wird ein spezifischer Pfad für den aktuellen Dienst bereitgestellt.

Konfigurationen und Docker Compose

Im Ordner files/ werden alle Dateien abgelegt, die auf dem Zielsystem bereitgestellt werden sollen. Dies umfasst Konfigurationsdateien und Ressourcen, die spezifisch für den jeweiligen Dienst sind. Jede Rolle enthält eine docker-compose.yml, die den Dienst definiert:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
version: '3.9'
services:
  service1:
    image: hello-world
    user: "${UID}:${GID}"
    networks:
      - helloworld_services
  service2:
    image: hello-world
    user: "${UID}:${GID}"
    networks:
      - helloworld_services

networks:
  helloworld_services:
    internal: true

Die Dienste service1 und service2 verwenden das hello-world-Image und laufen mit den Benutzerrechten, die im Inventory des letzten Kapitels definiert wurden. Diese Konfiguration minimiert Sicherheitsrisiken, da die Dienste nicht mit root-Rechten ausgeführt werden. Das interne Netzwerk helloworld_services ermöglicht eine isolierte Kommunikation zwischen den Containern und blockiert externe Zugriffe, was die Sicherheit des Systems erheblich erhöht.

Meta-Informationen der Rolle

Die Datei meta/main.yml definiert Abhängigkeiten zu anderen Rollen, um sicherzustellen, dass alle notwendigen Voraussetzungen erfüllt sind:

1
2
dependencies:
  - role: docker

Da die Bereitstellung der Dienste auf Docker basiert, stellt diese Abhängigkeit sicher, dass die Rolle docker zuvor ausgeführt wurde. Sollte eine Abhängigkeit von mehreren Rollen genutzt werden, wird sie nur einmalig aufgelöst.

Aufgaben zur Anwendung der Rolle

Die Datei tasks/main.yml beschreibt die spezifischen Schritte, die für die Bereitstellung des Dienstes notwendig sind:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
- name: Ensure directory exists
  file:
    path: "{{ helloworld_directory }}"
    state: directory

- name: Copy Docker Compose file
  copy:
    src: docker-compose.yml
    dest: "{{ helloworld_directory }}/docker-compose.yml"

- name: Start services with Docker Compose
  community.docker.docker_compose_v2:
    project_src: "{{ helloworld_directory }}"
    state: present
    remove_orphans: true
  environment:
    UID: "{{ uid }}"
    GID: "{{ gid }}"

Die Aufgaben umfassen das Erstellen des Zielverzeichnisses, das Kopieren der notwendigen Dateien und das Starten der Container. Diese klare Struktur fördert die Wiederverwendbarkeit und Modularität, was zukünftige Anpassungen erleichtert.

Ansible-Rolle bereitstellen und Docker Compose Dienste prüfen

Damit wir diese Rolle nun mittels Ansible auf unsere Hosts bereitstellen können, müssen wir zunächst die site.yml anpassen:

1
2
3
4
5
- name: Apply to all hosts
  hosts: all
  roles:
    - docker
    - hello-world

Anschließend führen wir das Playbook erneut aus:

1
ansible-playbook site.yml

Nachdem die Rolle mittels Ansible auf dem Host bereitgestellt und die Dienste mit Docker und Docker Compose gestartet wurden, ist es wichtig zu prüfen, ob sie korrekt laufen und keine Fehler aufgetreten sind. Dieser Schritt gewährleistet, dass die Container wie erwartet funktionieren und ihre Ausgaben überprüft werden können. Dies ist besonders nützlich, um sicherzustellen, dass die Konfiguration korrekt ist und die Dienste fehlerfrei bereitgestellt wurden.

Bevor du die folgenden Schritte ausführen kannst, melde dich zunächst per SSH auf dem entsprechenden Host an, auf dem die Dienste gestartet wurden. Nach der Anmeldung kannst du überprüfen, ob die Dienste wie erwartet laufen, und dir deren Logs anzeigen lassen.

  1. Status der Container prüfen

    1
    2
    3
    4
    5
    
    docker ps
    # Ausgabe:
    CONTAINER ID   IMAGE         COMMAND        CREATED          STATUS          PORTS     NAMES
    abc123def456   hello-world   "/hello"       5 minutes ago    Up 5 minutes              service1
    ghi789jkl012   hello-world   "/hello"       5 minutes ago    Up 5 minutes              service2
    

    Diese Ausgabe zeigt die Container-IDs, verwendeten Images, Startbefehle, Laufzeitstatus und Container-Namen (service1, service2).

  2. Logs der Dienste anzeigen Um die Logs aller Container zu sehen, kannst du den folgenden Befehl verwenden. Achte darauf, dass du dich im Verzeichnis der docker-compose.yml-Datei befindest (in unserem Fall /opt/services/hello-world):

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    $ cd /opt/services/hello-world
    $ docker compose logs
    service1  |
    service1  | Hello from Docker!
    service1  | This message shows that your installation appears to be working correctly.
    service1  |
    service2  |
    service2  | Hello from Docker!
    service2  | This message shows that your installation appears to be working correctly.
    service2  |
    

    Diese Logs zeigen die Ausgaben der Container service1 und service2, die im hello-world-Image definiert sind. Die Nachrichten bestätigen, dass die Dienste korrekt gestartet wurden.

Mit diesen Schritten kannst du sicherstellen, dass deine Dienste korrekt gestartet wurden und ihre Ausgaben wie erwartet funktionieren.

Fazit und Ausblick

In diesem Blogbeitrag haben wir die Grundlagen und Vorteile von Docker und Docker Compose ausführlich beleuchtet. Wir haben gezeigt, wie diese Technologien in Kombination mit Ansible eine leistungsstarke Basis für die Automatisierung und Orchestrierung in Homelabs und Hybrid-Cloud-Umgebungen schaffen. Durch die Einführung von vorgefertigten Images und der klaren Struktur von Docker Compose konnten wir eine effiziente und skalierbare Methode zur Dienstbereitstellung demonstrieren.

In der praktischen Umsetzung haben wir anhand der Rolle hello-world veranschaulicht, wie Docker Compose mit Ansible integriert wird, um Dienste automatisiert bereitzustellen. Dieses Beispiel bietet eine solide Grundlage für komplexere Anwendungen.

Im nächsten Blogbeitrag, Caddy als Reverse Proxy: Sichere deine Hybrid-Cloud, werden wir dieses Wissen nutzen, um den Caddy-Webserver auf unseren Hosts bereitzustellen. Caddy ermöglicht es uns, flexible und sichere Webdienste anzubieten, während wir weiterhin auf die Automatisierung und Skalierbarkeit setzen, die durch Docker, Docker Compose und Ansible möglich wird. Bleib dran, um zu erfahren, wie wir unsere Infrastruktur weiter optimieren und erweitern können!

Erstellt mit Hugo
Theme Stack gestaltet von Jimmy