【AWS IoT Core】MQTTでメッセージをS3に送ってみる (CloudFormation)

# はじめに
業務の方でAWS IoT Coreを触ることになりそうなので、キャッチアップとして入門レベルの実装をしてみたいと思います。

今回は、下記のような構成を作ります。AWSリソースは、CloudFormationで作成します。
1. MQTTX(ツール)からAWS IoT CoreにMQTTでメッセージを送信
2. 受信したメッセージをAmazon S3に保存

# 概要
## AWSにおけるIoTサービス達
AWSでは、下記のIoTサービスが提供されており、今回はそのうちの「AWS IoT Core」に入門してみます。

```mermaid
graph TB
    subgraph "デバイス側" %% デバイス側は緑系
        Device[デバイス/エッジ]
        subgraph "デバイスソフトウェア"
            DS1[FreeRTOS]
            DS2[AWS IoT Greengrass]
            DS3[AWS Kinesis Video Streams]
            DS4[AWS IoT ExpressLink]
        end
        style Device fill:#e6ffe6
        style DS1 fill:#ccffcc
        style DS2 fill:#ccffcc
        style DS3 fill:#ccffcc
        style DS4 fill:#ccffcc
    end

    subgraph "AWS"
        subgraph "接続"
            CN[AWS IoT Core]
            style CN fill:#cce5ff
        end

        subgraph "コントロール"
            CT1[AWS IoT Device Defender]
            CT2[AWS IoT Device Management]
            CT3[AWS IoT FleetWise]
            style CT1 fill:#b3d9ff
            style CT2 fill:#b3d9ff
            style CT3 fill:#b3d9ff
        end

        subgraph "分析サービス"
            AN1[AWS IoT SiteWise]
            AN2[AWS IoT TwinMaker]
            style AN1 fill:#99ccff
            style AN2 fill:#99ccff
        end
    end

    Device --> CN
    CN --> CT1
    CN --> CT2
    CN --> CT3
    CN --> AN1
    CN --> AN2
```

### デバイスソフトウェア
| サービス名 | 概要 |
|------------|------|
| FreeRTOS | 小型マイクロコントローラー向けのリアルタイムOS |
| AWS IoT Greengrass | エッジデバイス上でのローカル処理と機械学習実行 |
| AWS Kinesis Video Streams | ビデオストリームの収集・処理・保存・分析 |
| AWS IoT ExpressLink | 組み込み向け接続モジュールとソフトウェア |

### 接続
| サービス名 | 概要 |
|------------|------|
| ★**AWS IoT Core** | IoTデバイスをクラウドに接続するマネージドサービス |

### コントロール
| サービス名 | 概要 |
|------------|------|
| AWS IoT Device Defender | IoTデバイスのセキュリティ監視・監査 |
| AWS IoT Device Management | 大規模IoTデバイスの一括管理 |
| AWS IoT FleetWise | 車両データ収集・変換・転送 |


### 分析サービス
| サービス名 | 概要 |
|------------|------|
| AWS IoT SiteWise | 産業機器データの収集・整理・分析 |
| AWS IoT TwinMaker | デジタルツイン作成サービス |
| 
AWS IoT Events
<br>（2025年12月15日終了予定） | イベント検出とアクション実行 |
| 
AWS IoT Analytics
<br>（2025年12月15日終了予定） | IoTデータの高度な分析 |

:::note warn
**サービス終了のお知らせ**
下記、AWS IoT Events と AWS IoT Analyticsについて、サービス終了のお知らせがAWSから出ているので注意が必要です。
- AWS IoT Eventsは、2026年5月20日に終了
- AWS IoT Analyticsは、2025年12月15日に終了

https://aws.amazon.com/about-aws/whats-new/2025/05/aws-service-changes/
:::

## AWS IoT Coreとは
AWS IoT Coreは、IoTデバイスをAWSクラウドに安全に接続&通信するためのフルマネージドサービスです。IoTデバイスから送信されたデータは、AWS IoT Coreを通って、他のAWSサービスへ転送したり、処理したりすることができます。

