LoginSignup
8
5

More than 3 years have passed since last update.

ROSプロジェクトをJetson TX2にCircleCIでcolcon bundleしてAWS Greengrass&RoboMakerでデプロイする

Last updated at Posted at 2019-12-30

はじめに

業務でROSプロジェクトをリリースするときに一番の悩みの種になるのが、CIとデプロイだと思います。数台であれば手動でセットアップしても管理可能だと思いますが、+100台のような大規模になる場合は、プロジェクトのバージョン、依存しているライブラリの管理、デプロイのタイミング等で考慮しなければいけないことが山のようにあります。今回は、NVIDIA JetsonにROSプロジェクトをリモートデプロイする手順をまとめました。

※本記事内容は2019年6月に検証した内容のため、公開時では最新情報と異なる可能性があります。実際に試すときは最新の情報をご確認してください。

前提知識

下記の知識があるとベストですが、なくても大筋は理解できるように基本的な部分から説明する予定です。不要な方は、読み飛ばしください。

  • ROSの仕組みの基礎的な知識
  • CircleCIの基礎的な知識
  • colconの基礎的な知識
  • AWS Greengrass & RoboMakerの基礎的な知識

環境

環境 バージョン
開発機 Ubuntu 16.04.6 LTS
ROS Kinetic Kame
colcon-core 0.3.22
colcon-ros 0.3.10
Jetson TX2 Linux tegra-ubuntu 4.4.38-tegra

事前調査

まずはじめに必要な情報をまとめます。

AWS RoboMakerでデプロイするには何をする必要があるか?

1つめのドキュメントで詳しく解説してあるため、こちらを読めばほぼ理解できるかと思います。結論からいうと、ROSの最新ビルドツールであるcolconのbundleという機能を用いてデプロイしていることがわかります。

Jetson TX2でROSを動かすために

Jetson TX2は、aarch64プラットフォームのため、ROSプロジェクトをどこかでクロスコンパイルする必要があります。RoboMakerでTurtleBot3にデプロイするでCloud9を使いクロスコンパイルする紹介記事がありますが、こちらはARMHFプラットフォームであり、aarch64は非対応でしたので今回は、CircleCIでクロスコンパイルしてbundleを行います。

CircleCIでarm64のdockerコンテナを動かすには

Support for ARM based Docker images. , Making your Docker images ARM compatible for Raspberry Piにもある通り、CircleCIでARMベースのコンテナを動かすことはできません。しかしCircleCIのExecutorタイプの選び方を読むと、Executorにはdocker・machine・macosの3つが存在し、machineを選択すると専用の仮想マシン環境(VM)が使用できることがわかります。つまり自分でコンテナさえ用意できればarm64でもCircleCIが使用できることがわかりました。

ROSのdockerイメージについて

ROSのオフィシャルイメージを確認すると、arm64v8というタグが確認でき、これがaarch64に対応しています。今回はこちらを元にコンテナを作成します。

これでタイトルの通り、ROSプロジェクトをJetson TX2にCircleCIでcolcon bundleしてAWS Greengrass&RoboMakerでデプロイできることがわかったので実際にデプロイをしてみましょう。

手順

これまでのドキュメントを見てきてわかる通り、いくつかの要素によって構成されているため少々複雑な手順となります。そのため今回は1つずつ順をおって構築までの手順を説明していこうと思います。

テスト用のROSプロジェクトを用意する

好きなプロジェクトを用意してください。ここではチュートリアルにある一番簡単なシンプルな配信者(Publisher)と購読者(Subscriber)を書く(C++)を用います。今回はせっかくCircleCIを用いるので一番簡単な下記のようなテストを用意しました。テストの書き方については、ソースを確認するのが一番だとおもいます。もしあまりROSプロジェクトの経験がない場合は、一度catkin_makeでコンパイルして動作を確認しましょう。

#include <ros/ros.h>
#include <gtest/gtest.h>
#include <std_msgs/String.h>

#include "common_talker.h"

#include <thread>
#include <atomic>
#include <chrono>

using namespace std;
using namespace std::chrono;
using namespace std::this_thread;

struct AnyHelper
{
  AnyHelper() : count(0)
  {
  }

  void cb(const std_msgs::String::ConstPtr& msg)
  {
    ROS_INFO("%s", msg->data.c_str());
    ++count;
  }

  uint32_t count;
};

class UTestSuite : public ::testing::Test
{
private:
public:
  UTestSuite() = default;
  ~UTestSuite() override = default;
};

