🐳 Modul 4: Container und Docker
Lernziele: Sie verstehen die Unterschiede zwischen Containern und VMs, kennen die Docker-Architektur, können Dockerfiles schreiben, Docker Compose nutzen und Container für Netzwerk-Automatisierung einsetzen.
⏱️ Dauer: ca. 90 Minuten | 🔧 Praxis: 6 Übungen
Teil 1: Container vs. Virtuelle Maschinen
Die Umzugskarton-Analogie
Stellen Sie sich vor, Sie ziehen um. Es gibt zwei Strategien:
Strategie A: Ganzes Haus mitnehmen (VM)
Sie packen nicht nur Ihre Möbel ein – Sie nehmen das gesamte Haus mit. Fundament, Wände, Dach, Heizung, alles. Funktioniert überall, aber extrem schwer und langsam zu transportieren.
Strategie B: Umzugskartons (Container)
Sie packen nur das ein, was Sie wirklich brauchen – Möbel, Bücher, Kleidung. Das neue Haus (Betriebssystem) ist schon da. Schnell, leicht, effizient.
Technische Architektur im Vergleich
Der fundamentale Unterschied liegt in der Virtualisierungsebene:
Vergleichstabelle: VM vs. Container
| Eigenschaft | Virtuelle Maschine | Container |
|---|---|---|
| Startzeit | Minuten | Sekunden |
| Größe | Gigabytes (10-50 GB) | Megabytes (50-500 MB) |
| Ressourcen | Fest reserviert | Dynamisch geteilt |
| Isolation | Komplett (eigenes OS) | Prozess-Level |
| Betriebssystem | Beliebig (Linux, Windows) | Teilt Host-Kernel |
| Instanzen pro Host | 10-20 | 100-1000+ |
- VMs: Wenn Sie ein anderes OS brauchen (Windows auf Linux), komplette Isolation kritisch ist, oder Legacy-Software läuft
- Container: Für Microservices, CI/CD-Pipelines, konsistente Entwicklungsumgebungen, und wenn Geschwindigkeit zählt
Die Magie hinter Containern: Linux-Kernel-Features
Container sind keine neue Technologie – sie nutzen Linux-Features, die seit Jahren existieren:
- Namespaces: Isolieren Prozesse, Netzwerk, Dateisystem (jeder Container sieht nur "sein" System)
- cgroups: Limitieren CPU, RAM, I/O pro Container
- Union Filesystems: Ermöglichen Layer-basierte Images (nur Änderungen speichern)
Teil 2: Docker-Architektur verstehen
Docker ist wie ein Fertiggericht-System: Es gibt Rezepte (Dockerfiles), fertige Gerichte im Kühlregal (Images), und die Mikrowelle zum Aufwärmen (Docker Engine).
Die Docker-Komponenten
Docker Daemon (dockerd)
Der Hintergrund-Dienst, der Container erstellt, startet und verwaltet. Läuft als Root-Prozess auf dem Host.
Docker CLI
Die Kommandozeile (docker-Befehl). Kommuniziert mit dem Daemon über eine REST-API.
Docker Images
Read-only Templates mit allen Abhängigkeiten. Bestehen aus Layern – jede Änderung wird als neuer Layer gestapelt.
Container
Laufende Instanz eines Images. Hat einen schreibbaren Layer oben drauf (wird beim Löschen verworfen).
Registry
Image-Ablage. Docker Hub (öffentlich), oder private Registries (GitLab Container Registry, Harbor, Nexus).
Dockerfile
Text-Datei mit Bauanleitung für ein Image. Jede Zeile erzeugt einen Layer.
Der Docker-Workflow visualisiert
┌─────────────┐ docker build ┌─────────────┐
│ Dockerfile │ ──────────────────▶ │ Image │
└─────────────┘ └──────┬──────┘
│
docker push
│
▼
┌─────────────┐ ┌─────────────┐
│ Registry │ ◀──────────────────│ Docker Hub │
│ (GitLab) │ docker pull │ / Private │
└──────┬──────┘ └─────────────┘
│
docker run
│
▼
┌─────────────┐
│ Container │ ◀── Laufende Instanz
└─────────────┘
Image-Layer: Das Zwiebel-Prinzip
Images bestehen aus Schichten (Layers) – wie eine Zwiebel. Jeder Befehl im Dockerfile erzeugt einen neuen Layer:
┌────────────────────────────────────┐ │ Layer 5: CMD ansible-playbook │ ← Startbefehl ├────────────────────────────────────┤ │ Layer 4: RUN ansible-galaxy... │ ← Collections ├────────────────────────────────────┤ │ Layer 3: RUN pip install... │ ← Python-Pakete ├────────────────────────────────────┤ │ Layer 2: RUN apt-get update... │ ← System-Pakete ├────────────────────────────────────┤ │ Layer 1: python:3.11-slim │ ← Basis-Image └────────────────────────────────────┘
- Caching: Unveränderte Layer werden wiederverwendet → schnellere Builds
- Sharing: Mehrere Images teilen sich gleiche Basis-Layer → weniger Speicher
- Best Practice: Häufig ändernde Befehle ans Ende des Dockerfiles
Teil 3: Docker-Befehle meistern
Images verwalten
# Image von Docker Hub herunterladen
docker pull python:3.11
# Mit spezifischem Tag
docker pull python:3.11-slim
# Alle lokalen Images anzeigen
docker images
# Image löschen
docker rmi python:3.11
# Ungenutzte Images aufräumen
docker image prune -aContainer starten und verwalten
# Container interaktiv starten (mit Terminal)
docker run -it python:3.11 bash
# Container im Hintergrund starten
docker run -d --name mein-python python:3.11 sleep infinity
# Container mit automatischem Löschen nach Exit
docker run --rm -it python:3.11 python -c "print('Hello!')"
# Laufende Container anzeigen
docker ps
# Alle Container (auch gestoppte)
docker ps -a
# Container stoppen
docker stop mein-python
# Container starten (nach Stop)
docker start mein-python
# Container löschen
docker rm mein-python
# Alle gestoppten Container löschen
docker container pruneIn laufende Container verbinden
# Shell in laufendem Container öffnen
docker exec -it mein-python bash
# Einzelnen Befehl ausführen
docker exec mein-python cat /etc/os-release
# Als Root einloggen (falls User anders)
docker exec -it --user root mein-python bashDateien und Volumes
# Verzeichnis in Container mounten (Bind Mount)
docker run -it -v $(pwd):/workspace python:3.11 bash
# Benanntes Volume erstellen und nutzen
docker volume create mein-volume
docker run -it -v mein-volume:/data python:3.11 bash
# Datei in Container kopieren
docker cp lokale-datei.txt mein-python:/tmp/
# Datei aus Container kopieren
docker cp mein-python:/tmp/ergebnis.txt ./Netzwerk
# Port weiterleiten (Host:Container)
docker run -d -p 8080:80 nginx
# Container im Host-Netzwerk (kein NAT)
docker run --network host nginx
# Eigenes Netzwerk erstellen
docker network create mein-netzwerk
# Container in Netzwerk starten
docker run -d --network mein-netzwerk --name web nginx
# Netzwerke anzeigen
docker network lsMachen Sie sich mit Docker vertraut:
- Docker-Installation prüfen:
# Version anzeigen docker --version # Detaillierte Infos docker info # Test-Container ausführen docker run hello-world - Interaktiven Ubuntu-Container starten:
# Ubuntu-Container mit Bash docker run -it ubuntu:22.04 bash # Im Container: System erkunden cat /etc/os-release whoami pwd ls -la # Container verlassen exit - Container-Lebenszyklus verstehen:
# Container im Hintergrund starten docker run -d --name test-container ubuntu:22.04 sleep 300 # Status prüfen docker ps # In Container verbinden docker exec -it test-container bash exit # Container stoppen und löschen docker stop test-container docker rm test-container
Teil 4: Dockerfiles schreiben
Ein Dockerfile ist wie ein Kochrezept: Schritt für Schritt wird beschrieben, wie das Image gebaut wird.
Dockerfile-Befehle
| Befehl | Beschreibung | Beispiel |
|---|---|---|
| FROM | Basis-Image | FROM python:3.11-slim |
| RUN | Befehl ausführen (beim Build) | RUN pip install ansible |
| COPY | Dateien ins Image kopieren | COPY requirements.txt /app/ |
| ADD | Wie COPY, kann auch URLs/Archive | ADD app.tar.gz /app/ |
| WORKDIR | Arbeitsverzeichnis setzen | WORKDIR /app |
| ENV | Umgebungsvariable setzen | ENV ANSIBLE_HOST_KEY_CHECKING=False |
| EXPOSE | Port dokumentieren | EXPOSE 8080 |
| CMD | Standard-Befehl (überschreibbar) | CMD ["python", "app.py"] |
| ENTRYPOINT | Haupt-Befehl (nicht überschreibbar) | ENTRYPOINT ["ansible-playbook"] |
Beispiel: Python-Netzwerk-Tools Container
# Basis-Image: Schlankes Python
FROM python:3.11-slim
# Metadaten
LABEL maintainer="netzwerk-team@firma.de"
LABEL description="Python mit Netzwerk-Automatisierungs-Tools"
# System-Pakete installieren
RUN apt-get update && apt-get install -y --no-install-recommends \
openssh-client \
iputils-ping \
dnsutils \
curl \
&& rm -rf /var/lib/apt/lists/*
# Python-Pakete installieren
COPY requirements.txt /tmp/
RUN pip install --no-cache-dir -r /tmp/requirements.txt
# Arbeitsverzeichnis
WORKDIR /workspace
# Standard-Befehl
CMD ["python3"]netmiko==4.2.0
napalm==4.1.0
paramiko==3.4.0
netaddr==0.9.0
jinja2==3.1.2
pyyaml==6.0.1
requests==2.31.0
nornir==3.4.1
nornir-netmiko==1.0.1Image bauen und taggen
# Image bauen (im Verzeichnis mit Dockerfile)
docker build -t netzwerk-tools:latest .
# Mit Version taggen
docker build -t netzwerk-tools:1.0.0 .
# Für GitLab Registry taggen
docker build -t registry.gitlab.com/mein-projekt/netzwerk-tools:1.0.0 .
# Build-Cache ignorieren (sauberer Build)
docker build --no-cache -t netzwerk-tools:latest .- Projektverzeichnis erstellen:
mkdir ~/docker-netzwerk cd ~/docker-netzwerk - requirements.txt anlegen:
cat > requirements.txt << 'EOF' netmiko==4.2.0 netaddr==0.9.0 paramiko==3.4.0 jinja2==3.1.2 pyyaml==6.0.1 EOF - Dockerfile erstellen:
cat > Dockerfile << 'EOF' FROM python:3.11-slim RUN apt-get update && apt-get install -y --no-install-recommends \ openssh-client iputils-ping \ && rm -rf /var/lib/apt/lists/* COPY requirements.txt /tmp/ RUN pip install --no-cache-dir -r /tmp/requirements.txt WORKDIR /workspace CMD ["python3"] EOF - Image bauen:
docker build -t netzwerk-python:1.0 . - Container testen:
docker run -it --rm netzwerk-python:1.0 # Im Python-Prompt testen: from netaddr import IPNetwork for ip in IPNetwork('192.168.1.0/30'): print(ip) exit()
Bauen Sie einen Container mit Netzwerk-Analyse-Tools:
- Neues Verzeichnis und Dockerfile:
mkdir ~/docker-nettools cd ~/docker-nettools cat > Dockerfile << 'EOF' FROM ubuntu:22.04 LABEL maintainer="ihr-name" LABEL purpose="Network Troubleshooting Tools" # Interaktive Prompts vermeiden ENV DEBIAN_FRONTEND=noninteractive # Netzwerk-Tools installieren RUN apt-get update && apt-get install -y --no-install-recommends \ iputils-ping \ traceroute \ dnsutils \ netcat-openbsd \ curl \ wget \ tcpdump \ nmap \ iproute2 \ net-tools \ iperf3 \ mtr \ && rm -rf /var/lib/apt/lists/* WORKDIR /work CMD ["/bin/bash"] EOF - Image bauen:
docker build -t nettools:1.0 . - Tools testen:
# Container mit Host-Netzwerk starten (für echte Tests) docker run -it --rm --network host nettools:1.0 # Im Container: ping -c 3 8.8.8.8 dig google.com nmap -sn 192.168.1.0/24 # Vorsicht: nur eigenes Netz! traceroute google.com exit
Teil 5: Volumes und persistente Daten
Container sind flüchtig wie Seifenblasen – wenn sie weg sind, sind die Daten auch weg. Für persistente Daten brauchen Sie Volumes.
Drei Arten von Volumes
Bind Mounts
Host-Verzeichnis direkt einbinden. Gut für Entwicklung.
-v /host/pfad:/container/pfadNamed Volumes
Docker-verwaltete Volumes. Portabel und einfach.
-v volume-name:/container/pfadtmpfs Mounts
Im RAM, nicht persistent. Für temporäre/sensible Daten.
--tmpfs /container/pfad# Bind Mount: Lokales Verzeichnis einbinden
docker run -it -v $(pwd):/workspace python:3.11 bash
# Named Volume erstellen und nutzen
docker volume create ansible-data
docker run -it -v ansible-data:/data python:3.11 bash
# Volume inspizieren
docker volume inspect ansible-data
# Alle Volumes anzeigen
docker volume ls
# Ungenutzte Volumes löschen
docker volume prune- Test-Skript erstellen:
mkdir ~/docker-volume-test cd ~/docker-volume-test cat > hello.py << 'EOF' #!/usr/bin/env python3 from datetime import datetime from netaddr import IPNetwork print(f"=== Netzwerk-Analyse Tool ===") print(f"Ausgeführt: {datetime.now()}") print() subnet = "10.100.0.0/29" print(f"Subnet: {subnet}") print("Nutzbare IPs:") for ip in IPNetwork(subnet).iter_hosts(): print(f" - {ip}") EOF - Mit Bind Mount ausführen:
docker run --rm -v $(pwd):/workspace netzwerk-python:1.0 python /workspace/hello.py - Interaktiv entwickeln:
# Container starten, Verzeichnis gemountet docker run -it --rm -v $(pwd):/workspace netzwerk-python:1.0 bash # Im Container: Skript bearbeiten und testen cd /workspace python hello.py # Änderungen am Host sichtbar! exit - Named Volume für persistente Daten:
# Volume erstellen docker volume create test-data # Daten hineinschreiben docker run --rm -v test-data:/data python:3.11 bash -c "echo 'Persistent!' > /data/test.txt" # Daten lesen (in neuem Container!) docker run --rm -v test-data:/data python:3.11 cat /data/test.txt # Volume aufräumen docker volume rm test-data
Teil 6: Ansible im Container
Ansible in einem Container zu betreiben bringt enorme Vorteile:Konsistente Versionen, keine Abhängigkeitskonflikte, und perfekt für CI/CD-Pipelines.
Ansible-Container Dockerfile
# Ansible Container für NDFC/Netzwerk-Automatisierung
FROM python:3.11-slim
# Labels für Wartbarkeit
LABEL maintainer="netzwerk-team@firma.de"
LABEL version="1.0"
LABEL description="Ansible mit Cisco DCNM/NDFC Collections"
# Umgebungsvariablen
ENV ANSIBLE_HOST_KEY_CHECKING=False
ENV ANSIBLE_RETRY_FILES_ENABLED=False
ENV PYTHONUNBUFFERED=1
# System-Abhängigkeiten
RUN apt-get update && apt-get install -y --no-install-recommends \
openssh-client \
sshpass \
git \
&& rm -rf /var/lib/apt/lists/*
# Python-Pakete installieren
RUN pip install --no-cache-dir \
ansible-core==2.15.* \
ansible-pylibssh \
jmespath \
netaddr \
requests \
paramiko
# Ansible Collections installieren
RUN ansible-galaxy collection install \
cisco.dcnm \
ansible.netcommon \
ansible.utils
# Arbeitsverzeichnis
WORKDIR /ansible
# Ansible-Version anzeigen beim Start
CMD ["ansible", "--version"]Container bauen und nutzen
# Image bauen
docker build -t ansible-ndfc:1.0 -f Dockerfile.ansible .
# Ansible-Version prüfen
docker run --rm ansible-ndfc:1.0
# Playbook ausführen (mit gemounteten Dateien)
docker run --rm \
-v $(pwd):/ansible \
-v ~/.ssh:/root/.ssh:ro \
ansible-ndfc:1.0 \
ansible-playbook -i inventory.yml playbook.yml
# Interaktiv für Tests
docker run -it --rm \
-v $(pwd):/ansible \
ansible-ndfc:1.0 bash- Ansible-Container Verzeichnis erstellen:
mkdir ~/docker-ansible cd ~/docker-ansible - Dockerfile erstellen:
cat > Dockerfile << 'EOF' FROM python:3.11-slim ENV ANSIBLE_HOST_KEY_CHECKING=False ENV PYTHONUNBUFFERED=1 RUN pip install --no-cache-dir \ ansible-core==2.15.* \ jmespath \ netaddr RUN ansible-galaxy collection install ansible.utils WORKDIR /ansible CMD ["ansible", "--version"] EOF docker build -t ansible-test:1.0 . - Test-Playbook erstellen:
cat > playbook.yml << 'EOF' --- - name: Container-Test Playbook hosts: localhost connection: local gather_facts: yes vars: vlans: - id: 100 name: SERVERS - id: 200 name: CLIENTS - id: 300 name: MANAGEMENT tasks: - name: System-Informationen anzeigen ansible.builtin.debug: msg: | Ansible läuft in Container! Hostname: {{ ansible_hostname }} Python: {{ ansible_python_version }} - name: VLAN-Konfiguration generieren ansible.builtin.debug: msg: "VLAN {{ item.id }}: {{ item.name }}" loop: "{{ vlans }}" - name: IP-Berechnung mit netaddr ansible.builtin.debug: msg: "Gateway für 10.{{ item.id }}.0.0/24: 10.{{ item.id }}.0.1" loop: "{{ vlans }}" EOF - Playbook im Container ausführen:
docker run --rm -v $(pwd):/ansible ansible-test:1.0 \ ansible-playbook playbook.yml - Interaktive Ansible-Shell:
docker run -it --rm -v $(pwd):/ansible ansible-test:1.0 bash # Im Container: ansible localhost -m debug -a "msg='Hello from Container!'" ansible localhost -m setup | head -50 exit
Teil 7: Docker Compose
Docker Compose ist wie ein Orchester-Dirigent: Es startet mehrere Container gleichzeitig und koordiniert sie.
Warum Docker Compose?
- Multi-Container Apps: Web-Server + Datenbank + Cache gleichzeitig
- Deklarativ: YAML-Datei beschreibt gewünschten Zustand
- Networking: Container können sich per Namen erreichen
- Entwicklung: Komplexe Setups mit einem Befehl starten
docker-compose.yml Struktur
version: '3.8'
services:
# Service 1: Web-Anwendung
web:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html:ro
depends_on:
- api
# Service 2: API Backend
api:
build: ./api
environment:
- DATABASE_URL=postgres://db:5432/app
depends_on:
- db
# Service 3: Datenbank
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: secret
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:Compose-Befehle
# Alle Services starten
docker compose up
# Im Hintergrund starten
docker compose up -d
# Logs anzeigen
docker compose logs -f
# Status prüfen
docker compose ps
# In Service verbinden
docker compose exec web sh
# Alles stoppen
docker compose down
# Stoppen und Volumes löschen
docker compose down -vNetzwerk-Automatisierung mit Compose
version: '3.8'
services:
# Ansible Automation Container
ansible:
build: .
volumes:
- ./playbooks:/ansible/playbooks
- ./inventory:/ansible/inventory
- ./group_vars:/ansible/group_vars
- ~/.ssh:/root/.ssh:ro
environment:
- ANSIBLE_HOST_KEY_CHECKING=False
- NDFC_HOST=${NDFC_HOST}
- NDFC_USER=${NDFC_USER}
- NDFC_PASSWORD=${NDFC_PASSWORD}
working_dir: /ansible
command: tail -f /dev/null # Container am Leben halten
# Git-Server für lokale Tests (optional)
gitea:
image: gitea/gitea:latest
ports:
- "3000:3000"
volumes:
- gitea-data:/data
volumes:
gitea-data:- Projektverzeichnis erstellen:
mkdir ~/compose-demo cd ~/compose-demo - docker-compose.yml anlegen:
cat > docker-compose.yml << 'EOF' version: '3.8' services: # Python Netzwerk-Tools nettools: image: python:3.11-slim volumes: - ./scripts:/scripts working_dir: /scripts command: tail -f /dev/null networks: - automation-net # Web-UI für Dokumentation docs: image: nginx:alpine ports: - "8080:80" volumes: - ./docs:/usr/share/nginx/html:ro networks: - automation-net # Redis für Caching (Beispiel) cache: image: redis:alpine networks: - automation-net networks: automation-net: driver: bridge EOF - Dateien für Services erstellen:
# Script-Verzeichnis mkdir scripts cat > scripts/analyze.py << 'EOF' #!/usr/bin/env python3 import socket print("=== Container Netzwerk-Info ===") print(f"Hostname: {socket.gethostname()}") print(f"IP: {socket.gethostbyname(socket.gethostname())}") print() # Test: Andere Container erreichen for host in ['docs', 'cache']: try: ip = socket.gethostbyname(host) print(f"{host}: {ip} ✓") except: print(f"{host}: nicht erreichbar") EOF # Docs-Verzeichnis mkdir docs cat > docs/index.html << 'EOF' <!DOCTYPE html> <html> <head><title>Automation Docs</title></head> <body> <h1>🚀 Netzwerk-Automatisierung</h1> <p>Dokumentation läuft in einem Container!</p> <ul> <li>nettools: Python mit Netzwerk-Libraries</li> <li>docs: Diese Nginx-Seite</li> <li>cache: Redis für Caching</li> </ul> </body> </html> EOF - Services starten:
# Starten docker compose up -d # Status prüfen docker compose ps # Logs anzeigen docker compose logs - Services testen:
# Web-UI öffnen echo "Öffnen Sie: http://localhost:8080" # Python-Script im Container ausführen docker compose exec nettools python /scripts/analyze.py # Redis testen docker compose exec cache redis-cli ping - Aufräumen:
docker compose down
Teil 8: Docker Troubleshooting
Wenn Container nicht tun, was sie sollen, sind diese Befehle Ihre besten Freunde:
Container-Logs analysieren
# Logs eines Containers anzeigen
docker logs container-name
# Live-Logs (wie tail -f)
docker logs -f container-name
# Letzte 100 Zeilen
docker logs --tail 100 container-name
# Mit Zeitstempeln
docker logs -t container-name
# Logs seit bestimmter Zeit
docker logs --since 10m container-nameContainer inspizieren
# Alle Infos zu einem Container
docker inspect container-name
# Spezifische Infos extrahieren (IP-Adresse)
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' container-name
# Environment-Variablen anzeigen
docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' container-name
# Mounted Volumes anzeigen
docker inspect -f '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{println}}{{end}}' container-nameIn laufenden Container debuggen
# Shell im Container öffnen
docker exec -it container-name bash
# Oder für Alpine-basierte Images:
docker exec -it container-name sh
# Als Root einloggen
docker exec -it --user root container-name bash
# Prozesse im Container anzeigen
docker exec container-name ps aux
# Netzwerk-Status prüfen
docker exec container-name netstat -tlnp
docker exec container-name cat /etc/resolv.confRessourcen-Verbrauch überwachen
# Live-Statistiken aller Container
docker stats
# Für bestimmte Container
docker stats container-name
# Einmalig (nicht live)
docker stats --no-streamHäufige Probleme und Lösungen
| Problem | Diagnose | Lösung |
|---|---|---|
| Container startet nicht | docker logs | Fehlermeldung prüfen, oft fehlende Env-Vars |
| Port schon belegt | netstat -tlnp | Anderen Port wählen: -p 8081:80 |
| Volume-Berechtigung | ls -la /mount | chmod auf Host oder --user |
| Container beendet sofort | docker ps -a | CMD/ENTRYPOINT prüfen, Prozess läuft nicht |
| Kein Internet im Container | docker exec ... ping 8.8.8.8 | Docker-Netzwerk oder Firewall prüfen |
| Image zu groß | docker history image | Multi-stage Build, slim-Image nutzen |
- Container beendet sofort? Starten Sie mit
tail -f /dev/nullals CMD - Keine Shell im Container? Nutzen Sie
docker cpum Dateien zu extrahieren - Netzwerk-Probleme? Testen Sie mit
--network host
Teil 9: Best Practices für Container in CI/CD
Dockerfile-Optimierung
# ✅ GOOD: Spezifische Version nutzen
FROM python:3.11.7-slim-bookworm
# ✅ GOOD: Kombinierte RUN-Befehle (weniger Layer)
RUN apt-get update && apt-get install -y --no-install-recommends \
openssh-client \
&& rm -rf /var/lib/apt/lists/*
# ✅ GOOD: Requirements zuerst (Cache nutzen)
COPY requirements.txt /tmp/
RUN pip install --no-cache-dir -r /tmp/requirements.txt
# ✅ GOOD: Code als letztes (ändert sich oft)
COPY . /app
# ✅ GOOD: Non-root User
RUN useradd -m appuser
USER appuser
# ✅ GOOD: HEALTHCHECK definieren
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:8080/health || exit 1Image-Größe reduzieren
# Stage 1: Build
FROM python:3.11 AS builder
COPY requirements.txt .
RUN pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt
# Stage 2: Runtime (schlank)
FROM python:3.11-slim
COPY --from=builder /wheels /wheels
RUN pip install --no-cache-dir /wheels/*
COPY app/ /app/
CMD ["python", "/app/main.py"]Security Best Practices
✅ Tun
- • Spezifische Image-Tags nutzen
- • Non-root User verwenden
- • Secrets als Env-Vars (nicht im Image)
- • Images regelmäßig updaten
- • Minimale Base-Images (slim, alpine)
- • .dockerignore nutzen
❌ Vermeiden
- • latest Tag in Produktion
- • Root im Container
- • Passwörter im Dockerfile
- • Veraltete Base-Images
- • Unnötige Pakete installieren
- • SSH im Container
CI/CD-Integration (GitLab CI)
stages:
- build
- test
- deploy
variables:
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
build-image:
stage: build
image: docker:24
services:
- docker:24-dind
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $IMAGE_TAG .
- docker push $IMAGE_TAG
run-ansible:
stage: deploy
image: $IMAGE_TAG
script:
- ansible-playbook -i inventory.yml playbook.yml
only:
- main.dockerignore nicht vergessen
# Git
.git
.gitignore
# IDE
.vscode
.idea
*.swp
# Python
__pycache__
*.pyc
.pytest_cache
.venv
venv
# Secrets (NIEMALS ins Image!)
*.pem
*.key
.env
secrets/
# Build-Artefakte
*.log
.coverage
htmlcov/Zusammenfassung
✅ Das haben Sie gelernt
- ☑️ Container vs. VMs: Unterschiede in Architektur und Anwendung
- ☑️ Docker-Architektur: Daemon, CLI, Images, Container, Registry
- ☑️ Dockerfiles schreiben und optimieren
- ☑️ Volumes für persistente Daten nutzen
- ☑️ Docker Compose für Multi-Container-Setups
- ☑️ Netzwerk-Tools (Ansible, Python, netmiko) in Containern
- ☑️ Troubleshooting: logs, exec, inspect
- ☑️ Best Practices für CI/CD-Integration
📋 Spickzettel: Docker-Befehle
Im nächsten Modul lernen Sie, wie Container in CI/CD-Pipelines eingesetzt werden. Dort bauen wir automatisierte Workflows, die Ansible-Container nutzen, um Netzwerk-Konfigurationen zu deployen.