### 構成要素
![IoT Core 構成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560123/d3fc0d43-52f9-4901-afb6-4f8489f894b7.png)
| 構成要素 | 説明 |
|---------|------|
| **DveiceGateway** | デバイスからメッセージを受け取るゲートウェイ<br>下記の通信プロトコルに対応<br>• HTTPS<br>• MQTT<br>• MQTT over WebSocket |
| **Topic** | メッセージの宛先を示す階層構造の文字列<br>デバイスからはTopicを宛先としてメッセージを送ることが出来る |
| **RuleEngine** | 受信したメッセージを処理するためのSQLベースのルール<br>フィルタリング、日時を付与するなどメッセージを加工できる |
| **Action** | ルールに基づいて実行される<br>他のAWSサービスなどにメッセージを送ることが出来る |
| **Policy** | デバイスがアクセスできるリソースとアクションを定義できる |
| **Certificate** | デバイス認証のためのX.509証明書の発行・管理ができる |
| **IoT Shadow** | デバイスの仮想的な状態をAWS上で管理できる |

:::note info
**MQTTとは**
MQTTは、「Message Queuing Telemetry Transport」の略称で、IoTの接続に使用される通信プロトコルです。下記のようにPub/Sub形式で通信が可能です。
```mermaid
graph LR
    subgraph "Publisher"
        P1[センサー]
        P2[スマートデバイス]
    end

    B[MQTT Broker<br>メッセージブローカー]

    subgraph "Subscriber"
        S1[アプリケーション]
        S2[データベース]
    end

    P1 -->|Publish| B
    P2 -->|Publish| B
    B -->|Subscribe| S1
    B -->|Subscribe| S2

    style B fill:#f9f,stroke:#333,stroke-width:4px
```
参考：https://dev.classmethod.jp/articles/mqtt_illustrations/
:::

# やってみる
今回は、下記を構築し、S3にメッセージが保存されることを確認してみます。
![IoT Core 構成 やってみる.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560123/03c330c8-bd61-4c11-bf9e-77c0b2fcd97b.png)


## 準備：MQTTクライアントツール(MQTTX)をインストールする
下記のサイトを参考にMQTTXというツールをインストールします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560123/4172e3fb-6adb-4632-bedb-1dc37e1fd6b8.png)

https://dev.classmethod.jp/articles/mqtt-client-mqttx/

<details><summary>詳細はこちら</summary>

MQTTXの公式サイトを開いて、「ダウンロード」ボタンを押します
https://mqttx.app/ja
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560123/7d07e9f1-1c1c-480c-997d-1c263cc3d78b.png)

自身の環境にあったものをダウンロードします。(筆者はWindows版をダウンロードしました)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560123/59d4b10d-5506-4223-8fec-3970bfc60598.png)

インストーラーを起動して、「次へ」ボタンを押します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560123/11d3c5de-fe77-45f9-90fd-3bd464942490.png)

インストールフォルダを指定して、「インストール」ボタンを押します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560123/65239e7e-fc73-4bab-b0bc-aedeff5ac556.png)

「完了」ボタンを押して準備完了です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560123/c6bdddbf-9304-444d-b64d-a85a5db5be4e.png)

</details>

## Step1. AWS IoT X.509証明書を発行する
証明書の発行自体は、CloudFormationで書けないようなので、手動で発行を行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560123/05005ac0-0215-4e78-9175-ee65b6b053f3.png)

AWSマネージドコンソールからAWS IoT Coreの画面を開きます
左メニューから管理 > セキュリティ > 証明書を開き、「証明書を作成」ボタンを押します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560123/b0260bc9-4bde-4994-b6f7-84068c4ed42b.png)

証明書のステータスを「アクティブ」に変えて、「作成」ボタンを押します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560123/8cce66d7-b620-46fb-9293-92bc0854246e.png)

ダイアログが表示されるので、下記の4点セットをダウンロードしておきます
- デバイス証明書
- パブリックキーファイル
- プライバシーキーファイル
- Amazon 信頼サービスエンドポイント(RSAの方)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560123/ff99dabe-3713-41b3-becc-00770193f9e5.png)

できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560123/5564b29a-49a8-4273-a0ca-4aee03ead66b.png)

## Step2. ClouFormationテンプレートを作成する
次に必要なAWSサービスをデプロイするためのCloudFormationを作成していきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560123/57ca313d-676d-41f4-8389-0a4ff542e33b.png)

<details><summary>完成版はこちら</summary>