TEST_F(UTestSuite, creationTest)
{
  ASSERT_NO_THROW(SampleTalker sample_node;) << "constcut node failed";
}

TEST_F(UTestSuite, addValueOne)
{
  SampleTalker sample_node;
  ASSERT_EQ(2, sample_node.countUp(1)) << "failed to count EXIT";
}

TEST(PublishTest, simplePubTest)
{
  ros::NodeHandle nh;
  AnyHelper h;

  SampleTalker sample_node;
  ros::Subscriber sub = nh.subscribe("chatter", 0, &AnyHelper::cb, &h);

  EXPECT_EQ(sub.getNumPublishers(), 1U);

  std_msgs::String new_msg;
  new_msg.data = "message:";

  sample_node.sendMessage();
  ros::Duration(1.0).sleep();

  ros::spinOnce();
  EXPECT_EQ(h.count, 1U);
}

int main(int argc, char** argv)
{
  ros::init(argc, argv, "common_test");
  testing::InitGoogleTest(&argc, argv);

  thread t([] {
    while (ros::ok())
      ros::spin();
  });

  auto res = RUN_ALL_TESTS();

  ros::shutdown();
  return res;
}

colconでビルドできるようにする

つぎにcolconでビルドをできるようにします。catkin_makeとcolconでは、実行ファイルを生成する仕組みが異なるため、実際にはinstallを自分で設定をする必要があります。今回は下記に簡単に追記例も示しますが、Optional Step: Specifying Installable Targetsを確認して自分に必要な設定を記述します。

install(TARGETS ${PROJECT_NAME} common_talker common_listener
 ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
 LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
 RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
 )

install(DIRECTORY include/${PROJECT_NAME}/
 DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
 FILES_MATCHING PATTERN "*.h"
 )

install(FILES
 launch/common.launch
 DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
 )

catkin_makeからcolconへの移行はいくつかハマりポイントがあるので、いつか記事をまとめようとおもいます。それでは、colconをインストールしてビルド&テスト&bundleしていきましょう。

$ sudo apt-get update
$ sudo apt-get install python3-pip python3-apt
$ pip3 install -U setuptools
$ pip3 install -U colcon-common-extensions colcon-ros-bundle
$ colcon build
$ colcon test
$ colcon test-result --all --verbose
$ colcon bundle

bundleはすべての依存関係を構築しているため多少時間がかかるかもしれません。ここまでエラーなくできたら、bundleしたパッケージでROSが動くか確認しましょう。まず作業用環境を作成します。

$ mkdir -p output_test
$ cd output_test

次に作業環境にoutput.tarをoutput_test移動させ、bundleしたパッケージの展開をします。詳細な手順と仕組みは、こちらで確認できます。

$ tar -xvf output.tar
$ mkdir -p dependencies
$ tar -zxvf dependencies.tar.gz -C dependencies/
$ mkdir -p workspace
$ tar -zxvf workspace.tar.gz -C workspace/

次に環境設定を行います。

$ BUNDLE_CURRENT_PREFIX=/home/wnwn/output_test/dependencies source /home/wnwn/output_test/dependencies/setup.sh
$ BUNDLE_CURRENT_PREFIX=/home/wnwn/output_test/workspace source /home/wnwn/output_test/workspace/setup.sh

最後に現状でrosのコマンドがbundle環境を正常に参照できているか確認します。

$ which roslaunch
/home/wnwn/output_test/opt/ros/kinetic/bin/roslaunch

これでビルドしたROSパッケージをroslaunchから起動できていればcolconへの移行作業はおわりです。

ローカルのdockerでarm64コンテナを動かす

x86_64のUbuntuでarm64のコンテナを使用するには、QEMUを使用します。

$ sudo docker pull arm64v8/ros:kinetic-ros-base
$ sudo docker run -it arm64v8/ros:kinetic-ros-base
standard_init_linux.go:211: exec user process caused "exec format error"
$ sudo docker ps -a
CONTAINER ID        IMAGE                          COMMAND                  CREATED             STATUS                      PORTS               NAMES
99f47fee2e9c        arm64v8/ros:kinetic-ros-base   "/ros_entrypoint.sh …"   24 seconds ago      Exited (1) 23 seconds ago                       distracted_rubin
$ sudo apt install qemu-user-static
$ sudo docker cp /usr/bin/qemu-aarch64-static 99f47fee2e9c:/usr/bin/qemu-aarch64-static
$ sudo docker commit 99f47fee2e9c arm64v8/ros:kinetic-ros-base-cross
sha256:4b09be00eb91a3202eb83103281ffc6e8e32632345b1b5b3b32ccecf3df53b15
$ sudo docker run -v ~/catkin_pkg:/root/ros_ws/ -it arm64v8/ros:kinetic-ros-base-cross
$ apt-get update
$ apt-get install python3-pip python3-apt
$ pip3 install -U setuptools
$ pip3 install -U colcon-common-extensions colcon-ros-bundle
$cd ~/ros_ws
$ colcon build
$ colcon test
$ colcon test-result --all --verbose

