そのデバイスはエッジコンピューティングに対応してる?「AWS IoT Device Tester 」を使ってEC2でGreengrassが動くかチェックしてみる #reinvent

2018.12.29

どうも!大阪オフィスの西村祐二です。

re:InventでAmazon FreeRTOSやAWS IoT Greengrassがデバイスで動作するかチェックできるツール「AWS IoT Device Tester」が発表されました。

速報記事は下記になります。

[速報]Amazon FreeRTOSやAWS IoT Greengrassがデバイスで動作するかチェックできるツール「AWS IoT Device Tester」が発表されました! #reinvent

今回はこのツールを使って、EC2でAWS IoT Greengrassが動作するかチェックしてみたいと思います。

AWS IoT Device Testerの動作イメージ

AWS IoT Device Testerのコンポーネントとして、Test ManagerとTest Casesがあります。

Test Managerは、テスト対象デバイスに接続されているホストコンピュータ (Windows、Mac、または Linux) 上で動作します。

テスト対象デバイスとホストコンピュータはSSH接続をしてテストを実行します。

そのため、自分のPCに「AWS IoT Device Tester」のファイルをダウンロードする必要があります。

引用元:https://docs.aws.amazon.com/ja_jp/greengrass/latest/developerguide/device-tester-for-greengrass-ug.html

自分のPCにAWS IoT Device Testerをダウンロード

下記URLより、自分のPCにあったOSのソフトウェアをダウンロードします。私の環境はMacなのでMac用のファイルをダウンロードします。

※注意点
AWS IoT Greengrass のバージョンごとに対応する AWS IoT Device Testerのバージョンがあります。使用している AWS IoT Greengrass のバージョンに対応する AWS IoT Device Testerのバージョンをダウンロードする必要があります。今回のバージョンはAWS IoT Greengrass v1.7.0を使います。

https://aws.amazon.com/jp/greengrass/device-tester/

AWS IoT Greengrass v1.7.0をダウンロード

AWS IoT Device Testerでは、テスト対象のデバイスが特定バージョンのAWS IoT Greengrassと互換性があるかどうかをテストすることができます。

今回はAWS IoT Greengrass v1.7.0と互換性があるか確かめるために、そのバージョンをホストコンピュータ(Mac)にダウンロードしておきます。

ファイルをダウンロードするためにマネージメントコンソールにアクセスし、「AWS IoT」 => ソフトウェア  => 「AWS IoT Greengrass Core ソフトウェア」の欄からダウンロードします。

今回はEC2を対象としているため、x86_64のソフトウェアをダウンロードします。

AWS IoT Device TesterにAWS IoT Greengrass v1.7.0をセット

先程ダウンロードしたAWS IoT Greengrass v1.7.0のtar.gzファイル(greengrass-linux-x86-64-1.7.0.tar.gz)を 「AWS IoT Device Tester」の<device-tester-extract-location>/products/greengrass/ggcにコピーします。

※AWS IoT Greengrassのtar.gzファイルの名前を変更すると正常に動作しない場合があるので注意。

ディレクトリ構成としては下記画像のようになります。

AWS IoT Greengrassサービスロールを設定

ユーザーに代わってアクションを実行するためアクセス許可が必要になります。そのためにサービスロールの設定を行います。

※既にサービスロールを設定済の方はスキップしてください。

AWS CLIはすでに設定済である前提で進めていきます。

▼AWS CLI コマンドを使用してサービスロールを作成します。

$ aws iam create-role --role-name GreengrassServiceRole --assume-role-policy-document '{ 
	"Version": "2012-10-17",
	"Statement": [{
		"Effect": "Allow",
		"Principal": {
			"Service": "greengrass.amazonaws.com"
		},
		"Action": "sts:AssumeRole"
	}]
}'

▼このコマンドで生成されるサービスロールのARNを使用して AWS IoT Greengrassサービスロールを AWSアカウントに関連付けます。

次の AWS CLI コマンドを使用してAWSGreengrassResourceAccessRolePolicyを AWS IoT Greengrass サービスロールにアタッチします。

$ aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/service-role/AWSGreengrassResourceAccessRolePolicy --role-name GreengrassServiceRole

▼このコマンドを使用して AWS IoT GreengrassサービスロールをAWS アカウントに関連付けます。

$ aws greengrass associate-service-role-to-account --role-arn <your-greengrass-service-role-arn>

