Home/Modul 4

🐳 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

⚠️⚠️ Arbeitsumgebung wechselt: GitLab Linux-Maschine

Ab diesem Modul arbeiten wir auf der GitLab Linux-Maschine! Dort ist Docker installiert und dort laufen später auch die Container in der CI/CD-Pipeline.

So verbinden Sie sich: Öffnen Sie auf Ihrem Desktop MRemoteNG und verbinden Sie sich mit der GitLab-Maschine (SSH).

MRemoteNG Verbindung zur GitLab Linux-Maschine
🔍 Vergrößern
MRemoteNG: Verbindung zur GitLab-Maschine herstellen

Alle Befehle und Dateien in diesem und den folgenden Modulen werden auf der Linux-Konsole ausgeführt. Dateien erstellen wir mit vi.

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:

Virtuelle Maschine (VM):
App 1
App 2
App 3
Guest OS
Guest OS
Guest OS
Hypervisor (VMware, KVM, Hyper-V)
Host-Betriebssystem
Hardware
Container:
App 1
App 2
App 3
Container Runtime (Docker Engine)
Host-Betriebssystem (Linux Kernel)
Hardware

Vergleichstabelle: VM vs. Container

EigenschaftVirtuelle MaschineContainer
StartzeitMinutenSekunden
GrößeGigabytes (10-50 GB)Megabytes (50-500 MB)
RessourcenFest reserviertDynamisch geteilt
IsolationKomplett (eigenes OS)Prozess-Level
BetriebssystemBeliebig (Linux, Windows)Teilt Host-Kernel
Instanzen pro Host10-20100-1000+
💡Wann was nutzen?
  • 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
└────────────────────────────────────┘
💡Warum Layer wichtig sind
  • 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

Ausgabe des docker images Befehls mit einer Liste lokaler Images
🔍 Vergrößern
docker images zeigt alle lokal vorhandenen Images mit Tags und Größe
# 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 -a

Container starten und verwalten

Ausgabe von docker ps mit laufenden Containern und deren Status
🔍 Vergrößern
docker ps zeigt alle laufenden Container mit ID, Image, Status und Ports
# 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 prune

In 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 bash

Dateien 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 ls
🔧Übung 1: Docker-Version prüfen und erste Container starten (~10 Min)

Machen Sie sich mit Docker vertraut:

  1. Docker-Installation prüfen:
    # Version anzeigen
    docker --version
    
    # Detaillierte Infos
    docker info
    
    # Test-Container ausführen
    docker run hello-world
    Terminal mit docker --version und docker info Ausgabe
    🔍 Vergrößern
    docker info zeigt Details zur Docker-Installation und Konfiguration
  2. 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
  3. 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

BefehlBeschreibungBeispiel
FROMBasis-ImageFROM python:3.11-slim
RUNBefehl ausführen (beim Build)RUN pip install ansible
COPYDateien ins Image kopierenCOPY requirements.txt /app/
ADDWie COPY, kann auch URLs/ArchiveADD app.tar.gz /app/
WORKDIRArbeitsverzeichnis setzenWORKDIR /app
ENVUmgebungsvariable setzenENV ANSIBLE_HOST_KEY_CHECKING=False
EXPOSEPort dokumentierenEXPOSE 8080
CMDStandard-Befehl (überschreibbar)CMD ["python", "app.py"]
ENTRYPOINTHaupt-Befehl (nicht überschreibbar)ENTRYPOINT ["ansible-playbook"]

Beispiel: Python-Netzwerk-Tools Container

Dockerfile geöffnet in VS Code mit Syntax-Highlighting
🔍 Vergrößern
Ein Dockerfile in VS Code mit farbiger Syntax-Hervorhebung
📄 Dockerfile
# 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"]
📄 requirements.txt
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.1

Image bauen und taggen

Terminal zeigt docker build Ausgabe mit Layer-Build-Prozess
🔍 Vergrößern
docker build zeigt jeden Layer-Schritt und den Build-Fortschritt
# 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 198.18.133.100:5050/workshop/netzwerk-tools:1.0.0 .