```yaml:templete.yaml
AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  NamePrefix:
    Type: String
    Default: "dev"
    
  CertificateId:
    Type: String

Resources:
  # ------------------------------------------------------------
  # Amazon S3 リソース
  # ------------------------------------------------------------
  # S3バケット
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub "${NamePrefix}-iot-message-bucket"

  # ------------------------------------------------------------
  # AWS IoT Core リソース
  # ------------------------------------------------------------
  # IoTポリシー
  IoTPolicy:
    Type: AWS::IoT::Policy
    Properties:
      PolicyName: !Sub "${NamePrefix}_iot_policy"
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - iot:Connect
              - iot:Publish
              - iot:Subscribe
              - iot:Receive
            Resource: "*"
  
  # ポリシーに証明書を設定
  PolicyAttachment:
    Type: AWS::IoT::PolicyPrincipalAttachment
    Properties:
      PolicyName: !Ref IoTPolicy
      Principal: !Sub "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:cert/${CertificateId}"

  # モノを作成
  IoTThing:
    Type: AWS::IoT::Thing
    Properties:
      ThingName: !Sub "${NamePrefix}_iot_thing"

  # モノに証明書を設定
  CertificateAttachment:
    Type: AWS::IoT::ThingPrincipalAttachment
    Properties:
      ThingName: !Ref IoTThing
      Principal: !Sub "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:cert/${CertificateId}"

  # TopicRule用のIAMロール
  TopicRuleRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service: iot.amazonaws.com
      Policies:
        - PolicyName: TopicRulePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
            - Effect: Allow
              Action: 
                - s3:PutObject
              Resource: !Sub "${S3Bucket.Arn}/*"

  # IoT TopicRule
  IoTTopicRule:
    Type: AWS::IoT::TopicRule
    Properties:
      RuleName: !Sub ${NamePrefix}_iot_topic_rule
      TopicRulePayload:
        Actions:
          - S3:
              BucketName: !Ref S3Bucket
              Key: "iot-data/${topic()}/${timestamp()}"
              RoleArn: !GetAtt TopicRuleRole.Arn
        AwsIotSqlVersion: "2016-03-23"
        RuleDisabled: false
        Sql: !Sub "SELECT * FROM 'iot/topic/sensor'"

  # ログ保存用のIAMロール
  IoTLoggingRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: iot-logging-role
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: iot.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: IoTLoggingPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: "*"
  # ログ設定
  IoTLogging:
    Type: AWS::IoT::Logging
    Properties:
      AccountId: !Ref AWS::AccountId
      DefaultLogLevel: ERROR
      RoleArn: !GetAtt IoTLoggingRole.Arn
```
</details>



### S3バケットを作成
AWS IoT Coreから送られたメッセージを貯めるためのバケットを作成します。
```yaml
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub "${NamePrefix}-iot-message-bucket"
```

### 認証・認可の設定
証明書を使用して、IoT Coreの機能にアクセスできるようIoTポリシーを設定します。

```yaml
  # IoTポリシー
  IoTPolicy:
    Type: AWS::IoT::Policy
    Properties:
      PolicyName: !Sub "${NamePrefix}_iot_policy"
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - iot:Connect
              - iot:Publish
              - iot:Subscribe
              - iot:Receive
            Resource: "*"
  
  # ポリシーに証明書を設定
  PolicyAttachment:
    Type: AWS::IoT::PolicyPrincipalAttachment
    Properties:
      PolicyName: !Ref IoTPolicy
      Principal: !Sub "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:cert/${CertificateId}"
```

### モノ(クライアント)を作成 
AWS IoT Coreでは、IoTデバイスを「モノ」という単位で管理することが出来ます。
今回は、MQTTXツールを「モノ」として作成します。

```yaml
  # モノを作成
  IoTThing:
    Type: AWS::IoT::Thing
    Properties:
      ThingName: !Sub "${NamePrefix}_iot_thing"

  # モノに証明書を設定
  CertificateAttachment:
    Type: AWS::IoT::ThingPrincipalAttachment
    Properties:
      ThingName: !Ref IoTThing
      Principal: !Sub "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:cert/${CertificateId}"
```

### RuleEngineとActionを設定する
RuleEngineとAictionでは、全スルーでそのままS3へメッセージを配信する設定にします。
IoT CoreからS3へアクセスが必要なので、IAMポリシーも設定しています。

