Behebung von 'ModuleNotFoundError' beim Erstellen eines Docker-Images

DockerBeginner
Jetzt üben

Einführung

Beim Erstellen von Docker-Images für Python-Anwendungen stoßen Entwickler häufig auf die Fehlermeldung 'ModuleNotFoundError'. Dieser Fehler tritt auf, wenn Python ein Modul oder Paket, das Ihre Anwendung benötigt, nicht finden kann. Für Docker-Anfänger kann die Fehlersuche besonders herausfordernd sein.

In diesem praktischen Lab erstellen Sie eine einfache Python-Anwendung, containerisieren sie mit Docker, stoßen auf die ModuleNotFoundError und lernen praktische Möglichkeiten, sie zu beheben. Am Ende werden Sie verstehen, wie man Python-Abhängigkeiten in Docker-Images richtig verwaltet und dieses häufige Problem in Ihren Projekten vermeidet.

Erstellen einer einfachen Python-Anwendung

Lassen Sie uns eine einfache Python-Anwendung erstellen und Docker einrichten, um sie auszuführen. Dies hilft uns zu verstehen, wie die ModuleNotFoundError in einer Docker-Umgebung auftritt.

Verstehen der Python-Anwendungsstruktur

Zuerst erstellen wir ein Projektverzeichnis und navigieren dorthin:

mkdir -p ~/project/docker-python-app
cd ~/project/docker-python-app

Nun erstellen wir eine einfache Python-Anwendung, die ein Drittanbietermodul importiert. Wir erstellen zwei Dateien:

  1. Eine Hauptanwendungsdatei
  2. Eine Requirements-Datei zur Auflistung der Abhängigkeiten

Erstellen Sie die Hauptanwendungsdatei:

nano app.py

Fügen Sie den folgenden Code zu app.py hinzu:

import requests

def main():
    response = requests.get("https://www.example.com")
    print(f"Status code: {response.status_code}")
    print(f"Content length: {len(response.text)} characters")

if __name__ == "__main__":
    main()

Dieses einfache Skript verwendet die requests-Bibliothek, um eine HTTP-Anfrage an example.com zu senden und einige grundlegende Informationen über die Antwort auszugeben.

Nun erstellen wir eine Requirements-Datei:

nano requirements.txt

Fügen Sie die folgende Zeile zu requirements.txt hinzu:

requests==2.28.1

Erstellen eines einfachen Dockerfiles

Nun erstellen wir ein einfaches Dockerfile, das die ModuleNotFoundError demonstriert:

nano Dockerfile

Fügen Sie den folgenden Inhalt zum Dockerfile hinzu:

FROM python:3.9-slim

WORKDIR /app

COPY app.py .

## We're intentionally NOT copying or installing requirements
## to demonstrate the ModuleNotFoundError

CMD ["python", "app.py"]

Dieses Dockerfile:

  • Verwendet das Python 3.9 slim Image als Basis
  • Setzt das Arbeitsverzeichnis auf /app
  • Kopiert unsere Anwendungsdatei
  • Gibt den Befehl an, um unsere Anwendung auszuführen

Beachten Sie, dass wir absichtlich die Datei requirements.txt nicht kopiert oder Abhängigkeiten installiert haben. Dies verursacht die ModuleNotFoundError, wenn wir versuchen, den Container auszuführen.

Erstellen und Ausführen des Docker-Images

Lassen Sie uns das Docker-Image erstellen:

docker build -t python-app-error .

Sie sollten eine ähnliche Ausgabe wie diese sehen:

Sending build context to Docker daemon  3.072kB
Step 1/4 : FROM python:3.9-slim
 ---> 3a4bac80b3ea
Step 2/4 : WORKDIR /app
 ---> Using cache
 ---> a8a4f574dbf5
Step 3/4 : COPY app.py .
 ---> Using cache
 ---> 7d5ae315f84b
Step 4/4 : CMD ["python", "app.py"]
 ---> Using cache
 ---> f5a9b09d7d8e
Successfully built f5a9b09d7d8e
Successfully tagged python-app-error:latest

Nun lassen Sie uns den Docker-Container ausführen:

docker run python-app-error

Sie sollten eine Fehlermeldung ähnlich dieser sehen:

Traceback (most recent call last):
  File "/app/app.py", line 1, in <module>
    import requests
ModuleNotFoundError: No module named 'requests'

Dies ist die ModuleNotFoundError, auf die wir uns in diesem Lab konzentrieren. Der Fehler tritt auf, weil wir das benötigte requests-Modul nicht in unser Docker-Image aufgenommen haben.

Verstehen und Beheben der ModuleNotFoundError

