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

当运行docker compose up时,如何优雅地停止停靠的Python ROS2节点?,python,python-3.x,docker,docker-compose,ros2,Python,Python 3.x,Docker,Docker Compose,Ros2,我在Docker容器中运行了一个基于Python的ROS2节点,我正试图通过捕获SIGTERM/SIGINT信号和/或捕获KeyboardInterrupt异常来处理节点的正常关闭 问题是当我使用docker compose在容器中运行节点时。我似乎无法抓住集装箱停止/关闭的“时刻”。我已经在docker文件和docker compose文件中明确添加了 以下是节点代码的示例: import signal import sys import rclpy def stop_node(*args)

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

问题是当我使用
docker compose
在容器中运行节点时。我似乎无法抓住集装箱停止/关闭的“时刻”。我已经在docker文件和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示例:

FROM osrf/ros2:nightly

ENV DEBIAN_FRONTEND=noninteractive
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654
RUN apt-get update && \
    apt-get install -y vim
WORKDIR /nodes
COPY mynode.py .
ADD run-node.sh /run-node.sh
RUN chmod +x /run-node.sh
STOPSIGNAL SIGTERM
以下是docker-compose.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
以下是run-node.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组合
使用默认驱动程序创建网络“ros-node_default”
正在创建mynode容器。。。完成
附加到mynode容器
mynode容器|正在创建节点。。
mynode容器|正在运行的节点。。
^突然停止。。。(再次按Ctrl+C以强制)
正在停止mynode容器。。。完成
$
我试过:

  • 将呼叫移动到
    signal.signal
  • 使用
    atexit
    代替
    信号
  • 使用
    docker stop
    docker kill--signal
我也检查了这个问题,但没有明确的解决方案,我不确定使用ROS/rclpy是否会使我的设置有所不同(另外,我的主机是Ubuntu 18.04,而该用户在Windows上)


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

entrypoint: /bin/bash -c "/run-node.sh"
由于这是一个裸字符串,Docker将其包装在一个
/bin/sh-c
包装器中。因此,容器的主要过程如下

/bin/sh-c'/bin/bash-c”/run node.sh“
反过来,bash脚本保持运行。它启动一个Python脚本,并作为其父脚本一直运行,直到该脚本退出。(两个级别的
sh-c
wrappers可以运行,也可以不运行。)

这里重要的一点是,这个包装器外壳(而不是您的脚本)是接收信号的主容器进程,并且(事实证明)

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

exec python3/nodes/mynode.py
可能会有帮助


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

#/垃圾箱/垃圾箱
#进行设置
#(“.”与“来源”相同,但为标准)
. “/opt/ros/$ros_发行版/setup.bash”
#运行主命令
执行官“$@”
同样,主脚本应该以“shebang”行开头,如

#/usr/bin/env蟒蛇3
导入。。。
您的Dockerfile已经包含可以直接运行包装器的设置,您可能需要一个类似的主脚本
runchmod
行。但是你可以加上

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

docker运行--rm-it mynode sh