# Build-Cache ignorieren (sauberer Build)
docker build --no-cache -t netzwerk-tools:latest .
🔧Übung 2: Python-Container mit Netzwerk-Libraries (~15 Min)
  1. Projektverzeichnis erstellen:
    mkdir docker-netzwerk
    cd docker-netzwerk
  2. requirements.txt anlegen:
    vi requirements.txt

    Fügen Sie folgenden Inhalt ein (mit i in den Insert-Modus wechseln, dann :wq zum Speichern):

    📄 requirements.txt
    netmiko==4.2.0
    netaddr==0.9.0
    paramiko==3.4.0
    jinja2==3.1.2
    pyyaml==6.0.1
  3. Dockerfile erstellen:
    vi Dockerfile
    📄 Dockerfile
    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"]
  4. Image bauen:
    docker build -t netzwerk-python:1.0 .
  5. Container testen:
    docker run -it --rm netzwerk-python:1.0
    📄 Im Python-Prompt
    from netaddr import IPNetwork
    for ip in IPNetwork('192.168.1.0/30'):
        print(ip)
    
    exit()
🔧Übung 3: Eigenes Dockerfile für Netzwerk-Analyse (~15 Min)

Bauen Sie einen Container mit Netzwerk-Analyse-Tools:

  1. Neues Verzeichnis erstellen:
    mkdir docker-nettools
    cd docker-nettools
    vi Dockerfile
    📄 Dockerfile
    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"]
  2. Image bauen:
    docker build -t nettools:1.0 .
  3. Tools testen:
    # Container starten
    docker run -it --rm nettools:1.0
    
    # Im Container:
    ping -c 3 8.8.8.8
    dig google.com
    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/pfad
💾

Named Volumes

Docker-verwaltete Volumes. Portabel und einfach.

-v volume-name:/container/pfad
💨

tmpfs 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
🔧Übung 4: Container mit Volume mounten (~10 Min)
  1. Verzeichnis erstellen:
    mkdir docker-volume-test
    cd docker-volume-test
    vi hello.py
    📄 hello.py
    #!/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}")
  2. Mit Bind Mount ausführen:
    docker run --rm -v ${PWD}:/workspace netzwerk-python:1.0 python /workspace/hello.py
  3. 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
  4. 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

📄 Dockerfile.ansible
# 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 $env:USERPROFILE.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
🔧Übung 5: Ansible in Container ausführen (~15 Min)
  1. Ansible-Container Verzeichnis erstellen:
    mkdir docker-ansible
    cd docker-ansible
  2. Dockerfile erstellen:
    vi Dockerfile
    📄 Dockerfile
    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"]
    docker build -t ansible-test:1.0 .
  3. Test-Playbook erstellen:
    vi playbook.yml
    📄 playbook.yml
    ---
    - 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 }}"
  4. Playbook im Container ausführen:
    docker run --rm -v ${PWD}:/ansible ansible-test:1.0 ansible-playbook playbook.yml
  5. 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

📄 docker-compose.yml
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 -v

Netzwerk-Automatisierung mit Compose

📄 docker-compose.yml
version: '3.8'

services:
  # Ansible Automation Container
  ansible:
    build: .
    volumes:
      - ./playbooks:/ansible/playbooks
      - ./inventory:/ansible/inventory
      - ./group_vars:/ansible/group_vars
      - $env:USERPROFILE.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:
🔧Übung 6: Docker Compose mit Troubleshooting (~20 Min)

