Amazon Web Services ブログ

AWS RoboMaker を使用した ROS アプリケーション CI パイプラインの構築

AWS RoboMaker を使用した ROS アプリケーション CI パイプラインの構築

動的で変化する実世界環境向けのロボットアプリケーションの構築およびテストは、困難で複雑なタスクです。開発者は、事実上無限の実世界の状況を考慮し、さまざまなハードウェア関連の問題に耐えられるようにアプリケーションを構築し、チームのメンバーによって記述されたコードと正常に統合できるアルゴリズムおよびアプリケーションを作成する必要があります。多くの企業にとって、これは一見克服できない課題であり、ロボットおよびアプリケーションの俊敏性と市場投入までの時間に大きな悪影響を及ぼします。
ロボット用のアプリケーションを構築する場合、開発者は、物理的なロボットで時間と費用をかけて行うデプロイとテストのプロセスを経る前に、コードをシミュレーション環境で実行し、反復することで、速度を上げることができます。シミュレーションでのテストは、開発者が物理的なロボットにアクセスできない場合や、将来遭遇する実世界環境に日常的にロボットを持ち込むことができない場合に特に役立ちます。  AWS RoboMaker を使用すると、シミュレーションでの ROS アプリケーションのテストがこれまでになく簡単になります。 AWS RoboMaker は、フルマネージド型のロボティクスシミュレーションサービスを提供します。このサービスを使用して、物理ベースの複数の回帰テストを実行し、シミュレーションの複雑さに基づいて基盤インフラストラクチャを自動的にスケーリングします。このブログでは、継続的インテグレーションおよび継続的デリバリーパイプライン (CI/CD) の一部として AWS RoboMaker でパラメーター化されたシミュレーションを実行することによる、ROS アプリケーションの自動テストのアプローチについて説明します。このソリューションにより、開発者は、本番環境のお客様に影響を与える前に、バグを迅速かつ正確に特定できます。定期的にコードを統合し、ロボットが実世界で遭遇するさまざまな条件下で整合的に動作するようにすることで、お客様は、メンテナンスが必要となるテストのインフラストラクチャの量を減らしながら、テスト速度とカバレッジを向上させることができます。高レベルのアーキテクチャは次のとおりです。

シナリオを使用した ROS アプリケーションのテスト

ロボットソフトウェアアプリケーションをテストする方法は複数あります。以下では、シナリオベーステストを使用したテストアプローチを紹介します。 シナリオは、実世界の条件、アクターのふるまい、期待される結果を定義するパラメーターセットです。これにより、開発者は、シミュレーションで実行するテストを定義するパラメーターからシミュレーションアプリケーションコードを疎結合化できます。この疎結合化により、QA エンジニアとのコラボレーションが容易になり、チームが回帰テストを実行する方法が標準化されます。また、QA エンジニアは、目的のテストシナリオをより完全にカバーできる (パラメーターのさまざまな組み合わせを用いた) さまざまなテストケースを簡単に定義できる柔軟性を獲得します。 これらのシナリオは、AWS RoboMaker シミュレーションサービスを使用した簡単な API 呼び出しを通じて自動的に実行されます。

  1. GitHub にサインインし、AWS RoboMaker CloudWatch Monitoring Sample Applicationフォークします。
  2. リポジトリを任意の開発環境に複製し、このチュートリアルの新しいブランチを作成します。

    git clone <MY_CLONED_REPO>
    cd aws-robomaker-sample-application-cloudwatch
    git checkout -b <MY_INTEGRATION_BRANCH_NAME>

今回の例では、AWS RoboMaker のサンプルアプリケーションセクションで提供される AWS RoboMaker CloudWatch Monitoring Sample Application を使用します。unittest ライブラリを使用して簡単なナビゲーションテストノードを作成します。これにより、ナビゲーションの目標セットが定義され、シミュレーションが実行されます。テストノードはシミュレーションアプリケーションにパッケージ化され、カスタム起動ファイルでトリガーされます。

