AWS IoT CoreにローカルLinuxコンテナから接続してみた
はじめに
AWS IoT Coreを触ってみたいと思いまして、まずは「手元のLinuxコンテナから接続できるのか?」を試してみることにしました。
今回はAWS IoT CoreでThingを作成し、ダウンロードした接続キットをFedoraコンテナで動かして、MQTT5のPublish/Subscribeまで試してみます。
検証環境
- macOS Tahoe 26.6.2
- Podman 5.7.0
- コンテナ: Fedora Linux 44(
fedora:latest) - AWS IoT Core(
ap-northeast-1)
AWS IoT Thingを作成する
今回はAWS IoT Coreコンソールのクイック接続を使いました。公式手順はTry the AWS IoT Core quick connect tutorialにまとまっています。
- Connect > Connect one device を開く
- Create a new thing を選択し、Thing名に
lab_iot-machine_01と入力する - Platformに Linux/OSX、SDKに Node.js を選択する
- 接続キットの
connect_device_package.zipをダウンロードする



接続キットには、主に次のファイルが含まれています。
- デバイス証明書
- 公開鍵と秘密鍵
- AWS IoT Policyのコピー
- サンプルを実行する
start.sh
ローカルコンテナ側を準備する
接続キットを展開する
connect_device_package.zip は、コンテナから見える /workspace に置いてそのまま展開しました。ファイル名の変更や別フォルダーへの移動はしていません。
cd /workspace
unzip connect_device_package.zip
Fedoraに不足していたパッケージを入れる
fedora:latest には必要なコマンドが揃っていなかったため、次のパッケージをインストールしました。
unzipcurlopensslgitnodejsiputils
dnf install -y unzip curl openssl
dnf install -y git
dnf install nodejs
dnf install -y iputils
iputils は、後からAWS IoTのendpointへ ping を試すために追加しました。


start.sh を実行する前に必要だったこと

AWS IoT Coreの画面では、接続キットを展開して start.sh を実行するよう案内されます。ただ、今回のFedoraコンテナでは、必要なパッケージのインストールとSDK・サンプルのbuildを先に行う必要がありました。
| 状況 | 原因 | 対応 |
|---|---|---|
uzip: command not found |
unzip のタイプミス |
コマンドを修正 |
git: command not found |
FedoraにGitがなかった | dnf install -y git |
npm: command not found |
Node.jsとnpmがなかった | dnf install nodejs |
Cannot find module .../dist/index.js |
clone済みのSDKフォルダーが残り、start.sh がインストール処理をskipした |
SDKとサンプルを手動でbuild |
tsc: command not found |
SDK側の依存関係が未インストールのまま、サンプル側からSDKの prepare が呼ばれた |
SDK側から先にdevDependenciesを含めてinstall |
start.sh は、SDKフォルダーが存在しない場合だけ、cloneと npm install を実行する作りになっています。
if [ ! -d ./aws-iot-device-sdk-js-v2 ]; then
git clone https://github.com/aws/aws-iot-device-sdk-js-v2.git --recursive
cd aws-iot-device-sdk-js-v2
npm install
cd samples/node/mqtt/mqtt5_x509
npm install
fi
最初の実行では、SDKをcloneした直後に npm: command not found で止まりました。しかしSDKフォルダーは残ったため、再実行時は条件に入らず、インストールとbuildが未完了でも npm install がskipされました。その結果、まだ存在しない dist/index.js を実行しようとしていました。そこで、SDK側から順番に依存関係を入れてbuildしました。
cd /workspace/aws-iot-device-sdk-js-v2
rm -rf node_modules
npm install --include=dev
npm run build
cd samples/node/mqtt/mqtt5_x509
rm -rf node_modules dist
npm install --include=dev
cd /workspace
bash start.sh
実行結果
実際の実行ログです。
[root@5a13d8c9d00e workspace]# bash start.sh
Running pub/sub sample application...
Starting MQTT5 X509 PubSub Sample
==== Creating MQTT5 Client ====
==== Starting client ====
Lifecycle Connection Attempt
Connecting to endpoint: 'atczphvn9h1th-ats.iot.ap-northeast-1.amazonaws.com' with client ID 'sdk-nodejs-v2'
Lifecycle Connection Success with reason code: 0
==== Subscribing to topic 'sdk/test/js' ====
Suback received with reason code: 1
==== Sending 5 message(s) ====
Publishing message to topic 'sdk/test/js': Hello from mqtt5 sample [1]
PubAck received with 0
==== Received message from topic 'sdk/test/js': Hello from mqtt5 sample [1] ====
Publishing message to topic 'sdk/test/js': Hello from mqtt5 sample [2]
PubAck received with 0
==== Received message from topic 'sdk/test/js': Hello from mqtt5 sample [2] ====
Publishing message to topic 'sdk/test/js': Hello from mqtt5 sample [3]
PubAck received with 0
==== Received message from topic 'sdk/test/js': Hello from mqtt5 sample [3] ====
Publishing message to topic 'sdk/test/js': Hello from mqtt5 sample [4]
PubAck received with 0
==== Received message from topic 'sdk/test/js': Hello from mqtt5 sample [4] ====
Publishing message to topic 'sdk/test/js': Hello from mqtt5 sample [5]
PubAck received with 0
==== Received message from topic 'sdk/test/js': Hello from mqtt5 sample [5] ====
5 message(s) received.
==== Unsubscribing from topic 'sdk/test/js' ====
Unsubscribed with 0
==== Stopping Client ====
Lifecycle Disconnected with reason code: None
Lifecycle Stopped
==== Client Stopped! ====


