注意:本文源于我们与 Prefect 社区成员一起部署他们的 Prefect 工作流程的经验,但不需要 Prefect 知识来完成它。
大纲
- 动机
- 制作一个 Python 包
- 编写 Dockerfile
- 构建和使用图像
动机
数据科学家和分析师经常回避将他们的 Python 工作打包到 Docker 容器中进行生产。从我们支持Prefect 社区的经验来看,很多数据专业人士的错误印象是 Python 打包和构建 Docker 容器对他们来说太难了,无法尝试。通常情况下,它归结为迈出了第一步,他们不知道从哪里开始。在本演练中,我们将提供一个端到端的最小示例来说明如何执行此操作。
众所周知,使用容器提供了一个独立的环境来一致地运行工作流,但使用容器还有其他实际好处。
- 可移植性——AWS ECS 等服务可以使用 Docker 容器并运行批处理作业。这消除了始终打开计算资源的需要,因为工作负载可以由更小、更轻的机器部署。Kubernetes 等工具也使用容器作为编排单元。
- 可扩展性——分布式计算框架(如 Dask)要求集群在调度程序和工作线程之间安装相同的模块。Docker 镜像通常在集群启动期间提供,以便任务可以跨机器以相同的行为运行。
- 可靠性——Dask worker 或 Kubernetes pod 死掉是很常见的。拥有 Docker 镜像允许这些进程以相同的配置再次启动。这些服务也可以为用户自动完成。
制作 Python 包
我们将从制作一个 Python 包开始。但在此之前,我们将展示最终的目录结构如下所示。这些文件和目录将一一说明。我们将从组件和工作流文件夹开始,然后进入requirements.txt、setup.py和Dockerfile。
mypackage/
├── components/
│ ├── __init__.py
│ ├── componentA.py
│ ├── componentB.py
├──工作流/
│ ├── flow.py
├── requirements.txt
├── Dockerfile
└── setup.py
成分
Prefect 社区成员遇到的最常见用例是希望将一组通用实用程序和功能移植到他们的执行环境中。上面目录结构中的 components 文件夹代表了我们的应用程序需要的这些常用功能。
如果您发现组件在多个项目中被重用,那么将它们抽象到它们自己的包中可能是值得的。此外,将通用逻辑放入一个包中,无论当前的工作目录如何,都可以将其导入,这使得在 Docker 容器中运行时更轻松。
在这里,我们在组件文件中定义了一些快速类。这只是简单的 Python。目标是创建一些我们可以在我们的主要 Python 脚本中导入的东西。
组件A.py
class ComponentA:
def __init__(self, n=2) -> None:
self.n = n
组件B.py
class ComponentB:
def __init__(self, n=2) -> None:
self.n = n
__init__.py
该文件将是空的。这个文件的存在告诉 Python 这个目录代表一个子模块。例如,mypy和pytest需要空的__init__.py文件来解释代码结构。有关更多信息,请参阅此 StackOverflow 帖子。
工作流程
上面目录结构中的这个文件夹将保存我们的主应用程序。flow.py的代码可以在下面看到。在其中,我们有一个基本的 Prefect Flow,但您不需要在这里了解 Prefect(但我们会稍微解释一下)。重点是我们导入了ComponentA和ComponentB,并在 Python 脚本中使用了它。
流.py
对于那些好奇的人来说,Prefect是一个工作流编排系统,可以调度和监控工作流。完美流代表整个端到端逻辑(如 Python 脚本),而使用@task装饰器定义的任务代表工作流的各个组件(如函数)。
Prefect 是通用的,能够运行从 ETL 到机器学习作业的流程。任何可以用 Python 编写的东西都可以用 Prefect 来编排。部署后,我们上面的流程将在运行时使用test:latest映像(如 的配置中所指定DockerRun
)。
要求
该requirements.txt文件应该是最熟悉的用户。它包含运行代码所需的所有 Python 要求。理想情况下,需求应该是版本化的,否则,您可以在下次安装它们时获得升级版本。对于这个工作流程,我们只有一个要求。
要求.txt
<span style="background-color:#f2f2f2"><span style="color:#292929">级长==0.15.4</span></span>
您可以使用以下命令为您的项目创建此文件,该命令会将您的所有包输出到requirements.txt文件中。
<span style="background-color:#f2f2f2"><span style="color:#292929">pip冻结>需求.txt</span></span>
setup.py 文件
到目前为止,一切都可能并不新鲜。我们创建了一些 Python 类,将它们导入到flow.py文件中,然后创建了我们的需求。该setup.py是什么可能对一些用户来说是新的。这个文件会告诉pip如何安装我们的包。
设置文件
有了这个文件,我们现在可以在本地安装我们的包pip install -e .
。我们这里的setup.py非常小。pip 使用包名称和版本来跟踪包,但它们不会影响包在 Python 代码中的使用方式。该find_packages函数调用经过与子目录__init__.py,包括他们mypackage中。请注意,workflow/flow.py
不会包含在库中,因为工作流文件夹中没有__init__.py。
该-e在pip install -e .
告诉点子安装在开发模式库,使编辑的代码可以无需重新安装效果。
该requirements.txt文件被读入,然后传递给设置功能。这告诉 pip 将这些依赖项与我们的包一起安装。有了这个,我们现在有了一个 Python 库,只要我们复制代码,我们就可以将其安装在其他机器上。
编写 Dockerfile
这是我们代码库的最后一部分。目标是复制我们刚刚创建和安装的 Python 包。我们将显示代码,然后一行一行地检查它。
文件
- FROM - 这是我们将用于图像的基本图像。Spark 或 Dask 等项目。当您使用不限于 Python 的工具时,这些特别有用。例如,Spark 在容器中也需要 Java,使用基础 Spark 镜像就是其中之一。
- WORKDIR — 设置容器的工作目录。如果它不存在,它将被创建
- ADD — 在这里,我们将当前目录中的所有文件添加到容器 WORKDIR
- RUN——这是我们安装我们的库的地方(-e并不是真正需要的,因为那是为了开发)。由于我们之前构建setup.py文件的方式,这也将安装所有要求。
构建图像
随着Dockerfile创建的,我们可以从项目的根目录创建图像。这可以在 CLI 中使用以下命令完成。请注意,这假设您已安装 Docker 并且它正在运行。
<span style="background-color:#f2f2f2"><span style="color:#292929">码头工人建造。-t 测试:最新</span></span>
现在已经创建了镜像,您可以使用 docker push命令将其推送到您的注册表(Dockerhub、AWS ECR 等)。这些注册管理机构将有不同的方式来执行此操作,因此我们不会在此处进行介绍。
使用图像
为了以交互方式运行图像,
<span style="background-color:#f2f2f2"><span style="color:#292929">docker run --name mycontainername -i -t test:latest sh</span></span>
从这里,你应该在app目录中,你可以运行:
<span style="background-color:#f2f2f2"><span style="color:#292929">python工作流/flow.py</span></span>
结论
在本演练中,我们了解了如何通过包含requirements.txt和setup.py文件来创建 Python 包。然后我们就可以将它复制到一个 Docker 镜像中并安装它以供一般使用。
让镜像在本地机器上工作已经提供了一些好处,但可能需要将镜像上传到注册表以用于生产用例。软件开发 WX zhiyan0112