Как использовать команду docker compose run для выполнения одноразовых задач

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

💡 Этот учебник переведен с английского с помощью ИИ. Чтобы просмотреть оригинал, вы можете перейти на английский оригинал

Введение

В этой лабораторной работе вы научитесь эффективно использовать команду docker compose run для выполнения разовых задач в ваших сервисах Docker Compose. Это мощный метод для запуска административных команд, отладки или выполнения специфических операций без запуска всего стека сервисов.

Мы рассмотрим различные сценарии, включая переопределение команды сервиса по умолчанию, открытие портов сервиса для взаимодействия, ручное сопоставление портов, выполнение команд без запуска связанных сервисов и автоматическое удаление контейнера после выполнения. К концу этой лабораторной работы вы будете уверенно использовать docker compose run для разнообразных разовых задач.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL docker(("Docker")) -.-> docker/ContainerOperationsGroup(["Container Operations"]) docker(("Docker")) -.-> docker/ImageOperationsGroup(["Image Operations"]) docker(("Docker")) -.-> docker/NetworkOperationsGroup(["Network Operations"]) docker/ContainerOperationsGroup -.-> docker/run("Run a Container") docker/ContainerOperationsGroup -.-> docker/ps("List Running Containers") docker/ContainerOperationsGroup -.-> docker/stop("Stop Container") docker/ContainerOperationsGroup -.-> docker/rm("Remove Container") docker/ImageOperationsGroup -.-> docker/pull("Pull Image from Repository") docker/NetworkOperationsGroup -.-> docker/network("Manage Networks") subgraph Lab Skills docker/run -.-> lab-555091{{"Как использовать команду docker compose run для выполнения одноразовых задач"}} docker/ps -.-> lab-555091{{"Как использовать команду docker compose run для выполнения одноразовых задач"}} docker/stop -.-> lab-555091{{"Как использовать команду docker compose run для выполнения одноразовых задач"}} docker/rm -.-> lab-555091{{"Как использовать команду docker compose run для выполнения одноразовых задач"}} docker/pull -.-> lab-555091{{"Как использовать команду docker compose run для выполнения одноразовых задач"}} docker/network -.-> lab-555091{{"Как использовать команду docker compose run для выполнения одноразовых задач"}} end

Запуск разовой команды с переопределением команды сервиса

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

Сначала загрузим простой Docker-образ, который будем использовать для демонстрации. Мы воспользуемся образом ubuntu.

docker pull ubuntu:latest

Вы увидите вывод, указывающий на загрузку образа:

Using default tag: latest
latest: Pulling from library/ubuntu
...
Status: Downloaded newer image for ubuntu:latest
docker.io/library/ubuntu:latest

Теперь запустим разовую команду в контейнере на основе образа ubuntu. Используем команду docker run с именем образа и командой, которую хотим выполнить. Например, выполним команду ls -l /, чтобы вывести содержимое корневой директории в контейнере.

docker run ubuntu ls -l /

Эта команда создаст новый контейнер из образа ubuntu, выполнит внутри него команду ls -l / и завершит работу. Вы увидите вывод, похожий на этот, отображающий содержимое корневой директории:

total 68
drwxr-xr-x   2 root root  4096 Oct 26 00:00 bin
drwxr-xr-x   2 root root  4096 Oct 26 00:00 boot
drwxr-xr-x   5 root root   360 Nov  1 00:00 dev
drwxr-xr-x  19 root root  4096 Nov  1 00:00 etc
drwxr-xr-x   2 root root  4096 Oct 26 00:00 home
drwxr-xr-x   7 root root  4096 Oct 26 00:00 lib
drwxr-xr-x   2 root root  4096 Oct 26 00:00 lib64
drwxr-xr-x   2 root root  4096 Oct 26 00:00 media
drwxr-xr-x   2 root root  4096 Oct 26 00:00 mnt
drwxr-xr-x   2 root root  4096 Oct 26 00:00 opt
drwxr-xr-x   2 root root  4096 Oct 04 14:00 proc
drwx------   2 root root  4096 Oct 26 00:00 root
drwxr-xr-x   2 root root  4096 Oct 26 00:00 run
drwxr-xr-x   2 root root  4096 Oct 26 00:00 sbin
drwxr-xr-x   2 root root  4096 Oct 26 00:00 srv
drwxr-xr-x   2 root root  4096 Oct 26 00:00 sys
drwxrwxrwt   2 root root  4096 Oct 26 00:00 tmp
drwxr-xr-x  11 root root  4096 Oct 26 00:00 usr
drwxr-xr-x  12 root root  4096 Oct 26 00:00 var

В данном случае команда по умолчанию для образа ubuntu — это обычно оболочка типа /bin/bash. Указав ls -l / после имени образа, мы говорим Docker выполнить именно эту команду вместо стандартной.

Попробуем другую команду, например, pwd, чтобы вывести текущую рабочую директорию внутри контейнера.

docker run ubuntu pwd

Вывод должен быть /, что указывает на корневую директорию как рабочую по умолчанию.

/

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

Запуск команды с включенными портами сервиса

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

