Введение
При создании образов Docker для Python-приложений разработчики часто сталкиваются с сообщением 'ModuleNotFoundError'. Эта ошибка возникает, когда Python не может найти модуль или пакет, который требуется вашему приложению. Для начинающих пользователей Docker это может быть особенно сложной задачей для устранения неполадок.
В этой практической лабораторной работе вы создадите простое Python-приложение, контейнеризируете его с помощью Docker, столкнетесь с ModuleNotFoundError и изучите практические способы ее решения. К концу вы поймете, как правильно управлять зависимостями Python в образах Docker и избегать этой распространенной проблемы в своих проектах.
Создание простого Python-приложения
Давайте создадим базовое Python-приложение и настроим Docker для его запуска. Это поможет нам понять, как возникает ModuleNotFoundError в среде Docker.
Понимание структуры Python-приложения
Сначала создадим каталог проекта и перейдем в него:
mkdir -p ~/project/docker-python-app
cd ~/project/docker-python-app
Теперь давайте создадим простое Python-приложение, которое импортирует сторонний модуль. Мы создадим два файла:
- Основной файл приложения
- Файл требований для перечисления зависимостей
Создайте основной файл приложения:
nano app.py
Добавьте следующий код в app.py:
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()
Этот простой скрипт использует библиотеку requests для отправки HTTP-запроса на example.com и вывода некоторой базовой информации об ответе.
Теперь давайте создадим файл требований:
nano requirements.txt
Добавьте следующую строку в requirements.txt:
requests==2.28.1
Создание базового Dockerfile
Теперь давайте создадим простой Dockerfile, который продемонстрирует ModuleNotFoundError:
nano Dockerfile
Добавьте следующее содержимое в Dockerfile:
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"]
Этот Dockerfile:
- Использует образ Python 3.9 slim в качестве базового
- Устанавливает рабочую директорию в
/app - Копирует файл нашего приложения
- Указывает команду для запуска нашего приложения
Обратите внимание, что мы намеренно не скопировали файл requirements.txt и не установили какие-либо зависимости. Это вызовет ModuleNotFoundError при попытке запуска контейнера.
Сборка и запуск образа Docker
Давайте соберем образ Docker:
docker build -t python-app-error .
Вы должны увидеть вывод, похожий на этот:
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
Теперь давайте запустим контейнер Docker:
docker run python-app-error
Вы должны увидеть сообщение об ошибке, похожее на это:
Traceback (most recent call last):
File "/app/app.py", line 1, in <module>
import requests
ModuleNotFoundError: No module named 'requests'
Это и есть ModuleNotFoundError, на котором мы фокусируемся в этой лабораторной работе. Ошибка возникает, потому что мы не включили требуемый модуль requests в наш образ Docker.
Понимание и исправление ModuleNotFoundError
Теперь, когда мы столкнулись с ModuleNotFoundError, давайте разберемся, почему это произошло и как это исправить.
Почему возникает ModuleNotFoundError в Docker?
ModuleNotFoundError возникает в Docker по нескольким распространенным причинам:
- Отсутствие установки зависимостей: Мы не установили необходимые пакеты Python в образе Docker.
- Неправильный PYTHONPATH: Интерпретатор Python не может найти модули в ожидаемых местах.
- Проблемы со структурой файлов: Структура кода приложения не соответствует тому, как выполняются импорты.
В нашем случае ошибка возникла из-за того, что мы не установили пакет requests в нашем образе Docker. В отличие от нашей локальной среды разработки, где у нас может быть этот пакет, установленный глобально, контейнеры Docker являются изолированными средами.
Метод 1: Установка зависимостей с помощью pip в Dockerfile
Давайте изменим наш Dockerfile, чтобы установить необходимые зависимости:
nano Dockerfile
Обновите Dockerfile следующим содержимым:
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"]
Давайте соберем и запустим этот обновленный образ:
docker build -t python-app-fixed-1 .
Вы должны увидеть вывод, который включает установку пакета:
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
Теперь давайте запустим исправленный контейнер:
docker run python-app-fixed-1
Вы должны увидеть вывод, похожий на этот:
Status code: 200
Content length: 1256 characters
Отлично! Приложение теперь успешно работает, потому что мы установили необходимую зависимость.
Метод 2: Использование requirements.txt для управления зависимостями
Хотя прямая установка пакетов работает, лучше использовать файл requirements.txt для более организованного управления зависимостями. Давайте обновим наш Dockerfile:
nano Dockerfile
Обновите Dockerfile следующим содержимым:
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"]
Этот подход имеет несколько преимуществ:
- Он отделяет управление зависимостями от кода
- Упрощает обновление зависимостей
- Соответствует лучшим практикам для кэширования слоев Docker-образа
Давайте соберем и запустим этот обновленный образ:
docker build -t python-app-fixed-2 .
Вы должны увидеть вывод, аналогичный предыдущей сборке, но на этот раз он использует requirements.txt:
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
Теперь давайте запустим контейнер:
docker run python-app-fixed-2
Вы должны увидеть тот же успешный вывод:
Status code: 200
Content length: 1256 characters
Вы успешно исправили ModuleNotFoundError, используя два разных метода!
Лучшие практики для избежания ModuleNotFoundError
Теперь, когда мы исправили непосредственную проблему, давайте рассмотрим некоторые лучшие практики, чтобы избежать ModuleNotFoundError в образах Docker.
Понимание кэширования Docker для эффективных сборок
Docker использует многослойный подход к сборке образов. Каждая инструкция в Dockerfile создает новый слой. Когда вы пересобираете образ, Docker повторно использует кэшированные слои, если это возможно, что может значительно ускорить процесс сборки.
Для Python-приложений вы можете оптимизировать кэширование, выполнив следующие действия:
- Копирование и установка требований перед копированием кода приложения
- Размещение часто изменяемых файлов (например, кода приложения) в более поздних слоях
Давайте обновим наш Dockerfile, чтобы следовать этим лучшим практикам:
nano Dockerfile
Обновите Dockerfile следующим оптимизированным содержимым:
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"]
Давайте соберем этот оптимизированный образ:
docker build -t python-app-optimized .
И запустите его, чтобы убедиться, что он работает:
docker run python-app-optimized
Вы должны увидеть тот же успешный вывод:
Status code: 200
Content length: 1256 characters
Использование файла .dockerignore
Чтобы сделать ваши сборки Docker более эффективными, рекомендуется использовать файл .dockerignore для исключения файлов и каталогов, которые не нужны в образе Docker. Это уменьшает размер контекста сборки и повышает производительность сборки.
Давайте создадим файл .dockerignore:
nano .dockerignore
Добавьте следующее содержимое:
__pycache__
*.pyc
*.pyo
*.pyd
.Python
.git
.gitignore
*.log
*.pot
*.env
Создание более сложной структуры приложения
Для больших приложений с несколькими модулями важно правильно структурировать ваш проект. Давайте создадим немного более сложный пример:
mkdir -p myapp
Создайте файл модуля:
nano myapp/__init__.py
Оставьте этот файл пустым (он просто помечает каталог как пакет Python).
Теперь создайте файл модуля с некоторой функциональностью:
nano myapp/utils.py
Добавьте следующий код:
def get_message():
return "Hello from myapp.utils module!"
Теперь обновите наше основное приложение, чтобы использовать этот модуль:
nano app.py
Замените содержимое на:
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()
Соберите и запустите обновленное приложение:
docker build -t python-app-modules .
docker run python-app-modules
Вы должны увидеть вывод, который включает наше пользовательское сообщение:
Status code: 200
Content length: 1256 characters
Hello from myapp.utils module!
Дополнительные лучшие практики
Вот некоторые дополнительные лучшие практики, чтобы избежать ModuleNotFoundError в Docker:
Виртуальные окружения (virtual environments): Хотя это не строго необходимо в Docker (поскольку контейнеры изолированы), использование виртуальных окружений может помочь обеспечить согласованность между разработкой и производством.
Зафиксированные зависимости (pinned dependencies): Всегда указывайте точные версии зависимостей, чтобы обеспечить согласованность в разных средах.
Многоэтапные сборки (multi-stage builds): Для производственных образов рассмотрите возможность использования многоэтапных сборок для создания меньших образов только с необходимыми зависимостями.
Регулярные обновления зависимостей: Регулярно обновляйте свои зависимости, чтобы получать исправления безопасности и улучшения.
Следуя этим лучшим практикам, вы минимизируете вероятность столкновения с ModuleNotFoundError в ваших контейнерах Docker и создадите более эффективные и удобные в обслуживании образы Docker.
Резюме
В этой лабораторной работе вы узнали, как идентифицировать, устранять неполадки и исправлять ModuleNotFoundError при работе с образами Docker для Python-приложений. Вы получили практический опыт в:
- Создании базового Python-приложения и контейнеризации его с помощью Docker
- Понимании, почему ModuleNotFoundError возникает в средах Docker
- Исправлении проблем с зависимостями с использованием прямой установки пакетов и requirements.txt
- Внедрении лучших практик Docker, таких как правильное кэширование слоев и структура файлов
- Создании более сложной структуры приложения с несколькими модулями
- Использовании .dockerignore для оптимизации сборок Docker
Эти навыки помогут вам создавать более надежные и удобные в обслуживании образы Docker для ваших Python-приложений. Следуя лучшим практикам, рассмотренным в этой лабораторной работе, вы сможете избежать распространенных ошибок, таких как ModuleNotFoundError, и оптимизировать свой рабочий процесс разработки Docker.
Помните, что правильное управление зависимостями имеет решающее значение при работе с контейнеризованными приложениями. Всегда убеждайтесь, что ваши образы Docker включают все необходимые зависимости, правильно структурированный код и соответствуют лучшим практикам эффективности и удобства обслуживания.



