AWS IoT RuleでMQTTメッセージをCloudWatch Logsへ保存してみた
はじめに
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.tf、iam-role.tf、iam-policy.tf、iot-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:DescribeLogStreamslogs:CreateLogStreamlogs: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から thingName、observedAt、message を選択しています。さらに、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




メッセージをテストする
インフラの準備後、今回のテスト用に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が完了したことを確認しました。

続いて、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"
}

今回の確認範囲は、正常なメッセージが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の最初の検証としては分かりやすかったです。