上記でビルドしたパッケージが実行できれば問題ありませんが、今回のわたしの環境の場合は、os.networkInterfaces() throws EAFNOSUPPORT under qemu-user-static on x86 hostのため動かず、qemuの最新バージョンを取ってきて入れなおしました。動いた方は、下記の手順は不要です。

$ wget https://github.com/multiarch/qemu-user-static/releases/download/v4.0.0-2/qemu-aarch64-static.tar.gz
$ tar -zxvf qemu-aarch64-static.tar.gz
$ sudo docker cp ./qemu-aarch64-static 99f47fee2e9c:/usr/bin/qemu-aarch64-static
$ sudo docker commit 99f47fee2e9c arm64v8/ros:kinetic-ros-base-cross1
$ sudo docker run -v ~/catkin_pkg:/root/ros_ws/ -it arm64v8/ros:kinetic-ros-base-cross1
$ apt-get update
$ apt-get install python3-pip python3-apt
$ pip3 install -U setuptools
$ pip3 install -U colcon-common-extensions colcon-ros-bundle
$ colcon build
$ colcon test
$ colcon test-result --all --verbose

colcon bundleの依存関係にarm64を追加する

buildとtestができるようになりましたが、ここでそのままcolcon bundleを実行すると依存関係が解決できていません。その理由は、--apt-sources-listオプションをつけないとデフォルトでxenial.sources.listが呼ばれます。その中身がこれでARM Supportにarmhfしか含まれていません。そのため、これをコピーして下記のように書き換えます。

deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports/ xenial main restricted universe multiverse
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports/ xenial-updates main restricted universe multiverse
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports/ xenial-backports main restricted
deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports/ xenial-security main restricted universe multiverse

そして用意したmy.xenial.sources.listを下記のように指定して実行します。

colcon bundle --apt-sources-list my.xenial.sources.list

これでarm64のコンテナでbundleができるようになりました。

Jetson TX2でローカルでbundleしたプロジェクトを動かす

SCPで前述で作成したoutput.tarをJetson TX2に移動します。移動したら先ほどbundleで作成したプロジェクトが動くか確認する手順と同じで順で確認します。

$ mkdir -p output_test
$ cd output_test

$ tar -xvf output.tar
$ mkdir -p dependencies
$ tar -zxvf dependencies.tar.gz -C dependencies/
$ mkdir -p workspace
$ tar -zxvf workspace.tar.gz -C workspace/
$ BUNDLE_CURRENT_PREFIX=/home/nvidia/output_test/dependencies source /home/nvidia/output_test/dependencies/setup.sh
$ BUNDLE_CURRENT_PREFIX=/home/nvidia/output_test/workspace source /home/nvidia/output_test/workspace/setup.sh
$ which roslaunch
/home/nvidia/output_test/opt/ros/kinetic/bin/roslaunch

これでx86_64でホストしたdockerのarm64イメージでcolcon bundleしたROSプロジェクトがJetson TX2で動くことが確認できました。

AWSの準備する

わたしは、CloudFormationの知識があまりないため、Terraformを使用していますが、TerraformはGreengrassとRoboMakerにまだ対応していないため、できるならばCloudFormationで管理したほうが良いのかなと思います。今回のプロジェクトで使用するサービスは下記ですが、必要に応じて権限を作成してください。

  • iam
  • lambda(sts:AssumeRole)
  • greengrass(sts:AssumeRole)
  • robomaker:UpdateRobotDeployment
  • s3
  • ecr

ECRにイメージを登録する

AWSのECRに登録するDockerファイルの例を示します。

FROM arm64v8/ros:kinetic-ros-base-tx2

LABEL maintainer="wnwn"

