클러스터 설정의 개요

ROS2에서는 네트워크 상에서 다수의 노드를 동시에 운영하여 분산된 시스템을 구현할 수 있다. 이는 멀티 노드 클러스터를 형성하여 각 노드가 서로 데이터를 주고받으며 통신하는 환경을 구성하는 방식이다. 멀티 노드 클러스터를 설정할 때 중요한 것은 DDS(Datapath Discovery Service) 기반의 ROS2 통신 방식이 적용된다는 점이다. 이를 활용하여 분산된 환경에서 성능을 최적화하고, 통신의 안정성을 보장할 수 있다.

DDS의 이해

ROS2의 통신은 DDS를 기반으로 하고 있으며, RTPS(Real-Time Publish-Subscribe Protocol)가 이를 지원한다. 이는 퍼블리셔와 서브스크라이버 간의 통신을 중개하며, 멀티 노드 환경에서의 메시지 전달 및 데이터 공유를 담당한다.

ROS2 멀티 노드 클러스터 환경 구성 요소

ROS2 멀티 노드 클러스터 환경을 구성하기 위해 필요한 주요 요소는 다음과 같다: 1. 노드(Node): 클러스터 내에서 실행되는 각각의 ROS2 노드이다. 각 노드는 데이터를 퍼블리시하거나 구독할 수 있다. 2. 토픽(Topic): 노드 간 데이터를 교환하기 위한 통신 채널이다. 3. 네임스페이스(Namespace): 클러스터 내에서 각 노드 및 토픽을 구분하는 데 사용된다. 4. QoS(품질 서비스) 설정: 데이터 전달의 신뢰성, 대역폭, 지연 등을 설정한다.

멀티 노드 클러스터의 통신 모델

멀티 노드 클러스터에서는 각 노드가 서로 데이터를 교환할 수 있도록 통신 모델을 설정한다. 여기서 중요한 개념은 퍼블리셔(Publisher)서브스크라이버(Subscriber) 모델이다. 퍼블리셔는 데이터를 송신하고, 서브스크라이버는 그 데이터를 수신한다. 이때 QoS 설정을 통해 통신 품질을 최적화할 수 있다.

퍼블리셔와 서브스크라이버 예제

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

class MultiNodePublisher : public rclcpp::Node
{
public:
    MultiNodePublisher() : Node("multi_node_publisher")
    {
        publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
        timer_ = this->create_wall_timer(500ms, std::bind(&MultiNodePublisher::publishMessage, this));
    }

private:
    void publishMessage()
    {
        auto message = std_msgs::msg::String();
        message.data = "Hello from the publisher!";
        RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
        publisher_->publish(message);
    }

    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
    rclcpp::TimerBase::SharedPtr timer_;
};

class MultiNodeSubscriber : public rclcpp::Node
{
public:
    MultiNodeSubscriber() : Node("multi_node_subscriber")
    {
        subscription_ = this->create_subscription<std_msgs::msg::String>(
            "topic", 10, std::bind(&MultiNodeSubscriber::messageCallback, this, std::placeholders::_1));
    }

private:
    void messageCallback(const std_msgs::msg::String::SharedPtr msg) const
    {
        RCLCPP_INFO(this->get_logger(), "Received: '%s'", msg->data.c_str());
    }

    rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};

int main(int argc, char **argv)
{
    rclcpp::init(argc, argv);
    auto publisher_node = std::make_shared<MultiNodePublisher>();
    auto subscriber_node = std::make_shared<MultiNodeSubscriber>();
    rclcpp::spin(publisher_node);
    rclcpp::spin(subscriber_node);
    rclcpp::shutdown();
    return 0;
}

네트워크 구조와 연결 설정

ROS2 클러스터에서는 각 노드가 네트워크 상에서 특정 IP 주소와 포트를 통해 통신하게 된다. 클러스터 환경에서 노드들이 서로를 발견하고 연결을 설정하기 위해서는 피어 디스커버리(Peer Discovery)가 필요하다. DDS는 이러한 피어 디스커버리 과정을 자동으로 처리한다.

피어 디스커버리 과정

  1. Initial Discovery Phase: 각 노드는 시작 시 네트워크 상의 다른 노드들을 찾기 위해 멀티캐스트(Multicast) 패킷을 보낸다.
  2. Peer Announcement: 노드를 찾은 후, 해당 노드의 위치와 통신 정보를 공유한다.
  3. Session Establishment: 노드 간 통신이 시작되며, 각 노드는 서로에게 연결 요청을 보내고 응답을 받는다.

