使用 docker-compose up 运行时如何优雅地停止 Dockerized Python ROS2 节点?
Posted
技术标签:
【中文标题】使用 docker-compose up 运行时如何优雅地停止 Dockerized Python ROS2 节点?【英文标题】:How to gracefully stop a Dockerized Python ROS2 node when run with docker-compose up? 【发布时间】:2019-06-15 07:33:21 【问题描述】:我有一个在 Docker 容器中运行的基于 Python 的 ROS2 节点,我试图通过捕获 SIGTERM
/SIGINT
信号和/或捕获 KeyboardInterrupt
异常来处理节点的正常关闭。
问题是当我使用docker-compose
在容器中运行节点时。当容器被停止/杀死时,我似乎无法捕捉到“时刻”。我在 Dockerfile 中明确添加了 STOPSIGNAL
,在 docker-compose 文件中添加了 stop_signal
。
这是一个节点代码示例:
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"
时,我收到“正在停止节点..”消息。但是当我执行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
和docker kill --signal
我也检查了这个Python inside docker container, gracefully stop 问题,但那里没有明确的解决方案,我不确定使用 ROS/rclpy 是否会使我的设置有所不同(另外,我的主机是 Ubuntu 18.04,而该用户在窗户)。
是否可以在我的 stop_node
方法中捕捉到容器的停止?
【问题讨论】:
【参考方案1】:当您的 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
包装器的两个级别可能会或可能不会继续运行。)
这里的重要部分是这个包装外壳,而不是你的脚本,是接收信号的主要容器进程,并且(事实证明)won't receive SIGTERM unless it's explicitly coded to。
这里要做的最重要的重组是让你的包装脚本exec Python 脚本。这导致它替换包装器,因此它成为主进程并接收信号。如果没有其他事情将最后一行更改为
exec python3 /nodes/mynode.py
可能会有所帮助。
我会在这里更进一步,确保将尽可能多的代码内置到您的 Docker 映像中,并尽量减少显式 shell 包装器的数量。 “做一些初始化,然后exec
something”是一个非常常见的 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 包装器。由于您的入口点脚本现在将运行任何命令,因此很容易单独更改它。例如,如果你想要一个完成环境变量设置的交互式 shell 来尝试调试容器启动,你可以只覆盖命令部分
docker run --rm -it mynode sh
【讨论】:
以上是关于使用 docker-compose up 运行时如何优雅地停止 Dockerized Python ROS2 节点?的主要内容,如果未能解决你的问题,请参考以下文章
使用 docker-compose up 运行时如何优雅地停止 Dockerized Python ROS2 节点?
如何在系统启动时运行 docker-compose up -d?
Docker-compose up 在本地完美运行,但在 gcp 上出现错误无法将主机名 db 转换为地址:名称解析中的临时故障