目標の数とシミュレーションの世界は、環境変数を通じてパラメーター化されるため、さまざまなシナリオを簡単かつ柔軟に定義して実行できます。テストが完了すると、ノードはシミュレーションジョブにテスト結果を自己タグ付けします。実行中のシミュレーションジョブにタグを付けると、ダウンストリームパイプラインステップ、ダッシュボード、および通知は、テスト結果を簡単に参照し、レポートできるようになります。最後に、シミュレーションに結果をタグ付けした後、テストノードは、シミュレーションジョブを自己終了します。 この方法では、消費したシミュレーションユニットの期間および数に対してのみ課金されます。各シミュレーションユニットは 1 vCPU で 2 GB RAM であり、最も近い分単位で請求されます。価格設定の詳細および詳細なコスト見積もりの​​計算については、価格設定ページをご覧ください。

以下のテストノードは、ナビゲーションのステータスを監視し、1 つまたは複数のナビゲーションで成功または失敗をアサートするように設定できるほか、短期および長期のテストに対応できます。Gazebo の世界は起動ファイルでパラメーター化されており、さまざまな世界でテストを実行できます。この簡単な例では、AWS RoboMaker Small House World および AWS RoboMaker Bookstore World でテストを実行します。最後に、実行中の RoboMaker シミュレーションジョブをキャンセルし、タグ付けする 2 つのユーティリティメソッドが含まれています。

#!/usr/bin/env python

import rospy
import rostest
import time
import os
import unittest

from rosgraph_msgs.msg import Clock
from ros_monitoring_msgs.msg import MetricList
from robomaker_simulation_msgs.msg import Tag
from robomaker_simulation_msgs.srv import Cancel, AddTags

METRIC_NAME = "distance_to_goal"

class NavigationTest(unittest.TestCase):
    """
    このテストケースは、予想される多くの目標を送信し、そのステータスを監視します。
    ロボットがすべての宛先に到達すると、テストに合格のマークが付けられます。
    """    

    def cancel_job(self):
        rospy.wait_for_service("/robomaker/job/cancel")
        requestCancel = rospy.ServiceProxy("/robomaker/job/cancel", Cancel)
        response = requestCancel()
        if response.success:
            self.is_cancelled = True
            rospy.loginfo("Successfully requested cancel job")
            self.set_tag(name=self.test_name + "_Time_Elapsed_End", value= str(time.time()).split(".", 1)[0])
        else:
            rospy.logerr("Cancel request failed: %s", response.message)
    
    def set_tag(self, name, value):
        rospy.wait_for_service("/robomaker/job/add_tags")
        requestAddTags = rospy.ServiceProxy("/robomaker/job/add_tags", AddTags)
        tags = ([Tag(key=name, value=value)])
        response = requestAddTags(tags)
        if response.success:
            rospy.loginfo("Successfully added tags: %s", tags)
        else:
            rospy.logerr("Add tags request failed for tags (%s): %s", tags, response.message)

    def setUp(self):
        self.latch = False
        self.successful_navigations = 0
        self.test_name = "Robot_Monitoring_Tests_" + str(time.time()).split(".", 1)[0]
        self.is_completed = False
        rospy.loginfo("Test Name: %s", self.test_name)
        self.navigation_success_count = rospy.get_param('NAVIGATION_SUCCESS_COUNT')
        self.timeout = rospy.get_param("SIM_TIMEOUT_SECONDS")
        
    def set_latched(self):
        self.latch = True
    
    def set_unlatched(self):
        self.latch = False
    
    def increment_navigations(self):
        self.successful_navigations = self.successful_navigations + 1
    
    def is_complete(self):
        return self.successful_navigations >= self.navigation_success_count
        
    def check_timeout(self, msg):
        """
            タイムアウトになったらテストをキャンセルします。タイムアウトは
            /clock トピック (シミュレーション時間) に基づきます。
        """
        if msg.clock.secs > self.timeout and not self.is_cancelled:
            rospy.loginfo("Test timed out, cancelling job")
            self.set_tag(name=self.test_name + "_Status", value="Failed")
            self.set_tag(name=self.test_name + "_Timed_Out", value=str(self.timeout))
            self.cancel_job()
            
    def check_complete(self, msgs):
        for msg in msgs.metrics:
            if msg.metric_name == METRIC_NAME:
                rospy.loginfo("Metric Name: %s", msg.metric_name)
                rospy.loginfo("Metric Value: %s", msg.value)
                """
                    目標までの距離のメトリックが 0.5 を下回り、
                    目標数を達成した場合、作業は完了し、
                    ジョブの成功をタグ付けしてキャンセルします。それ以外の場合は、
                    目標までの距離が 1 を超えると、 
                    新しい目標への進捗状況を確認し続けます。ナビゲーションスタックが
                    目標までの距離と考えているものを使用していることに注意してください。
                    実際には、正確を期すためにグラウンドトゥルース値を使用します。
                """
                if msg.value <= .5 and self.is_completed == False and self.latch == False:
                    self.set_latched()
                    self.increment_navigations()
                    self.set_tag(name=self.test_name + "_Successful_Nav_" + str(self.successful_navigations), value=str(self.successful_navigations))
                    if self.is_complete():
                        self.is_completed = True
                        self.set_tag(name=self.test_name + "_Status", value="Passed")
                        self.cancel_job()
                elif msg.value > 1 and self.is_completed == False:
                    self.set_unlatched()
                    
    def test_navigation(self):
    	try:
                self.is_cancelled = False
                self.set_tag(name=self.test_name + "_Time_Elapsed_Start", value= str(time.time()).split(".", 1)[0])
                rospy.Subscriber("/metrics", MetricList, self.check_complete)
                rospy.Subscriber("/clock", Clock, self.check_timeout)
                rospy.spin()
    	except Exception as e:
                rospy.logerror("Error", e)
                self.set_tag(name=self.test_name, value="Failed")
                #ここでジョブをキャンセルし、サービスにシミュレーションを停止させます。終了しません。
                self.cancel_job()

    def runTest(self):
        #Start the navigation test
        self.test_navigation()

