创建一个云运行作业,该作业与云运行服务共享代码。

3

我看到谷歌云运行现在有jobs,这太令人惊奇了!

我想实现的是既有一个主容器提供Web流量,又有一个作业容器,可以根据主Web服务中的某些业务逻辑激活。

我不确定如何在这两个容器之间共享代码,即服务和任务。

我假设我可以将整个Web服务构建为任务容器,并在其中使用Procfile

web: python3 app/scripts/main.py

现在脚本模块可以从app中提取任意代码。

是否有更好的方式,例如使用两个Dockerfiles、两个阶段构建等方法来实现这一点?

1个回答

1

Python 代码

要在两个服务之间共享 Python 代码,有几种方法可供选择。显然,Python 的主要代码共享机制是 。您可以通过将 __init__.py 文件添加到文件夹中来轻松地创建一个包 (您可能已经知道这些)。

因此,在两个镜像中轻松存在代码的关键是确保代码被打包到包中,并且在镜像内部很容易访问。让我们探讨如何做到这一点。

Python 包路径

首先,需要注意的是 Python 如何解析包。Python 使用 sys.path 属性搜索模块,类似于 Linux 命令行使用 $PATH

从 Python 外部,您可以使用环境变量 $PYTHONPATH 影响 sys.path。您还可以在运行时添加到 sys.path

包初始化+导出

__init__.py 文件类似于 Python 对象上同名的魔术方法;如果需要,您可以使用它来初始化模块级别的代码。您从 __init__.py 导入和导出的内容可以在模块级别上可用。例如:

# in some_module/__init__.py:

from x import y


# in __main__.py

from some_module import y

目录结构/包路径

假设你的代码库中有一个名为common,一个名为pipeline,还有一个名为app的模块。共享代码位于common中,而管道/应用程序特定代码位于这些模块中。在需要的地方,apppipeline都从common导入。

以下是该目录的布局:

project/
   |
  src/
   |
   |--common/
   |    |
   |   __init__.py
   |
   |--pipeline/
   |    |
   |   __main__.py
   |
   |--app/
        |
       __main__.py

使用这种布局,我们需要确保在Python模块路径上可以找到common。我们可以通过以下方式实现:
  • 环境变量。如果您正在运行入口点(例如此示例树中的__main__.py),则可能很容易控制环境变量,因此您可以将PYTHONPATH设置为包括src/;如果这样做,则可以导入common,但是无法导入pipelinemain,因为它们没有__init__文件,只有__main__

  • 运行时修改。您可以将路径传递给调用(作为参数),然后将该路径写入sys.path,从而将其添加到模块路径。在此调用之后,应该可以导入common

  • 路径挂钩。使用sys.path_hooks可以让您在运行时响应导入请求。

  • PTH文件。旧版本的Python 2.x支持PTH文件,可让您从site-packages路径指向新路径。这些是一种高级角落情况选项 - 其他选项在客观上更好 - 但如果您愿意,我可以解释它们的工作原理。

以下是在运行时更改sys.path的示例:
import sys
sys.path.append('/whatever/dir/you/want')

以下是一份有关IT技术的翻译示例,涉及更改“PYTHONPATH”的内容:
这里举一个从Python外部更改“PYTHONPATH”的例子:
PYTHONPATH=/whatever/dir/you/want python3

顺便问一下,__main__.py 是什么?
正如上面提到的,__init__.py 是初始化一个模块。另一方面,__main__.py 充当模块的 主入口点。如果Python包中存在此文件,则可以执行以下操作: python -m module 它将运行 module__main__.py。例如,如果你有 app/__main__.py,那么可以执行以下操作: python -m app 将运行app__main__.py

将所有内容整合在一起

当你构建Python Docker镜像时,必须确保上述的 PYTHONPATH 或者 sys.path 更改是有效的。在运行时,Python以相同的方式查找模块(即从Docker镜像内部),因此应用相同的规则。
只要从app中可以导入common,你的代码应该可以加载和工作,pipeline也是一样;之所以将app/pipeline隔离开来(即不要允许它们互相导入)是因为pipeline然后可以从app镜像中省略,反之亦然。
因此,在你的Dockerfile中,可以执行以下操作来构建app镜像:
RUN mkdir -p /code
COPY ./common /code/
COPY ./app /code/
ENV PYTHONPATH /code/