AWS IoT Device Tester用のIAMユーザを作成

テスト対象デバイスとAWSとをつなぐために認証設定を行うことが必要になります。

いつも使っているアカウント情報でもよいですが、役割を混在させたくないので今回はテスト用のIAMユーザを作成したいと思います。

(現状スイッチロールなどは未対応のようです。今後のアップデートに期待です。)

▼IAMユーザの作成はマネージメントコンソールから行っていきます。

▼今回はユーザ名を「Device_Tester」として、プログラムによるアクセスにチェックを付けておきます。

次はこのIAMユーザに付与する権限の設定を行います。

IAMユーザにアタッチするポリシーを作成

▼IAMユーザの作成を進めていくと、ポリシーを設定する画面に推移します。

Device Tester用のポリシーはまだ未作成のためこれから作成していきます。そのため、「ポリシーの作成」をクリックします。

▼ポリシーの設定は公式ドキュメントにテンプレートが記載されていますので、それをコピペします。

https://docs.aws.amazon.com/ja_jp/greengrass/latest/developerguide/policy-template.html

▼ポリシー名を今回は「AWS_IoT_Device_Tester_Policy」として作成します。

▼IAMユーザの作成画面に戻り、先程作成したポリシーをアタッチします。

▼IAMユーザ作成が完了すると、認証情報が記載されたCSVがダウンロードできるようになるので必ずDLしておいてください。

AWS IoT Device Tester用の認証情報を設定する

「AWS IoT Device Tester」を使ってAWSと通信するための認証方法として環境変数認証情報ファイル(AWS CLIと同じ)の2つの方法が用意されています。

私はもっぱらスイッチロールしていろんなAWSアカウントをいききするため、環境変数はなるべく汚したくありません。そのため、今回は認証情報ファイルを使った認証設定を行います。

設定方法は下記のようにdevice_testerを追加しました。

aws_access_key_idaws_secret_access_keyは前項でダウンロードしたCSVに記載の情報を設定します。

~/.aws/credentials

[default]
aws_access_key_id = XXXXXXXXXXXXXXXXX
aws_secret_access_key = xxxxxxxxxxxxxx

[device_tester]
aws_access_key_id = YYYYYYYYYYYYYYYYY
aws_secret_access_key = yyyyyyyyyyyy

~/.aws/config

[default]
region = ap-northeast-1
output = json

[profile device_tester]
region=us-east-1
output=json

テスト対象デバイスのEC2インスタンスを用意

今回は、テスト対象デバイスとしてEC2インスタンスを利用します。

AMIは「amzn-ami-hvm-2016.09.1.20170119-x86_64-ebs」を使います。

リージョンはus-east-1に作成しています。

詳細は下記ブログを参照して、ローカルマシン(Mac)とSSHで接続できるようにしておいてください。

デバイスがなくても大丈夫!EC2でAWS IoT Greengrassを動かす #reinvent

※ノンパスでのsudo設定が必要
一部のオペレーションにはルートアクセスが必要です。これらのオペレーションを自動化するには、パスワードの入力を求めることなく、AWS IoT Device Testerで sudoを使用してコマンドを実行できる必要があります。
EC2の場合ec2-userはノンパスでsudo実行可能なので特に設定は必要ありませんが、他のデバイスでは自分で設定が必要なのでご注意ください。

EIPを設定しておく

IPが固定のほうがいろいろと都合がよいので、EIPをEC2インスタンスに設定しておきます。

マネージメントコンソールからIPを払い出します。

払い出したIPをインスタンスに関連付けます。

EIPを設定すると下記のようにIP指定でSSHすることができるようになります。

$ ssh -i "hoge.pem" ec2-user@xxx.xxx.xxx.xxx

(オプション)AWS IoT Device Testerを使った接続のためのSSH設定

EC2インスタンスにSSH接続する際はpemファイルを使った接続が自動的に設定されますが、他のデバイスでは自分でSSH設定する必要があります。そのため、SSHの接続方法を備忘録のために記載しておきます。

そのため、とりあえず動かしたい人はここの章はスキップしてもらって大丈夫です。

AWS IoT Device Tester用のSSHキーを作成

下記コマンドより、SSHキーを作成します。

/Users/nishimura.yuji/.ssh/device_tester_id_rsaのところは自分の環境に合わせて変更してください。