if __name__ == "__main__":
    rospy.init_node("navigation_test", log_level=rospy.INFO)
    rostest.rosrun("test_nodes", "navigation_test", NavigationTest)
    

ここでは、マルチレイヤーの起動ファイルを使用してシミュレーションテストを実行しています。最高位の起動ファイルは worlds.launch です。ここでは、SIMULATION_WORLD 環境変数を使用して、この変数で定義されたワールドの正しい起動ファイルを含めます。

<launch>
    <!-- 環境変数 SIMULATION_WORLD に基づいたシミュレーションワールドを生成します。-->
    <include file="$(find cloudwatch_simulation)/launch/$(env SIMULATION_WORLD)_turtlebot_navigation.launch">
    </include>
</launch>

たとえば、環境変数が bookstore として設定されている場合、以下の起動ファイルが含まれます。テストノードは、この 2 番目の起動ファイルに含まれます。CI/CD ワークフローに他の環境変数を含める方法の詳細については、このブログで後述します。

<launch>
  <!-- 
        ランダムに所定の 
        目標に無限にナビゲートする Turtlebot を備えた書店。

        ナビゲーションノードは、 
        仮想マップを使用するため、シミュレーションアプリケーションにあり、 
        実際のロボットにはデプロイしないでください。

        環境変数 TURTLEBOT3_MODEL を「burger」に設定する必要があります

        現在サポートされているのは Turtlebot Burger のみです。Waffle および Waffle PI の SLAM マップを 
        生成する必要があります。
  -->

  <!-- true の場合、事前に定義されたルートを永久にたどります -->
  <arg name="follow_route" default="true"/>

  <!-- 
       AWS RoboMaker Simulation では常に GUI を false に設定します
       roslaunch コマンドラインで gui:=true を使用して、GUIで実行します。
  -->
  <arg name="gui" default="false"/>

  <!-- ワールドおよびロボット -->
  <include file="$(find aws_robomaker_bookstore_world)/launch/bookstore_turtlebot_navigation.launch">
    <arg name="gui" value="$(arg gui)"/>
    <arg name="x_pos" value="-3.5"/>
    <arg name="y_pos" value="5.5"/>
    <arg name="yaw"   value="0.0"/>
  </include>

  <!-- ナビゲーションルートの目標をランダムな順序でロボットに送信します -->
  <group if="$(arg follow_route)">
    <node pkg="aws_robomaker_simulation_common" type="route_manager" name="route_manager" output="screen">
      <rosparam file="$(find aws_robomaker_bookstore_world)/routes/route.yaml" command="load"/> 
    </node>
  </group>