Для демонстрации мы будем использовать простой образ веб-сервера. Загрузим образ nginx — популярного веб-сервера.

docker pull nginx:latest

Вы увидите вывод, указывающий на загрузку образа:

Using default tag: latest
latest: Pulling from library/nginx
...
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest

Образ nginx по умолчанию настроен на прослушивание порта 80 внутри контейнера. Чтобы сделать этот порт доступным с хоста, нам нужно сопоставить порт хоста с портом контейнера, используя флаг -p в команде docker run. Сопоставим порт 8080 на хосте с портом 80 в контейнере.

Теперь вместо запуска стандартного сервиса Nginx выполним простую команду echo "Hello from the container", сохранив при этом сопоставление портов.

docker run -p 8080:80 nginx echo "Hello from the container"

Можно ожидать, что это запустит сервер Nginx и затем выведет "Hello from the container". Однако, когда вы указываете команду после имени образа в docker run, эта команда заменяет команду по умолчанию. В данном случае контейнер выполнит команду echo и завершит работу. Сервер Nginx не будет запущен, несмотря на указанное сопоставление портов.

Вывод будет следующим:

Hello from the container

Если попытаться обратиться к http://localhost:8080 через браузер или с помощью curl, соединение будет отклонено, так как сервер Nginx не запущен.

curl http://localhost:8080

Вывод, скорее всего, будет таким:

curl: (7) Failed to connect to localhost port 8080 after ... connection refused

Это демонстрирует важный момент: при переопределении команды по умолчанию с помощью docker run <image> <command> контейнер выполнит только указанную команду и не запустит сервис, для работы которого предназначен образ. Поэтому сопоставление портов, хотя и настроено, не будет активно, так как сервис, прослушивающий этот порт, не запущен.

Для выполнения команды при работающем сервисе с активными портами обычно требуется запустить сервис в фоновом режиме и затем выполнить команду. Однако команда docker run предназначена для выполнения одной команды с последующим завершением работы. Чтобы выполнить команду параллельно с работающим сервисом, обычно используют docker exec для уже запущенного контейнера. Мы рассмотрим docker exec в следующих шагах.

Главный вывод этого шага: указание команды в docker run переопределяет entrypoint/команду по умолчанию, поэтому сервис, который должен запускаться автоматически, не будет запущен, даже если указано сопоставление портов.

Запуск команды с ручным сопоставлением портов

На этом шаге мы продолжим изучать сопоставление портов с помощью docker run, уделяя внимание явному указанию портов хоста и контейнера. Хотя предыдущий шаг показал, что переопределение команды по умолчанию предотвращает запуск сервиса, понимание ручного сопоставления портов критически важно, когда вы хотите сделать сервис доступным.

Мы продолжим использовать образ nginx. Напомним, что образ nginx предоставляет порт 80 внутри контейнера. Чтобы сделать его доступным с хоста, используем флаг -p с форматом порт_хоста:порт_контейнера.

Запустим контейнер nginx и сопоставим порт 8081 хоста с портом 80 контейнера. В этот раз мы не будем указывать переопределяющую команду, поэтому стандартный сервис Nginx запустится. Также запустим его в detached-режиме (-d), чтобы он работал в фоне.

docker run -d -p 8081:80 nginx

Вы увидите длинную строку символов — это ID контейнера, указывающий, что контейнер запущен в фоновом режиме.

<container_id>

Теперь, когда контейнер запущен и порты сопоставлены, вы можете получить доступ к приветственной странице Nginx с хоста через curl или браузер.

curl http://localhost:8081

Вы должны увидеть HTML-содержимое стандартной приветственной страницы Nginx. Это подтверждает, что сервер Nginx работает внутри контейнера и доступен с вашего хоста через порт 8081.

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</body>
</html>

Это демонстрирует, как ручное сопоставление портов позволяет контролировать, какой порт хоста используется для доступа к сервису, работающему на определенном порту внутри контейнера. Это важно для избежания конфликтов портов на хосте и обеспечения доступности сервисов в контейнерах.

Чтобы остановить работающий контейнер, используйте команду docker stop с ID или именем контейнера. ID контейнера можно узнать, выполнив docker ps.

docker ps

Эта команда покажет список работающих контейнеров. Найдите ID контейнера nginx, который вы только что запустили.

CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS                  NAMES
<container_id>   nginx     "nginx -g 'daemon off"   About a minute ago   Up About a minute   0.0.0.0:8081->80/tcp   <container_name>

Теперь остановите контейнер, используя его ID. Замените <container_id> на фактический ID из вашего вывода.

docker stop <container_id>

ID контейнера будет выведен снова, подтверждая его остановку.

<container_id>

Если после остановки контейнера попытаться снова обратиться к http://localhost:8081, соединение будет отклонено.

Запуск команды без запуска связанных сервисов

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

Хотя Docker Compose является стандартным инструментом для управления многоконтейнерными приложениями и имеет функции для выполнения одноразовых команд в конкретных сервисах, здесь мы продемонстрируем базовые концепции Docker. Поскольку Docker Compose не предустановлен в этой среде, мы сосредоточимся на использовании команды docker run с сетевыми настройками.

