基于健康检查的Docker容器出现故障时如何重新启动?

95

我正在使用 Docker版本17.09.0-ce,并且我看到容器被标记为不健康。有没有选项可以让容器重新启动而不是保持不健康的状态?


1
我认为这种情况会发生在你以Docker Swarm模式启动并将其作为服务运行,而不是普通的Docker容器。 - Tarun Lalwani
1
出于某些特定原因,我没有使用Swarm或任何编排工具。 - Govind Kailas
然后,您可以使用另一个脚本,使用docker events -f event=health_status(https://docs.docker.com/engine/reference/commandline/events/#extended-description) 来检查容器的健康状况,并根据结果采取相应的措施。 - Tarun Lalwani
7个回答

106

在最初的 PR 中(https://github.com/moby/moby/pull/22719),“重新启动不健康容器”功能已存在,但在讨论后被移除,并被认为以后可以作为 RestartPolicy 的增强功能。

目前,您可以使用此解决方法自动重新启动不健康的容器:https://hub.docker.com/r/willfarrell/autoheal/

这是一个示例组合文件:

version: '2'
services:
  autoheal:
    restart: always
    image: willfarrell/autoheal
    environment:
      - AUTOHEAL_CONTAINER_LABEL=all
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

只需在此执行docker-compose up -d


这样做不会阻止你指定自己的镜像名称吗?此外,环境变量或卷是否必要?看起来卷并不相关。你可以将此镜像用作依赖项吗? - obesechicken13
1
@obesechicken13,这个容器的卷是为了让 Docker 套接字在容器内部可用。 - Connor
14
这带来了巨大的安全风险。 - InsOp
1
仍有一个未解决的功能请求:从不健康状态触发重新启动 - cdalxndr
5
那么你有什么建议?如何在不给容器访问Docker的情况下完成这项任务? - ave

46

通过设置智能的HEALTHCHECK和适当的重启策略,您可以自动重新启动不健康的容器。

Docker重启策略应该是always或者unless-stopped之一。

相反,HEALTHCHECK应该实现一个逻辑,当容器不健康时杀死它。

在下面的示例中,我使用了curl及其内部重试机制,并将其(在发生故障/服务不健康的情况下)传输到kill命令。

HEALTHCHECK --interval=5m --timeout=2m --start-period=45s \
   CMD curl -f --retry 6 --max-time 5 --retry-delay 10 --retry-max-time 60 "http://localhost:8080/health" || bash -c 'kill -s 15 -1 && (sleep 10; kill -s 9 -1)'
重要的一步是理解重试逻辑已经包含在curl命令中,Docker重试实际上是强制性的但无用的。 如果curl HTTP请求失败3次,则执行kill命令。 首先,它向容器中的所有进程发送SIGTERM信号,以允许它们正常停止,然后在10秒后发送SIGKILL信号来完全杀死容器中的所有进程。必须注意,当容器的PID1死亡时,容器本身也会死亡并触发重启策略。 注意:在bash和sh中,kill行为不同。 在bash中,您可以使用-1向所有PID大于1的进程发送信号以使其死亡。
  • kill文档:https://linux.die.net/man/1/kill
  • curl文档:https://curl.haxx.se/docs/manpage.html
  • docker重新启动文档:https://docs.docker.com/compose/compose-file/compose-file-v2/#restart

  • 很酷,感谢解释。假设容器已经重新启动,但问题无法解决...它不会陷入无限循环吗? - Niklas
    2
    是的,它会进入一个无限循环。唯一停止它的方法是通过 docker compose stopdocker compose rm -f。有一个超级复杂的替代方案来解决这个问题。即:在容器内挂载Docker套接字,在容器内的.sh文件中实现重试逻辑,在卷上写入计数器以使其持久化,并且当计数器大于尝试次数时,使用Docker套接字向容器本身发送stop消息。 :) - Naramsim
    1
    @cdalxndr 的 healthcheck retries 参数与 @naramsim 在评论中解释的目的不同。 - AATHITH RAJENDRAN
    1
    @Naramsim 正如你在 Gotchas 中指出的,-1 信号会影响除 init (PID 1) 以外的所有进程。因此我的容器不会停止也不会重新启动。我在这里漏掉了什么吗?即使我显式地发送 TERM 或 SIGKILL 给 PID 1,它也会被忽略。 - Danilo Ramirez
    1
    可以考虑将 --retry-connrefused 作为 curl 命令的选项添加进去。否则,如果服务器由于某种原因没有启动,curl 将在第一次尝试时失败。 - robertsLando
    显示剩余2条评论

    14

    通过简单的crontab规则,不健康的docker容器可以重新启动:

    * * * * * docker ps -f health=unhealthy --format "docker restart {{.ID}}" | sh
    

    4
    可能更安全的做法是使用 docker ps -q -f health=unhealthy | xargs docker restart 而不是调用一个 shell。 - slhck
    此外,当没有不健康的容器时,可以使用-r--no-run-if-empty标志来运行xargs命令,例如: docker ps -q -f health=unhealthy | xargs --no-run-if-empty docker restart - undefined

    12

    对于独立容器而言,Docker并没有原生集成在健康检查失败时重启容器的功能,但我们可以使用Docker事件和脚本实现相同的效果。在Swarm中,健康检查得到更好的集成。将健康检查集成到Swarm中后,当服务中的容器不健康时,Swarm会自动关闭不健康的容器并启动一个新的容器以维护服务的副本数量指定的容器计数。


    12

    您可以尝试在Dockerfile中添加类似于以下内容的内容:

    HEALTHCHECK --interval=5s --timeout=2s CMD curl --fail http://localhost || kill 1
    

    不要忘记--restart always选项。

    kill 1将在容器中杀死pid为1的进程并强制退出容器。通常由CMD或ENTRYPOINT启动的进程具有pid 1。

    不幸的是,这种方法可能不会将容器的状态更改为不健康,因此请谨慎使用。


    2
    这不是在第一个失败的curl时就杀死了容器吗? - Naramsim
    @Naramsim 你说得对,第一次curl失败会导致容器停止,但是使用--restart always参数可以让它重新启动。另外,正如文档所述,“健康检查将在容器启动interval秒后首次运行”。因此,增加interval时间可以防止容器启动时间过长导致的快速失败。 - What
    我用适当的方式回答了这个问题,以重新启动一个不健康的容器。 - Naramsim

    6
    根据https://codeblog.dotsandbrackets.com/docker-health-check/,创建容器并添加"restart: always"。在使用healthcheck时,请注意以下几点:对于独立容器,Docker没有本地集成以在健康检查失败时重新启动容器,但我们可以使用Docker事件和脚本来实现相同的功能。与Swarm相比,Health check更好地集成了Swarm。当将healthcheck集成到Swarm中时,如果服务中的容器不健康,Swarm会自动关闭不健康的容器并启动一个新的容器,以维护服务的副本计数中指定的容器计数。

    3
    请将您的回答全部放在响应中,而不是链接。 - Rob Anthony
    1
    我解决了这个问题。在使用健康检查时,请注意以下几点:对于独立容器,Docker没有本地集成以在健康检查失败时重新启动容器,但我们可以使用Docker事件和脚本来实现相同的功能。健康检查与Swarm更好地集成。当健康检查与Swarm集成时,当服务中的容器不健康时,Swarm会自动关闭不健康的容器并启动一个新的容器,以维护服务的副本计数中指定的容器数量。 - myfreax

    5
    Docker有几种方法可以获取容器健康状态的详细信息。您可以配置健康检查及其运行频率。此外,还可以对容器内运行的应用程序运行健康检查,例如http(这将使用curl --fail选项)。您可以查看health_status事件以获取详细信息。
    要获取有关不健康容器的详细信息,可以使用inspect命令:docker inspect --format='{{json .State.Health}}' container-name (有关详细信息,请参见https://blog.newrelic.com/2016/08/24/docker-health-check-instruction/)。
    您应该首先解决导致“不健康”标记的错误条件(任何时候运行健康检查命令并获得退出代码1),这可能需要Docker重新启动容器,具体取决于错误情况。如果您正在自动启动/重启容器,则捕获启动错误或记录它们和健康检查状态可以帮助快速解决错误。如果您对自动启动感兴趣,请查看链接。

    1
    如果您使用的是Windows系统,则必须使用双引号,否则它将无法正常工作:docker inspect --format="{{json .State.Health}}" name-of-your-container - AbsolutelyFreeWeb

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