</launch>

AWS でシナリオベースのテストインフラストラクチャを作成する

まず、AWS RoboMaker Simulation で起動されるシナリオを定義する JSON ドキュメントを作成します。CI パイプラインでこのドキュメントを使用して、シミュレーションジョブリクエストの配列を作成します。

{
	"scenarios": {
		"<SCENARIO_NAME>": {
			"robotEnvironmentVariables": {},
			"simEnvironmentVariables": {}
		}
	},
	"simulations": [{
		"scenarios": ["<SCENARIO_NAME>"],
		"params": CreateSimulationJobParams
	}]
}

シナリオは、環境変数のセットを定義することによって作成されます。AWS RoboMaker シミュレーションでは、シミュレーションアプリケーションからロボットアプリケーションを疎結合化できます。ロボットアプリケーションには、シミュレーションアプリケーションがシミュレーションに必要な Gazebo ワールドとアセットを含む物理的なロボットで実行するためのコードが含まれます。AWS RoboMaker は、これらのアプリケーションの両方を同時に実行します。したがって、シナリオは、ロボットとシミュレーションアプリケーションの両方で使用する環境変数によって定義されます。TurtleBot3 の Waffle Pi モデルを使用して、書店シミュレーションの世界で単一のナビゲーションテストを実行するシナリオの例を次に示します。

...
	"scenarios": {
		"QuickNavBookStore": {
			"robotEnvironmentVariables": {
				"ROS_AWS_REGION": "us-west-2"
			},
			"simEnvironmentVariables": {
				"ROS_AWS_REGION": "us-west-2",
				"TURTLEBOT3_MODEL": "waffle_pi",
				"NAVIGATION_SUCCESS_COUNT": "1",
				"SIMULATION_WORLD": "bookstore",
				"SIM_TIMEOUT_SECONDS": "600"
			}
		}
	}
...

上記の例では、シミュレーションアプリケーション用に 4 つの環境変数を設定しています。その環境変数は、以下のとおりです。

  • ROS_AWS_REGION: ROS アプリケーションパッケージに含まれている AWS SDK が使用するリージョン。
  • TURTLEBOT3_MODEL: シミュレーションで使用する turtlebot モデル。「burger」または「waffle_pi」を選択できます。waffle pi モデルにはカメラセンサーがあります。
  • NAVIGATION_SUCCESS_COUNT: ROS アプリケーションは、ランダムなナビゲーション目標を設定します。目標に到達すると、新しい目標を設定し、新しい目標に向かって動き始めます。この環境変数は、テストで完了するナビゲーション目標の数を定義します。
  • SIMULATION_WORLD: テストで使用するシミュレーションワールド。この値は、Gazebo シミュレーションワールドのオプションとして「bookstore」と「small_house」をサポートします。

環境変数のさまざまな組み合わせで、好きなだけシナリオを定義できます。 単一の AWS RoboMaker CreateSimulationJob API 呼び出しは、シミュレーションとシナリオのペアごとに実行されます。たとえば、以下の JSON ファイルは、2 つの AWS RoboMaker シミュレーションジョブを作成します。定義されたシナリオ (MultiNavBookStore および QuickNavSmallHouse) ごとに 1 つです。params フィールドには、create_simulation_job メソッドが期待するものと同じリクエスト構文が必要です。これは、シミュレーションジョブアセットのパラメーターです。