이 과정을 통해 클러스터 내 모든 노드는 서로를 발견하고 통신할 수 있는 상태가 된다.

클러스터에서의 네임스페이스 설정

네임스페이스는 멀티 노드 클러스터에서 매우 중요한 역할을 한다. 여러 노드가 동일한 토픽 이름을 사용할 수 있으나, 각기 다른 네임스페이스를 사용하여 충돌을 피할 수 있다. ROS2에서는 네임스페이스를 사용하여 노드를 그룹화하고, 동일한 네임스페이스 내에서 통신하는 노드 간의 충돌을 방지할 수 있다.

네임스페이스의 활용 예제

다음 예제는 동일한 클러스터 내에서 다른 네임스페이스를 사용하는 퍼블리셔와 서브스크라이버의 구현이다.

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

class NamespacePublisher : public rclcpp::Node
{
public:
    NamespacePublisher() : Node("namespace_publisher", "/namespace1")
    {
        publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
        timer_ = this->create_wall_timer(500ms, std::bind(&NamespacePublisher::publishMessage, this));
    }

private:
    void publishMessage()
    {
        auto message = std_msgs::msg::String();
        message.data = "Hello from namespace1!";
        RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
        publisher_->publish(message);
    }

    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
    rclcpp::TimerBase::SharedPtr timer_;
};

class NamespaceSubscriber : public rclcpp::Node
{
public:
    NamespaceSubscriber() : Node("namespace_subscriber", "/namespace2")
    {
        subscription_ = this->create_subscription<std_msgs::msg::String>(
            "/namespace1/topic", 10, std::bind(&NamespaceSubscriber::messageCallback, this, std::placeholders::_1));
    }

private:
    void messageCallback(const std_msgs::msg::String::SharedPtr msg) const
    {
        RCLCPP_INFO(this->get_logger(), "Received: '%s'", msg->data.c_str());
    }

    rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};

int main(int argc, char **argv)
{
    rclcpp::init(argc, argv);
    auto publisher_node = std::make_shared<NamespacePublisher>();
    auto subscriber_node = std::make_shared<NamespaceSubscriber>();
    rclcpp::spin(publisher_node);
    rclcpp::spin(subscriber_node);
    rclcpp::shutdown();
    return 0;
}

이 예제에서는 퍼블리셔가 /namespace1 네임스페이스에서 실행되고, 서브스크라이버는 /namespace2 네임스페이스에서 실행된다. 이를 통해 여러 네임스페이스가 같은 클러스터 내에서 충돌 없이 운영될 수 있다.

네트워크 성능 최적화를 위한 QoS 설정

ROS2 클러스터에서 네트워크 성능을 최적화하기 위해서는 QoS(품질 서비스) 설정이 매우 중요하다. ROS2에서는 다양한 QoS 설정을 통해 네트워크 트래픽을 제어하고, 성능과 신뢰성을 균형 있게 유지할 수 있다. QoS는 주로 네트워크 지연 시간, 데이터 손실 방지, 전송 대역폭 등을 제어하는 데 사용된다.

주요 QoS 정책

  1. Reliability: 데이터 전송이 신뢰성 있게 이루어질지 여부를 설정한다.
  2. Reliable: 데이터 전송을 신뢰성 있게 처리하며, 데이터 손실을 최소화한다.
  3. Best Effort: 데이터 전송 시 손실이 발생할 수 있지만, 더 빠른 전송이 가능한다.

  4. Durability: 노드가 데이터를 얼마나 오래 유지할지를 설정한다.

  5. Transient Local: 노드가 종료된 후에도 데이터가 일정 시간 동안 유지된다.
  6. Volatile: 노드가 종료되면 데이터가 바로 삭제된다.

  7. History: 퍼블리셔가 얼마나 많은 데이터를 유지할지를 설정한다.

  8. Keep Last: 마지막 N개의 데이터를 유지한다.
  9. Keep All: 모든 데이터를 유지한다.

  10. Depth: 퍼블리셔가 구독자에게 보낼 수 있는 메시지의 큐 크기를 설정한다.

QoS 설정 예제

다음은 퍼블리셔와 서브스크라이버에서 QoS를 설정하는 예제이다.

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

