Устранение неполадок с немедленным завершением работы контейнеров Docker

DockerBeginner
Практиковаться сейчас

Введение

В этой практической лабораторной работе вы узнаете, как выявлять и устранять распространенную проблему 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 контейнера состоит из нескольких состояний:

  1. Created (Создан): Контейнер создан, но еще не запущен
  2. Running (Запущен): Контейнер выполняет определенные процессы
  3. Paused (Приостановлен): Процессы контейнера временно приостановлены
  4. Stopped (Остановлен): Контейнер завершил работу или был остановлен
  5. 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 завершил работу немедленно, потому что:

  1. Команда по умолчанию для образа Ubuntu — /bin/bash
  2. При запуске без флагов -it (интерактивный, терминал) нет ввода в оболочку bash
  3. Без ввода и без конкретной команды для выполнения bash немедленно завершает работу
  4. Когда основной процесс (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 продолжал работать, нам нужно либо:

  1. Предоставить интерактивный сеанс, или
  2. Переопределить команду по умолчанию долго выполняющимся процессом

Давайте попробуем интерактивный подход:

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

При диагностике контейнеров, которые немедленно завершают работу, помните эти ключевые моменты:

  1. Контейнеры завершают работу, когда завершается их основной процесс
  2. Проверьте журналы и коды выхода, чтобы понять, почему они остановились
  3. Если контейнер должен продолжать работать, убедитесь, что его основной процесс не завершается

Устранение распространенных проблем с завершением работы контейнера

Теперь, когда мы понимаем, почему контейнеры немедленно завершают работу, давайте рассмотрим распространенные причины неожиданного завершения работы контейнеров и способы их устранения.

Создание контейнера с проблемой

Давайте создадим простую ситуацию, когда контейнер неожиданно завершает работу. Сначала создайте каталог для наших тестовых файлов:

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 ожидал переменную окружения, которая не была предоставлена.

Диагностика проблемы

Когда контейнер неожиданно завершает работу, выполните следующие шаги по устранению неполадок:

  1. Проверьте код выхода, чтобы определить, является ли это ошибкой:
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
  1. Изучите журналы контейнера:
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...

Контейнер все еще завершает работу, но на этот раз это происходит потому, что наш скрипт достигает конца и завершается нормально. Если бы мы хотели, чтобы контейнер продолжал работать, нам нужно было бы изменить наш скрипт, чтобы включить бесконечный цикл или долго выполняющийся процесс.

Распространенные причины завершения работы контейнера

Вот несколько распространенных причин, по которым контейнеры могут неожиданно завершать работу:

  1. Отсутствующие переменные окружения: Как мы только что продемонстрировали
  2. Отсутствующие зависимости: Отсутствующие библиотеки или системные пакеты
  3. Сбои соединения: Невозможность подключиться к базе данных или другой службе
  4. Проблемы с разрешениями: Недостаточные разрешения для доступа к файлам или ресурсам
  5. Ограничения ресурсов: Контейнер исчерпывает память или процессорное время
  6. Сбои приложений: Ошибки в коде приложения

Методы устранения неполадок

Для каждой из этих проблем вот эффективные подходы к устранению неполадок:

  1. Просмотр журналов: Всегда сначала проверяйте журналы контейнера с помощью docker logs
  2. Переопределение точки входа (entrypoint): Используйте docker run --entrypoint /bin/sh -it my-image, чтобы войти в контейнер и провести расследование
  3. Добавление операторов отладки: Измените свое приложение, чтобы добавить больше ведения журнала
  4. Проверка использования ресурсов: Используйте docker stats для мониторинга использования ресурсов контейнера
  5. Проверка окружения: Запустите 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:

  1. Корректная обработка ошибок со значениями по умолчанию
  2. Проверки работоспособности контейнера для мониторинга
  3. Соответствующие политики перезапуска для автоматического восстановления
  4. Лимиты ресурсов для предотвращения исчерпания ресурсов
  5. Правильная настройка ведения журналов для упрощения устранения неполадок

Эти методы помогут вам создать устойчивые контейнеры, которые могут восстанавливаться после сбоев и обеспечивать лучшую наблюдаемость при возникновении проблем.

Резюме

В этой лабораторной работе вы изучили основные навыки для устранения неполадок и решения проблем с завершением работы контейнеров Docker:

  • Понимание жизненного цикла контейнера Docker и причин завершения работы контейнеров при завершении их основного процесса
  • Определение распространенных причин немедленного завершения работы контейнеров с помощью журналов и кодов выхода
  • Реализация надежной обработки ошибок в контейнерных приложениях
  • Добавление проверок работоспособности контейнеров для мониторинга состояния приложения
  • Настройка политик перезапуска для автоматического восстановления после сбоев
  • Установка соответствующих лимитов ресурсов для предотвращения сбоев контейнеров
  • Реализация правильных стратегий ведения журналов для упрощения отладки

Эти навыки составляют основу надежных развертываний контейнеров Docker. По мере продолжения работы с Docker в реальных сценариях вы обнаружите, что эти методы устранения неполадок неоценимы для поддержания стабильных и устойчивых контейнерных приложений.

Помните, что наиболее распространенными причинами немедленного завершения работы контейнеров являются:

  1. Основной процесс завершает свою задачу (по замыслу)
  2. Отсутствующие переменные окружения или конфигурация
  3. Ошибки или исключения в приложении
  4. Ограничения ресурсов или проблемы с подключением

Применяя методы диагностики и лучшие практики, рассмотренные в этой лабораторной работе, вы сможете быстро выявлять и решать эти проблемы, обеспечивая надежную работу ваших контейнеров Docker в любой среде.