{
	"scenarios": {
	    "QuickNavBookStore": {
	      "robotEnvironmentVariables": {
	        "ROS_AWS_REGION": "us-west-2"
	      },
	      "simEnvironmentVariables": {
	        "ROS_AWS_REGION": "us-west-2",
	        "TURTLEBOT3_MODEL": "waffle_pi",
	        "NAVIGATION_SUCCESS_COUNT": "1",
	        "SIMULATION_WORLD": "bookstore"
	      }
	    },
	    "MultiNavBookStore": {
	      "robotEnvironmentVariables": {
	        "ROS_AWS_REGION": "us-west-2"
	      },
	      "simEnvironmentVariables": {
	        "ROS_AWS_REGION": "us-west-2",
	        "TURTLEBOT3_MODEL": "waffle_pi",
	        "NAVIGATION_SUCCESS_COUNT": "3",
	        "SIMULATION_WORLD": "bookstore"
	      }
	    }
	},
	"simulations": [{
		"scenarios": [
			"MultiNavBookStore",
	                "QuickNavBookStore"
	             ],
		"params": {
			"failureBehavior": "Fail",
			"iamRole": "<IAM_ROLE>",
			"maxJobDurationInSeconds": 600,
			"outputLocation": {
				"s3Bucket": "<S3_BUCKET>",
				"s3Prefix": "<S3_PREFIX>"
			},
			"robotApplications": [{
				"application": "<ROBOT_APP_ARN>",
				"applicationVersion": "$LATEST",
				"launchConfig": {
					"launchFile": "await_commands.launch",
					"packageName": "cloudwatch_robot"
				}
			}],
			"simulationApplications": [{
				"application": "<SIMULATION_APP_ARN>",
				"applicationVersion": "$LATEST",
				"launchConfig": {
                    "packageName": "cloudwatch_simulation",
                    "launchFile": "worlds.launch"
				}
			}],
			"vpcConfig": {
				"assignPublicIp": true,
				"subnets": [ "<SUBNET_1>", "<SUBNET_2>" ],
            	                "securityGroups": [ "<SECURITY_GROUP>" ]
			}
		}
	}]
}

上記の JSON 構造で新しい scenarios.json ファイルを作成し、サンプルアプリケーションのベースディレクトリに保存します。

AWS CodePipline で CI パイプラインを作成する

シナリオが定義され、テストノードが作成されたので、アプリケーションを自動的にビルドおよびバンドルし、コード統合のたびにシミュレーションセットを起動するために必要な CI インフラストラクチャをセットアップします。AWS CodePipeline は、お客様が継続的インテグレーションパイプラインを簡単にセットアップおよび設定できるようにするサービスです。今回はこのサービスを使用しますが、TravisCIJenkins などの一般的なツールも使用できます。

まず、3 つの段階で新しい AWS Code Pipeline をセットアップします。第 1 段階では、Git リポジトリに接続します。AWS Code Pipeline は、ソースとして、GitHub、AWS CodeCommit、AWS S3、AWS ECR、AWS CodeStar Connections (BitBucket) をサポートしています。第 2 段階では、AWS CodeBuild によって提供されるマネージドビルドサーバーにソースコードが複製されます。ビルドサーバーの基盤として、Docker ハブの OSRF が提供する docker イメージ (docker.io/ros) を使用します。最終段階となる第 3 段階では、AWS RoboMaker シミュレーションジョブの進行状況を作成および監視します。

最初のタスク は、AWS RoboMaker シミュレーションジョブのバッチを起動および監視するために必要なインフラストラクチャをデプロイすることです。これを行うには、AWS Step Functions を使用します。これは、お客様がクラウド内でステートマシンを簡単に構築して操作できるようにするサービスです。以下のアプリケーションはサーバーレスアプリケーションとしてパッケージ化されており、SAM Local CLI (Serverless Application Model Command Line Interface) を使用してデプロイします。 ここをクリックして、インストール手順に従ってください。デプロイされると、Step Functions のワークフローは次のようになります。