$ ssh-keygen -t rsa -b 4096
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/nishimura.yuji/.ssh/id_rsa): /Users/nishimura.yuji/.ssh/device_tester_id_rsa
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /Users/nishimura.yuji/.ssh/device_tester_id_rsa.
Your public key has been saved in /Users/nishimura.yuji/.ssh/device_tester_id_rsa.pub.

device_tester_id_rsa.pubdevice_tester_id_rsaが作成されているかと思います。

~/.ssh/authorized_keysファイルにパブリックキーを追加

下記のように公開鍵の情報をテスト対象デバイスの~/.ssh/authorized_keysファイルに書き込みます。

$ ssh-copy-id -i /Users/nishimura.yuji/.ssh/device_tester_id_rsa ec2-user@<remote-device-ip>

AWS IoT Device Testerの設定

やっと事前準備がおわり、ここからが本番です。

ホストコンピュータ(Mac)でAWS IoT Device Testerの設定ファイルを修正します。

設定ファイルはconfig.jsondevice.jsonの2つがあります。

下記のように設定を行いました。「device_tester」プロファイルを使うように認証設定をしています。

また、リージョンはus-west-2だとTES service role is not associated with this accountというエラーになってしまったためus-east-1に変更しています。

configs/config.json

{
  "log": {
    "location": "../logs/"
  },
  "configFiles": {
    "root": "../configs",
    "device": "../configs/device.json"
  },
  "testPath": "../tests/",
  "reportPath": "../results/",
  "certificatePath": "../certificates/",
  "awsRegion": "us-east-1",
	"auth": {
		"method": "file",
		"credentials": {
			"profile": "device_tester"
		}
	}
}

IDとは適当に設定し、IPやpemファイルのパスを設定しました。

configs/device.json

[
  {
    "id": "test-pool-id",
    "sku": "test-sku",
    "features": [
      {
        "name": "os",
        "value": "linux"
      },
      {
        "name": "arch",
        "value": "x86_64"
      }
    ],
    "kernelConfigLocation": "",
    "greengrassLocation": "",
    "devices": [
      {
        "id": "test-ec2",
        "connectivity": {
          "protocol": "ssh",
          "ip": "xxx.xxx.xxx.xxx",
          "auth": {
            "method": "pki",
            "credentials": {
              "user": "ec2-user",
              "privKeyPath": "/Users/hoge/hoge.pem"
            }
          }
        }
      }
    ]
  }
]

テストを実行

下記コマンドでテストを実行することができます。

$ ./bin/devicetester_mac_x86-64 run-suite --suite-id GGQ_1 --pool-id test-pool-id

テスト結果を確認

resultsディレクトリ配下に結果のファイルが出力されます。

GGQ_Report.xmlは下記のような出力になりました。

GGQ_Report.xml

<testsuites name="GGQ results" time="260" tests="7" failures="2" errors="0" disabled="0">
	<testsuite name="version" package="" tests="1" failures="0" time="148" disabled="0" errors="0" skipped="0">
		<testcase classname="Test GGC Version" name="Test GGC Version ggc_version_check_test_1: checks whether version of greengrass provided is correct"></testcase>
	</testsuite>
	<testsuite name="dependencies" package="" tests="6" failures="2" time="112" disabled="0" errors="0" skipped="0">
		<testcase classname="GGC Dependencies Checker Test Context" name="ggc dependencies check: cgroups_check_test_1: checks if the required cgroups are mounted">
			<failure type="Failure">s: &#39;memory&#39; and &#39;device&#39; cgroups are not enabled and mounted.: Process exited with status 1</failure>
		</testcase>
		<testcase classname="GGC Dependencies Checker Test Context" name="ggc dependencies check: ggc_user_group_check_test_1: checks whether the ggc user and group exist"></testcase>
		<testcase classname="GGC Dependencies Checker Test Context" name="ggc dependencies check: kernel_configs_check_test_1: checks if the required and optional kernel configs are enabled"></testcase>
		<testcase classname="GGC Dependencies Checker Test Context" name="ggc dependencies check: platform_security_validation_test_1: checks whether required hardlinks and symlinks protection is enabled">
			<failure type="Failure">s: Hardlink/Symlink protection is not enabled.: Process exited with status 1</failure>
		</testcase>
		<testcase classname="GGC Dependencies Checker Test Context" name="ggc dependencies check: software_packages_check_test_1: checks whether device has the required software packages to run greengrass"></testcase>
		<testcase classname="GGC Dependencies Checker Test Context" name="ggc dependencies check: system_configs_check_test_1: checks whether device has the required system configuration to run greengrass"></testcase>
	</testsuite>