テスト後にサンプルの中身を確認する
ここまでで接続テストは成功しましたが、実行中は証明書やPolicy、MQTT5サンプルの中身まで詳しく見ていませんでした。そこでテスト後に、接続キットとサンプルコードが実際に何をしていたのか確認してみました。
実行後のファイル構成
今回関係したファイルだけ抜き出すと、 /workspace は次のような構成になっていました。

/workspace/
├── connect_device_package.zip
├── start.sh
├── lab_iot-machine_01.cert.pem
├── lab_iot-machine_01.public.key
├── lab_iot-machine_01.private.key
├── lab_iot-machine_01-Policy
├── root-CA.crt
└── aws-iot-device-sdk-js-v2/
└── samples/node/mqtt/mqtt5_x509/
├── package.json
├── index.ts
└── dist/index.js
接続キットを展開した時点で入っていたのは、証明書、公開鍵、秘密鍵、Policyのコピー、start.sh です。root-CA.crt と aws-iot-device-sdk-js-v2 は、start.sh の実行によってダウンロード・cloneされました。
各ファイルの役割は次のとおりです。
lab_iot-machine_01.cert.pem:start.shが--certに指定するX.509デバイス証明書lab_iot-machine_01.private.key:start.shが--keyに指定する秘密鍵lab_iot-machine_01.public.key: 接続キットに含まれる公開鍵。今回のサンプルからは直接読み込まれないlab_iot-machine_01-Policy: 接続キットに含まれていたIoT PolicyのJSONindex.ts: MQTT5 X.509サンプルのTypeScriptソースdist/index.js:index.tsをbuildした、start.shが実際に実行するJavaScript
root-CA.crt は start.sh がダウンロードしますが、今回のNode.jsサンプルにはファイルパスが渡されておらず、index.ts からも参照されていません。
start.sh は何をサンプルへ渡していたのか
接続キットの start.sh は、最後に次のコマンドを実行しています。
node aws-iot-device-sdk-js-v2/samples/node/mqtt/mqtt5_x509/dist/index.js \
--endpoint atczphvn9h1th-ats.iot.ap-northeast-1.amazonaws.com \
--key lab_iot-machine_01.private.key \
--cert lab_iot-machine_01.cert.pem \
--client_id sdk-nodejs-v2 \
--topic sdk/test/js
ここで渡しているのは、AWS IoT endpoint、秘密鍵、証明書、MQTT Client ID、Topicです。Thing名の lab_iot-machine_01 自体は接続パラメータとして渡しておらず、Client IDには sdk-nodejs-v2 を使っています。
メッセージ本文と送信回数は指定していないため、サンプル側のデフォルトである Hello from mqtt5 sample と5回が使われました。
MQTT5のサンプルは何をしていたのか
確認したソースコードは、AWS公式GitHubリポジトリのmqtt5_x509サンプルでも参照できます。
QoS(Quality of Service)は、MQTTの配信品質を表すレベルです。今回使われたQoS 1(At Least Once)では、送信側は相手からPUBACK(受信確認)を受け取るまで再送するため、少なくとも1回の配信が行われます。ただし、同じメッセージが重複して届く可能性があります。
処理の流れは次のとおりです。
MQTT5 clientを作成
↓
AWS IoT Coreへ接続
↓
sdk/test/jsをQoS 1でSubscribe
↓
同じsdk/test/jsへQoS 1で5回Publish
↓
同じclientで5件Receive
↓
Unsubscribe
↓
clientをStopしてClose
サンプルコードでは、証明書と秘密鍵のパスを newDirectMqttBuilderWithMtlsFromPath() に渡してMQTT5 clientを作成しています。その後、sdk/test/js をQoS 1でSubscribeし、同じTopicへ5件Publishしています。
実行ログでは、各Publishの後に同じTopicの Received message が1件ずつ出力されています。接続から受信まで同じClient IDを使用しているため、別のデバイスや別のclientへの送信はテストしていません。
ログに出ているreason codeは、AWS IoT CoreのMQTT reason code一覧でも確認できます。Connectionの 0 とPubAckの 0 はSuccess、Subackの 1 はGranted QoS 1です。
Policyファイルに書かれていたこと
start.sh が実行するコマンドには、lab_iot-machine_01-Policy が含まれていません。Node.jsサンプルでも、このファイルは読み込んでいません。
ローカルのPolicyファイルには、今回のClient IDとTopicに一致する次の記述がありました。
iot:Connect: Resourceにclient/sdk-nodejs-*iot:Subscribe: Resourceにtopicfilter/sdk/test/jsiot:Publish、iot:Receive: Resourceにtopic/sdk/test/js
start.sh が指定したClient IDは sdk-nodejs-v2、Topicは sdk/test/js なので、Policyファイル内の値と一致しています。Policyには iot:PublishRetain もありますが、今回のサンプルコードにはRETAINの指定がありません。
一方、ローカルのPolicyファイルだけでは、このPolicyがAWS側でどの証明書にアタッチされているかは確認できません。これはAWS IoT Coreコンソールから確認できます。
- Manage > All devices > Things から
lab_iot-machine_01を開く - Certificates から今回の証明書を開く
- 証明書詳細の Things でThingとの関連付けを確認する
- Policies でアタッチされたPolicyを確認する
- Policyを開き、Policy documentがローカルの
lab_iot-machine_01-Policyと一致するか確認する

