EC2インスタンスからAWS IoT CoreにMQTTメッセージを送信する
本記事では、AWS IoT Coreを使用してEC2インスタンスからMQTTメッセージを送信する方法を確認します。
AWS IoT Coreサービスは IoT デバイスを AWS IoTサービスおよび他の AWSサービスと接続します。AWS IoT Core には、IoT デバイスとクラウドを接続してその間のメッセージを処理する、デバイスゲートウェイとメッセージブローカーが含まれています。
AWS IoT Core の開始方法
今回は、公式チュートリアルを参考に、EC2インスタンスをIoTデバイス(Thing)として設定し、AWS IoT Coreに接続してメッセージを送信します。これにより、AWS IoT Coreの基本操作を学び、IoTデバイスからのメッセージ送信を実現します。
構成
- EC2インスタンスをIoTデバイス(Thing)として設定します。
- EC2インスタンスからAWS IoT Coreに接続し、MQTTメッセージを送信します。
- AWS IoT Device SDK for Pythonを使用して、メッセージの送信を行います。
リソース
AWS IoT Core関係の以下のリソースを作成します。
- モノのオブジェクト(thing objects)
- AWS IoT ポリシー
- クライアント証明書
- AWS IoT Device SDKを使用したAWS IoT Coreへの接続
Thing(モノ)
モノのオブジェクトは、AWS IoTによって管理されるデバイスを表します。今回はEC2インスタンスをIoTデバイスとして見立て、このモノのオブジェクトを作成します。
AWS IoT Core に接続されたデバイスは、AWS IoT レジストリでモノのオブジェクトによって表されます。モノのオブジェクトは、特定のデバイスまたは論理エンティティを表します。物理的なデバイスやセンサー (電球、または電気をつけるための壁にあるスイッチなど) は、モノとして扱うことができます。
モノのオブジェクトを作成する
ポリシー
AWS IoT ポリシーはデバイスが使用する証明書に紐づくもので、デバイスがAWS IoTに対して実行可能なアクションを定義するものです。
デバイスは、AWS IoT Core での認証に X.509 証明書を使用します。証明書には AWS IoT ポリシーがアタッチされています。これらのポリシーは、デバイスで実行できる AWS IoT オペレーション (MQTT トピックへのサブスクライブや公開など) を決定します。デバイスは AWS IoT Core に接続してメッセージを送信するときに、証明書を提示します。
AWS IoT ポリシーを作成する
今回は検証ということで、以下の公式のチュートリアルに記載されているものを参考に作成しました。
トピック名には「test/topic」、クライアントIDには「fa-151」が指定されています。これらはEC2インスタンスがサンプルスクリプトを実行する際に指定することになる値です。
EC2インスタンス
モノとして動作するEC2インスタンスのユーザデータです。
dnfを使用してPython11とGitをインストールします。これらはAWS IoT Device SDK for Pythonの取得と実行のために使用します。
curlコマンドを使用して、Amazon認証機関証明書(AmazonRootCA1.pem)を取得します。この証明書はIoTデバイスからAWS IoT CoreにMQTTメッセージを送信する際に、送信先が正しいかを確認するためのサーバ証明のために使用します。
aws iot create-keys-and-certificateコマンドで証明書を作成します。モノがAWS IoT Coreと通信する際に使用するクライアント証明書(device.pem.crt)を生成します。証明書はEC2インスタンスに生成されるとともに、AWS IoT側にも登録されます。
またコマンドを実行すると、証明書を作成する際に使用した公開鍵(public.pem.key)と秘密鍵(private.pem.key)も取得することができます。特に秘密鍵の方は、AWS IoT CoreにMQTTメッセージを送信する際に使用します。このコマンドの実行結果を、テキストファイル(output.txt)に保存します。これは証明書がAWS IoTに登録された際に、証明書に割り振られたARNを取得するためです。jqコマンドを使用して、これを取得して変数に格納します。ちなみにこのコマンドの実行結果は以下のフォーマットで返されます。
{
"certificateArn": "arn:aws:iot:[region]:[account-id]:cert/[cert-id]",
"certificateId": "[cert-id]",
"certificatePem": "-----BEGIN CERTIFICATE-----\n[cert-pem]\n-----END CERTIFICATE-----\n",
"keyPair": {
"PublicKey": "-----BEGIN PUBLIC KEY-----\n[public-key]\n-----END PUBLIC KEY-----\n",
"PrivateKey": "-----BEGIN RSA PRIVATE KEY-----\n[private-key]\n-----END RSA PRIVATE KEY-----\n"
}
}
Code language: JSON / JSON with Comments (json)
aws iot attach-thing-principalコマンドを実行して、モノと証明書を関連付けます。それぞれ先述のモノのリソース名と証明書のARNを指定します。
aws iot attach-policyコマンドを実行して、AWS IoT ポリシーと証明書を関連づけます。注意点は、AWS IoT ポリシーに関連づける対象はモノではなく、証明書であるという点です。先述のポリシー名と証明書のARNを指定します。
aws iot describe-endpointコマンドを実行して、MQTTメッセージの送信先であるエンドポイントを取得します。エンドポイントは以下の通り、2種類ありますが、下記の引用に従い、endpoint-typeオプションに「iot:Data-ATS」を指定します。
すべてのカスタマーには iot:Data-ATS および iot:Data エンドポイントがあります。各エンドポイントは X.509 証明書を使用して、クライアントを認証します。Symantec 認証機関の広範な不信用に関連する問題を避けるために、新しい iot:Data-ATS エンドポイントタイプを使用することを強くお勧めします。
デバイスの AWS IoT への接続
このコマンドの実行結果をテキストファイル(endpoint.txt)に保存し、エンドポイント名を変数に格納します。ここでもjqコマンドを使用して、以下のJSON形式のデータからエンドポイント名を抽出します。
{
"endpointAddress": "[endpoint-id]-ats.iot.[region].amazonaws.com"
}
Code language: JSON / JSON with Comments (json)
pipを使用して、AWS IoT Device SDK for Python(awsiotsdk)をインストールします。そしてGitを使用して、本SDKのサンプルスクリプトも取得します。
最後にサンプルスクリプトpubsub.pyを実行します。このサンプルスクリプトはAWS IoT Coreにテストメッセージ(Hello World!)を送信します。このスクリプトを実行するために、これまで取得したエンドポイント、Amazon認証機関証明書、クライアント証明書、クライアント証明書用の秘密鍵を渡します。加えて2つのパラメータを指定します。1つ目はクライアントIDです。こちらについては以下に引用した通りですが、モノの名前を指定します。
一般的なデバイスのユースケースでは、デフォルトの MQTT クライアント ID としてモノの名前が使用されます。MQTT クライアント ID、証明書、またはシャドウ状態をモノのレジストリ名として使用するというマッピングは強制されませんが、レジストリと Device Shadow サービスの両方で、モノの名前を MQTT クライアント ID として使用することをお勧めします。
AWS IoT によるデバイスの管理
2つ目はトピック名です。こちらについては以下に引用した通りですが、今回は「test/topic」を指定します。
MQTT トピックでは、AWS IoT メッセージを識別します。AWS IoT クライアントは、メッセージのトピック名を指定してパブリッシュするメッセージを識別します。クライアントは、トピックフィルターを AWS IoT Core に登録して、サブスクライブ (受信) するメッセージを識別します。メッセージブローカーはトピック名とトピックフィルターを使用して、パブリッシュするクライアントからサブスクライブするクライアントに、 メッセージを振り分けます。
MQTT トピック
証明書
先述のEC2インスタンスにおいてaws iot create-keys-and-certificateコマンドで生成された証明書です。そしてaws iot attach-thing-principalコマンドおよびaws iot attach-policyコマンドによって、この証明書にモノとポリシーがアタッチされています。
IAMロール
EC2インスタンスがAWS IoT Coreにアクセスできるよう、IAMロールを作成します。信頼ポリシーでec2.amazonaws.comを許可し、必要な権限を持つIAMポリシーをアタッチします。
CloudFormationテンプレート
Thing(モノ)
Resources:
Thing:
Type: AWS::IoT::Thing
Properties:
ThingName: !Ref Prefix
Code language: YAML (yaml)
ポリシー
Resources:
Policy:
Type: AWS::IoT::Policy
Properties:
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- iot:Publish
- iot:Receive
Resource:
- !Sub "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topic/${TopicName}"
- Effect: Allow
Action:
- iot:Subscribe
Resource:
- !Sub "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topicfilter/${TopicName}"
- Effect: Allow
Action:
- iot:Connect
Resource:
- !Sub "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:client/${Thing}"
PolicyName: !Ref Prefix
Code language: YAML (yaml)
EC2インスタンス
Resources:
Instance:
Type: AWS::EC2::Instance
Properties:
IamInstanceProfile: !Ref InstanceProfile
ImageId: !Ref ImageId
InstanceType: !Ref InstanceType
NetworkInterfaces:
- AssociatePublicIpAddress: true
DeviceIndex: 0
GroupSet:
- !Ref InstanceSecurityGroup
SubnetId: !Ref PublicSubnet1
UserData: !Base64
Fn::Sub: |
#!/bin/bash -xe
dnf update -y
dnf install python3.11-pip -y
dnf install -y git
mkdir ~/certs
curl -o ~/certs/Amazon-root-CA-1.pem \
https://www.amazontrust.com/repository/AmazonRootCA1.pem
aws iot create-keys-and-certificate \
--set-as-active \
--certificate-pem-outfile "~/certs/device.pem.crt" \
--public-key-outfile "~/certs/public.pem.key" \
--private-key-outfile "~/certs/private.pem.key" > ~/output.txt
certificate_arn=`cat ~/output.txt | jq -r .certificateArn`
aws iot attach-thing-principal \
--thing-name "${Thing}" \
--principal "$certificate_arn"
aws iot attach-policy \
--policy-name "${Policy}" \
--target "$certificate_arn"
aws iot describe-endpoint \
--endpoint-type iot:Data-ATS > ~/endpoint.txt
endpoint=`cat ~/endpoint.txt| jq -r .endpointAddress`
python3.11 -m pip install awsiotsdk
cd ~ && git clone https://github.com/aws/aws-iot-device-sdk-python-v2.git
cd ~/aws-iot-device-sdk-python-v2/samples && python3.11 pubsub.py \
--endpoint "$endpoint" \
--ca_file ~/certs/Amazon-root-CA-1.pem \
--cert ~/certs/device.pem.crt \
--key ~/certs/private.pem.key \
--client_id "${Thing}" \
--topic "${TopicName}" \
--count 5
Code language: YAML (yaml)
IAMロール
Resources:
InstanceRole:
Type: AWS::IAM::Role
DeletionPolicy: Delete
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: sts:AssumeRole
Principal:
Service:
- ec2.amazonaws.com
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
Policies:
- PolicyName: InstancePolicy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- iot:AttachPolicy
- iot:AttachThingPrincipal
- iot:CreateKeysAndCertificate
- iot:DescribeEndpoint
Resource:
- "*"
Code language: YAML (yaml)
テンプレート全体
動作確認
ユーザデータ
EC2インスタンスにアクセスし、ユーザーデータで定義した処理の実行状況を確認します。インスタンスへのアクセスはSSM Session Managerを使用します。SSM Session Managerに関しては、以下のページをご確認ください。
treeコマンドで/rootディレクトリの内容を確認します。
sh-5.2$ sudo tree /root
/root
├── aws-iot-device-sdk-python-v2
│ ├── ...
│ ├── samples
│ │ │ ├── ...
│ │ │ ├── pubsub.py
│ │ │ └── ...
│ └── ...
├── certs
│ ├── Amazon-root-CA-1.pem
│ ├── device.pem.crt
│ ├── private.pem.key
│ └── public.pem.key
├── endpoint.txt
└── output.txt
Code language: Bash (bash)
各種ファイルが正常に配置されていることが確認できます。
AWS IoT Coreへメッセージ送信
journalctlコマンドでインスタンスのログを確認し、AWS IoT CoreにMQTTメッセージを送信していることを確認します。
sh-5.2$ sudo journalctl --no-pager
...
Feb 24 06:15:19 ip-10-0-1-45.ap-northeast-1.compute.internal cloud-init[1672]: + python3.11 pubsub.py --endpoint a2oxckhng7gmur-ats.iot.ap-northeast-1.amazonaws.com --ca_file /root/certs/Amazon-root-CA-1.pem --cert /root/certs/device.pem.crt --key /root/certs/private.pem.key --client_id fa-151 --topic test/topic --count 5
...
Feb 24 06:15:25 ip-10-0-1-45.ap-northeast-1.compute.internal cloud-init[1672]: Connecting to a2oxckhng7gmur-ats.iot.ap-northeast-1.amazonaws.com with client ID 'fa-151'...
Feb 24 06:15:25 ip-10-0-1-45.ap-northeast-1.compute.internal cloud-init[1672]: Connected!
Feb 24 06:15:25 ip-10-0-1-45.ap-northeast-1.compute.internal cloud-init[1672]: Subscribing to topic 'test/topic'...
Feb 24 06:15:25 ip-10-0-1-45.ap-northeast-1.compute.internal cloud-init[1672]: Connection Successful with return code: 0 session present: False
Feb 24 06:15:25 ip-10-0-1-45.ap-northeast-1.compute.internal cloud-init[1672]: Subscribed with 1
Feb 24 06:15:25 ip-10-0-1-45.ap-northeast-1.compute.internal cloud-init[1672]: Sending 5 message(s)
Feb 24 06:15:25 ip-10-0-1-45.ap-northeast-1.compute.internal cloud-init[1672]: Publishing message to topic 'test/topic': Hello World! [1]
Feb 24 06:15:25 ip-10-0-1-45.ap-northeast-1.compute.internal cloud-init[1672]: Received message from topic 'test/topic': b'"Hello World! [1]"'
Feb 24 06:15:25 ip-10-0-1-45.ap-northeast-1.compute.internal cloud-init[1672]: Publishing message to topic 'test/topic': Hello World! [2]
Feb 24 06:15:25 ip-10-0-1-45.ap-northeast-1.compute.internal cloud-init[1672]: Received message from topic 'test/topic': b'"Hello World! [2]"'
Feb 24 06:15:25 ip-10-0-1-45.ap-northeast-1.compute.internal cloud-init[1672]: Publishing message to topic 'test/topic': Hello World! [3]
Feb 24 06:15:25 ip-10-0-1-45.ap-northeast-1.compute.internal cloud-init[1672]: Received message from topic 'test/topic': b'"Hello World! [3]"'
Feb 24 06:15:25 ip-10-0-1-45.ap-northeast-1.compute.internal cloud-init[1672]: Publishing message to topic 'test/topic': Hello World! [4]
Feb 24 06:15:25 ip-10-0-1-45.ap-northeast-1.compute.internal cloud-init[1672]: Received message from topic 'test/topic': b'"Hello World! [4]"'
Feb 24 06:15:25 ip-10-0-1-45.ap-northeast-1.compute.internal cloud-init[1672]: Publishing message to topic 'test/topic': Hello World! [5]
Feb 24 06:15:25 ip-10-0-1-45.ap-northeast-1.compute.internal cloud-init[1672]: Received message from topic 'test/topic': b'"Hello World! [5]"'
Feb 24 06:15:25 ip-10-0-1-45.ap-northeast-1.compute.internal cloud-init[1672]: 5 message(s) received.
Feb 24 06:15:25 ip-10-0-1-45.ap-northeast-1.compute.internal cloud-init[1672]: Disconnecting...
Feb 24 06:15:25 ip-10-0-1-45.ap-northeast-1.compute.internal cloud-init[1672]: Connection closed
Feb 24 06:15:25 ip-10-0-1-45.ap-northeast-1.compute.internal cloud-init[1672]: Disconnected!
...
Code language: Bash (bash)
AWS IoTエンドポイントにMQTTメッセージを送信している箇所を抽出しました。これはユーザデータにおいて定義したAWS IoT Device SDKのサンプルプログラムの実行結果です。サンプルスクリプトを実行してAWS IoTに接続し、5回メッセージを送信していることがわかります。
最後に、AWS IoT MQTTクライアントを使用して、EC2インスタンスから送信されたメッセージを確認します。
AWS IoTコンソールで「Subscribe to a topic」を選択し、トピック名に「test/topic」を入力して「Subscribe」をクリックします。
メッセージが表示され、EC2インスタンスから送信されたものであることが確認できます。
モノとAWS IoT ポリシーを削除する前に、証明書をデタッチすること!
CloudFormationスタックを削除する前に、証明書とのアタッチを解除する必要があります。
これを実行せずにリソースを削除しようとすると、削除に失敗するのでご注意ください。
まとめ
EC2インスタンスをIoTデバイスとして設定し、AWS IoT CoreにMQTTメッセージを送信する方法を確認しました。AWS IoT Device SDK for Pythonを使用し、証明書の作成やポリシーの設定を行いました。これにより、IoTデバイスからクラウドへのメッセージ送信の基本を理解できます。