Nachdem wir nun auf die ModuleNotFoundError gestoßen sind, wollen wir verstehen, warum sie aufgetreten ist und wie man sie behebt.

Warum tritt die ModuleNotFoundError in Docker auf?

Die ModuleNotFoundError tritt in Docker aus mehreren häufigen Gründen auf:

  1. Fehlende Abhängigkeitsinstallation: Wir haben die erforderlichen Python-Pakete nicht im Docker-Image installiert.
  2. Falsche PYTHONPATH: Der Python-Interpreter kann die Module nicht an den erwarteten Orten finden.
  3. Probleme mit der Dateistruktur: Die Struktur des Anwendungscodes stimmt nicht mit der Art und Weise überein, wie Importe durchgeführt werden.

In unserem Fall trat der Fehler auf, weil wir das requests-Paket nicht in unserem Docker-Image installiert haben. Im Gegensatz zu unserer lokalen Entwicklungsumgebung, in der wir dieses Paket möglicherweise global installiert haben, sind Docker-Container isolierte Umgebungen.

Methode 1: Installieren von Abhängigkeiten mit pip im Dockerfile

Ändern wir unser Dockerfile, um die erforderlichen Abhängigkeiten zu installieren:

nano Dockerfile

Aktualisieren Sie das Dockerfile mit dem folgenden Inhalt:

FROM python:3.9-slim

WORKDIR /app

COPY app.py .

## Fix Method 1: Directly install the required package
RUN pip install requests==2.28.1

CMD ["python", "app.py"]

Lassen Sie uns dieses aktualisierte Image erstellen und ausführen:

docker build -t python-app-fixed-1 .

Sie sollten eine Ausgabe sehen, die die Paketinstallation enthält:

Sending build context to Docker daemon  3.072kB
Step 1/5 : FROM python:3.9-slim
 ---> 3a4bac80b3ea
Step 2/5 : WORKDIR /app
 ---> Using cache
 ---> a8a4f574dbf5
Step 3/5 : COPY app.py .
 ---> Using cache
 ---> 7d5ae315f84b
Step 4/5 : RUN pip install requests==2.28.1
 ---> Running in 5a6d7e8f9b0c
Collecting requests==2.28.1
  Downloading requests-2.28.1-py3-none-any.whl (62 kB)
Collecting charset-normalizer<3,>=2
  Downloading charset_normalizer-2.1.1-py3-none-any.whl (39 kB)
Collecting certifi>=2017.4.17
  Downloading certifi-2022.9.24-py3-none-any.whl (161 kB)
Collecting idna<4,>=2.5
  Downloading idna-3.4-py3-none-any.whl (61 kB)
Collecting urllib3<1.27,>=1.21.1
  Downloading urllib3-1.26.12-py2.py3-none-any.whl (140 kB)
Installing collected packages: urllib3, idna, charset-normalizer, certifi, requests
Successfully installed certifi-2022.9.24 charset-normalizer-2.1.1 idna-3.4 requests-2.28.1 urllib3-1.26.12
 ---> 2b3c4d5e6f7g
Removing intermediate container 5a6d7e8f9b0c
Step 5/5 : CMD ["python", "app.py"]
 ---> Running in 8h9i0j1k2l3m
 ---> 3n4o5p6q7r8s
Removing intermediate container 8h9i0j1k2l3m
Successfully built 3n4o5p6q7r8s
Successfully tagged python-app-fixed-1:latest

Nun lassen Sie uns den korrigierten Container ausführen:

docker run python-app-fixed-1

Sie sollten eine ähnliche Ausgabe wie diese sehen:

Status code: 200
Content length: 1256 characters

Großartig! Die Anwendung läuft jetzt erfolgreich, weil wir die erforderliche Abhängigkeit installiert haben.

Methode 2: Verwenden von requirements.txt für die Abhängigkeitsverwaltung

Obwohl die direkte Installation von Paketen funktioniert, ist es eine bessere Vorgehensweise, eine requirements.txt-Datei für eine organisiertere Abhängigkeitsverwaltung zu verwenden. Aktualisieren wir unser Dockerfile:

nano Dockerfile

Aktualisieren Sie das Dockerfile mit dem folgenden Inhalt:

FROM python:3.9-slim

WORKDIR /app

## Copy requirements first to leverage Docker cache
COPY requirements.txt .

## Fix Method 2: Use requirements.txt
RUN pip install -r requirements.txt

## Copy the rest of the application
COPY app.py .

CMD ["python", "app.py"]

