等待正在运行的容器的正确异步方式



我在基于aiohttp的应用程序中使用python docker SDK。我的目标是在请求到来时启动容器,等待它完成所有工作并返回响应。我想同时处理许多这样的请求。

我在分离模式下运行容器,所以问题不在于启动容器本身。问题是如何定义容器何时完成工作。docker包的Container实例有一个方法wait(),但是它阻塞了,所以我不能像那样使用它。所以我想出了别的办法:我启动一个容器,创建新的asyncio任务,并(在该任务中)检查容器是否改变了它的状态。它看起来差不多是这样的:

import docker
import asyncio
import time
def wait_for_finish(event, container):
try:
container.reload()
timeout = time.time() + 600
while time.time() < timeout:
if container.status == 'running' or container.status == 'created':
await asyncio.sleep(0.5)
try:
container.reload()
except requests.exceptions.HTTPError:
break
else:
break
finally:
event.set()
docker_client = docker.from_env()
container = docker_client.containers.run(**kwargs)
event = asyncio.Event()
asyncio.create_task(
wait_for_finish(event, container)
)
await event.wait()
event.clear()

这很简单,但它工作得很好。但我的问题是:是这样的"傻瓜";等待完成容器的方式(状态不同于runningcreated)是一个好方法?老实说,我不喜欢这个解决方案,因为有几个原因,我认为这种"等待"是错误的。但我不知道到底是什么……

我知道解决方案,如aiodocker或run_in_executor/threading的方式,甚至用subprocess替换docker sdk,但现在我想使用-如果可能的话-asynciodocker sdk的组合,而不需要例如阅读


编辑

我最终得到了这样的解决方案:我通过aiohttp直接向Docker API发出异步请求,这允许我异步使用提到的wait()方法:

await docker_socket_session.post(f'http://localhost/containers/{container_id}/wait', timeout=timeout)

它工作得很好,至少现在我没有看到任何有意义的缺点。
感谢您的帮助

您可以将两个现有函数链接在一起以避免轮询循环,并通常简化此代码。

docker-py的Container对象有一个wait方法,它阻塞直到容器停止,然后返回。这将取代轮询循环,并且在容器退出之前不会返回;但是,这是一个阻塞呼叫。

asyncio.to_thread接受一个函数,在一个单独的线程中运行它,并返回一个可等待对象,当函数完成时将被触发。这将允许您将阻塞I/O函数桥接到asyncio中,而不会实际阻塞主执行。(文档指出这是Python 3.9中的新功能。)

你应该能够把这些组合在一起:

import docker
import asyncio
docker_client = docker.from_env()
container = docker_client.containers.run(**kwargs)
awaitable = asyncio.to_thread(container.wait)
result = await awaitable

(请记住,容器可以指定自己的用户ID并挂载任意主机内容;如果容器作为根运行并挂载主机/目录,那么这种方法可能会对整个系统进行根管理。考虑使用像RabbitMQ这样的消息队列或像Celery这样的框架来调度作业,不添加Docker依赖,限制并发性,在作业失败时添加一些弹性,并且没有接管主机的潜力。

最新更新