4

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

问题是当我使用docker-compose. 当容器被停止/杀死时,我似乎无法捕捉到“时刻”。我已经STOPSIGNAL 在 Dockerfile 和 docker stop_signal-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",我收到“正在停止节点..”消息。但是当我这样做时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 stopdocker kill --signal

我还在docker 容器中检查了这个 Python,优雅地停止了问题,但那里没有明确的解决方案,而且我不确定使用 ROS/rclpy 是否会使我的设置有所不同(另外,我的主机是 Ubuntu 18.04,而那个用户是在 Windows 上)。

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

4

1 回答 1

5

当你的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包装器可能会或可能不会继续运行。)

这里重要的部分是这个包装外壳,而不是你的脚本,是接收信号的主要容器进程,并且(事实证明)不会接收 SIGTERM 除非它被明确编码为.

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

exec python3 /nodes/mynode.py

可能会有所帮助。


我会在这里更进一步,确保尽可能多的代码内置到您的 Docker 映像中,并尽量减少显式 shell 包装器的数量。“做一些初始化,然后做一些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 包装器。由于您的入口点脚本现在将运行任何命令,因此很容易单独更改它。例如,如果您想要一个完成环境变量设置的交互式 shell 来尝试调试容器启动,您可以只覆盖命令部分

docker run --rm -it mynode sh
于 2019-06-15T10:21:14.403 回答