当运行dockercompose时,如何优雅地停止停靠的Python ROS2节点?

2024-04-27 23:37:10 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个基于Python的ROS2节点在Docker容器中运行,我试图通过捕捉SIGTERM/SIGINT信号和/或捕捉keyboardInterrupt异常来处理节点的正常关闭。在

问题是当我使用docker-compose在容器中运行节点时。我似乎没法抓住集装箱被停/停的“时刻”。我在Dockerfile中显式地添加了^{} ,在docker compose文件中添加了^{}。在

以下是节点代码示例:

import signal
import sys
import rclpy

def stop_node(*args):
    print("Stopping node..")
    rclpy.shutdown()
    return True

def main():
    rclpy.init(args=sys.argv)
    print("Creating node..")
    node = rclpy.create_node("mynode")
    print("Running node..")
    while rclpy.ok():
        rclpy.spin_once(node)

if __name__ == '__main__':
    try:
        signal.signal(signal.SIGINT, stop_node)
        signal.signal(signal.SIGTERM, stop_node)
        main()
    except:
        stop_node()

以下是重新创建映像的Dockerfile示例:

^{pr2}$

这是码头工人的样本-合成.yml公司名称:

version: '3'

services:
  mynode:
    container_name: mynode-container
    image: mynode
    entrypoint: /bin/bash -c "/run-node.sh"
    privileged: true
    stdin_open: false
    tty: true
    stop_signal: SIGTERM

这是跑步-节点.sh脚本:

source /opt/ros/$ROS_DISTRO/setup.bash
python3 /nodes/mynode.py

当我在容器中手动运行节点时(使用python3 mynode.py或通过/run-node.sh)或者当我执行docker run -it mynode /bin/bash -c "/run-node.sh"时,我得到“Stopping node..”消息。但是,当我docker-compose up时,当我通过Ctrl+C或docker-compose down停止容器时,我从来没有看到该消息。在

$ docker-compose up
Creating network "ros-node_default" with the default driver
Creating mynode-container ... done
Attaching to mynode-container
mynode-container | Creating node..
mynode-container | Running node..

^CGracefully stopping... (press Ctrl+C again to force)
Stopping mynode-container ... done
$

我试过:

  • 将调用移动到signal.signal
  • 使用atexit代替signal
  • 使用docker stop和{}

我也检查了这个Python inside docker container, gracefully stop问题,但没有明确的解决方案,我不确定使用ROS/rcpy是否会使我的设置有所不同(同样,我的主机是Ubuntu 18.04,而那个用户在Windows上)。在

是否可以在我的stop_node方法中捕捉容器的停止?在


Tags: composedockerrunimportcreatingnodesignal节点
1条回答
网友
1楼 · 发布于 2024-04-27 23:37:10

当您的docker-compose.yml文件显示:

entrypoint: /bin/bash -c "/run-node.sh"

由于这是一个裸字符串,Docker将其包装在/bin/sh -c包装中。所以你的容器的主要过程是

^{pr2}$

反过来,bash脚本保持运行。它启动一个Python脚本,并作为其父脚本一直运行,直到该脚本退出为止。{cd3>两个级别的包装器可能不运行

这里的重要部分是这个包装器shell,而不是您的脚本,是接收信号的主容器进程,并且(结果是)won't receive SIGTERM unless it's explicitly coded to。在

这里要做的最重要的重组是使包装脚本exec为Python脚本。这导致它替换包装器,因此它成为主进程并接收信号。如果没有其他改变最后一行

exec python3 /nodes/mynode.py

可能会有帮助。在


我会在这里再深入一点,确保这些代码大部分都内置在Docker映像中,并尽量减少显式shell包装器的数量。”进行一些初始化,然后execsomething“是一种非常常见的Docker模式,您可以编写此脚本并将其作为图像的入口点:

#!/bin/sh
# Do the setup
# ("." is the same as "source", but standard)
. "/opt/ros/$ROS_DISTRO/setup.bash"
# Run the main CMD
exec "$@"

类似地,你的主脚本应该以“shebang”开头,比如

#!/usr/bin/env python3
import ...

您的Dockerfile已经包含了能够直接运行包装器的设置,您可能需要一个类似的RUN chmod行来处理主脚本。但是你可以加上

ENTRYPOINT ["/run-node.sh"]
CMD ["/nodes/my-node.py"]

因为这两个脚本都是可执行的,并且有“shebang”行,所以可以直接运行它们。使用JSON语法可以防止Docker添加额外的shell包装器。由于entrypoint脚本现在将运行任何命令,因此很容易单独更改它。例如,如果您希望一个完成环境变量设置的交互式shell尝试调试容器启动,则可以仅重写命令部分

docker run  rm -it mynode sh

相关问题 更多 >