```yaml
  # TopicRule用のIAMロール
  TopicRuleRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service: iot.amazonaws.com
      Policies:
        - PolicyName: TopicRulePolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
            - Effect: Allow
              Action: 
                - s3:PutObject
              Resource: !Sub "${S3Bucket.Arn}/*"

  # IoT TopicRule
  IoTTopicRule:
    Type: AWS::IoT::TopicRule
    Properties:
      RuleName: !Sub ${NamePrefix}_iot_topic_rule
      TopicRulePayload:
        Actions:
          - S3:
              BucketName: !Ref S3Bucket
              Key: "iot-data/${topic()}/${timestamp()}"
              RoleArn: !GetAtt TopicRuleRole.Arn
        AwsIotSqlVersion: "2016-03-23"
        RuleDisabled: false
        Sql: !Sub "SELECT * FROM 'iot/topic/sensor'"
```

### (デバッグ用) IoT Core全体のログ設定
疎通した際にエラーが起きてもいいようにCloudWatchへログを出力する設定をしておきます。
注意点としては、特定のTopicなどのログではなくIoT Core全体のログ設定となります。

```yaml
  # ログ保存用のIAMロール
  IoTLoggingRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: iot-logging-role
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: iot.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: IoTLoggingPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: "*"
  # ログ設定
  IoTLogging:
    Type: AWS::IoT::Logging
    Properties:
      AccountId: !Ref AWS::AccountId
      DefaultLogLevel: ERROR
      RoleArn: !GetAtt IoTLoggingRole.Arn
```

### CloudFormationをデプロイする

<details><summary>詳細はこちら</summary>

AWSマネージドコンソールからCloudFormationの画面を開き、スタックの作成 > 新しいリソースを使用を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560123/32678383-5f75-4f94-be2e-6c3c11e59ff3.png)

テンプレートの指定で「テンプレートファイルのアップロード」のラジオボタンを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560123/f6c038ec-a17d-49ef-87bf-22c01154e7b2.png)

その下の「ファイルの選択」から先ほど作成したテンプレートファイルを指定して、「次へ」ボタンを押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560123/2d109240-68f7-46ca-a7c7-9b94e60170ad.png)

任意のスタック名を入力して、「次へ」ボタンを押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560123/c576c6d0-06aa-42ed-97cb-bf5d4ec6f1ec.png)

一番最後のチェックボックスにチェックを入れ、「次へ」ボタンを押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560123/d8f5a230-932c-49cf-bfa4-707df018bcd8.png)

最後に「送信」ボタンを押して完了です。（ステータスがCREATE_COMPLETEになることを確認して下さい)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560123/369ebdc7-ce11-485c-9423-6aca5fc3def4.png)

</details>



## Step3. MQTTXの設定を行う
### ドメインを確認する
IoT Coreのドメインを確認します。MQTTXから接続する際に使います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560123/6554bd51-5adf-43c2-9b49-40fc9c9f4142.png)

### MQTTXを設定する
下記の手順を参考にMQTTXの設定を行います。
https://dev.classmethod.jp/articles/mqtt-client-mqttx/#mqttx%25E5%2581%25B4%25E3%2581%25AE%25E8%25A8%25AD%25E5%25AE%259A

## Step4. 疎通してみる
### MQTTXからIoT Coreまで届いているか確認する
AWS IoT Core側でテスト > MQTTテストクライアント を開いて、「サブスクライブ」ボタンを押します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560123/091e482c-4c13-478c-a7eb-7182f79c3ec2.png)

MQTTXにて、送信先のTopicを設定し、画面右下の送信ボタンを押します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560123/e3100a4d-b2a7-4c08-84ac-a5b245d46f40.png)

AWS IoT Core側に届きました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560123/9b86cf22-edcf-41e5-b8c7-efb733c46447.png)

### IoT CoreからS3まで届いているか確認する
AWSマネージドコンソールからS3バケットを確認すると、IoT CoreからS3までも問題なく届いていました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/560123/ea6fa069-6879-4e49-86b7-3d3d8436d8b0.png)

:::note info
**届かない場合はCloudWatchのログを確認しよう**
先ほどのCloudFormationでAWS IoT Coreのログを有効化しています。
なので疎通で行き詰ったら、CloudWatchのログを見ると解決できるかもしれません。
:::

# まとめ
今回は、AWS IoT Coreに入門してみましたが、機能が多いですね...(奥が深い...)

あとIoT Coreから多数のAWSサービスへメッセージを送ることができるのですが、アーキテクチャを考えたときに、SNSを挟むべきか？SQSに積んでから処理するか？それとも直接S3に突っ込むか？など、実際に使うとなると悩みそうだなというのも感じました。(知識ないだけですが...)

今度は、Shadow機能など使えていない機能がまだまだあるので、時間があるときに試したいなーと思っています。