Смоделируем простой сценарий с двумя контейнерами: веб-приложением и базой данных. Будем использовать образ ubuntu для представления нашего веб-приложения и образ postgres для базы данных.

Сначала загрузим образ postgres:

docker pull postgres:latest

Вы увидите вывод, указывающий на загрузку образа:

Using default tag: latest
latest: Pulling from library/postgres
...
Status: Downloaded newer image for postgres:latest
docker.io/library/postgres:latest

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

docker network create my-app-network

Вы увидите идентификатор созданной сети:

<network_id>

Запустим контейнер postgres и подключим его к нашей сети. Также установим пароль для пользователя PostgreSQL.

docker run -d --network my-app-network --name my-database -e POSTGRES_PASSWORD=mypassword postgres

Вы увидите идентификатор контейнера, указывающий, что контейнер с базой данных запущен в фоне:

<container_id>

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

Используя только docker run, если мы запустим контейнер веб-приложения, которому нужно подключиться к my-database, он должен находиться в той же сети.

Выполним команду в контейнере ubuntu, подключенном к той же сети, имитируя команду, которая может взаимодействовать с базой данных. Просто попробуем пропинговать контейнер базы данных по его имени (my-database).

docker run --network my-app-network ubuntu ping -c 4 my-database

Эта команда:

  1. Создаст новый контейнер из образа ubuntu
  2. Подключит его к сети my-app-network
  3. Выполнит команду ping -c 4 my-database внутри контейнера

Поскольку контейнер ubuntu находится в той же сети, что и my-database, он может разрешить имя my-database в IP-адрес контейнера с базой данных и пропинговать его.

Вы увидите вывод с результатами ping-запросов:

PING my-database (172.18.0.2) 56(84) bytes of data.
64 bytes from my-database.my-app-network (172.18.0.2): icmp_seq=1 ttl=64 time=0.050 ms
64 bytes from my-database.my-app-network (172.18.0.2): icmp_seq=2 ttl=64 time=0.054 ms
64 bytes from my-database.my-app-network (172.18.0.2): icmp_seq=3 ttl=64 time=0.054 ms
64 bytes from my-database.my-app-network (172.18.0.2): icmp_seq=4 ttl=64 time=0.054 ms

--- my-database ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3060ms
rtt min/avg/max/mdev = 0.050/0.053/0.054/0.001 ms

Это демонстрирует, что можно выполнить одноразовую команду в контейнере и обеспечить её взаимодействие с другими контейнерами в той же сети, без необходимости запускать стандартный сервис контейнера (в данном случае контейнер ubuntu не имеет типичного "сервиса"). Ключевой момент — подключение контейнера с выполняемой командой к той же сети, где находятся сервисы, с которыми нужно взаимодействовать.

Наконец, очистим запущенный контейнер базы данных и сеть.

docker stop my-database
my-database
docker rm my-database
my-database
docker network rm my-app-network
my-app-network

Запуск команды с автоматическим удалением контейнера

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

Для демонстрации мы снова будем использовать образ ubuntu. Мы выполним простую команду, например, вывод сообщения с последующим завершением работы. Для автоматического удаления контейнера после выполнения команды используем флаг --rm с командой docker run.

Запустим контейнер с флагом --rm и выполним команду echo "Этот контейнер будет автоматически удален":

docker run --rm ubuntu echo "Этот контейнер будет автоматически удален"

Эта команда:

  1. Создаст новый контейнер из образа ubuntu
  2. Выполнит команду echo "Этот контейнер будет автоматически удален" внутри контейнера
  3. После завершения команды echo контейнер будет автоматически удален

Вы увидите вывод команды echo:

Этот контейнер будет автоматически удален

После завершения команды контейнер останавливается и удаляется. Чтобы убедиться в удалении контейнера, можно использовать команду docker ps -a, которая выводит список всех контейнеров, включая остановленные.

docker ps -a

Вы не должны видеть только что запущенный контейнер в списке. Если бы команда выполнялась без флага --rm, контейнер остался бы в выводе со статусом "Exited".

Рассмотрим другой пример. Запустим команду с паузой в несколько секунд с помощью sleep и завершением работы, также с флагом --rm.

docker run --rm ubuntu sh -c "echo 'Начало паузы...'; sleep 5; echo 'Пауза завершена.'"

Эта команда использует sh -c для выполнения простого скрипта, который выводит сообщения до и после 5-секундной паузы.

Первое сообщение появится сразу:

Начало паузы...

Через примерно 5 секунд вы увидите второе сообщение:

Пауза завершена.

После завершения скрипта контейнер будет автоматически удален. Это можно снова проверить командой docker ps -a.

docker ps -a

Контейнер, выполнявший команду sleep, не должен присутствовать в списке.

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

Итоги

В этой лабораторной работе мы изучили, как использовать команду docker compose run для выполнения одноразовых задач в сервисе Docker Compose. Мы начали с понимания того, как переопределить команду по умолчанию, определенную в конфигурации сервиса, что позволяет запускать произвольные команды для административных целей или отладки.

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