SAM CLI ツールで 3 つのコマンドを使用します。最初のコマンドsam build —use-container -m ./requirements.txt は、Python Lambda 関数パッケージをビルドし、必要な依存関係をインストールします。正しいバージョンの Python で事前にプロビジョニングされたコンテナを活用します。2 つ目のコマンドsam package は、infrastructure-as-code SAM テンプレート (template.yml) ファイルを使用して、S3 で Lambda 関数パッケージ (zip ファイル) をステージングし、起動準備の整った CloudFormation テンプレートを作成します。このコマンドのパラメーターの --output-template-file package.yml --s3-bucket <YOUR_S3_BUCKET> は、作成のための出力 CloudFormation テンプレートファイル (package.yml) と、パッケージ化された Lambda 関数の zip パッケージを格納する S3 バケットの場所を定義します。最後のコマンドsam deploy は、AWS アカウントで CloudFormation テンプレートを起動します。デプロイが既に存在する場合、変更点を探し、デルタのみをデプロイします。

このツールを使用する前に、まず、AWS CLI がインストールされ、SAM ローカル CLI で使用できる設定済みの AWS デフォルトプロファイルがあることを確認してください。AWS CLI をまだインストールしていない場合は、これらの指示に従ってください。CLI で活用される IAM ユーザー認証情報には、ユーザーが CloudFormation テンプレートを起動し、VPC、Lambda、IAM、および RoboMaker リソースを作成し、定義された S3 バケットにアップロードできる IAM ポリシーがアタッチされている必要があります。

準備ができたら、次のコマンドを実行して、サーバーレスバックエンドをビルド、パッケージ化、デプロイします。

git clone https://github.com/aws-samples/aws-robomaker-simulation-launcher
cd aws-robomaker-simulation-launcher
sam build --use-container -m ./requirements.txt
sam package --output-template-file package.yml --s3-bucket <YOUR_S3_BUCKET>
sam deploy --template-file package.yml --stack-name cicd --capabilities CAPABILITY_NAMED_IAM --s3-bucket <YOUR_S3_BUCKET>

バックエンドシミュレーションインフラストラクチャが作成されたので、AWS CodePipeline を使用して CI パイプラインを作成および設定します。

  1. AWS コンソールで AWS CodePipeline を開きます。[Create Pipeline] をクリックします。
  2. パイプラインの名前を設定し、[Create a New Service Role] ラジオボタンを選択し、ロールをパイプラインで使用できるようにするチェックボックスをクリックします。[Advanced Settings] はデフォルトのままにします。[Next] を押します。 詳細設定を使用すると、パイプラインアセットに使用する S3 バケットと KMS キーを変更したい場合は、これらをカスタマイズできます。

第 1 段階: ソース

第 1 段階で設定するのは、ソースプロバイダーです。

  1. ソースプロバイダーGitHub に設定します。
  2. リポジトリを選択し、上記で作成したブランチを選択します。
  3. 検出モードGitHub web hooks に設定します。
  4. [Next] を押して、この段階の設定を完了します。

第 2 段階: ビルド

