AWS IoT RuleでMQTTメッセージをCloudWatch Logsへ保存してみた

AWS IoT RuleでMQTTメッセージをCloudWatch Logsへ保存してみた

AWS IoT Ruleを使い、ローカルコンテナから送信したMQTTメッセージをCloudWatch Logsへ出力する方法をTerraformで実装してみました。デバイスの権限分離とインフラの最小構成を意識した検証です。
2026.08.28

はじめに

AWS IoT Coreへデバイスを接続できるようになると、次はPublishされたメッセージを保存して確認したくなります。

今回はAWS IoT Ruleを使い、ローカルコンテナから送信したMQTTメッセージをCloudWatch Logsへ出力してみます。インフラはTerraformで最小構成にしました。

事前に必要なのは、AWS IoT Coreへメッセージを送信できるThingまたはデバイス環境です。Thingの作成方法はFleet Provisioningに限りません。今回は自分の検証環境として、Fleet Provisioningで登録済みのローカルコンテナを使用しました。

手元にMQTTメッセージをAWS IoT Coreに送信できるAWS IoT Thingを用意できてない方は、こちらのブログをご参考ください:AWS IoT Fleet Provisioningでローカルコンテナを自動登録してみた

検証環境

  • AWS IoT Core(ap-northeast-1
  • Terraform 1.15.8
  • AWS Provider 6.62.0
  • AWS IoT Coreへメッセージを送信できるローカルコンテナ

今回使用したコンテナはFleet Provisioningで登録済みです。個別のデバイス証明書を保存しており、次のTopicへPublishできる状態です。

factory/line-a/<ThingName>/telemetry

インフラを準備する

サービスの関係

今回作る経路は次のとおりです。

ローカルコンテナ
└── MQTTメッセージをPublish
    └── factory/line-a/<ThingName>/telemetry

AWS IoT Core MQTT broker
        ↓ Topic filterが一致
AWS IoT Rule
├── IoT SQLでpayloadを選択
├── topic(3)からThing名を追加
└── CloudWatch Logs action
    ├── IAM RoleをAssumeRole
    │   └── IAM Policyで対象Log groupへの書き込みを許可
    └── /aws/iot/lab-rules/telemetry

主な構成要素は3つです。

  • AWS IoT Rule: MQTT Topicを条件にメッセージを選択し、IoT SQLの結果を別のAWS Serviceへ渡します。
  • IAM RoleとPolicy: AWS IoT CoreがRoleを引き受け、指定したCloudWatch LogsのLog groupだけへ書き込みます。
  • CloudWatch LogsのLog group: Ruleが処理したメッセージを保存し、結果を画面から確認できるようにします。

デバイスのIoT Policyは、MQTT brokerへConnectして自分のTopicへPublishするためのものです。一方、CloudWatch Logsへ書き込むIAM RoleはAWS IoT Coreが使用します。

そのため、コンテナへIAM認証情報やCloudWatch Logsの権限を追加する必要はありません。

IaC(Terraform)

今回使用したTerraformは次のリポジトリで公開しています。

Terraformは、同じディレクトリにあるすべての .tf ファイルを1つのModuleとして読み込みます。今回は作成するServiceや役割が分かるように、cloudwatch-logs.tfiam-role.tfiam-policy.tfiot-rule.tf に分けました。

基本的な実行コマンドは次のとおりです。

git clone https://github.com/cm-obuchi-hugo-examples/iot-rules-and-observibility-minimal.git
cd iot-rules-and-observibility-minimal

terraform init
terraform fmt -check
terraform validate
terraform plan -out=tfplan
terraform apply tfplan

terraform plan では、既存のThing、証明書、IoT Policyが変更対象に含まれていないことを確認してからApplyしました。

以下では、実際のTerraformから主要部分を抜き出して確認します。変数定義などを含む全体はリポジトリを参照してください。

cloudwatch-logs.tf

最初に、Ruleの出力先となるLog groupを作成します。

# Create the destination explicitly so Terraform manages its retention and the
# IAM policy can target one exact group.
resource "aws_cloudwatch_log_group" "telemetry" {
  name              = var.telemetry_log_group_name
  retention_in_days = var.log_retention_days
}

今回の値は次のとおりです。

Log group: /aws/iot/lab-rules/telemetry
Retention: 7 days

Log groupを先に作ることで、後述するIAM Policyをこの出力先だけに限定できます。logs:CreateLogGroup や任意のLog groupへ書き込む権限は付けていません。

このLog groupは、IoT Ruleが変換したメッセージの保存先です。AWS IoT Core自体の診断ログを保存する AWSIotLogsV2 とは別のものです。

iam-role.tf

次に、AWS IoT CoreがAssumeRoleするIAM Roleを作成します。

# A role's trust policy answers "who may assume this role?"
data "aws_iam_policy_document" "iot_assume_role" {
  statement {
    effect  = "Allow"
    actions = ["sts:AssumeRole"]

    principals {
      type        = "Service"
      identifiers = ["iot.amazonaws.com"]
    }
  }
}

resource "aws_iam_role" "rule_action" {
  name               = var.rule_action_role_name
  assume_role_policy = data.aws_iam_policy_document.iot_assume_role.json
}

Trust Policyの iot.amazonaws.com は、このRoleをAWS IoT Coreが引き受けられることを示しています。

aws_iam_policy_document はPolicyのJSONをTerraform内で組み立てるData Sourceです。このBlock自体がAWS上にIAM resourceを作るわけではなく、生成したJSONを aws_iam_role が使用します。

iam-policy.tf

Trust Policyが「誰がRoleを引き受けられるか」を決めるのに対し、Permission Policyは「引き受けたRoleが何をできるか」を決めます。

data "aws_iam_policy_document" "write_telemetry_logs" {
  statement {
    sid       = "DescribeTelemetryStreams"
    effect    = "Allow"
    actions   = ["logs:DescribeLogStreams"]
    resources = [aws_cloudwatch_log_group.telemetry.arn]
  }

  statement {
    sid    = "WriteTelemetryStreams"
    effect = "Allow"
    actions = [
      "logs:CreateLogStream",
      "logs:PutLogEvents",
    ]
    resources = ["${aws_cloudwatch_log_group.telemetry.arn}:*"]
  }
}

resource "aws_iam_role_policy" "write_telemetry_logs" {
  name   = "lab-iot-write-telemetry-log"
  role   = aws_iam_role.rule_action.id
  policy = data.aws_iam_policy_document.write_telemetry_logs.json
}

許可したCloudWatch LogsのActionは次の3つです。

  • logs:DescribeLogStreams
  • logs:CreateLogStream
  • logs:PutLogEvents

ResourceにはTerraformで作成したLog groupのARNを参照しています。これにより、AWSアカウントIDやリージョンをTerraformが補完し、Log groupへの依存関係も自動的に作られます。

CloudWatch Logs actionに必要な権限は、AWS公式ドキュメントのCloudWatch Logs rule actionでも確認できます。

iot-rule.tf

最後に、MQTTメッセージを選択するIoT Ruleを作成します。

resource "aws_iot_topic_rule" "telemetry_to_logs" {
  name        = var.topic_rule_name
  description = "Send lab device telemetry to CloudWatch Logs"
  enabled     = true
  sql_version = "2016-03-23"

  sql = <<-SQL
    SELECT
      thingName,
      observedAt,
      message,
      topic(3) AS sourceThing
    FROM '${var.telemetry_topic_filter}'
  SQL

  cloudwatch_logs {
    log_group_name = aws_cloudwatch_log_group.telemetry.name
    role_arn       = aws_iam_role.rule_action.arn
    batch_mode     = false
  }

  depends_on = [aws_iam_role_policy.write_telemetry_logs]
}

今回のTopic filterは次の値です。

factory/line-a/+/telemetry

MQTTの + は1階層だけに一致するWildcardです。たとえば、次のTopicが対象になります。

factory/line-a/lab-iot-machine-03/telemetry

Topicを階層ごとに見ると、次のようになります。

topic(1) = factory
topic(2) = line-a
topic(3) = lab-iot-machine-03
topic(4) = telemetry

IoT SQLではpayloadから thingNameobservedAtmessage を選択しています。さらに、topic(3)sourceThing として結果へ追加します。

これにより、デバイスがpayloadへ入れたThing名と、実際にPublishされたTopicから取得したThing名を同じEventで確認できます。

batch_mode = false にしたため、今回は1つのMQTTメッセージを1つのLog eventとして確認します。

depends_on は、IAM RoleのARNだけでなく、Inline Policyの作成完了も待ってからRuleを作るために追加しています。

Terraform Apply後に作成されたResourceを確認する

terraform apply の完了後、次のResourceが作成されていることを確認しました。

  • CloudWatch Logs: /aws/iot/lab-rules/telemetry
  • IAM Role: lab-iot-rule-telemetry-logs-role
  • Inline Policy: lab-iot-write-telemetry-log
  • AWS IoT Rule: lab_iot_telemetry_to_logs

2
3
4
5

メッセージをテストする

インフラの準備後、今回のテスト用にFleet Provisioningで登録した lab-iot-machine-03 のコンテナを起動しました。保存済みのデバイス証明書を再利用し、Claim certificateはマウントしていません。

ここでFleet Provisioningを使っているのは自分のコンテナ環境を準備した方法によるものです。AWS IoT Coreへ対象Topicのメッセージを送信できれば、別の方法で作成したThingやデバイスでも同じRuleをテストできます。

コンテナは次のTopicへメッセージを1件Publishしました。

factory/line-a/lab-iot-machine-03/telemetry

コンテナ側では、既存の認証情報を使用してPublishが完了したことを確認しました。

7

続いて、CloudWatch Logsの /aws/iot/lab-rules/telemetry を開き、最新のLog streamを確認しました。

保存されたEventは次の形式です。observedAt の実値は省略しています。

{
  "thingName": "lab-iot-machine-03",
  "observedAt": "<timestamp>",
  "message": "hello from local container",
  "sourceThing": "lab-iot-machine-03"
}

6

今回の確認範囲は、正常なメッセージがTopic filterに一致し、IoT RuleのCloudWatch Logs actionによって保存されるところまでです。Error action、AWS IoT Service logging、CloudWatch metrics、失敗時の動作はこの最小テストには含めていません。

不要になったResourceを削除する

今回Terraformで作成したResourceは、次のコマンドで削除できます。

terraform plan -destroy
terraform destroy

Destroy planを確認してから実行します。既存のThing、証明書、IoT Policyなど、デバイスを準備したResourceはこのTerraformの管理対象に含まれていません。

おわりに

AWS IoT Ruleを使い、ローカルコンテナからPublishしたMQTTメッセージをCloudWatch Logsで確認できました。

今回の構成では、デバイスはMQTTへのPublishだけを担当し、その後のCloudWatch Logsへの書き込みはAWS IoT CoreがIAM Roleを使って行います。TerraformをServiceごとのファイルに分けたことで、この権限の境界も追いやすくなりました。

最小構成でも、Topic filter、IoT SQL、Rule actionの関係を実際のEventまで確認できたので、AWS IoT Ruleの最初の検証としては分かりやすかったです。

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事