简介
在本实验中,你将学习如何高效使用 docker compose run
命令在 Docker Compose 服务中执行一次性任务。这是一种强大的技术,可用于运行管理命令、调试或执行特定操作,而无需启动完整的服务堆栈。
我们将探讨多种场景,包括覆盖默认服务命令、启用服务端口进行交互、手动映射端口、在不启动关联服务的情况下运行命令,以及在执行后自动移除容器。完成本实验后,你将熟练掌握使用 docker compose run
处理各种一次性任务的技巧。
在本实验中,你将学习如何高效使用 docker compose run
命令在 Docker Compose 服务中执行一次性任务。这是一种强大的技术,可用于运行管理命令、调试或执行特定操作,而无需启动完整的服务堆栈。
我们将探讨多种场景,包括覆盖默认服务命令、启用服务端口进行交互、手动映射端口、在不启动关联服务的情况下运行命令,以及在执行后自动移除容器。完成本实验后,你将熟练掌握使用 docker compose run
处理各种一次性任务的技巧。
在这一步中,我们将学习如何在 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
这样的 shell。通过在镜像名称后提供 ls -l /
,我们告诉 Docker 运行这个特定命令而不是默认命令。
让我们尝试另一个命令,例如 pwd
来打印容器内的当前工作目录。
docker run ubuntu pwd
输出应该是 /
,表示根目录是默认工作目录。
/
这演示了如何轻松地在容器内执行任意命令,而无需交互式进入容器或修改镜像的默认命令。
在这一步中,我们将探讨如何在 Docker 容器中运行命令的同时,保持容器内服务配置的端口处于可用状态。当你需要运行临时命令进行调试或管理,但仍希望主服务能从容器外部访问时,这个功能非常有用。
我们将使用一个简单的 Web 服务器镜像进行演示。让我们拉取流行的 Web 服务器镜像 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 端口。要使这个端口能从宿主机访问,我们需要使用 docker run
命令的 -p
参数将主机端口映射到容器端口。让我们将主机的 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
提供命令会覆盖默认的入口点/命令,因此即使指定了端口映射,默认配置运行的服务也不会启动。
在这一步中,我们将继续探索 docker run
的端口映射功能,重点介绍如何显式指定主机和容器端口。虽然上一步展示了覆盖默认命令会阻止服务运行,但理解手动端口映射对于你确实需要访问服务的情况至关重要。
我们将继续使用 nginx
镜像。提醒一下,nginx
镜像在容器内部暴露了 80 端口。为了从宿主机访问这个端口,我们使用 -p
参数,后跟 host_port:container_port
。
让我们运行 nginx
容器,并将主机的 8081 端口映射到容器的 80 端口。这次我们不会提供覆盖命令,因此默认的 Nginx 服务将会启动。我们还将以分离模式 (-d
) 运行它,使其在后台运行。
docker run -d -p 8081:80 nginx
你会看到一长串字符,这是容器 ID,表示容器已在分离模式下启动。
<container_id>
现在容器正在运行且端口已映射,你可以使用 curl
或网页浏览器从宿主机访问 Nginx 欢迎页面。
curl http://localhost:8081
你应该能看到默认 Nginx 欢迎页面的 HTML 内容。这确认了 Nginx 服务器正在容器内运行,并且可以通过主机的 8081 端口访问。
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</body>
</html>
这演示了手动端口映射如何让你控制使用哪个主机端口来访问容器内特定端口上运行的服务。这对于避免宿主机上的端口冲突以及使容器化服务可访问至关重要。
要停止正在运行的容器,你可以使用 docker stop
命令后跟容器 ID 或名称。你可以通过运行 docker ps
来查找容器 ID。
docker ps
这将显示正在运行的容器列表。找到你刚刚启动的 nginx
容器的容器 ID。
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 容器中运行命令,而不启动其他关联服务。这对于运行数据库迁移、设置脚本或调试命令特别有用,无需启动整个应用栈。
虽然 Docker Compose 是管理多容器应用的标准工具,并具备在特定服务上运行一次性命令的功能,但我们将在此演示底层的 Docker 概念。由于本环境未预装 Docker Compose,我们将重点使用 docker run
命令配合网络功能。
让我们模拟一个包含两个容器的简单场景:一个 web 应用和一个数据库。我们将使用通用的 ubuntu
镜像代表 web 应用,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
你将看到网络 ID 被打印出来:
<network_id>
接着运行 postgres
容器并连接到我们的网络,同时为 PostgreSQL 用户设置密码:
docker run -d --network my-app-network --name my-database -e POSTGRES_PASSWORD=mypassword postgres
容器 ID 会被打印出来,表示数据库容器正在后台运行:
<container_id>
假设我们的"web 应用"容器需要运行一个与数据库交互的命令,比如数据库迁移脚本。通常如果使用 Docker Compose,我们可以在 web 服务上运行命令,Docker Compose 会处理网络设置和连接。
仅使用 docker run
时,如果 web 应用容器尝试连接 my-database
,它通常需要位于同一网络中。
让我们在连接到同一网络的 ubuntu
容器中运行命令,模拟可能与数据库交互的命令。我们将尝试通过名称 (my-database
) ping 数据库容器:
docker run --network my-app-network ubuntu ping -c 4 my-database
这个命令将:
ubuntu
镜像创建新容器my-app-network
ping -c 4 my-database
命令由于 ubuntu
容器与 my-database
容器在同一网络,它能将名称 my-database
解析为数据库容器的 IP 地址并进行 ping 操作。
你将看到显示 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
镜像进行演示。我们将运行一个简单的命令,比如打印一条消息然后退出。要实现在命令完成后自动移除容器,我们需要在 docker run
命令中使用 --rm
参数。
让我们使用 --rm
参数运行容器,并执行 echo "这个容器将被自动移除"
命令:
docker run --rm ubuntu echo "这个容器将被自动移除"
这个命令会:
ubuntu
镜像创建新容器echo "这个容器将被自动移除"
命令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
运行休眠命令的容器不应该出现在容器列表中。
对于设计为执行特定任务后立即退出的容器,使用 --rm
参数是一个好习惯,它有助于管理磁盘空间并保持容器列表的整洁。
在本实验中,我们学习了如何使用 docker compose run
命令在 Docker Compose 服务中执行一次性任务。我们首先了解了如何覆盖服务配置中定义的默认命令,这让我们能够运行任意命令以进行管理或调试。
接着,我们探索了如何为这些一次性任务管理网络连接,特别是通过启用服务端口和手动映射端口的方式。最后,我们学习了如何在不启动关联服务的情况下运行命令,以及如何在命令完成后自动移除容器,从而确保环境的整洁。