lai*_*son 3 python linux scaling subprocess kubernetes
我有一个应用程序可以执行以下操作:
subprocess.Popen(["python3", "-u", "sub-program-1.py"])
Run Code Online (Sandbox Code Playgroud)
因此Python程序可以按需启动多个长寿命进程。
如果我停止主Python程序并再次启动,它知道sub-program-1.py
应该启动,因为数据库中有一条关于状态的记录告诉它。
因此,当 Docker 容器、Pod、虚拟机或任何你称之为的东西只有一个副本时,它就可以正常工作。
如果我将应用程序扩展到 3 个副本,subprocess
则无法实现。
每个 Docker 容器都会启动sub-program-1.py
,而我想在一个容器上启动
如果一个容器发生故障,应用程序应该足够智能,能够故障转移sub-program-1.py
到另一个容器
一个应用程序应该足够智能,能够平衡容器之间的子进程,例如:sub-program-1.py
-sub-program-9.py
理想情况下应该通过每个容器放置 3 个进程来进行扩展,因此总共有 9 个子进程正在运行 - 我不需要这个很精确,最简单的解决方案就可以了来平衡它
我尝试探索 RQ(Redis 队列)和类似的解决方案,但它们主要关注任务,最好是短期任务。就我而言,它们是长期存在的进程。例如sub-program-1.py
可以活几个月甚至几年。
方案是这样的:
主要 Python 应用程序 -> sub-program-1.py
、sub-program-2.py
等。
这里有没有任何简单的解决方案,没有开销?
将每个子程序的状态写入数据库是否是一个选项(还可以根据数据库中的状态检测子进程何时无法将其故障转移到另一个容器),或者您是否会合并其他工具来解决subprocess
扩展问题?
另一种选择是启动sub-program-1.py
所有容器并扩展其中的操作。sub-program-1.py
基本上就是调用一些第三方的API,根据用户的喜好做一些操作。因此,根据每个用户偏好扩展这些 API 调用很复杂,当同时调用 API 时,它在后台有多个线程。简而言之,sub-program-1.py
与 user1 绑定,sub-program-2.py
与 user2 绑定,等等。那么是否值得通过选择此选项使其变得复杂?
更新
Ifsubprocess
仅在独立应用程序中使用,没有人尝试在 Github、库等上以可找到的规模实现此机制。
你会如何用Python解决这个问题?
我想到了数据库中的这些条目:
ProcessName ProcessHostname LastHeartBeat Enabled
process1 host-0 2022-07-10 15:00 true
process2 null null true
process3 host-1 2022-07-10 14:50 true
Run Code Online (Sandbox Code Playgroud)
所以为了解决我上面写的三点:
每个容器都会尝试拾取尚未拾取的进程(其中 是null
或 的旧日期LastHeartBeat
)。当第一个容器拾取进程时,它会写入日期
LastHeartBeat
,然后用于subprocess
启动进程。LastHeartBeat
其他容器如果不断更新则无法拾取。
如果进程失败,它不会写入LastHeartBeat
,因此其他容器会按照第 1 点中所述拾取该进程。如果失败的容器无法到达数据库,它将停止操作并重新启动(如果它甚至能够做到exit
)。如果它无法到达 DB,则不会执行任何操作。也就是说,不要运行同一进程两次。
为了平衡容器之间的进程,运行较少进程的容器可以选择一个新进程。该信息位于数据库表中以供做出决定。
你会用不同的方式解决吗?您有什么推荐的最佳实践吗?
谢谢
TL;DR - 这是一个经典的整体应用程序扩展问题,您可以通过将应用程序重新设计为微服务架构来轻松解决此问题,因为您的应用程序功能本质上是在组件之间解耦的。完成此操作后,一切实际上都归结为以原生微服务友好的方式部署应用程序,并且您的所有设计要求都将得到满足。
编辑:您当前正在尝试在微服务系统中“扩展”您的应用程序(1 个 Pod 中的多个进程/容器),这违背了使用它的全部目的。您必须坚持使用 1 个子进程 <===> 1 个 pod 才能使设计真正发挥作用。否则,您只会引入巨大的复杂性,这违背了微服务的许多设计原则。如果您有兴趣,请在下面了解更多详细信息。
让我首先总结一下您所说的所有内容,以便我们能够连贯地讨论设计。
据我从您提供的所有信息中了解到您的申请要求,以下情况属实:
processes
成为long-lived
。application
产生这些long-lived
进程的父进程。processes
需要按需启动。(动态缩放 - 和横向扩展;请参阅下面的(7)了解process
每1 个container
)application
应该只生成 1process
个sub-process.py
.container
失败,您希望application
能够智能地将流量切换到container
也运行您的长期运行的健康流量process
。application
能够在当前运行的所有processes
/之间共享负载containers
。process
与用户请求相关,因为它调用第三方API
系统来执行其功能。process
因此,为了简化设计,容器内只放一个是有利的。目前,您的应用程序设置方式如下:
application
生成多个相同的sub-process.py
进程。application
用户,接收请求,sub-process.py
根据需要生成进程,并在一个计算单元(容器、虚拟机等)内很好地扩展processes
执行其操作,并将响应返回给用户,然后将application
其返回给用户。现在,让我们讨论一下您上面提到的当前方法,看看您所描述的挑战是什么。
这意味着只需为您的应用程序创建更多容器即可。我们知道它不满足要求,因为将应用程序扩展到多个副本会启动所有副本processes
并使它们active
。这不是您想要的,因此不同容器中的这些副本之间没有关系(因为子进程与application
每个容器中的运行相关,而不是与整个系统相关)。这显然是因为application
不同容器中的 s 彼此不知道(更重要的是,每个子进程都在生成)。
所以,这不满足我们的要求(3)、(4)、(5)。
为了尝试满足(3)、(4)和(5),我们引入了一个database
对于我们整个系统至关重要的一个,它可以在我们的系统中保存不同的状态数据processes
以及如何containers
将某些数据“绑定”到进程并管理它们。然而,正如您指出的那样(加上我自己的想法),这也有一定的局限性:
short-lived
流程。race conditions
。database
和一些已知规则(您定义为最后 3 点)来实现我们的目标。尤其是一个编排组件,它将知道何时按需启动容器。这是非常复杂的。这无法满足我们的要求(1)和(7),最重要的是,这是重新发明轮子!
让我们来谈谈 Kubernetes 以及为什么它正是您所需要的。
现在让我们看看如何以最小的努力重新设计整个问题并满足我们的所有要求。
我建议您可以非常简单地将 yourapplication
与 your processes
. 这很容易做到,因为您的应用程序正在接受用户请求并将它们转发到相同的工作人员池,这些工作人员通过进行第 3 方 API 调用来执行操作。从本质上讲,这完美地映射到微服务上。
user1 =====> |===> worker1 => someAPIs
user2 =====> App |===> worker2 => someAPIs
user2 =====> |===> worker3 => someAPIs
...
Run Code Online (Sandbox Code Playgroud)
我们可以明智地利用这一点。请注意,不仅元素解耦,而且所有工作人员都执行一组相同的功能(这可能会根据使用输入产生不同的输出)。本质上你将替换
user1 =====> |===> worker1 => someAPIs
user2 =====> App |===> worker2 => someAPIs
user2 =====> |===> worker3 => someAPIs
...
Run Code Online (Sandbox Code Playgroud)
通过对服务的 API 调用,该服务可以根据需要为您提供工作人员:
output = some_api(my_worker_service, user_input)
Run Code Online (Sandbox Code Playgroud)
这意味着,您的应用程序设计已被保留,您只需将流程放置在不同的系统上即可。所以,应用程序现在看起来像这样:
user1 =====> |===> worker1 => someAPIs
user2 =====> App ==>worker_service |===> worker2 => someAPIs
user2 =====> |===> worker3 => someAPIs
...
Run Code Online (Sandbox Code Playgroud)
应用程序重新设计的这一重要组成部分就位后,让我们重新审视之前设计中的问题,看看这是否对我们有帮助以及 Kubernetes 如何发挥作用。
当您描述使用数据库来维护state
整个系统以及能够检索containers
系统中当前状态并做出某些决策的编排逻辑时,您绝对走在正确的道路上。这正是Kubernetes 的工作原理!
现在让我们看看 Kubernetes 如何解决我们的问题
processes
在 Kubernetes 中可以长久存在。因此,满足了要求(1),并且也减轻了我们数据库设计的限制(1)。service
管理所有工人的系统。processes
所以,要求(2)满足。它还能够processes
按需扩展,因此满足要求(3)。它还将保持最小process
计数,1
这样我们就不会产生不必要的进程,从而满足要求(4)。它将足够智能,仅将流量转发到processes
at healthy
。因此,满足要求(5)。它还会在processes
其管辖的所有对象之间对流量进行负载平衡,因此满足要求 (6)。该服务还将减轻我们第二个设计的限制(4)和(5)。processes
需要调整规模,以确保您只使用所需的资源。因此,满足要求(7)。etcd
,该数据库存储整个集群的状态并始终保持更新,并适应竞争条件(多个组件更新相同的信息 - 它只是让第一个到达的组件获胜而另一个组件失败)一,强制其重试)。我们已经从第二个设计中解决了问题(2)。processes
开箱即用的编排逻辑,因此无需编写任何代码。这减轻了我们第二个设计的限制(3)。因此,我们不仅能够满足我们的所有要求,还能够实现我们想要实现的解决方案,而无需为编排编写任何额外的代码!(您只需稍微重组您的程序并引入 API)。
请注意,在 k8s 文献中,最小的计算单元被称为pod
执行单个功能。从架构上讲,这与您对 的描述相同sub-process
。因此,每当我谈论“Pod”时,我只是指您的sub-processes
.
您将(大致)采取以下步骤来实现建议的设计。
application
,sub-process.py
在它们之间引入 API。sub-process.py
成容器镜像。Deployment
使用您的图像创建一个sub-process.py
,并将最小重复计数设置为 1,最大重复计数设置为您想要的任何数字,例如 10,以进行自动缩放。Deployment
通过创建一个Service
. 这就是我讲的“worker 服务”,你application
会向这个服务“提交”请求。除了简单地向 API 端点发出请求之外,它不必担心任何事情,其他一切都由 k8s 处理。Service
。现在,其运作方式如下:
application
.application
将其转发到Service
API 端点。Service
API 请求并将其转发到Pods
正在运行您的sub-process.py
功能的服务器之一。如果收到多个请求,Service
将平衡所有可用 Pod 之间的请求。如果 pod 发生故障,K8s 将把它从服务中“移除”,这样请求就不会失败。Service
达到饱和,Deployment
则会触发自动扩展并为其创建更多 pod,Service
并且负载共享将再次恢复(横向扩展)。如果资源利用率随后降低,则会Deployment
删除某些不再使用的 pod,并且您将恢复到 1 个 pod(缩小)。如果您愿意,您也可以将您的
frontend
应用程序放入Deployment
和Service
中,这将使您拥有更加友好的云原生微服务架构。用户将与您的 API 进行交互,该front-end
API 将调用Service
管理您的sub-process.py
工作人员的 API,并返回结果。
我希望这对您有所帮助,并且您可以体会到微服务架构如何清晰地融入您所拥有的设计模式,以及如何非常简单地适应它并根据需要扩展您的应用程序!不仅如此,以这种方式表达您的设计还允许您通过简单地管理一组也可以与版本控制一起使用的 YAML 清单(文本文件)来重新设计/管理/测试不同的版本!
归档时间: |
|
查看次数: |
743 次 |
最近记录: |