如何在 Docker 容器启动时运行 Python 脚本

DockerBeginner
立即练习

介绍

Docker 是一个强大的工具,用于容器化(containerizing)应用程序,这使得开发、部署和管理软件变得更容易。在本教程中,我们将探讨如何在 Docker 容器启动时运行 Python 脚本。这种方法允许你自动化应用程序的部署并简化你的开发工作流程。在本实验(Lab)结束时,你将能够创建一个 Docker 容器,该容器会在启动时自动执行你的 Python 脚本。

创建一个简单的 Python 脚本

在第一步中,我们将创建一个简单的 Python 脚本,该脚本将在我们的 Docker 容器启动时执行。此脚本将打印一条消息以及当前的日期和时间。

让我们首先为我们的项目创建一个新目录:

mkdir -p ~/project/python-docker
cd ~/project/python-docker

现在,让我们使用 nano 文本编辑器创建一个名为 app.py 的简单 Python 脚本:

nano app.py

在 nano 编辑器中,添加以下 Python 代码:

import datetime
import time

print("Hello from the Docker container!")
print(f"Current date and time: {datetime.datetime.now()}")

## Keep the container running to demonstrate it's working
print("Container is running...")
while True:
    print("Container still running... Press Ctrl+C to stop.")
    time.sleep(10)

通过按 Ctrl+O,然后 Enter 来保存文件,并通过按 Ctrl+X 退出 nano。

此脚本将:

  1. 打印欢迎消息
  2. 显示当前的日期和时间
  3. 每 10 秒显示一条消息,以保持容器运行

让我们测试我们的 Python 脚本,以确保它正常工作:

python3 app.py

你应该看到类似于以下的输出:

Hello from the Docker container!
Current date and time: 2023-10-12 14:30:45.123456
Container is running...
Container still running... Press Ctrl+C to stop.

Ctrl+C 停止脚本。

现在我们有了一个可运行的 Python 脚本,我们可以继续创建一个 Docker 容器,该容器将在启动时运行此脚本。

创建一个 Dockerfile

现在我们已经准备好了 Python 脚本,让我们创建一个 Dockerfile 来定义应该如何构建我们的 Docker 容器。

Dockerfile 是一个文本文件,其中包含构建 Docker 镜像所需的所有命令。此镜像将包含我们的 Python 脚本和所有必要的依赖项。

在同一目录 (~/project/python-docker) 中,创建一个名为 Dockerfile 的新文件:

nano Dockerfile

将以下内容添加到 Dockerfile 中:

## Use the official Python image as base
FROM python:3.9-slim

## Set working directory
WORKDIR /app

## Copy the Python script to the container
COPY app.py .

## Command to run when the container starts
CMD ["python", "app.py"]

通过按 Ctrl+O,然后 Enter,再按 Ctrl+X 来保存并退出 nano。

让我们了解 Dockerfile 中的每一行都做了什么:

  1. FROM python:3.9-slim:这指定要使用的基础镜像。我们使用官方的 Python 3.9 镜像,并使用 slim 变体来保持较小的体积。

  2. WORKDIR /app:这会将容器内的工作目录设置为 /app

  3. COPY app.py .:这会将我们的 Python 脚本从宿主机复制到容器中的当前工作目录(即 /app)。

  4. CMD ["python", "app.py"]:这指定了容器启动时要运行的命令。在这种情况下,它将运行我们的 Python 脚本。

现在,让我们使用此 Dockerfile 构建我们的 Docker 镜像:

docker build -t python-app .

此命令从当前目录 (.) 中的 Dockerfile 构建一个 Docker 镜像,并使用名称 python-app 进行标记(-t 代表 tag)。

你应该看到类似于以下的输出:

Sending build context to Docker daemon  3.072kB
Step 1/4 : FROM python:3.9-slim
 ---> 1bc6a8a2a52f
Step 2/4 : WORKDIR /app
 ---> Using cache
 ---> 2a3b7a28c9e5
Step 3/4 : COPY app.py .
 ---> Using cache
 ---> 9a0b7a1c8c2d
Step 4/4 : CMD ["python", "app.py"]
 ---> Using cache
 ---> 3b0c7a2d9f1e
Successfully built 3b0c7a2d9f1e
Successfully tagged python-app:latest

输出可能有所不同,但如果一切顺利,你应该看到 "Successfully built" 和 "Successfully tagged" 消息。