第 2 段階では、ビルドサーバーをセットアップします。ビルドサーバーでの動作を定義するために、buildspec ファイルを作成し、ROS ソースコードとともに GitHub リポジトリに含めます。この例では、次の buildspec ファイルを使用して依存関係を準備し、colcon build および bundle コマンドを実行します。この定義ファイルには、次のセクションが含まれます。

  • 環境変数: build コマンドで参照する変数。
  • フェーズ: ビルドの各フェーズのコマンド定義。
    • インストールフェーズ: ビルドプロセス自体の依存関係をインストールするために実行する必要があるコマンド。Colcon は、ROS レベルのアプリケーション依存関係を処理します。この場合、colcon の gazebo 依存関係の Gazebo キーも追加し、apt ソースを更新し、colcon と colcon bundle をインストールします。
      注意: ビルド時間を改善するために、これらのアイテムがプリインストールされた独自の Dockerfile を作成できます。
    • ビルド前のフェーズ: ビルドの前に実行する必要がある設定コマンド。このフェーズには、ROS ワークスペースコマンド (rosdep 更新およびワークスペースレベルの依存関係の準備とインストール) が含まれています。
    • ビルドフェーズ: ここで、colcon build コマンドが実行されます。
    • ビルド後のフェーズ: コードがビルドされたら、次に、コードをバンドルにパッケージ化し、S3 にアップロードします。これは、colcon bundle のビルド後のフェーズで行われます。
  • キャッシュ: colcon bundle はキャッシュをサポートし、パッケージ化されたオーバーレイ (ダウンロードおよびインストールされた依存関係を含む) をローカルフォルダーに保存します。この場合、ビルドおよびバンドルされたアプリケーションをキャッシュして、将来のビルドを高速化します。キャッシュするディレクトリへのファイルパスのほか、ロボットおよびシミュレーションアプリケーションのソースコードは、環境変数で参照されます。
  1. ビルドプロジェクトを追加する前に、クローン化されたサンプルアプリケーションコードのベースディレクトリに buildspec ファイルを追加します。次のビルドコマンドを使用して buildspec.yml という名前の新しいファイルを作成します。
    version: 0.2 
    env:
      variables:
        S3_BUCKET: <YOUR_S3_BUCKET>
        APP_NAME: cicd
        CACHE_DIR: cache
        ROBOT_WS: robot_ws
        SIMULATION_WS: simulation_ws
        ROS_VERSION: kinetic
    phases: 
      install: 
        commands: 
           - apt-get update
           - apt-get install -y python3-pip python3-apt apt-transport-https ca-certificates wget
           - wget http://packages.osrfoundation.org/gazebo.key 
           - apt-key add gazebo.key
           - echo "deb http://packages.osrfoundation.org/gazebo/ubuntu-stable `lsb_release -cs` main" > /etc/apt/sources.list.d/gazebo-stable.list
           - apt-get update
           - pip3 install -U setuptools pip
           - pip3 install colcon-ros-bundle
           - pip3 install awscli
      pre_build:
        commands:
          - . /opt/ros/$ROS_VERSION/setup.sh
          - rosdep update
          - sudo rosdep fix-permissions
          - rosws update --target-workspace ./$ROBOT_WS
          - rosdep install --from-paths ./$ROBOT_WS/src --ignore-src -r -y
          - rosws update --target-workspace ./$SIMULATION_WS
          - rosdep install --from-paths ./$SIMULATION_WS/src --ignore-src -r -y
      build: 
        commands: 
          - COLCON_LOG_PATH="$CACHE_DIR/$ROBOT_WS/logs" colcon build --base-paths "./$ROBOT_WS" --build-base "$CACHE_DIR/$ROBOT_WS/build" --install-base "$CACHE_DIR/$ROBOT_WS/install"
          - COLCON_LOG_PATH="$CACHE_DIR/$SIMULATION_WS/logs" colcon build --base-paths "./$SIMULATION_WS" --build-base "$CACHE_DIR/$SIMULATION_WS/build" --install-base "$CACHE_DIR/$SIMULATION_WS/install"
      post_build: 
        commands: 
          - COLCON_LOG_PATH="$CACHE_DIR/$ROBOT_WS/logs" colcon bundle --base-paths "./$ROBOT_WS" --build-base "$CACHE_DIR/$ROBOT_WS/build" --install-base "$CACHE_DIR/$ROBOT_WS/install" --bundle-base "$CACHE_DIR/$ROBOT_WS/bundle"
          - COLCON_LOG_PATH="$CACHE_DIR/$SIMULATION_WS/logs" colcon bundle --base-paths "./$SIMULATION_WS" --build-base "$CACHE_DIR/$SIMULATION_WS/build" --install-base "$CACHE_DIR/$SIMULATION_WS/install" --bundle-base "$CACHE_DIR/$SIMULATION_WS/bundle"
          - aws s3 cp $CACHE_DIR/$ROBOT_WS/bundle/output.tar s3://$S3_BUCKET/bundles/x86/robotApp.tar 
          - aws s3 cp $CACHE_DIR/$SIMULATION_WS/bundle/output.tar s3://$S3_BUCKET/bundles/x86/simulationApp.tar
    cache:
      paths:
        - '$CACHE_DIR/**/*'
        - '$ROBOT_WS/src/deps/**/*'
        - '$SIMULATION_WS/src/deps/**/*'
        
  2. ビルドプロバイダーとして [AWS CodeBuild] を選択します。使用している地域を選択し、[Create project ] を押します。CodeBuild 構成設定でポップアップが開きます。
  3. ポップアップで、ビルドプロジェクトの名前を入力します。

  4. [Environment] セクションで、[Custom Image] を選択します。[Environment] タイプを [Linux] に設定し、[Image Registry] を [Other Registry] に設定します。 外部レジストリ URLdocker.io/ros:kinetic と入力します。ここでは、ROS Kinetic を使用しています。 ただし、アプリケーションが別のリリースの ROS を使用している場合は、ここで使用するリリースを定義できます。
  5. [New Service Role] ラジオボタンを選択します。追加の設定はデフォルトのままにします。
  6. 上記で作成した GitHub ソースコードで buildspec.yml ファイルを使用します。したがって、[Use a buildspec file] を選択します。
  7. [Continue] を押して [CodePipeline] に進みます。 これにより、ポップアップが閉じられ、CodeBuild プロジェクトが AWS CodePipeline ウィザードに挿入されます。[Next] を押します。
  8. [Skip the deploy stage] を押します。次に [Skip] を押します。
  9. CodePipeline の設定を確認し、[Create Pipeline] を押します。

