terraformでAWS IoT Rules EngineからDynamoDBに書き込む設定する

terraformでAWS IoT Rules Engineを設定していたらハマったのでメモ。 公式terraformリファレンス の記述が貧弱なのでその辺りの補強を兼ねている。

前提

  • DynamoDBは既に設定されている
  • DeviceLog というテーブルが作成されている

HCLの記述

# AWS IoT Rules Engineの設定本体
resource "aws_iot_topic_rule" "main" {
  name        = "ExampleRule"
  description = "Example rule for AWS IoT Rules Engine"
  enabled     = true
  sql_version = "2016-03-23"
  sql         = "SELECT * FROM 'devices/#'"

  dynamodb {
    hash_key_field = "DeviceId"
    hash_key_type  = "STRING"
    # トピック `devices/<device-id>/status` から `<device-id>` を取得する
    hash_key_value = "$${topic(2)}"

    range_key_field = "Timestamp"
    range_key_type  = "NUMBER"
    # JSON ペイロードから `timestamp` の値を取得する
    range_key_value = "$${timestamp}"

    role_arn   = aws_iam_role.dynamodb_put_only_role.arn
    table_name = "DeviceLog"
    operation  = "INSERT"
  }
}

# Rules Engine用ロール
resource "aws_iam_role" "dynamodb_put_only_role" {
  name = "DynamoDbDeviceLogPutOnlyRole"

  assume_role_policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Principal": {
                "Service": "iot.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
EOF
}

# Rules Engine用ロールのポリシ
resource "aws_iam_role_policy" "dynamodb_put_only_policy" {
  name   = "DynamoDbDeviceLogPutOnlyRolePolicy"
  role   = aws_iam_role.dynamodb_put_only_role.id
  policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "dynamodb:PutItem",
            "Resource": "arn:aws:dynamodb:*:*:table/DeviceLog"
        }
    ]
}
EOF
}

HCLの記述でundocなところ

HashKeyValueに含める値の設定

${topic()} はリクエストされたトピック名を取得することを意味する。 () 内に引数を渡すと、トピックを / で分割したスライスにアクセスすることができる。

例えば、 ${topic(2)} を指定した場合、トピック名が devices/DeviceId-X123/status であると、得られる結果は DeviceId-X123 になり、 DeviceId フィールドに DeviceId-X123 を格納することが出来る。

HCL内に記述がないが ${timestamp()} を指定すると処理した時刻が置換される。整数値で、単位はミリセカンド単位。

${something-value} のように指定すると、JSONペイロードに含まれるキーを探索して値をセットできる。 例えば、 ${tm} を設定した場合、次のような JSON から tm の値 1606875290000 を取り出して使う形となる。

{
  "status": "ok",
  "tm": 1606875290000
}

なお、上記HCL内では $ が特別な意味を持つのでエスケープして設定している。 この辺りのドキュメントどこにあるんだ…。

Basic Ingestを使う場合

エッジデバイスからBasic Ingestを使って送信する際は次のようなトピックになる。

$aws/rules/ExampleRule/devices/DeviceId-X123/status

Rules Engineで取り扱う際は $aws/rules/ExampleRule/ を取り除いた値、つまり devices/DeviceId-X123/status を扱うことになる(そのため、前述のサンプルで SQL の設定値を SELECT * FROM 'devices/#' にしている)。

不便なところ

  • terraformでAWS IoT Rulesを使ってる人が少ないのか、たまにバグっぽい挙動を見かける
    • 再現性忘れてしまった…
  • terraformのAWS IoT Rulesの設定では、CloudWatch Logsに書き込みを実施することが出来ない
    • エラーログを書き込むように設定したいが出来ない
    • ちなみにAWS Consoleでは設定できる
  • {$topic()}${timestamp()} のようなマジックナンバーのような値の資料が見当たらない