가상 머신과 Docker

|

가상 머신과 Docker

Docker는 현재까지 많이 사용되어왔던 VMware, Microsoft Hyper-V, VirtualBox, Xen, 리눅스 KVM 등이 가상 머신과 비슷합니다.

가상 머신에 리눅스를 설치하고 다양한 서버 프로그램 및 DB를 설치합니다. 그리고 그 위에 App 또는 웹서비스 등을 실행했습니다. 이렇게 만들어진 가상 머신 이미지를 여러 서버에 복사해서, 이미지 한 장으로 서버를 계속해서 만들어낼 수 있습니다.


가상 머신

가상 머신은 간편하긴 했지만 성능면에서 부족한 부분이 많았습니다. 가상화 기술은 1960년대에 나왔지만 초기의 가상 머신들은 게스트의 하드웨어와 명령어(Instruction)를 전부 에뮬레이팅해야 했기 때문에 속도가 아주 느렸습니다.

이후 인텔과 AMD에서 CPU 차원의 가상화를 지원하기 시작했습니다. Intel VT-x나 AMD-V라는 기술로 HVM(Hardware Virtual Machine)이 가능해졌습니다. CPU의 하이퍼바이저(Hypervisor)가 하드웨어와 명령어를 빠른 속도로 처리해줄 수 있어서 성능이 많이 올라갔고 이러한 방식을 전가상화(Full Virtualization)이라고 했습니다.

Image

그러다가 Xen(젠)이라는 소프트웨어기반 하이버파이저가 나오면서 가상화에 획기적인 성능 향상이 이루어졌습니다. 이 방식은 게스트 OS를 수정하여 호스트와 동일한 성능을 내도록 하는 방법이었고, 성능 덕에 아주 큰 인기를 끌게 되었습니다. 이를 반가상화(Paravirtualization)이라고 합니다. 커널 수정이 필요한 단점이 있었지만, 서버의 OS를 Linux를 많이 쓰게 되고 Linux가 오픈 소스였기 때문에 큰 문제가 되지 않았습니다.


Docker

Docker는 반가상화보다 더 경량화된 방식입니다.

Image

위 그림과 같이 게스트 OS를 아예 설치하지 않습니다. 그 대신 서버 운영에 필요한 프로그램들과 라이브러리들만 설치할 수 있고, 시스템콜(Systemcall)과 같은 OS 자원은 호스트 OS와 공유합니다. 덕분에 이미지의 용량이 크게 줄어들었습니다.

Docker는 하드웨어 가상화가 없기 때문에 메모리 접근, 파일 시스템, 네트워크 속도가 기존의 가상 머신들에 비해 월등히 빠릅니다.

또한 Docker는 가상 머신과 달리 이미지의 용량이 작기 때문에 이미지의 생성/배포에 특화된 기능을 제공합니다. 하나의 파일로 이미지가 관리되기 때문에 버전 관리가 용이하고 Push/Pull 등의 명령어를 통해 이미지 업로드/다운로드가 자유롭습니다.

Docker는 베이스 이미지(Base image)로부터 새로운 이미지를 쉽게 만들 수 있습니다. 이 때 바뀐 부분만 이미지로 생성하고, 나중에 실행할 때는 베이스 이미지와 바뀐 부분을 합쳐서 실행합니다. 이런식으로 바뀐 부분들을 Layer라고 합니다.

Image


Docker 컨테이너

Docker 컨테이너는 이미지를 실행한 상태를 말합니다. 하나의 이미지로 여러 개의 컨테이너를 만들 수 있습니다.

ROS 용어 정리

|

ROS Terminology

노드(Node)

노드는 ROS에서 최소 단위의 실행 프로세스를 가리키는 용어입니다. 하나의 프로그램이라고 생각하면 되며, ROS에서는 하나의 목적에 하나의 노드를 개발하는 것을 추천하고 있습니다.

노드들을 관리하는 마스터(Master)가 있는데, 각 노드들은 생성되면서 마스터에 발행자(Publisher), 구독자(Subscriber), 토픽(Topic), 서비스의 각 이름, 메시지 형태, URI 주소와 포트 등을 등록합니다. 이 정보들을 기반으로 각 노드들끼리 서로 통신을 할 수 있습니다.