公式ドキュメントにも、証明書詳細の Policies からアタッチ済みPolicyを確認する手順があります。
今回確認できた範囲
今回の実行ログから直接確認できたのは、start.sh が指定した証明書、秘密鍵、Client ID、TopicでConnectionが成功し、sdk/test/js のSubscribe、5回のPublish、5件のReceive、Unsubscribeまで完了したことです。
ログにあるClient IDはThing名ではなく sdk-nodejs-v2 です。Topicの sdk/test/js も、サンプルのデフォルトではなく start.sh から指定されていました。
おわりに
ローカルのFedoraコンテナから、X.509証明書を使ってAWS IoT Coreへ接続し、MQTT5のPublish/Subscribeを確認できました。
今回一番ハマったのはAWS IoT Coreの設定ではなく、最小構成のFedoraに必要なツールがなく、途中で止まった start.sh がSDKフォルダーの存在だけを見てセットアップ済みと判断したことでした。最初に unzip、git、Node.jsを揃えてから実行するか、途中で失敗した場合はSDK側から依存関係とbuildを確認すると進めやすいです。
なお、接続キットには秘密鍵が含まれます。Gitや公開記事には載せず、検証後は証明書を無効化し、不要になったThing・証明書・Policyとその関連付けを削除しておきましょう。







