当运行docker compose up时,如何优雅地停止停靠的Python ROS2节点?
我在Docker容器中运行了一个基于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)
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