노드들은 마스터와 통신할 때는 XMLRPC 프로토콜을 이용하며, 각 노드간 통신에서는 TCP/IP 계열의 TCPROS 또는 XMLRPC를 사용합니다. 노드간 접속 요청 및 응답은 마스터를 거치기 때문에 XMLRPC를 사용하며, 메시지 통신은 마스터를 거치지 않고 바로 통신하기 때문에 TCPROS를 사용합니다.


패키지(Package)

패키지는 ROS를 구성하는 기본 단위입니다. 패키지에는 하나 이상의 노드가 포함됩니다.


마스터(Master)

마스터는 각 노드간 발견을 할 수 있도록 도와주는 네임 서버(Name Server)와 같은 역할을 합니다. XMLRPC 통신을 이용해서 각 노드들과 통신합니다.


메시지(Message)

메시지는 각 노드간 주고받는 데이터를 말하며, 그 안에 다양한 데이터 타입들을 정의해서 사용할 수 있습니다. 메시지를 사용하는 통신 방법으로 TCPROS, UDPROS 방식등이 있습니다.


토픽(Topic)

토픽은 어떠한 메시지를 전달할 ‘주제’입니다. 발행자(Publisher)는 특정 토픽에 대해 메시지를 발행을 하며, 구독자(Subscriber)는 특정 주제에 대해 구독을 함으로써 해당 메시지를 전달받을 수 있습니다. 구독자가 마스터에게 특정 토픽에 대한 구독을 요청하면, 마스터는 등록되어 있는 발행자 정보를 구독자에게 전달하고, 구독자는 발행자 노드에 직접 연결해서 메시지를 전달받을 수 있게 됩니다.


발행자(Publisher) 및 구독자(Subscriber)

발행자는 특정 토픽에 대해 메시지를 전송하는 노드를 말하며, 구독자는 특정 토픽에 대한 메시지를 전달받는 노드를 말합니다.


서비스(Service)

발행자/구독자의 통신은 비동기 방식으로 이루어집니다. 따라서 일반적으로 아주 훌륭하게 동작되지만, 경우에 따라서는 요청과 응답이 동시에 이루어져야하는 동기 방식이 필요할 때도 있습니다. ROS에서는 서비스라는 이름으로 메시지 동기 방식을 제공합니다.

서비스는 요청이 있을 때 응답하는 서비스 서버와 요청하고 응답받는 서비스 클라이언트로 나누어집니다.

서비스는 토픽과는 다른 일회성 메시지 통신이며, 요청과 응답이 완료되면 두 노드간의 연결은 끊어집니다.

ROS 2.0 기반 QT Widget 빌드용 CMakeFileList.txt

|

ROS 2.0 with QT

ROS 2.0에서 QT Widget 프로그램을 빌드하는 방법에 대해 포스팅합니다.

먼저 QT Widget 샘플 코드입니다. QTCreator를 이용해서 생성한 프로젝트라서 mainwindow.h, mainwindow.cpp, mainwindow.ui 등의 파일도 생성이 되는데 여기서는 CMakeLists.txt 파일에 대해서만 포스팅합니다.

CMakeLists.txt

cmake_minimum_required(VERSION 3.5)
project(qt_widget)

set(REQUIRED_QT_VERSION 5.9.0)

# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 14)
endif()

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic -fPIC)
endif()

set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)

find_package(Qt5Core REQUIRED)
find_package(Qt5Widgets REQUIRED)
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)

add_executable(${PROJECT_NAME} main.cpp mainwindow.cpp)

ament_target_dependencies(${PROJECT_NAME} rclcpp Qt5Core Qt5Widgets)

install(TARGETS ${PROJECT_NAME}
  DESTINATION lib/${PROJECT_NAME})

ament_package()

QT Console 프로그램을 ROS 2.0으로 빌드하기

|

ROS 2.0 with QT

ROS 2.0에서 QT 콘솔 프로그램을 빌드하는 방법에 대해 포스팅합니다.

먼저 QT 콘솔 샘플 코드입니다.

main.cpp

#include <QCoreApplication>
#include <QDebug>

int main(int argc, char *argv[]) {
  QCoreApplication a(argc, argv);

  qDebug() << "snowdeer QT Console";

  return a.exec();
}


CMakeLists.txt

cmake_minimum_required(VERSION 3.5)
project(qt_console)

set(REQUIRED_QT_VERSION 5.9.0)

# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 14)
endif()

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic -fPIC)
endif()

set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)