RUN apt-get update && \
    apt-get install -y curl ssh git python3-pip python3-apt doxygen graphviz clang-format&& \
     apt-get clean && \
     rm -rf /var/lib/apt/lists/*

# setup github & keychain
RUN mkdir -p ~/.ssh && \
    touch ~/.ssh/known_hosts && \
    ssh-keyscan github.com >> ~/.ssh/known_hosts

# setup pip
RUN pip3 install -U setuptools && \
    pip3 install -U colcon-common-extensions colcon-ros-bundle awscli && \
rm -rf ~/.cache/pip/

イメージのビルドは、下記のようなシェルにしています。

#!/usr/bin/env bash

# スクリプトが存在するディレクトリ
SCRIPT_DIR=$(cd $(dirname $0);pwd)

# プロジェクトのHOMEに移動する
cd ${SCRIPT_DIR}/

# 使用するdocker imageを取得
CONTAINER_ID=$(sudo docker ps -a | grep -E "arm64v8/ros:kinetic-ros-base " | head -1 | awk '{print $1}')
if [[ -z ${CONTAINER_ID} ]]; then
    sudo docker pull arm64v8/ros:kinetic-ros-base
    sudo docker run -it arm64v8/ros:kinetic-ros-base
    CONTAINER_ID=$(sudo docker ps -a | grep -E "arm64v8/ros:kinetic-ros-base " | awk '{print $1}')
fi

# setup for tx2(arm64)
TX2_ID=$(sudo docker images | grep -E "kinetic-ros-base-tx2 " | head -1 | awk '{print $3}')
if [[ -z ${TX2_ID} ]]; then
    wget https://github.com/multiarch/qemu-user-static/releases/download/v4.0.0-2/qemu-aarch64-static.tar.gz
    tar -zxvf qemu-aarch64-static.tar.gz
    sudo docker cp ./qemu-aarch64-static ${CONTAINER_ID}:/usr/bin/qemu-aarch64-static
    sudo docker commit ${CONTAINER_ID} arm64v8/ros:kinetic-ros-base-tx2
    rm -rf qemu-aarch64-static qemu-aarch64-static.tar.gz
fi

# for circleci
sudo docker build -t arm64v8/ros:kinetic-ros-base-tx2-circleci .

ECRにpushをします。AWSコマンドが使えることが前提です。************には、自分のAWS IDを使用します。

#!/usr/bin/env bash

# スクリプトが存在するディレクトリ
SCRIPT_DIR=$(cd $(dirname $0);pwd)

# プロジェクトのHOMEに移動する
cd ${SCRIPT_DIR}/

# ログインする
sudo sh -c "~/.local/bin/aws ecr get-login --region ap-northeast-1 --profile circleci --no-include-email | sh"

# タグの登録
sudo docker tag arm64v8/ros:kinetic-ros-base-tx2-circleci ************.dkr.ecr.ap-northeast-1.amazonaws.com/circleci_for_tx2
sudo docker push ************.dkr.ecr.ap-northeast-1.amazonaws.com/circleci_for_tx2

これでECRへの登録ができました。

CircleCIでarm64イメージを動かす

ここまでくるとあとは作業なだけですので、config.ymlを示します。実際に何をCIするかは、各自の自由です。

version: 2.1

orbs:
  slack: circleci/slack@3.2.0

references:
  default: &default
    working_directory: ~/catkin_ws

  tf_defaults: &tf_defaults
    <<: *default
    machine: true

build_tool_test_and_build_test: &build_tool_test_and_build_test
  run:
    name: build_tool_test_and_build_test
    command: |
      docker run --rm --privileged multiarch/qemu-user-static:register --reset
      $(aws ecr get-login --region ap-northeast-1 --no-include-email)
      docker run -v ~/.ssh/:/root/.ssh/  -v ~/catkin_ws/:/root/catkin_ws/ --entrypoint /root/catkin_ws/shell/circleci_test.sh -it ************.dkr.ecr.ap-northeast-1.amazonaws.com/circleci_for_tx2
colcon_bundle: &colcon_bundle
  run:
    name: colcon_bundle
    command: |
      docker run --rm --privileged multiarch/qemu-user-static:register --reset
      $(aws ecr get-login --region ap-northeast-1 --no-include-email)
      docker run -v ~/.ssh/:/root/.ssh/ -v ~/catkin_ws/:/root/catkin_ws/ --entrypoint /root/catkin_ws/shell/circleci_bundle.sh -it ************.dkr.ecr.ap-northeast-1.amazonaws.com/circleci_for_tx2
s3_upload: &s3_upload
  run:
    name: s3_upload
    command: |
      aws s3 cp ./bundle/output.tar s3://${S3SOURCE}/${CIRCLE_PROJECT_REPONAME}/${CIRCLE_TAG}/
jobs:
  test:
    <<: *tf_defaults
    steps:
      - add_ssh_keys:
          fingerprints:
            - "****************************************"
      - checkout
      - *build_tool_test_and_build_test
  upload:
    <<: *tf_defaults
    steps:
      - add_ssh_keys:
          fingerprints:
            - "****************************************"
      - checkout
      - *colcon_bundle
      - *s3_upload
      - slack/notify:
          title: "CIRCLE CI S3 UPLOAD NOTIFICATION"
          message: "*TAG*\n${CIRCLE_TAG}\n*COMMIT*\nhttps://github.com/****/${CIRCLE_PROJECT_REPONAME}/commit/${CIRCLE_SHA1}\n*SOURCE*\ns3://${S3SOURCE}/${CIRCLE_PROJECT_REPONAME}/${CIRCLE_TAG}/output.tar\n"