</testsuites>

デフォルトのEC2だとcgroupsHardlink/Symlink protectionでエラーとなるようです。

下記を参考にcgroupsHardlink/Symlink protectionの設定を行い再度テストを実行してみます。

デバイスがなくても大丈夫!EC2でAWS IoT Greengrassを動かす #reinvent

テスト完了までに1時間以上かかりましたが、特に異常なくテストが完了しました。

テスト完了後に出力されたレポートは下記のようになっていました。

failuresが0であることがテストをPassしたということになります。

GGQ_Report.xml

<testsuites name="GGQ results" time="6202" tests="27" failures="0" errors="0" disabled="0">
	<testsuite name="version" package="" tests="1" failures="0" time="156" disabled="0" errors="0" skipped="0">
		<testcase classname="Test GGC Version" name="Test GGC Version ggc_version_check_test_1: checks whether version of greengrass provided is correct"></testcase>
	</testsuite>
	<testsuite name="dependencies" package="" tests="6" failures="0" time="110" disabled="0" errors="0" skipped="0">
		<testcase classname="GGC Dependencies Checker Test Context" name="ggc dependencies check: cgroups_check_test_1: checks if the required cgroups are mounted"></testcase>
		<testcase classname="GGC Dependencies Checker Test Context" name="ggc dependencies check: ggc_user_group_check_test_1: checks whether the ggc user and group exist"></testcase>
		<testcase classname="GGC Dependencies Checker Test Context" name="ggc dependencies check: kernel_configs_check_test_1: checks if the required and optional kernel configs are enabled"></testcase>
		<testcase classname="GGC Dependencies Checker Test Context" name="ggc dependencies check: platform_security_validation_test_1: checks whether required hardlinks and symlinks protection is enabled"></testcase>
		<testcase classname="GGC Dependencies Checker Test Context" name="ggc dependencies check: software_packages_check_test_1: checks whether device has the required software packages to run greengrass"></testcase>
		<testcase classname="GGC Dependencies Checker Test Context" name="ggc dependencies check: system_configs_check_test_1: checks whether device has the required system configuration to run greengrass"></testcase>
	</testsuite>
	<testsuite name="mqtt" package="" tests="1" failures="0" time="394" disabled="0" errors="0" skipped="0">
		<testcase classname="Test Lambda and MQTT" name="Test Lambda and MQTT lambda_mqtt_test_1: GGAD should invoke 2 non-pinned Lambdas by publishing to a topic and those Lambdas in turn should send messages back to the GGAD"></testcase>
	</testsuite>
	<testsuite name="shadow" package="" tests="1" failures="0" time="773" disabled="0" errors="0" skipped="0">
		<testcase classname="Test GGC shadow service" name="Test Shadow Sync Service shadow_sync_test_1: an update to a local GGC shadow that is configured to be synced to the cloud should actually propagates as expected to the cloud"></testcase>
	</testsuite>
	<testsuite name="ipd" package="" tests="2" failures="0" time="410" disabled="0" errors="0" skipped="0">
		<testcase classname="Security IPD Testing" name="IPD Tests ipd_test_1: Should have connectivity info updated when new ip address is added to GGC and GGC is stopped and started"></testcase>
		<testcase classname="Security IPD Testing" name="IPD Tests ipd_test_2: Should have connectivity info updated with new ip address on GGC restart"></testcase>
	</testsuite>
	<testsuite name="deployment" package="" tests="1" failures="0" time="244" disabled="0" errors="0" skipped="0">
		<testcase classname="Test GGC Deployments" name="Deploy GGC with failing pinned lambda, redeploy without deployment_test_1: Should redeploy GGC successfully after failing pinned lambda deployed"></testcase>
	</testsuite>
	<testsuite name="dcm" package="" tests="2" failures="0" time="402" disabled="0" errors="0" skipped="0">
		<testcase classname="Security DCM Testing" name="DCM Tests dcm_test_1: Should re-generate server certificate when it is not there"></testcase>
		<testcase classname="Security DCM Testing" name="DCM Tests dcm_test_2: Should re-generate server certificate when it is expiring within 24 hours"></testcase>
	</testsuite>
	<testsuite name="lra" package="" tests="1" failures="0" time="544" disabled="0" errors="0" skipped="0">
		<testcase classname="Test LRA" name="Test LRA Read Write lra_test_1: Lambda functions correctly access local resources accordingly to the LRA configuration."></testcase>
	</testsuite>
	<testsuite name="combination" package="" tests="1" failures="0" time="342" disabled="0" errors="0" skipped="0">
		<testcase classname="Security Combination (IPD + DCM) Test Context" name="Security Combination IP Change Tests sec4_test_1: Should rotate server cert when IPD disabled and following changes are made:Add CIS conn info and Add another CIS conn info"></testcase>
	</testsuite>
	<testsuite name="spooler" package="" tests="1" failures="0" time="541" disabled="0" errors="0" skipped="0">
		<testcase classname="Test Spooler" name="Test Spooler Should forward message to AWS IoT Cloud spooler_test_1: Spooler configuration is not specified in group.json"></testcase>
	</testsuite>
	<testsuite name="native" package="" tests="1" failures="0" time="201" disabled="0" errors="0" skipped="0">
		<testcase classname="Test assertion for native lambdas" name="Native test assertion native_test_1: performs lambda invocations and assertions successfully"></testcase>
	</testsuite>
	<testsuite name="logging" package="" tests="1" failures="0" time="271" disabled="0" errors="0" skipped="0">
		<testcase classname="Test logging for Python lambdas" name="Test Python Lambda Logging logging_test_1: performs Logging successfully"></testcase>
	</testsuite>
	<testsuite name="tes" package="" tests="1" failures="0" time="457" disabled="0" errors="0" skipped="0">
		<testcase classname="Test TES" name="Test TES tes_test_1: Gets credentials and calls aws api correctly."></testcase>
	</testsuite>
	<testsuite name="network" package="" tests="1" failures="0" time="328" disabled="0" errors="0" skipped="0">
		<testcase classname="Test Lambda Networking" name="Test Lambda Networking lambda_network_test: can create UDP and TCP sockets"></testcase>
	</testsuite>
	<testsuite name="penetration" package="" tests="5" failures="0" time="809" disabled="0" errors="0" skipped="0">
		<testcase classname="GGC Penetration Testing" name="GGC Penetration Testing Check GGC only runs when hardlink and Symlink protection is on. Should not let GGC turn on when hardlink/symlink protection is off. penetration_test_1: GGC should not run when hardlink and symlink protection are off"></testcase>
		<testcase classname="GGC Penetration Testing" name="GGC Penetration Testing Check GGC only runs when hardlink and Symlink protection is on. Should not let GGC turn on when hardlink/symlink protection is off. penetration_test_2: GGC should not run when hardlink protection is off"></testcase>
		<testcase classname="GGC Penetration Testing" name="GGC Penetration Testing Check GGC only runs when hardlink and Symlink protection is on. Should not let GGC turn on when hardlink/symlink protection is off. penetration_test_3: GGC should not run when symlink is protection off"></testcase>
		<testcase classname="GGC Penetration Testing" name="GGC Penetration Testing Check GGC only runs when hardlink and Symlink protection is on. Should not let GGC turn on when hardlink/symlink protection is off. penetration_test_4: GGC should only run when hardlink and symlink is protection on"></testcase>
		<testcase classname="GGC Penetration Testing" name="Fake deploymet artifacts cannot be injected penetration_test_5: Should not let corrupt deployment files be injected during deployment."></testcase>
	</testsuite>
	<testsuite name="ota" package="" tests="1" failures="0" time="227" disabled="0" errors="0" skipped="0">
		<testcase classname="Test OTA updates in prod" name="OTA agent updates GGC the GGC is updated to the correct version and the ggc_pre/post_update scripts are correctly run ota_prod_test_1: released GGC 1.7.0 upgrades to current prod release GGC, no managedRespawn"></testcase>
	</testsuite>
</testsuites>

さいごに

いかがだったでしょうか。

なかなか事前準備が大変であるものの公式のツールかつフリーで厳密にAWS IoT Greengrassと互換性があるかの確認ができるので、AWS IoT Greengrass対応のデバイスを販売する際は必須の作業となりそうです。

誰かの参考になれば幸いです。