你现在已经成功创建了一个 Docker 镜像,其中包含你的 Python 脚本,并且当从该镜像启动容器时,它将运行该脚本。

运行 Docker 容器

现在我们已经构建了 Docker 镜像,我们可以基于此镜像运行一个容器。容器将在启动时自动执行我们的 Python 脚本。

要从我们的镜像运行容器,请使用以下命令:

docker run --name python-container python-app

此命令从我们在上一步中创建的 python-app 镜像启动一个名为 python-container 的新容器。

你应该看到类似于以下的输出:

Hello from the Docker container!
Current date and time: 2023-10-12 15:45:30.123456
Container is running...
Container still running... Press Ctrl+C to stop.
Container still running... Press Ctrl+C to stop.

容器现在正在运行并执行我们的 Python 脚本。该脚本旨在无限期运行,每 10 秒打印一条消息。

Ctrl+C 停止查看日志,但请注意,容器仍在后台运行。

要验证容器是否正在运行,请使用以下命令:

docker ps

你应该在正在运行的容器列表中看到你的容器:

CONTAINER ID   IMAGE        COMMAND           CREATED         STATUS         PORTS     NAMES
1a2b3c4d5e6f   python-app   "python app.py"   1 minute ago    Up 1 minute              python-container

要停止容器,请使用以下命令:

docker stop python-container

要再次启动容器,请使用:

docker start python-container

要查看正在运行的容器的日志:

docker logs python-container

这将显示我们 Python 脚本的输出。你可以按 Ctrl+C 退出日志视图。

当你完成时,要删除容器,首先停止它(如果它正在运行),然后删除它:

docker stop python-container
docker rm python-container

你现在已经成功创建并运行了一个 Docker 容器,该容器在启动时执行一个 Python 脚本。

在 Docker 中使用环境变量与 Python

环境变量是配置你的应用程序而无需更改代码的好方法。在这一步中,我们将修改我们的 Python 脚本以使用环境变量,并更新我们的 Dockerfile 以提供这些变量。

首先,让我们更新我们的 Python 脚本以读取一个环境变量:

nano app.py

修改脚本以包含环境变量支持:

import datetime
import time
import os

## Get the environment variable with a default value if not set
user_name = os.environ.get('USER_NAME', 'Guest')

print(f"Hello, {user_name}, from the Docker container!")
print(f"Current date and time: {datetime.datetime.now()}")

## Keep the container running to demonstrate it's working
print("Container is running...")
while True:
    print("Container still running... Press Ctrl+C to stop.")
    time.sleep(10)

通过按 Ctrl+O,然后 Enter,再按 Ctrl+X 来保存并退出 nano。

现在,让我们更新我们的 Dockerfile 以包含一个环境变量:

nano Dockerfile

更新 Dockerfile 以包含环境变量:

## Use the official Python image as base
FROM python:3.9-slim

## Set working directory
WORKDIR /app

## Set environment variable
ENV USER_NAME="Docker User"

## Copy the Python script to the container
COPY app.py .

## Command to run when the container starts
CMD ["python", "app.py"]

保存并退出 nano,然后重建 Docker 镜像:

docker build -t python-app-env .

现在,使用默认环境变量运行一个容器:

docker run --name python-env-container python-app-env

你应该在输出中看到带有 "Docker User" 的问候语:

Hello, Docker User, from the Docker container!
Current date and time: 2023-10-12 16:30:45.123456
Container is running...

Ctrl+C 停止查看日志。

停止并删除容器:

docker stop python-env-container
docker rm python-env-container

你也可以在运行容器时覆盖环境变量:

docker run --name python-env-container -e USER_NAME="LabEx Student" python-app-env

这次,你应该在问候语中看到你的自定义名称:

Hello, LabEx Student, from the Docker container!
Current date and time: 2023-10-12 16:35:15.789012
Container is running...

Ctrl+C 停止查看日志。

停止并删除容器:

docker stop python-env-container
docker rm python-env-container

在 Docker 中使用环境变量是一种常见的做法,可以使你的容器更具可配置性,而无需为小的更改而重建镜像。

在 Dockerfile 中使用 ENTRYPOINT 与 CMD

在 Docker 中,有两个指令用于指定容器启动时应该运行的命令:CMDENTRYPOINT。它们具有不同的用途和行为,理解它们之间的区别对于容器管理至关重要。