第 3 段階: シミュレート

最終段階となる第 3 段階では、一連の AWS RoboMaker シミュレーションジョブを起動して監視することにより、さまざまなシナリオをテストします。最終的な CI パイプラインは次のようになります。

ROS アプリケーション CI/CD パイプラインのアーキテクチャ図。

この時点で、最初の 2 つの段階 (ソースおよびビルド) で実行中の CI パイプラインがあります。

  1. AWS CodePipeline コンソールに戻り、新しく作成したパイプラインを開きます。
  2. CodePipeline コンソールページで [Edit] を押します。
  3. ビルドステージの後、[+ Add stage] を押します。
  4. 新しいステージの名前として [Simulate] と入力します。
  5. [Add action group] を押します。 アクション名として LaunchSimulations と入力します。
  6. アクションプロバイダーのドロップダウンで、[AWS Lambda] を選択します。
  7. 入力プロバイダーのドロップダウンで、[Source Artifact] を選択します。
  8. 上記の SAM アプリケーションの一部として、cicd-TriggerStepFunctions という AWS Lambda 関数を作成しました。ドロップダウンリストから cicd-TriggerStepFunctions を選択します。
  9. [Done] を押します。オーバーレイが閉じます。次に、ステージで [Done] を押します。そして、上部右隅の [Save] を押します。

お疲れさまでした。 これで、ROS CI パイプラインが完全に設定されました。

まとめ

このブログでは、ROS アプリケーションの機能をテストするためのメカニズムとしてシナリオを定義する方法について説明しました。マネージド型の ROS ビルドサーバーを使用して AWS CodePipeline を設定する手順を説明し、CI パイプラインの一部として自動 AWS RoboMaker シミュレーションジョブを実行する方法を示しました。今後のブログでは、AWS RoboMaker Fleet Management を使用した継続的なデプロイおよびデリバリーのほか、ROS アプリケーションをテストするための一般的なベストプラクティスについて説明する予定です。AWS RoboMaker の詳細については、ウェブサイトのリソースページをご覧ください。

構築がうまくいきますように。

Jeremy Wallace

Jeremy Wallace

Jeremy は、AWS のスペシャリストソリューションアーキテクトであり、クラウドロボティクスと AWS RoboMaker に注力しています。

Andrew Lafranchise

Andrew Lafranchise

Andrew Lafranchise は、AWS RoboMaker チームのシニア開発ソフトウェアエンジニアです。