【问题标题】:Proper async way of waiting for a running container等待正在运行的容器的正确异步方式
【发布时间】:2021-03-01 18:45:10
【问题描述】:

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

我在分离模式下运行容器,所以问题不在于启动容器本身。问题是如何定义容器何时完成工作。 Container docker 包的实例有一个方法 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)是一个好方法吗?说实话我不知道喜欢这个解决方案,因为几个原因,我认为这种“等待”有问题,但我不知道究竟是什么......

我知道诸如aiodockerrun_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 逻辑上,这里使用asyncio 有什么意义?
  • @gold_cy ,提供的示例不会阻止在有意义的时间内禁用整个应用程序的含义。 container.reload() 阻止,因为它向 docker api 发出同步请求,但我想我可以用直接向 docker api 的异步请求替换这一行,以检查容器的当前状态。我的问题是 - ** 在循环中显式检查容器的当前状态这样的方法在应用程序效率方面是一个很好的解决方案吗? **

标签: python docker python-asyncio aiohttp


【解决方案1】:

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

docker-py Container 对象有一个 wait method ,它会阻塞直到容器停止,然后返回。这将替换轮询循环,并且在容器退出之前不会返回;但是,这是一个阻塞调用。

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 并挂载任意主机内容;如果容器以 root 身份运行并挂载主机 / 目录,则这种方法可能会成为整个系统的 root。考虑使用消息队列,例如RabbitMQ 或 Celery 之类的框架来调度作业,无需添加 Docker 依赖项、限制并发性、在作业失败时增加一些弹性,并且没有接管主机的可能性。)

【讨论】:

  • 你的提议很有趣,但不幸的是 - 至少现在 - 我必须坚持使用 python3.7,所以我不能使用它。当然,我知道码头工人带来的某种危险,但无论如何,谢谢你的注意 - 更安全的是“现实世界”中的软件,更安全的是整个互联网社区:) 然而你的回应没有回答我的问题 - 这种使用轮询循环检查容器当前状态的方法在应用程序效率方面是一个很好的解决方案吗?或者我肯定应该使用其他东西?
猜你喜欢
  • 2020-08-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-10
  • 2022-01-03
  • 1970-01-01
  • 1970-01-01
  • 2019-12-10
相关资源
最近更新 更多