背景
我们使用Serverless Dagster Cloud来开发和部署 Dagster 代码,无需设置本地开发环境或任何云基础架构。当提交更改到 GitHub 时,GitHub Action会直接构建和部署代码到 Dagster Cloud,然后可以在界面上查看并与 Dagster 对象进行交互。 Dagster Cloud 可以利用一个远程环境来共享部署,并且可以利用自动创建的临时环境与合作者协作,实现了个人本地开发和共享远程环境的结合。
最初,我们在 Dagster Cloud Serverless 中使用了标准的基于 Docker 的构建流程,但很快发现这个流程会使“编辑-部署-运行”的循环变得非常缓慢。为了将过程加速,我们实现了一个在 Docker 镜像之外运送代码的系统。本文将描述我们分析的问题、选择的解决方案以及在此过程中做出的各种权衡。
当我们在 GitHub 上构建 Docker 镜像并将其部署到 Dagster Cloud 时,每次提交需要 3~5 分钟才会在 Dagster UI 中显示。在每次迭代中,无服务器开发人员通常会对代码进行微小更改,但是必须等待 3 分钟以上才能看到该更改的效果,这很快就会变得非常烦人。我们分析了“当你更改一行代码并提交时会发生什么”,并发现以下问题:
你可以看到,有两样东西花了最多的时间:
让我们分别看一下这两个问题。
有一些需要注意的关于构建 Docker 镜像的事情:
关于启动 Docker 容器,我们使用亚马逊云科技 Fargate,需要 45~90 秒的时间来提供和启动一个镜像。它不提供任何镜像缓存。启动一个新的容器会将所有层从注册表下载到已提供的容器中。
在 Docker 镜像构建和启动后,我们运行用户的代码来提取元数据,并在 UI 中显示。这是不可避免的,并且可能需要几秒钟、30 秒或更多时间,具体取决于如何计算元数据(例如可能会连接数据库以读取模式)。此代码服务器保持活动状态并服务元数据请求,直到推送代码的新版本,然后启动一个新的容器。
我们有一个关键要求是可重复性:我们需要能够多次重新部署完全相同的代码和环境。使用 Docker 镜像哈希作为代码和环境的标识符非常适合这个要求。
下面是我们探索和讨论过的一些备选方案:
做出这个决定的关键因素是因为我们意识到,虽然 Docker 镜像是行业标准,但在只需要同步一个小改变时,传输数百兆字节的镜像似乎是不必要的。考虑 git——它只传输差异,但却产生了完整且一致的代码库。
这让我们倾向于选择方案 4……如果我们能找到一个适合我们大部分工作工具的话。经过一些实验,我们发现 PEX 具有许多特性,非常适合我们的使用情况——在下一节中会详细介绍。
PEX 是 Python 可执行文件的缩写,是一个将 Python 包打包成名为文件的工具。这些可执行文件包含 Python 包和一些引导代码。例如,我们可以将 dagster 包和其依赖项打包成单个文件,然后运行它:
% pex dagster --python=python3.8 -o dagster.pex
% ./dagster.pex
Python 3.8.16 (default, Dec7 2022, 01:24:57)
[Clang 14.0.0 (clang-1400.0.29.202)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> import dagster
复制代码
将整个环境存储在单个文件中非常方便,可以轻松地将其传输到 S3 中进行存储。PEX 提供了更多功能,不仅仅是“文件中的虚拟环境” - 这里是我们使用的其他功能:
在运行时,pex 环境与其他全局包完全隔离。在环境中只有捆绑在 pex 文件中的包。我们将多个 pex 文件一起发送到同一台机器上,而不必担心环境隔离问题。
使用相同的输入包会生成完全相同的 pex 文件:
$ pex dagster pandas -o out.pex | sha256sum
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855-
$ pex dagster pandas -o out.pex | sha256sum
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855-
复制代码
这让我们可以使用内容寻址来识别这些 pex 文件,从而对实现可重复性更有信心。为了实现可重复性,除了使用 Docker 镜像哈希之外,我们还使用 pex 文件哈希。
多个 pex 文件可以在运行时合并,有效地将多个环境合并为一个环境。
% pex pandas -o pandas.pex
% pex dagster -o dagster.pex
% PEX_PATH=pandas.pex ./dagster.pex
Python 3.8.16 (default, Dec7 2022, 01:24:57)
[Clang 14.0.0 (clang-1400.0.29.202)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> import pandas
>>> import dagster
复制代码
我们使用这个功能将代码分成两个部分,在运行时合并起来:一个包含所有依赖项的 deps.pex 文件,和一个仅包含用户代码的 source.pex 文件。
我们在 Serverless Cloud 中使用 Linux
python:* -slim
衍生的基础镜像。只要有包的,工具就可以在任何平台上为 Linux 构建 pex 文件。
使用和 S3 存储 pex 文件,我们构建了一个系统,其中快速路径避免了构建和启动 Docker 镜像的开销。
我们的系统工作方式如下:当你将代码提交到 GitHub 时,GitHub 操作根据你的依赖关系是否与上一次部署不同,执行全量构建或快速构建。我们跟踪在和
requirements.txt
中指定的依赖关系集。
对于全量构建,我们将你的项目依赖项构建为文件,将你的代码构建为
source.pex
文件。这两个文件都会上传到 Dagster Cloud。对于快速构建,我们只构建并上传
source.pex
文件。
在 Dagster Cloud 中,我们可能会重复使用现有容器或为代码服务器提供新的容器。我们将和
source.pex
文件下载到此代码服务器上,并在隔离环境中使用它们运行你的代码。我们从不跨用户共享容器,容器上的所有环境都属于同一用户。快速部署的最佳时间和最差时间如下所示:
这里的要点是,在快速路径中——当我们进行快速构建并重用现有容器时——整个过程只需要大约 40 秒,而不是之前的 3 分钟多。我们称这个功能为快速部署,现在已经默认开启了所有新的无服务器注册。
这种方式可以显著提高部署速度(4~5 倍),但也带来了一些权衡和其他需要调整的因素:
你可能已经注意到,在最初的图表中,Download Docker based action 的操作大约需要 10 秒钟。我们是如何完全消除这个步骤的呢?
我们曾经将 GitHub action 代码打包成 Docker 镜像,并使用Docker container action。现在,我们将我们的 action 代码打包为 pex 文件,将其检入我们的 action 存储库并直接在 GitHub runner 上运行。这消除了下载和启动 Docker action 镜像所花费的时间,同时仍允许我们打包所有依赖项。
我们做出的另一个小优化是只使用一个 GitHub 工作流作业。在 GitHub 中,每次作业启动需要大约 10 秒钟来准备一个新的 runner。
将部署时间从超过 3 分钟缩短到 40 秒是一个显著的加速,我们对这个结果非常满意,特别是在测试自己的服务时。使用 pex 使我们能够在 Docker 之上构建一个可重复、一致的环境,我们很高兴能够探索使用 pex-on-docker 组合的其他可能性。
如果有兴趣,可以看 PEX 团另一篇关于使用 Docker 进行部署的博客文章,它描述了如何使用 pex 文件作为中间目标来加速 Docker 镜像构建。
原文链接: