Введение
В этой практической лабораторной работе вы узнаете, как выявлять и устранять распространенную проблему Docker: контейнеры, которые немедленно завершают работу после запуска. Эта проблема часто сбивает с толку новичков и может возникать по разным причинам, начиная от ошибок конфигурации и заканчивая проблемами с приложениями.
Выполнив эту лабораторную работу, вы поймете жизненный цикл контейнера Docker, научитесь диагностировать немедленное завершение работы контейнеров, освоите методы отладки и внедрите лучшие практики для обеспечения надежной работы ваших контейнеров. Эти навыки необходимы для всех, кто работает с Docker в средах разработки или производства.
Основы Docker контейнеров
Давайте начнем с изучения основ Docker контейнеров и ознакомимся с основными командами, используемыми для управления контейнерами.
Что такое Docker контейнер?
Docker контейнер — это облегченный, автономный и исполняемый пакет программного обеспечения, который включает в себя все необходимое для запуска приложения:
- Код
- Среда выполнения (Runtime)
- Системные инструменты
- Библиотеки
- Настройки
Контейнеры работают в изоляции от хост-системы и от других контейнеров, обеспечивая согласованность в разных средах.
Изучение Docker на вашей системе
Во-первых, давайте убедимся, что Docker правильно установлен и запущен в вашей системе:
docker --version
Вы должны увидеть вывод, похожий на:
Docker version 20.10.21, build baeda1f
Далее давайте проверим, запущены ли какие-либо контейнеры в данный момент:
docker ps
Эта команда выводит список запущенных контейнеров. Поскольку мы еще не запустили ни одного, вы должны увидеть только заголовки столбцов:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Чтобы просмотреть все контейнеры, включая остановленные:
docker ps -a
Понимание жизненного цикла контейнера
Жизненный цикл Docker контейнера состоит из нескольких состояний:
- Created (Создан): Контейнер создан, но еще не запущен
- Running (Запущен): Контейнер выполняет определенные процессы
- Paused (Приостановлен): Процессы контейнера временно приостановлены
- Stopped (Остановлен): Контейнер завершил работу или был остановлен
- Deleted (Удален): Контейнер был удален из системы
Давайте запустим простой контейнер и понаблюдаем за его жизненным циклом:
docker run hello-world
Вы должны увидеть вывод, похожий на:
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
...
Обратите внимание, что этот контейнер запустился и немедленно завершил работу после отображения сообщения. На самом деле это нормальное поведение для контейнера hello-world, поскольку он предназначен просто для отображения сообщения и завершения работы.
Проверьте наличие контейнера в списке всех контейнеров:
docker ps -a
Вы должны увидеть свой контейнер hello-world в списке со статусом "Exited (Завершен)":
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a1b2c3d4e5f6 hello-world "/hello" 30 seconds ago Exited (0) 30 seconds ago peaceful_hopper
Код выхода (0) указывает на то, что контейнер успешно завершил работу без ошибок.
Основные команды Docker для управления контейнерами
Вот некоторые основные команды Docker, которые вы будете использовать в этой лабораторной работе:
docker run [OPTIONS] IMAGE [COMMAND]: Создать и запустить контейнерdocker ps: Список запущенных контейнеровdocker ps -a: Список всех контейнеров (включая остановленные)docker logs [CONTAINER_ID]: Просмотр журналов контейнераdocker inspect [CONTAINER_ID]: Получить подробную информацию о контейнереdocker exec -it [CONTAINER_ID] [COMMAND]: Запустить команду в запущенном контейнереdocker stop [CONTAINER_ID]: Остановить запущенный контейнерdocker rm [CONTAINER_ID]: Удалить контейнер
Теперь, когда вы понимаете основы Docker контейнеров и их жизненный цикл, мы перейдем к устранению неполадок контейнеров, которые неожиданно завершают работу.
Выявление проблем с завершением работы контейнера
На этом этапе мы создадим Docker контейнер, который немедленно завершает работу, и узнаем, как диагностировать проблему.
Запуск контейнера, который немедленно завершает работу
Давайте сначала попробуем запустить контейнер Ubuntu:
docker run ubuntu
Вы заметите кое-что интересное — команда мгновенно завершается и возвращается к вашей командной строке. Где наш контейнер Ubuntu? Давайте проверим:
docker ps
Никакие контейнеры не запущены. Теперь проверьте все контейнеры, включая остановленные:
docker ps -a
Вы увидите вывод, похожий на:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f7d9e7f6543d ubuntu "/bin/bash" 10 seconds ago Exited (0) 10 seconds ago focused_galileo
a1b2c3d4e5f6 hello-world "/hello" 10 minutes ago Exited (0) 10 minutes ago peaceful_hopper
Контейнер Ubuntu запустился, а затем немедленно завершил работу с кодом состояния 0, что указывает на то, что он завершил работу без ошибок. Это ожидаемое поведение, но может сбивать с толку новичков.
Понимание причин завершения работы контейнеров
Контейнеры предназначены для запуска определенной команды или процесса. Когда этот процесс завершается или завершает работу, контейнер останавливается. Это фундаментальный принцип проектирования Docker.
Контейнер Ubuntu завершил работу немедленно, потому что:
- Команда по умолчанию для образа Ubuntu —
/bin/bash - При запуске без флагов
-it(интерактивный, терминал) нет ввода в оболочку bash - Без ввода и без конкретной команды для выполнения bash немедленно завершает работу
- Когда основной процесс (bash) завершает работу, контейнер останавливается
Просмотр журналов и информации о контейнере
Давайте рассмотрим журналы нашего завершившего работу контейнера Ubuntu, чтобы понять, что произошло. Сначала найдите ID контейнера в выводе docker ps -a, затем:
docker logs CONTAINER_ID
Замените CONTAINER_ID фактическим ID вашего контейнера. Скорее всего, вы не увидите вывода, потому что контейнер не создал никаких журналов перед завершением работы.
Для получения более подробной информации о контейнере:
docker inspect CONTAINER_ID
Это отобразит большой объект JSON с информацией о конфигурации и состоянии контейнера.
Давайте сосредоточимся на коде выхода:
docker inspect CONTAINER_ID --format='{{.State.ExitCode}}'
Вы должны увидеть:
0
Это подтверждает, что контейнер завершил работу нормально, а не из-за ошибки.
Поддержание работы контейнера
Чтобы контейнер Ubuntu продолжал работать, нам нужно либо:
- Предоставить интерактивный сеанс, или
- Переопределить команду по умолчанию долго выполняющимся процессом
Давайте попробуем интерактивный подход:
docker run -it ubuntu
Теперь вы находитесь внутри контейнера с приглашением bash:
root@3a4b5c6d7e8f:/#
Контейнер остается запущенным до тех пор, пока этот сеанс bash активен. Введите exit или нажмите Ctrl+D, чтобы выйти из контейнера.
exit
В качестве альтернативы мы можем поддерживать работу контейнера, предоставив команду, которая не завершается немедленно:
docker run -d ubuntu sleep 300
Это запускает контейнер Ubuntu и выполняет команду sleep 300, которая будет поддерживать работу контейнера в течение 300 секунд (5 минут).
Убедитесь, что контейнер запущен:
docker ps
Вы должны увидеть свой контейнер в запущенном состоянии:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9a8b7c6d5e4f ubuntu "sleep 300" 10 seconds ago Up 10 seconds hopeful_hopper
При диагностике контейнеров, которые немедленно завершают работу, помните эти ключевые моменты:
- Контейнеры завершают работу, когда завершается их основной процесс
- Проверьте журналы и коды выхода, чтобы понять, почему они остановились
- Если контейнер должен продолжать работать, убедитесь, что его основной процесс не завершается
Устранение распространенных проблем с завершением работы контейнера
Теперь, когда мы понимаем, почему контейнеры немедленно завершают работу, давайте рассмотрим распространенные причины неожиданного завершения работы контейнеров и способы их устранения.
Создание контейнера с проблемой
Давайте создадим простую ситуацию, когда контейнер неожиданно завершает работу. Сначала создайте каталог для наших тестовых файлов:
mkdir -p ~/project/docker-exit-test
cd ~/project/docker-exit-test
Теперь создайте простой скрипт Python с ошибкой:
nano app.py
Добавьте следующий код:
import os
## Attempt to read a required environment variable
database_url = os.environ['DATABASE_URL']
print(f"Connecting to database: {database_url}")
print("Application running...")
## Rest of the application code would go here
Сохраните и закройте файл (нажмите Ctrl+O, Enter, затем Ctrl+X).
Теперь создайте Dockerfile для сборки образа с этим приложением:
nano Dockerfile
Добавьте следующее содержимое:
FROM python:3.9-slim
WORKDIR /app
COPY app.py .
CMD ["python", "app.py"]
Сохраните и закройте файл.
Соберите образ Docker:
docker build -t exit-test-app .
Вы должны увидеть вывод, указывающий на то, что образ был успешно собран:
Successfully built a1b2c3d4e5f6
Successfully tagged exit-test-app:latest
Теперь запустите контейнер:
docker run exit-test-app
Вы должны увидеть, что контейнер немедленно завершает работу с ошибкой:
Traceback (most recent call last):
File "/app/app.py", line 4, in <module>
database_url = os.environ['DATABASE_URL']
File "/usr/local/lib/python3.9/os.py", line 679, in __getitem__
raise KeyError(key) from None
KeyError: 'DATABASE_URL'
Контейнер завершил работу, потому что наш скрипт Python ожидал переменную окружения, которая не была предоставлена.
Диагностика проблемы
Когда контейнер неожиданно завершает работу, выполните следующие шаги по устранению неполадок:
- Проверьте код выхода, чтобы определить, является ли это ошибкой:
docker ps -a
Найдите свой контейнер и обратите внимание на код выхода. Он должен быть ненулевым, что указывает на ошибку:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b1c2d3e4f5g6 exit-test-app "python app.py" 20 seconds ago Exited (1) 19 seconds ago vigilant_galileo
- Изучите журналы контейнера:
docker logs $(docker ps -a -q --filter ancestor=exit-test-app --latest)
Это извлекает журналы из самого последнего контейнера, созданного из нашего образа.
Сообщение об ошибке четко показывает проблему: скрипт Python пытается получить доступ к переменной окружения с именем DATABASE_URL, которая не существует.
Исправление проблемы
Теперь давайте исправим проблему, предоставив недостающую переменную окружения:
docker run -e DATABASE_URL=postgresql://user:password@db:5432/mydatabase exit-test-app
Теперь вы должны увидеть, что контейнер успешно работает:
Connecting to database: postgresql://user:password@db:5432/mydatabase
Application running...
Контейнер все еще завершает работу, но на этот раз это происходит потому, что наш скрипт достигает конца и завершается нормально. Если бы мы хотели, чтобы контейнер продолжал работать, нам нужно было бы изменить наш скрипт, чтобы включить бесконечный цикл или долго выполняющийся процесс.
Распространенные причины завершения работы контейнера
Вот несколько распространенных причин, по которым контейнеры могут неожиданно завершать работу:
- Отсутствующие переменные окружения: Как мы только что продемонстрировали
- Отсутствующие зависимости: Отсутствующие библиотеки или системные пакеты
- Сбои соединения: Невозможность подключиться к базе данных или другой службе
- Проблемы с разрешениями: Недостаточные разрешения для доступа к файлам или ресурсам
- Ограничения ресурсов: Контейнер исчерпывает память или процессорное время
- Сбои приложений: Ошибки в коде приложения
Методы устранения неполадок
Для каждой из этих проблем вот эффективные подходы к устранению неполадок:
- Просмотр журналов: Всегда сначала проверяйте журналы контейнера с помощью
docker logs - Переопределение точки входа (entrypoint): Используйте
docker run --entrypoint /bin/sh -it my-image, чтобы войти в контейнер и провести расследование - Добавление операторов отладки: Измените свое приложение, чтобы добавить больше ведения журнала
- Проверка использования ресурсов: Используйте
docker statsдля мониторинга использования ресурсов контейнера - Проверка окружения: Запустите
docker exec -it CONTAINER_ID env, чтобы проверить переменные окружения
Давайте попробуем метод переопределения точки входа с нашим образом:
docker run --entrypoint /bin/sh -it exit-test-app
Теперь вы находитесь внутри контейнера с оболочкой. Вы можете изучить окружение:
ls -la
cat app.py
echo $DATABASE_URL
Вы увидите, что DATABASE_URL не установлен. Выйдите из контейнера, когда закончите:
exit
Понимая, почему контейнеры завершают работу, и применяя эти методы устранения неполадок, вы можете быстро диагностировать и решить большинство проблем с завершением работы контейнеров.
Внедрение лучших практик для надежности контейнеров
Теперь, когда мы понимаем, как устранять проблемы с завершением работы контейнеров, давайте внедрим лучшие практики, чтобы сделать наши контейнеры более надежными и устойчивыми. Мы улучшим наше примерное приложение с надлежащей обработкой ошибок, проверками работоспособности и ведением журналов.
1. Улучшение обработки ошибок
Давайте изменим наше приложение Python, чтобы корректно обрабатывать отсутствующие переменные окружения:
cd ~/project/docker-exit-test
nano app.py
Обновите содержимое до:
import os
import time
import sys
## Get environment variables with defaults
database_url = os.environ.get('DATABASE_URL', 'sqlite:///default.db')
print(f"Connecting to database: {database_url}")
## Simulate a long-running process
try:
print("Application running... Press Ctrl+C to exit")
counter = 0
while True:
counter += 1
print(f"Application heartbeat: {counter}")
time.sleep(10)
except KeyboardInterrupt:
print("Application shutting down gracefully...")
sys.exit(0)
Сохраните и закройте файл.
Эта улучшенная версия:
- Использует
os.environ.get()со значением по умолчанию вместо вызова исключения - Реализует долго выполняющийся цикл, чтобы поддерживать работу контейнера
- Обрабатывает корректное завершение работы при завершении
Давайте пересоберем образ:
docker build -t exit-test-app:v2 .
И запустим улучшенный контейнер:
docker run -d --name improved-app exit-test-app:v2
Убедитесь, что контейнер работает:
docker ps
Вы должны увидеть, что ваш контейнер работает:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c1d2e3f4g5h6 exit-test-app:v2 "python app.py" 10 seconds ago Up 10 seconds improved-app
Просмотрите журналы, чтобы убедиться, что он работает:
docker logs improved-app
Вы должны увидеть вывод, подобный:
Connecting to database: sqlite:///default.db
Application running... Press Ctrl+C to exit
Application heartbeat: 1
Application heartbeat: 2
2. Реализация проверки работоспособности в Dockerfile
Проверки работоспособности позволяют Docker контролировать работоспособность вашего контейнера. Давайте обновим наш Dockerfile, чтобы включить проверку работоспособности:
nano Dockerfile
Обновите его до:
FROM python:3.9-slim
WORKDIR /app
## Install curl for health checks
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
COPY app.py .
## Add a health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
## Expose port for the health check endpoint
EXPOSE 8080
CMD ["python", "app.py"]
Теперь нам нужно добавить конечную точку проверки работоспособности в наше приложение:
nano app.py
Замените содержимое на:
import os
import time
import sys
import threading
from http.server import HTTPServer, BaseHTTPRequestHandler
## Get environment variables with defaults
database_url = os.environ.get('DATABASE_URL', 'sqlite:///default.db')
## Simple HTTP server for health checks
class HealthRequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == '/health':
self.send_response(200)
self.send_header('Content-type', 'text/plain')
self.end_headers()
self.wfile.write(b'OK')
else:
self.send_response(404)
self.end_headers()
def run_health_server():
server = HTTPServer(('0.0.0.0', 8080), HealthRequestHandler)
print("Starting health check server on port 8080")
server.serve_forever()
## Start health check server in a separate thread
health_thread = threading.Thread(target=run_health_server, daemon=True)
health_thread.start()
print(f"Connecting to database: {database_url}")
## Main application loop
try:
print("Application running... Press Ctrl+C to exit")
counter = 0
while True:
counter += 1
print(f"Application heartbeat: {counter}")
time.sleep(10)
except KeyboardInterrupt:
print("Application shutting down gracefully...")
sys.exit(0)
Сохраните и закройте файл.
Пересоберите образ с нашей проверкой работоспособности:
docker build -t exit-test-app:v3 .
Запустите контейнер с новой версией:
docker run -d --name healthcheck-app -p 8080:8080 exit-test-app:v3
Примерно через 30 секунд проверьте состояние работоспособности:
docker inspect --format='{{.State.Health.Status}}' healthcheck-app
Вы должны увидеть:
healthy
Вы также можете протестировать конечную точку работоспособности напрямую:
curl http://localhost:8080/health
Это должно вернуть OK.
3. Использование политик перезапуска Docker
Docker предоставляет политики перезапуска для автоматического перезапуска контейнеров при их завершении или возникновении ошибок:
docker run -d --restart=on-failure:5 --name restart-app exit-test-app:v3
Эта политика перезапустит контейнер до 5 раз, если он завершит работу с ненулевым кодом.
Доступные политики перезапуска:
no: Никогда не перезапускать (по умолчанию)always: Всегда перезапускать независимо от статуса выходаunless-stopped: Всегда перезапускать, если не остановлено вручнуюon-failure[:max-retries]: Перезапускать только при ненулевом выходе
4. Установка лимитов ресурсов
Чтобы предотвратить сбои контейнера из-за исчерпания ресурсов, установите соответствующие лимиты ресурсов:
docker run -d --name resource-limited-app \
--memory=256m \
--cpus=0.5 \
exit-test-app:v3
Это ограничивает контейнер 256 МБ памяти и половиной ядра процессора.
5. Правильная настройка ведения журналов
Для лучшей отладки настройте свое приложение для вывода структурированных журналов:
docker run -d --name logging-app \
--log-driver=json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
exit-test-app:v3
Это настраивает контейнер на использование драйвера журнала JSON с ротацией журналов (максимум 3 файла по 10 МБ каждый).
Резюме лучших практик
Внедряя эти лучшие практики, вы значительно улучшили надежность своих контейнеров Docker:
- Корректная обработка ошибок со значениями по умолчанию
- Проверки работоспособности контейнера для мониторинга
- Соответствующие политики перезапуска для автоматического восстановления
- Лимиты ресурсов для предотвращения исчерпания ресурсов
- Правильная настройка ведения журналов для упрощения устранения неполадок
Эти методы помогут вам создать устойчивые контейнеры, которые могут восстанавливаться после сбоев и обеспечивать лучшую наблюдаемость при возникновении проблем.
Резюме
В этой лабораторной работе вы изучили основные навыки для устранения неполадок и решения проблем с завершением работы контейнеров Docker:
- Понимание жизненного цикла контейнера Docker и причин завершения работы контейнеров при завершении их основного процесса
- Определение распространенных причин немедленного завершения работы контейнеров с помощью журналов и кодов выхода
- Реализация надежной обработки ошибок в контейнерных приложениях
- Добавление проверок работоспособности контейнеров для мониторинга состояния приложения
- Настройка политик перезапуска для автоматического восстановления после сбоев
- Установка соответствующих лимитов ресурсов для предотвращения сбоев контейнеров
- Реализация правильных стратегий ведения журналов для упрощения отладки
Эти навыки составляют основу надежных развертываний контейнеров Docker. По мере продолжения работы с Docker в реальных сценариях вы обнаружите, что эти методы устранения неполадок неоценимы для поддержания стабильных и устойчивых контейнерных приложений.
Помните, что наиболее распространенными причинами немедленного завершения работы контейнеров являются:
- Основной процесс завершает свою задачу (по замыслу)
- Отсутствующие переменные окружения или конфигурация
- Ошибки или исключения в приложении
- Ограничения ресурсов или проблемы с подключением
Применяя методы диагностики и лучшие практики, рассмотренные в этой лабораторной работе, вы сможете быстро выявлять и решать эти проблемы, обеспечивая надежную работу ваших контейнеров Docker в любой среде.