In dieser Übung erstellen Sie ein Multi-Container-Setup mit Docker Compose — aber es wird absichtlich ein Fehler eingebaut sein! Nutzen Sie Ihr Troubleshooting-Wissen, um das Problem zu finden und zu lösen.

  1. Projektverzeichnis erstellen:
    mkdir compose-demo
    cd compose-demo
  2. docker-compose.yml anlegen:
    vi docker-compose.yml
    📄 docker-compose.yml
    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
  3. Dateien für Services erstellen:
    mkdir scripts
    mkdir docs
    vi scripts/analyze.py
    📄 scripts/analyze.py
    #!/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")
    vi docs/index.html
    📄 docs/index.html
    <!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>
  4. Services starten:
    docker compose up -d
    💥 Hier passiert ein Fehler!

    Port 8080 ist auf dieser Maschine bereits durch GitLab belegt. Docker wird eine Fehlermeldung ausgeben, dass der Port nicht gebunden werden kann.

  5. 🔍 Troubleshooting — Finden Sie das Problem:

    Nutzen Sie die Befehle aus Teil 8, um den Fehler zu diagnostizieren:

    # Status prüfen — welcher Service läuft nicht?
    docker compose ps
    
    # Logs anschauen — was sagt die Fehlermeldung?
    docker compose logs docs
    
    # Welcher Prozess belegt Port 8080?
    ss -tlnp | grep 8080
    💡 Lösung anzeigen

    Problem: Port 8080 ist bereits von GitLab belegt. Der docs-Service kann nicht starten.

    Lösung: Ändern Sie den Port-Mapping in der docker-compose.yml auf einen freien Port:

    # Services stoppen
    docker compose down
    
    # docker-compose.yml bearbeiten: Port ändern
    vi docker-compose.yml
    # Ändern Sie "8080:80" zu "8888:80"
    
    # Neu starten
    docker compose up -d
    
    # Jetzt sollte es funktionieren!
    docker compose ps
  6. Services testen (nach dem Fix):
    # Web-UI im Browser öffnen: http://198.18.133.100:8888
    
    # Python-Script im Container ausführen
    docker compose exec nettools python /scripts/analyze.py
    
    # Redis testen
    docker compose exec cache redis-cli ping
  7. Aufräumen:
    docker compose down
✅ Erfolgskriterium: Sie haben den Port-Konflikt selbständig diagnostiziert, den Port in der docker-compose.yml geändert und die Web-UI erfolgreich im Browser aufgerufen.

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-name

Container 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-name

In 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.conf

Ressourcen-Verbrauch überwachen

Terminal mit docker stats zeigt CPU, Memory und Netzwerk-Nutzung
🔍 Vergrößern
docker stats zeigt Live-Ressourcenverbrauch aller Container
# Live-Statistiken aller Container
docker stats

# Für bestimmte Container
docker stats container-name

# Einmalig (nicht live)
docker stats --no-stream

Häufige Probleme und Lösungen

ProblemDiagnoseLösung
Container startet nichtdocker logsFehlermeldung prüfen, oft fehlende Env-Vars
Port schon belegtnetstat -tlnpAnderen Port wählen: -p 8081:80
Volume-Berechtigungls -la /mountchmod auf Host oder --user
Container beendet sofortdocker ps -aCMD/ENTRYPOINT prüfen, Prozess läuft nicht
Kein Internet im Containerdocker exec ... ping 8.8.8.8Docker-Netzwerk oder Firewall prüfen
Image zu großdocker history imageMulti-stage Build, slim-Image nutzen
⚠️Container-Debugging Tipps
  • Container beendet sofort? Starten Sie mit tail -f /dev/null als CMD
  • Keine Shell im Container? Nutzen Sie docker cp um Dateien zu extrahieren
  • Netzwerk-Probleme? Testen Sie mit --network host

Teil 9: Best Practices für Container in CI/CD

Dockerfile-Optimierung

📄 Dockerfile (optimiert)
# ✅ 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 1

Image-Größe reduzieren

📄 Multi-Stage Build
# 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

Warum Docker in CI/CD-Pipelines?

Stellen Sie sich vor: Ein Ansible-Playbook läuft auf dem Laptop eines Kollegen perfekt, aber auf dem CI/CD-Server schlägt es fehl. Warum? Andere Python-Version, fehlende Libraries, unterschiedliche Ansible-Collections. Genau dieses Problem löst Docker in Pipelines.

🎯 Die Kernvorteile

🔄 Definierter Zustand bei jedem Run

Jeder Pipeline-Run startet mit einem frischen Container aus dem gleichen Image. Keine "Altlasten" von vorherigen Runs — kein "bei mir funktioniert's".

📌 Versionierung der Umgebung

Das Dockerfile ist die Dokumentation der Umgebung. Python 3.11, Ansible 2.15, cisco.dcnm Collection 3.5 — alles festgehalten und reproduzierbar.

🧪 Identische Umgebung überall