理解 ENTRYPOINT 和 CMD

  • ENTRYPOINT:定义容器启动时将运行的可执行文件。它在运行时更难被覆盖。
  • CMD:为 ENTRYPOINT 提供默认参数,或者在未使用的 ENTRYPOINT 时指定整个命令。它在运行时很容易被覆盖。

让我们通过创建两个不同的 Dockerfile 来探索区别。

首先,创建一个新的 Python 脚本,该脚本接受命令行参数:

nano greeting.py

添加以下代码:

import sys

print("Script started!")

if len(sys.argv) > 1:
    print(f"Arguments provided: {sys.argv[1:]}")
    for arg in sys.argv[1:]:
        print(f"- {arg}")
else:
    print("No arguments provided.")

print("Script finished!")

保存并退出 nano。

现在,让我们创建一个使用 CMD 的 Dockerfile:

nano Dockerfile.cmd

添加以下内容:

FROM python:3.9-slim

WORKDIR /app

COPY greeting.py .

CMD ["python", "greeting.py", "default", "arguments"]

保存并退出 nano。

构建镜像:

docker build -t python-cmd -f Dockerfile.cmd .

现在,创建另一个使用 ENTRYPOINT 的 Dockerfile:

nano Dockerfile.entrypoint

添加以下内容:

FROM python:3.9-slim

WORKDIR /app

COPY greeting.py .

ENTRYPOINT ["python", "greeting.py"]
CMD ["default", "arguments"]

保存并退出 nano。

构建镜像:

docker build -t python-entrypoint -f Dockerfile.entrypoint .

测试 CMD 与 ENTRYPOINT

让我们从这两个镜像运行容器并观察差异。

首先,在没有额外参数的情况下,使用 CMD 镜像运行一个容器:

docker run --name cmd-container python-cmd

输出应该类似于:

Script started!
Arguments provided: ['default', 'arguments']
- default
- arguments
Script finished!

现在,使用 CMD 镜像运行一个容器,但提供自定义参数:

docker run --name cmd-container-custom python-cmd hello world

输出:

Script started!
No arguments provided.
Script finished!

请注意,整个命令被替换为 hello world,它们没有作为参数传递给我们的脚本。

现在,让我们在没有额外参数的情况下,使用 ENTRYPOINT 镜像运行一个容器:

docker run --name entrypoint-container python-entrypoint

输出:

Script started!
Arguments provided: ['default', 'arguments']
- default
- arguments
Script finished!

最后,使用 ENTRYPOINT 镜像运行一个容器并提供自定义参数:

docker run --name entrypoint-container-custom python-entrypoint hello world

输出:

Script started!
Arguments provided: ['hello', 'world']
- hello
- world
Script finished!

这次,我们的参数被正确地传递给 Python 脚本,因为 ENTRYPOINT 定义了可执行文件,并且提供给 docker run 的任何额外参数都会传递给该可执行文件。

最佳实践

  • 对于应该始终运行特定命令的容器(例如我们的 Python 脚本),使用 ENTRYPOINT
  • 使用 CMD 提供可以轻松覆盖的默认参数
  • 通过使用 ENTRYPOINT 作为命令和 CMD 作为默认参数来组合两者

清理容器:

docker rm cmd-container cmd-container-custom entrypoint-container entrypoint-container-custom

你现在已经了解了 Docker 中 CMDENTRYPOINT 之间的区别,这对于控制你的 Python 脚本在容器启动时的运行方式至关重要。

总结

在这个实验中,你已经学习了如何在 Docker 容器启动时运行 Python 脚本。以下是你已经完成的内容:

  1. 创建了一个在 Docker 容器中运行的简单 Python 脚本
  2. 使用 Dockerfile 构建了一个 Docker 镜像
  3. 运行了一个自动执行 Python 脚本的 Docker 容器
  4. 使用环境变量在 Docker 中配置你的 Python 应用程序
  5. 理解了 Dockerfile 中 CMD 和 ENTRYPOINT 之间的区别

这些技能是容器化 Python 应用程序并自动化其部署的基础。你现在可以:

  • 使用所有依赖项打包你的 Python 应用程序
  • 使用环境变量配置你的应用程序
  • 控制你的应用程序在 Docker 容器内部的启动方式
  • 为你的特定用例选择合适的启动机制(CMD 或 ENTRYPOINT)

有了这些知识,你可以构建更复杂的 Docker 应用程序,这些应用程序在启动时自动运行 Python 脚本,从而使你的部署过程更高效、更可靠。