我在基于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()
这很简单,但它工作得很好。但我的问题是:是这样的"傻瓜";等待完成容器的方式(状态不同于running
或created
)是一个好方法?老实说,我不喜欢这个解决方案,因为有几个原因,我认为这种"等待"是错误的。但我不知道到底是什么……
我知道解决方案,如aiodocker或run_in_executor/threading的方式,甚至用subprocess
替换docker sdk
,但现在我想使用-如果可能的话-asyncio
和docker 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依赖,限制并发性,在作业失败时添加一些弹性,并且没有接管主机的潜力。