workflows:
  version: 2
  test-and-deploy:
    jobs:
      - test
      - upload:
          filters:
            tags:
              only: /^v.*/
            branches:
              ignore: /.*/

わたしのプロジェクトは、外部の複数のリポジトリと内部のリポジトリをまとめたパッケージで構成されており、自分のプロジェクトについては、テストの他にもROS C++ Style Guideに則り、記述されたドキュメントの生成(doxygen)やコーディング規約(roscpp_code_format)のチェック(run-clang-format)、bundleファイルの管理やslack通知なども行っています。CircleCIが使えるようになれば、細かいところにも簡単に手が届いて便利ですね!

Jetson TX2にGreengrassをセットアップする

ここまで読んでくださったみなさま、おつかれさまでした。あともうすこしです。あとはすべて公式の手順のみで動かすことができます。まずTX2にGreengrassをセットアップしましょう。

Greengrass の環境設定 : 他のデバイスの設定

ネットで検索するとカーネルの再構築等をやっている記事が見受けられますが、わたしの場合は特に不要でした。手順が終了したら、greengrassを起動しましょう。

$ cd /greengrass/ggc/core/
$ sudo ./greengrassd start

もし正常に起動しない場合やAWSコンソールに表示されない時のトラブルシューティングは下記をみるとよいです。

AWS IoT Greengrass : Troubleshooting with Logs

colcon bundleして出来上がったパッケージをS3に配置する

RoboMakerからリモートデプロイするには、デプロイするパッケージをS3に配置する必要があります。わたしの場合、CircleCIでbundle終了後にS3に配置し、そのURLをslackに通知するようにしています。

AWSコンソールからRoboMakerでTX2にデプロイする

それでは、AWSコンソールからTX2にデプロイする設定をします。まずフリートを作成します。

image.png

フリート名称を入力し作成をクリックします。

image.png

次にロボットを作成します。

image.png

名称・アーキテクチャ・グループを選択します

image.png

作成が完了したらフリートに登録します。

スクリーンショット 2019-06-28 17.55.01.png

そしてロボットアプリケーションを作成します。

image.png

名前・ソフトウェアスイート名・バージョン・ARM64ソースファイルを入力します。

スクリーンショット 2019-06-28 17.56.15.png

最後にデプロイを作成します。
image.png

フリート・ロボットアプリケーションを入力します。

スクリーンショット 2019-06-28 17.58.12.png

バージョン(新規作成)を新規作成します。

image.png

パッケージ名・launchファイルを入力します。ここで作成を押すとすぐにデプロイが始まるので注意が必要です。

image.png

作成すると下記の状態になり進行中になります。成功が表示されればリモートデプロイ完了です。

スクリーンショット 2019-06-28 17.58.41.png

Jetson TX2でリモートデプロイしたROSプロジェクトが動いているか確認する

Jetson TX2にSSHで入り、rostopicをこれまでどおりechoすれば動いていることが確認できます。

まとめ

全部まとめてみると難しいようにみえますが、ひとつひとつみていくとそこまで複雑なことはやっていないと思います。ただドキュメントが不足していたり、そもそも記述がないこともあるので、その時はソースコードを読んでgithubのissueを確認するのが最も正確で近道です。

これでRoboMakerとCircleCIをつかえば大量のデバイスのデプロイを簡単に管理することができるようになりました。RoboMakerは、今後リソースの監視やシミュレーションまわりだったりどんどん機能がアップデートされていく予定なので楽しみですね!

8
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5