在通过docker-compose up运行时,如何优雅地停止Dockerized Python ROS2节点?

时间:2019-06-15 07:33:21

标签: python python-3.x docker docker-compose ros2

我在Docker容器中运行了一个基于Python的ROS2节点,并且我试图通过捕获SIGTERM / SIGINT信号和/或捕获{ {1}}异常。

问题是当我使用KeyboardInterrupt在容器中运行节点时。当容器停止/被杀死时,我似乎无法抓住“片刻”。我在Dockerfile中明确添加了STOPSIGNAL ,在docker-compose文件中明确添加了stop_signal

这是节点代码的示例:

docker-compose

以下是一个示例Dockerfile来重新创建映像:

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()

这是示例docker-compose.yml:

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

这是run-node.sh脚本:

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

当我手动运行容器内的节点(使用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"时,通过 Ctrl + C或docker-compose up停止容器时,我再也看不到该消息。

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
  • 使用signaldocker stop

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

是否可以通过我的docker kill --signal方法捕获容器的停止?

1 个答案:

答案 0 :(得分: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映像中,并尽量减少显式外壳包装程序的数量。 “先进行一些初始化,然后exec”是一种非常普遍的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包装器。由于您的入口点脚本现在可以运行任何命令,因此可以轻松地单独进行更改。例如,如果您希望完成环境变量设置的交互式外壳程序尝试调试容器启动,则可以仅覆盖命令部分

docker run --rm -it mynode sh