class QoSPublisher : public rclcpp::Node
{
public:
    QoSPublisher() : Node("qos_publisher")
    {
        rclcpp::QoS qos_profile(10);
        qos_profile.reliability(RMW_QOS_POLICY_RELIABILITY_RELIABLE);
        qos_profile.durability(RMW_QOS_POLICY_DURABILITY_TRANSIENT_LOCAL);
        publisher_ = this->create_publisher<std_msgs::msg::String>("qos_topic", qos_profile);
        timer_ = this->create_wall_timer(500ms, std::bind(&QoSPublisher::publishMessage, this));
    }

private:
    void publishMessage()
    {
        auto message = std_msgs::msg::String();
        message.data = "Hello with QoS!";
        RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
        publisher_->publish(message);
    }

    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
    rclcpp::TimerBase::SharedPtr timer_;
};

class QoSSubscriber : public rclcpp::Node
{
public:
    QoSSubscriber() : Node("qos_subscriber")
    {
        rclcpp::QoS qos_profile(10);
        qos_profile.reliability(RMW_QOS_POLICY_RELIABILITY_RELIABLE);
        subscription_ = this->create_subscription<std_msgs::msg::String>(
            "qos_topic", qos_profile, std::bind(&QoSSubscriber::messageCallback, this, std::placeholders::_1));
    }

private:
    void messageCallback(const std_msgs::msg::String::SharedPtr msg) const
    {
        RCLCPP_INFO(this->get_logger(), "Received: '%s'", msg->data.c_str());
    }

    rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};

int main(int argc, char **argv)
{
    rclcpp::init(argc, argv);
    auto publisher_node = std::make_shared<QoSPublisher>();
    auto subscriber_node = std::make_shared<QoSSubscriber>();
    rclcpp::spin(publisher_node);
    rclcpp::spin(subscriber_node);
    rclcpp::shutdown();
    return 0;
}

이 예제에서는 퍼블리셔와 서브스크라이버 모두 Reliable QoS와 Transient Local 설정을 사용한다. 이 설정은 네트워크 상에서 데이터 손실을 최소화하며, 퍼블리셔가 종료된 후에도 데이터를 유지하도록 한다.

멀티 노드 클러스터의 보안 설정

멀티 노드 클러스터 환경에서는 노드 간의 통신이 다양한 네트워크 상에서 이루어지기 때문에 보안 설정이 중요하다. ROS2에서는 SROS2(Secure ROS2)라는 보안 프레임워크를 제공하여 노드 간의 통신을 보호할 수 있다. 이를 통해 메시지의 암호화, 인증, 권한 부여 등을 구현할 수 있으며, 민감한 데이터가 외부로 유출되는 것을 방지할 수 있다.

SROS2 보안 요소

  1. 암호화(Encryption): 노드 간 전송되는 모든 데이터를 암호화하여, 외부에서 메시지를 가로채더라도 내용이 노출되지 않도록 한다.
  2. 인증(Authentication): 각 노드는 인증서를 통해 자신의 신원을 증명하며, 인증되지 않은 노드는 통신할 수 없다.
  3. 권한 부여(Authorization): 노드가 서로 통신할 수 있는 권한을 설정하여, 특정 노드만 허용된 데이터를 주고받을 수 있도록 제한한다.

SROS2 인증서 생성 및 배포

SROS2에서는 X.509 인증서를 사용하여 노드 간의 통신을 보호한다. 인증서를 생성하고 각 노드에 배포하는 과정을 통해 보안을 설정할 수 있다. 인증서를 생성하는 과정은 다음과 같다.

  1. CA(Certificate Authority) 생성: 인증서를 발급할 CA를 먼저 생성한다.
  2. 각 노드의 키 및 인증서 생성: 각 노드에 대해 고유한 키와 인증서를 생성한다.
  3. 인증서 배포: 생성된 인증서를 각 노드에 배포하고, 노드 간에 통신할 수 있도록 설정한다.

SROS2에서 인증서를 생성하는 명령은 다음과 같다.

# CA 생성
ros2 security create_keystore ~/keystore

# 노드별 인증서 생성
ros2 security create_key ~/keystore /my_node

생성된 인증서는 keystore 폴더에 저장되며, 이를 통해 노드 간 통신이 안전하게 보호된다.

멀티 노드 클러스터에서의 네트워크 트래픽 최적화