find_package(Qt5Core REQUIRED)
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)

add_executable(${PROJECT_NAME} main.cpp)

ament_target_dependencies(${PROJECT_NAME} rclcpp Qt5Core)

install(TARGETS ${PROJECT_NAME}
  DESTINATION lib/${PROJECT_NAME})

ament_package()

Simple Message Publisher 및 Subscriber 예제 (Node 클래스 상속)

|

간단하게 메시지를 발행/수신하는 예제 코드는 다음과 같습니다.

snow_publisher_using_class

package.xml

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format2.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="2">
  <name>snow_publisher</name>
  <version>0.4.0</version>
  <description>Snow ROS Message Publisher</description>
  <maintainer email="snowdeer0314@gmail.com">snowdeer</maintainer>
  <license>Apache License 2.0</license>

  <buildtool_depend>ament_cmake</buildtool_depend>

  <build_depend>rclcpp</build_depend>
  <build_depend>std_msgs</build_depend>

  <exec_depend>rclcpp</exec_depend>
  <exec_depend>std_msgs</exec_depend>
  
  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>


CMakeLists.txt

cmake_minimum_required(VERSION 3.5)
project(snow_publisher_using_class)

# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 14)
endif()

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

add_executable(${PROJECT_NAME} main.cpp)
ament_target_dependencies(${PROJECT_NAME} rclcpp std_msgs)

install(TARGETS ${PROJECT_NAME}
  DESTINATION lib/${PROJECT_NAME})

ament_package()


main.cpp

#include <cstdio>

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

using namespace std;
using namespace std::chrono_literals;

class SnowPublisher : public rclcpp::Node {
public:
  SnowPublisher()
  : Node("minimal_publisher") {
    mCount = 0;
    mPublisher = this->create_publisher<std_msgs::msg::String>("snowdeer_channel");
    mTimer = this->create_wall_timer(
      1000ms, std::bind(&SnowPublisher::publish_message, this));
  }

private:
  int mCount;
  rclcpp::TimerBase::SharedPtr mTimer;
  rclcpp::Publisher<std_msgs::msg::String>::SharedPtr mPublisher;
  
  void publish_message();
 
};

void SnowPublisher::publish_message() {
  mCount++;

  auto message = std_msgs::msg::String();
  message.data = "Hello, SnowDeer! " + std::to_string(mCount);

  RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str())
  mPublisher->publish(message);
}

int main(int argc, char * argv[]) {
  
  printf("This is SnowDeer's ROS Message Publisher.\n");

  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<SnowPublisher>());
  rclcpp::shutdown();

  return 0;
}


snow_subscriber_using_class

package.xml

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format2.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="2">
  <name>snow_subscriber</name>
  <version>0.4.0</version>
  <description>Snow ROS Message Subscriber</description>
  <maintainer email="snowdeer0314@gmail.com">snowdeer</maintainer>
  <license>Apache License 2.0</license>

  <buildtool_depend>ament_cmake</buildtool_depend>

  <build_depend>rclcpp</build_depend>
  <build_depend>std_msgs</build_depend>

  <exec_depend>rclcpp</exec_depend>
  <exec_depend>std_msgs</exec_depend>
  
  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>


CMakeLists.txt

cmake_minimum_required(VERSION 3.5)
project(snow_subscriber_using_class)

# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 14)
endif()

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

add_executable(${PROJECT_NAME} main.cpp)
ament_target_dependencies(${PROJECT_NAME} rclcpp std_msgs)

install(TARGETS ${PROJECT_NAME}
  DESTINATION lib/${PROJECT_NAME})

ament_package()


main.cpp

#include <cstdio>

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

using namespace std;
using std::placeholders::_1;

class SnowSubscriber : public rclcpp::Node {
public:
  SnowSubscriber()
  : Node("snowdeer_msg_subscriber") {
    mSubscriber = this->create_subscription<std_msgs::msg::String>(
      "snowdeer_channel", std::bind(&SnowSubscriber::receive_message, this, _1));
  }

private:
  rclcpp::Subscription<std_msgs::msg::String>::SharedPtr mSubscriber;

  void receive_message(const std_msgs::msg::String::SharedPtr msg);
  
};

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

int main(int argc, char * argv[]) {
  
  printf("This is SnowDeer's ROS Message Subscriber.\n");

  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<SnowSubscriber>());
  rclcpp::shutdown();

  return 0;
}