Entwickler-Laptop, Staging, Produktion — überall der gleiche Container. Was lokal funktioniert, funktioniert auch in der Pipeline.

🚀 Schnelle, saubere Pipelines

Kein pip install bei jedem Run — alles ist schon im Image. Pipeline startet in Sekunden statt Minuten.

🔒 Isolation und Sicherheit

Jeder Job läuft isoliert. Ein fehlerhaftes Playbook kann den CI/CD-Server nicht beschädigen — der Container wird einfach gelöscht.

⏪ Einfaches Rollback

Neues Ansible-Update bricht etwas? Einfach das Image-Tag auf die vorherige Version zurücksetzen. Sofort wieder lauffähig.

💡Praxis-Beispiel: Ohne vs. Mit Docker
❌ Ohne Docker:

Montag: Pipeline läuft mit Ansible 2.15.3. Mittwoch: Server-Update installiert Ansible 2.16.0. Donnerstag: Pipeline bricht ab wegen Breaking Changes in der neuen Version. Niemand versteht warum — es hat "doch gestern noch funktioniert".

✅ Mit Docker:

Das Image ansible-ndfc:1.0 enthält immer exakt Ansible 2.15.3. Egal ob Server-Updates passieren — der Container ist davon nicht betroffen. Erst wenn Sie bewusst ein neues Image ansible-ndfc:1.1 bauen, ändert sich die Umgebung.

CI/CD-Integration (GitLab CI)

📄 .gitlab-ci.yml
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

📄 .dockerignore
# 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/

❓ Quiz: Testen Sie Ihr Docker-Wissen (5 Min)

Versuchen Sie, die Fragen ohne Zurückblättern zu beantworten.

1. Was ist der Hauptunterschied zwischen einem Container und einer VM?

Antwort anzeigen

Eine VM virtualisiert die komplette Hardware inkl. eigenem Betriebssystem (Hypervisor-Ebene). Ein Container teilt sich den Kernel des Host-Betriebssystems und isoliert nur den Prozess. Dadurch sind Container deutlich leichter (MB statt GB) und starten in Sekunden statt Minuten.

2. Was ist der Unterschied zwischen einem Docker Image und einem Container?

Antwort anzeigen

Ein Image ist ein Read-only Template (wie eine Blaupause). Ein Container ist eine laufende Instanz eines Images mit einem zusätzlichen schreibbaren Layer. Aus einem Image können beliebig viele Container gestartet werden.

3. Warum sollten Sie Docker in CI/CD-Pipelines einsetzen?

Antwort anzeigen

Docker sorgt für einen definierten Zustand bei jedem Pipeline-Run. Jeder Job startet mit einem frischen Container — gleiche Versionen, gleiche Abhängigkeiten, keine Altlasten. Was lokal funktioniert, funktioniert auch in der Pipeline.

4. Was macht der Befehl docker run -v ${PWD}:/workspace python:3.11 bash?

Antwort anzeigen

Er startet einen Python-3.11-Container interaktiv mit Bash und mountet das aktuelle Verzeichnis ($PWD) als Bind Mount unter /workspace im Container. Änderungen an Dateien sind sowohl im Container als auch auf dem Host sichtbar.

5. Was ist Docker Compose und wann nutzt man es?

Antwort anzeigen

Docker Compose ist ein Tool zum Definieren und Starten von Multi-Container-Anwendungenmit einer YAML-Datei. Statt mehrere docker run-Befehle auszuführen, beschreibt man alle Services, Netzwerke und Volumes deklarativ und startet alles mit docker compose up.

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

docker pull <image>
Image laden
docker run -it <image>
Interaktiv starten
docker run -d <image>
Im Hintergrund
docker run -v <src>:<dst>
Volume mounten
docker run -p 8080:80
Port weiterleiten
docker ps
Laufende Container
docker ps -a
Alle Container
docker logs <name>
Logs anzeigen
docker exec -it <name> bash
Shell öffnen
docker inspect <name>
Details anzeigen
docker stop <name>
Stoppen
docker rm <name>
Löschen
docker images
Lokale Images
docker build -t <name> .
Image bauen
docker compose up -d
Compose starten
docker compose down
Compose stoppen
💡Nächste Schritte

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.