# 멀티 노드 클러스터 설정

#### 클러스터 설정의 개요

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 설정을 통해 통신 품질을 최적화할 수 있다.

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

```cpp
#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에서는 네임스페이스를 사용하여 노드를 그룹화하고, 동일한 네임스페이스 내에서 통신하는 노드 간의 충돌을 방지할 수 있다.

**네임스페이스의 활용 예제**

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

```cpp
#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**: 데이터 전송이 신뢰성 있게 이루어질지 여부를 설정한다.
   * `Reliable`: 데이터 전송을 신뢰성 있게 처리하며, 데이터 손실을 최소화한다.
   * `Best Effort`: 데이터 전송 시 손실이 발생할 수 있지만, 더 빠른 전송이 가능한다.
2. **Durability**: 노드가 데이터를 얼마나 오래 유지할지를 설정한다.
   * `Transient Local`: 노드가 종료된 후에도 데이터가 일정 시간 동안 유지된다.
   * `Volatile`: 노드가 종료되면 데이터가 바로 삭제된다.
3. **History**: 퍼블리셔가 얼마나 많은 데이터를 유지할지를 설정한다.
   * `Keep Last`: 마지막 N개의 데이터를 유지한다.
   * `Keep All`: 모든 데이터를 유지한다.
4. **Depth**: 퍼블리셔가 구독자에게 보낼 수 있는 메시지의 큐 크기를 설정한다.

**QoS 설정 예제**

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

```cpp
#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에서 인증서를 생성하는 명령은 다음과 같다.

```bash
# CA 생성
ros2 security create_keystore ~/keystore

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

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

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

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

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

**네트워크 트래픽 제어 예제**

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

```cpp
#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. **분산 데이터베이스 활용**: 분산 데이터베이스를 통해 노드 간의 데이터 일관성을 유지하고, 여러 노드에서 동시에 데이터를 처리할 수 있도록 한다.