只要存在/code/common/__init__.py,你现在应该能够从app中导入common
Docker镜像
如果两个服务要共享代码,它们应该尽可能地“一起”部署,这样代码始终保持同步;Docker镜像是一个很好的工具,因为它可以通过映像哈希地址对您的整个“修订”进行全面的可寻址。
然后,您的容器可以以几种方式检测它是否作为服务或作业运行,然后相应地调用代码(服务器将侦听和服务,作业将拉取参数并开始工作)。
以下是在此领域实现使用一个镜像的一些好选择,附有摘要的正/负比较:
实现使用一个镜像的方法
1) 环境变量。您可以在Cloud Run服务中分配一个环境变量,再次在Cloud Run作业中分配一个不同的环境变量,以便您的容器可以检测它在哪里运行。
利:
- 在Python中易于使用(例如:import os; os.environ["IS_JOB"]等) - 灵活,在几乎所有环境中都有效(即使您将来离开Cloud Run也是如此) - 适用于任何语言,不仅限于Python
弊:
- 很难传达结构,没有类型 - 可能会使测试复杂化;模拟环境可能很难掌握 - 可以在需要时任意访问,这意味着重构它可能很困难
2) 命令参数。也许您将一个参数传递给容器,告诉它是服务还是作业,就像一个环境变量一样。
利:
- 基本上与环境变量相同。 - 但只能由您的二进制文件访问,不会渗入其他代码。 - 只有一个地方可以在其中访问此代码,这比env vars更干净
弊:
- 必须在入口点处访问该值并保存它,而不是随时随地访问os.environ 3) 令牌身份验证。您可以为作业和服务分配不同的服务帐户,这对于安全卫生是一个好主意。基于此服务帐户的标识,您的容器可以检测它在哪里运行。
  • 优点:
    • 可以完全从代码中检测,不需要任何非自给式的输入,例如环境变量(即更为简洁和更好的抽象)
    • 完全类型化,可能是最容易测试/模拟的版本,因为有谷歌一流的Python SDK支持
  • 缺点:
    • 将您的代码与Cloud Run强烈耦合,或至少与Google Cloud强烈耦合。如果您想要迁移,这将使您的重构变得复杂。
    • 可能是最难编写的(尽管环境变量是一个低标准)

与多个镜像的比较

无论如何,最好使用一个镜像。让我们将这两种方法与不同的Dockerfile进行比较:

  • 运行成本更低。如果您有两个Docker镜像,您需要支付存储两个镜像、提供两个镜像等费用。根据您的开发速度,这个成本可能会令人惊讶,特别是如果您正在使用Cloud Artifact Registry或类似产品。

  • 构建时间更快。使用两个容器,您的Docker构建时间将加倍。也许您可以通过多阶段构建来节省这里的成本,但仍然需要存储、提供和上传/下载两个镜像,而不是一个。

  • 更易于保持同步。无需确保两个镜像哈希完全对齐,或同时更新和启动等。

  • 更易于比较状态。无需寻找以确定作业和服务是否处于相同的版本。如果哈希匹配,则可以使用。

  • 更易于测试。因为您只有一个软件输出,所以这可能使测试服务和作业如何一起工作更容易。在单元测试中,将代码放在一起,可以同时调用作业/服务。

使用两个镜像的好处相当少:

  • 诊断。如果它们在单独的镜像中,可能更容易区分或诊断问题,但这有点牵强,因为您最大的问题可能只是保持两个镜像同步。

  • 分别更新。如果代码更改仅涉及其中一个,那么您可能可以跳过作业或服务上的某些更新。如果这是一个重要问题,它可能会抵消一个单一镜像体系结构的好处。

  • 镜像大小。通过分离两个镜像的代码和职责,您可能可以节省一些空间,但是您在第一次复制镜像时就已经超出了这些节省的成本。

总的来说,如果我处在你的位置,我会制作一张图片;但是答案取决于你的应用程序、需求、工作流程等等。


2
这并没有回答如何在两个服务之间共享代码的问题。仅仅复制镜像并拥有两个入口是显而易见且不太高效的。此外,实际上没有任何解释如何实现这一点,只有一个高层次的优缺点分析。 - dendog
@dendog 对此我感到抱歉--你是想要关于如何在Python中具体实现这个的建议吗?如果你能给我一些指导,我可以更新我的答案。 - Sam Gammon
是的,我想知道如何在Python中两个服务之间共享代码。 - dendog
1
这是一个非常周到的回答 - 我会接受它。然而,对于SO来说有点啰嗦 - 我觉得问题可以用更简短的方式回答,但这是一篇不错的阅读 :) 另外,我认为多张图片是正确的选择 - 一张是工作,一张是Web应用程序 - 如果工作阻塞了你的Web应用程序就会崩溃! - dendog
@dendog 好的,非常感谢您的反馈。 - Sam Gammon
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接