Dieser Ansatz hat mehrere Vorteile:

  • Er trennt die Abhängigkeitsverwaltung vom Code
  • Er erleichtert die Aktualisierung von Abhängigkeiten
  • Er folgt Best Practices für das Caching von Docker-Image-Layern

Lassen Sie uns dieses aktualisierte Image erstellen und ausführen:

docker build -t python-app-fixed-2 .

Sie sollten eine ähnliche Ausgabe wie beim vorherigen Build sehen, aber dieses Mal wird requirements.txt verwendet:

Sending build context to Docker daemon  4.096kB
Step 1/5 : FROM python:3.9-slim
 ---> 3a4bac80b3ea
Step 2/5 : WORKDIR /app
 ---> Using cache
 ---> a8a4f574dbf5
Step 3/5 : COPY requirements.txt .
 ---> Using cache
 ---> b2c3d4e5f6g7
Step 4/5 : RUN pip install -r requirements.txt
 ---> Running in h8i9j0k1l2m3
Collecting requests==2.28.1
  Using cached requests-2.28.1-py3-none-any.whl (62 kB)
Collecting charset-normalizer<3,>=2
  Using cached charset_normalizer-2.1.1-py3-none-any.whl (39 kB)
Collecting idna<4,>=2.5
  Using cached idna-3.4-py3-none-any.whl (61 kB)
Collecting certifi>=2017.4.17
  Using cached certifi-2022.9.24-py3-none-any.whl (161 kB)
Collecting urllib3<1.27,>=1.21.1
  Using cached urllib3-1.26.12-py2.py3-none-any.whl (140 kB)
Installing collected packages: urllib3, idna, charset-normalizer, certifi, requests
Successfully installed certifi-2022.9.24 charset-normalizer-2.1.1 idna-3.4 requests-2.28.1 urllib3-1.26.12
 ---> n4o5p6q7r8s9
Removing intermediate container h8i9j0k1l2m3
Step 5/5 : COPY app.py .
 ---> t0u1v2w3x4y5
Step 6/6 : CMD ["python", "app.py"]
 ---> Running in z5a6b7c8d9e0
 ---> f1g2h3i4j5k6
Removing intermediate container z5a6b7c8d9e0
Successfully built f1g2h3i4j5k6
Successfully tagged python-app-fixed-2:latest

Nun lassen Sie uns den Container ausführen:

docker run python-app-fixed-2

Sie sollten die gleiche erfolgreiche Ausgabe sehen:

Status code: 200
Content length: 1256 characters

Sie haben die ModuleNotFoundError erfolgreich mit zwei verschiedenen Methoden behoben!

Best Practices zur Vermeidung der ModuleNotFoundError

Nachdem wir das unmittelbare Problem behoben haben, wollen wir uns einige Best Practices ansehen, um die ModuleNotFoundError in Docker-Images zu vermeiden.

Verstehen des Docker-Caching für effiziente Builds

Docker verwendet einen geschichteten Ansatz zum Erstellen von Images. Jede Anweisung in einem Dockerfile erstellt eine neue Schicht. Wenn Sie ein Image neu erstellen, verwendet Docker nach Möglichkeit zwischengespeicherte Schichten wieder, was den Build-Prozess erheblich beschleunigen kann.

Für Python-Anwendungen können Sie das Caching optimieren durch:

  1. Kopieren und Installieren von Anforderungen, bevor der Anwendungscode kopiert wird
  2. Beibehalten von häufig geänderten Dateien (wie Anwendungscode) in den späteren Schichten

Aktualisieren wir unser Dockerfile, um diese Best Practices zu befolgen:

nano Dockerfile

Aktualisieren Sie das Dockerfile mit dem folgenden optimierten Inhalt:

FROM python:3.9-slim

WORKDIR /app

## Copy requirements first for better caching
COPY requirements.txt .

## Install dependencies
RUN pip install --no-cache-dir -r requirements.txt

## Copy application code (changes more frequently)
COPY . .

## Make sure we run the application with Python's unbuffered mode for better logging
CMD ["python", "-u", "app.py"]

Lassen Sie uns dieses optimierte Image erstellen:

docker build -t python-app-optimized .

Und führen Sie es aus, um zu überprüfen, ob es funktioniert:

docker run python-app-optimized

Sie sollten die gleiche erfolgreiche Ausgabe sehen:

Status code: 200
Content length: 1256 characters

Verwenden einer .dockerignore-Datei

Um Ihre Docker-Builds effizienter zu gestalten, ist es eine gute Praxis, eine .dockerignore-Datei zu verwenden, um Dateien und Verzeichnisse auszuschließen, die im Docker-Image nicht benötigt werden. Dies reduziert die Größe des Build-Kontexts und verbessert die Build-Leistung.

Lassen Sie uns eine .dockerignore-Datei erstellen:

nano .dockerignore

Fügen Sie den folgenden Inhalt hinzu:

__pycache__
*.pyc
*.pyo
*.pyd
.Python
.git
.gitignore
*.log
*.pot
*.env

Erstellen einer komplexeren Anwendungsstruktur

Für größere Anwendungen mit mehreren Modulen ist es wichtig, Ihr Projekt korrekt zu strukturieren. Erstellen wir ein etwas komplexeres Beispiel:

mkdir -p myapp

Erstellen Sie eine Moduldatei:

nano myapp/__init__.py

Lassen Sie diese Datei leer (sie markiert das Verzeichnis lediglich als Python-Paket).

Erstellen Sie nun eine Moduldatei mit einigen Funktionen:

nano myapp/utils.py

Fügen Sie den folgenden Code hinzu:

def get_message():
    return "Hello from myapp.utils module!"

Aktualisieren Sie nun unsere Hauptanwendung, um dieses Modul zu verwenden:

nano app.py

Ersetzen Sie den Inhalt durch:

import requests
from myapp.utils import get_message

def main():
    response = requests.get("https://www.example.com")
    print(f"Status code: {response.status_code}")
    print(f"Content length: {len(response.text)} characters")
    print(get_message())

if __name__ == "__main__":
    main()

Erstellen und führen Sie die aktualisierte Anwendung aus:

docker build -t python-app-modules .
docker run python-app-modules

Sie sollten eine Ausgabe sehen, die unsere benutzerdefinierte Nachricht enthält:

Status code: 200
Content length: 1256 characters
Hello from myapp.utils module!

Zusätzliche Best Practices

Hier sind einige zusätzliche Best Practices, um die ModuleNotFoundError in Docker zu vermeiden:

  1. Virtuelle Umgebungen: Obwohl dies in Docker nicht unbedingt erforderlich ist (da Container isoliert sind), kann die Verwendung virtueller Umgebungen dazu beitragen, die Konsistenz zwischen Entwicklung und Produktion sicherzustellen.

  2. Gepinnten Abhängigkeiten: Geben Sie immer exakte Versionen von Abhängigkeiten an, um die Konsistenz in verschiedenen Umgebungen sicherzustellen.

  3. Multi-Stage-Builds: Für Produktions-Images sollten Sie die Verwendung von Multi-Stage-Builds in Betracht ziehen, um kleinere Images mit nur den erforderlichen Abhängigkeiten zu erstellen.

  4. Regelmäßige Abhängigkeitsaktualisierungen: Aktualisieren Sie Ihre Abhängigkeiten regelmäßig, um Sicherheitskorrekturen und Verbesserungen zu erhalten.

Durch Befolgen dieser Best Practices minimieren Sie die Wahrscheinlichkeit, dass Sie in Ihren Docker-Containern auf die ModuleNotFoundError stoßen, und erstellen effizientere, wartungsfreundlichere Docker-Images.

Zusammenfassung

In diesem Lab haben Sie gelernt, wie man die ModuleNotFoundError identifiziert, behebt und behebt, wenn man mit Docker-Images für Python-Anwendungen arbeitet. Sie haben praktische Erfahrung in folgenden Bereichen gesammelt:

  • Erstellen einer einfachen Python-Anwendung und Containerisierung mit Docker
  • Verstehen, warum die ModuleNotFoundError in Docker-Umgebungen auftritt
  • Beheben von Abhängigkeitsproblemen mithilfe der direkten Paketinstallation und requirements.txt
  • Implementieren von Docker Best Practices wie korrektes Layer-Caching und Dateistruktur
  • Erstellen einer komplexeren Anwendungsstruktur mit mehreren Modulen
  • Verwenden von .dockerignore zur Optimierung von Docker-Builds

Diese Fähigkeiten helfen Ihnen dabei, zuverlässigere und wartungsfreundlichere Docker-Images für Ihre Python-Anwendungen zu erstellen. Durch die Befolgung der in diesem Lab behandelten Best Practices können Sie häufige Fallstricke wie die ModuleNotFoundError vermeiden und Ihren Docker-Entwicklungsworkflow optimieren.

Denken Sie daran, dass eine ordnungsgemäße Abhängigkeitsverwaltung entscheidend ist, wenn Sie mit containerisierten Anwendungen arbeiten. Stellen Sie immer sicher, dass Ihre Docker-Images alle notwendigen Abhängigkeiten enthalten, den Code korrekt strukturiert haben und Best Practices für Effizienz und Wartbarkeit befolgen.