Raspberry PiとAWSを繋いでみる ~AWS IoTのジョブ~
こんにちは。CX事業本部のKyoです。引き続きラズパイとAWS IoT Coreを触っていきます。
前回はこちら。
概要
AWS IoTからAWS IoT Device Clientをセットアップしたラズパイに対して、ジョブを実行させます。ジョブ、という言葉の定義は公式ドキュメントに以下のように記されています。
ジョブは、AWS IoT に接続された 1 つ以上のデバイスに送信され実行されるリモート操作です。例えば、一連のデバイスに対して、アプリケーションのダウンロードとインストール、ファームウェア更新の実行、再起動、証明書のローテーション、またはリモートトラブルシューティングオペレーションの実行を指示するジョブを定義できます。
具体的には以下の手順を行います。
AWS IoT Device Client でのリモートアクション (ジョブ) をデモンストレーションする
ステップ 1: ジョブを実行するために Raspberry Pi を準備する
Raspberry Pi をプロビジョニングしてジョブをデモンストレーションする
前々回、前回でも行ったラズパイへの証明書セットアップ、AWS IoTへのリソース作成(デバイスのプロビジョニング、ポリシーの作成、ポリシーのアタッチ)を行いました。
今回のポリシーはこのような形です。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "iot:Connect" ], "Resource": [ "arn:aws:iot:<リージョン>:<アカウントID>:client/<デバイス名>" ] }, { "Effect": "Allow", "Action": [ "iot:Publish" ], "Resource": [ "arn:aws:iot:<リージョン>:<アカウントID>:topic/test/dc/pubtopic", "arn:aws:iot:<リージョン>:<アカウントID>:topic/$aws/events/job/*", "arn:aws:iot:<リージョン>:<アカウントID>:topic/$aws/events/jobExecution/*", "arn:aws:iot:<リージョン>:<アカウントID>:topic/$aws/things/<デバイス名>/jobs/*" ] }, { "Effect": "Allow", "Action": [ "iot:Subscribe" ], "Resource": [ "arn:aws:iot:<リージョン>:<アカウントID>:topicfilter/test/dc/subtopic", "arn:aws:iot:<リージョン>:<アカウントID>:topic/$aws/events/jobExecution/*", "arn:aws:iot:<リージョン>:<アカウントID>:topicfilter/$aws/things/<デバイス名>/jobs/*" ] }, { "Effect": "Allow", "Action": [ "iot:Receive" ], "Resource": [ "arn:aws:iot:<リージョン>:<アカウントID>:topic/test/dc/subtopic", "arn:aws:iot:<リージョン>:<アカウントID>:topic/$aws/things/<デバイス名>/jobs/*" ] }, { "Effect": "Allow", "Action": [ "iot:DescribeJobExecution", "iot:GetPendingJobExecutions", "iot:StartNextPendingJobExecution", "iot:UpdateJobExecution" ], "Resource": [ "arn:aws:iot:<リージョン>:<アカウントID>:topic/$aws/things/<デバイス名>" ] } ] }
AWS IoT Device Client を設定してジョブエージェントを実行する
おなじみのAWS IoT Device Clientの設定ファイルを作成します。今回はjobsが有効化されていますね。
{ "endpoint": "", "cert": "~/certs/jobs/device.pem.crt", "key": "~/certs/jobs/private.pem.key", "root-ca": "~/certs/AmazonRootCA1.pem", "thing-name": "", "logging": { "enable-sdk-logging": true, "level": "DEBUG", "type": "STDOUT", "file": "" }, "jobs": { "enabled": true, "handler-directory": "" }, "tunneling": { "enabled": false }, "device-defender": { "enabled": false, "interval": 300 }, "fleet-provisioning": { "enabled": false, "template-name": "", "template-parameters": "", "csr-file": "", "device-key": "" }, "samples": { "pub-sub": { "enabled": false, "publish-topic": "", "publish-file": "", "subscribe-topic": "", "subscribe-file": "" } }, "config-shadow": { "enabled": false }, "sample-shadow": { "enabled": false, "shadow-name": "", "shadow-input-file": "", "shadow-output-file": "" } }
ステップ 2: AWS IoT でジョブを作成して実行する
ジョブのジョブドキュメントを作成して保存する
まずジョブの内容を示したジョブドキュメントをJSON形式で作成します。今回の内容はHello Worldの出力です。
{ "operation": "echo", "args": ["Hello world!"] }
このJSONは任意のS3にアップロードしておきます。この際にジョブドキュメントのURIをメモしておきます。
より具体的なジョブドキュメントについてはAWS IoT Device Clientのリポジトリにサンプルがあったので覗いてみるといいと思います。
https://github.com/awslabs/aws-iot-device-client/tree/main/sample-job-docs
AWS IoTで 1 つの IoT デバイスに対してジョブを実行する
ラズパイ側でAWS IoT Device Client を起動します。
./aws-iot-device-client --config-file ~/dc-configs/dc-jobs-config.json
ラズパイ側のターミナルに以下のようなログが表示されたら準備完了です。
2023-04-0XTXX:XX:XX.XXXZ [INFO] {JobsFeature.cpp}: No pending jobs are scheduled, waiting for the next incoming job
ローカル側から以下のコマンドを実行します。
aws iot create-job \ --job-id hello-world-job-1 \ --document-source "<ジョブドキュメントのURI>" \ --targets "<デバイスのARN>" \ --target-selection SNAPSHOT
ラズパイ側で以下のようにジョブの実行が確認できました。
2023-04-07T10:31:45.524Z [DEBUG] {JobsFeature.cpp}: We have not seen a job yet, this is not a duplicate job notification 2023-04-07T10:31:45.524Z [DEBUG] {JobsFeature.cpp}: Attempting to update job execution status! 2023-04-07T10:31:45.525Z [INFO] {JobsFeature.cpp}: Executing job: hello-world-job-1 2023-04-07T10:31:45.525Z [DEBUG] {Retry.cpp}: Retryable function starting, it will retry until success 2023-04-07T10:31:45.525Z [INFO] {JobEngine.cpp}: About to execute step with name: echo 2023-04-07T10:31:45.525Z [DEBUG] {JobsFeature.cpp}: Created EphemeralPromise for ClientToken 7SlC3wUiQ7 in the updateJobExecution promises map 2023-04-07T10:31:45.525Z [DEBUG] {JobEngine.cpp}: Assuming executable is in PATH 2023-04-07T10:31:45.525Z [INFO] {JobEngine.cpp}: About to execute: echo Hello world! 2023-04-07T10:31:45.526Z [DEBUG] {JobEngine.cpp}: Child process now running 2023-04-07T10:31:45.526Z [DEBUG] {JobEngine.cpp}: Child process about to call execvp 2023-04-07T10:31:45.531Z [DEBUG] {JobEngine.cpp}: Parent process now running, child PID is 11214 2023-04-07T10:31:45.535Z [DEBUG] {11214}: Hello world! 2023-04-07T10:31:45.536Z [DEBUG] {JobEngine.cpp}: JobEngine finished waiting for child process, returning 0 2023-04-07T10:31:45.536Z [INFO] {JobsFeature.cpp}: Job exited with status: 0 2023-04-07T10:31:45.536Z [INFO] {JobsFeature.cpp}: Job executed successfully! 2023-04-07T10:31:45.536Z [DEBUG] {JobsFeature.cpp}: Attempting to update job execution status! 2023-04-07T10:31:45.537Z [DEBUG] {Retry.cpp}: Retryable function starting, it will retry until success 2023-04-07T10:31:45.537Z [DEBUG] {JobsFeature.cpp}: Created EphemeralPromise for ClientToken 7ud3y1DPkb in the updateJobExecution promises map 2023-04-07T10:31:45.620Z [DEBUG] {JobsFeature.cpp}: Ack received for PublishUpdateJobExecutionStatus with code {0} 2023-04-07T10:31:45.621Z [DEBUG] {JobsFeature.cpp}: Removing ClientToken 7SlC3wUiQ7 from the updateJobExecution promises map 2023-04-07T10:31:45.621Z [DEBUG] {JobsFeature.cpp}: Success response after UpdateJobExecution for job hello-world-job-1 2023-04-07T10:31:45.630Z [DEBUG] {JobsFeature.cpp}: Ack received for PublishUpdateJobExecutionStatus with code {0} 2023-04-07T10:31:45.631Z [DEBUG] {JobsFeature.cpp}: Removing ClientToken 7ud3y1DPkb from the updateJobExecution promises map 2023-04-07T10:31:45.631Z [DEBUG] {JobsFeature.cpp}: Success response after UpdateJobExecution for job hello-world-job-1 2023-04-07T10:31:46.395Z [INFO] {JobsFeature.cpp}: No pending jobs are scheduled, waiting for the next incoming job
マネジメントコンソールからもジョブの状態が確認できました。
おわりに
今回はAWS IoTのジョブ機能を使ってみました。イメージとしてはSSMのRun Commandのような印象を受けました。色々なことができそうなので活用方法を探っていければと思います。