멀티 노드 클러스터에서 성능을 최적화하기 위해서는 네트워크 트래픽 제어가 필수적이다. 노드 간에 많은 양의 데이터를 교환할 경우, 네트워크 혼잡이 발생하여 성능이 저하될 수 있기 때문에 QoS 설정 외에도 다음과 같은 최적화 전략을 고려해야 한다.

  1. 주기적 데이터 전송 제한: 필요하지 않은 경우 주기적인 데이터를 전송하지 않고, 이벤트 기반으로 데이터를 전송하는 것이 트래픽을 줄이는 데 도움이 된다.
  2. 메시지 압축: 전송되는 데이터를 압축하여 대역폭을 절약할 수 있다.
  3. 로컬 캐시 활용: 동일한 데이터를 여러 노드에서 구독하는 경우, 데이터를 중복 전송하지 않고 로컬 캐시를 활용하여 트래픽을 줄일 수 있다.
  4. 데이터 필터링: 불필요한 데이터는 전송하지 않고, 필요한 데이터만 전송하여 네트워크 부하를 줄이다.

네트워크 트래픽 제어 예제

ROS2의 네트워크 트래픽을 제어하기 위해, QoS 설정과 함께 데이터를 필터링하는 방법을 사용할 수 있다. 아래 예제는 퍼블리셔에서 데이터를 필터링하여 필요한 경우에만 데이터를 전송하는 방식이다.

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

class FilteredPublisher : public rclcpp::Node
{
public:
    FilteredPublisher() : Node("filtered_publisher")
    {
        publisher_ = this->create_publisher<std_msgs::msg::String>("filtered_topic", 10);
        timer_ = this->create_wall_timer(1000ms, std::bind(&FilteredPublisher::publishMessage, this));
    }

private:
    void publishMessage()
    {
        if (shouldPublish()) // 조건에 따라 메시지를 필터링
        {
            auto message = std_msgs::msg::String();
            message.data = "Filtered data published!";
            RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
            publisher_->publish(message);
        }
    }

    bool shouldPublish()
    {
        // 예: 임의의 조건에 따라 데이터를 필터링
        return true;
    }

    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
    rclcpp::TimerBase::SharedPtr timer_;
};

int main(int argc, char **argv)
{
    rclcpp::init(argc, argv);
    auto publisher_node = std::make_shared<FilteredPublisher>();
    rclcpp::spin(publisher_node);
    rclcpp::shutdown();
    return 0;
}

이 예제에서는 shouldPublish() 함수에서 조건을 설정하여, 조건이 충족되는 경우에만 데이터를 퍼블리싱하는 방식으로 네트워크 트래픽을 제어한다.

멀티 노드 클러스터의 확장성 문제

멀티 노드 클러스터를 확장할 때는 네트워크 성능, 데이터 처리량, 노드 간의 통신 지연 등을 고려해야 한다. 클러스터 내 노드 수가 증가하면, 네트워크 부하와 통신 지연이 증가할 수 있다. 이를 해결하기 위한 몇 가지 전략을 소개한다.

  1. 노드 분할 및 분산 처리: 각 노드를 역할에 따라 분할하고, 클러스터 내 다른 장치로 분산 배치하여 부하를 분산시킬 수 있다.
  2. 데이터 전송 간소화: 대량의 데이터를 전송하는 대신, 필요한 데이터만 선택적으로 전송하는 방식으로 트래픽을 줄일 수 있다.
  3. 분산 네트워크 구조: ROS2의 DDS 프로토콜을 활용하여 분산 네트워크를 구축하고, 노드 간의 효율적인 데이터 전송을 도모할 수 있다.

분산 시스템에서의 데이터 일관성 유지

멀티 노드 클러스터 환경에서는 분산된 노드들이 동일한 데이터를 처리할 수 있도록 일관성을 유지하는 것이 중요하다. 특히 센서 데이터와 같은 실시간 데이터를 여러 노드에서 동시에 처리할 경우, 데이터 일관성 문제가 발생할 수 있다.

데이터 일관성 문제 해결 방법

  1. 중앙 데이터 서버 활용: 클러스터 내에서 데이터를 중앙 서버에서 관리하고, 각 노드는 서버에서 최신 데이터를 가져오도록 설정할 수 있다.
  2. 데이터 동기화: 분산된 노드 간의 데이터를 주기적으로 동기화하여, 모든 노드가 동일한 데이터를 처리할 수 있도록 한다.
  3. 분산 데이터베이스 활용: 분산 데이터베이스를 통해 노드 간의 데이터 일관성을 유지하고, 여러 노드에서 동시에 데이터를 처리할 수 있도록 한다.