[Azure x IoT] AzureIoTHubとAzureFunctionを連携しよう(Terraform編)


はじめに

[Azure x IoT]Azure IoTHub とAzure Functionsを連携しようでは、UI操作を紹介しながらアプリケーションを作成しましたが、「そんなちまちまやらずにインフラ構築はterraformに任せてとりあえず試してみたい!」という方向けの記事です

本記事のゴール

以下をterraformで構築します

コード

下記レポジトリを参照してください

https://github.com/ironkicka/tff_az_templates/tree/main/iotHub_to_function

構築

以下はレポジトリのREADMEの転載です。

1.デプロイ

terraform plan
terraform apply

2.関数の中身のデプロイ

cd IoTHubTriggeredFunction && npm i && npm run deploy

3.IoTHub上にデバイスを作成

export IOTHUB_NAME=xxxx
export DEVICE_NAME=xxxx
az iot hub device-identity create -n $IOTHUB_NAME -d $DEVICE_NAME

4.SASトークンを発行

export IOTHUB_NAME=xxxx
export EXPIRATION_SECONDS=xxxx
az iot hub generate-sas-token -n $IOTHUB_NAME --du $EXPIRATION_SECONDS

5.IoTHubにメッセージを送信

export SAS=xxxx
export IOTHUB_NAME=xxxx
export DEVICE_NAME=xxxx
sh publish.sh

参考 publish.sh

curl -i -X POST \
-H "Content-Type:application/json" \
-H "Authorization:$SAS" \
-d '{"value":"hello,world from iothub"}' \
"https://$IOTHUB_NAME.azure-devices.net/devices/$DEVICE_NAME/messages/events?api-version=2018-06-30"

6.確認
メッセージを送信後、FunctionAppのモニターログに以下が表示されたら成功です。

解説

今回のアプリケーションを構築する上で中心的役割を果たすリソースのみ解説していきます

IotHub

resource "azurerm_iothub" "my-iot-hub" {
  name = "terra-iot-hub"
  resource_group_name = azurerm_resource_group.rg.name
  location = var.resource_group_location
  sku {
    capacity = 1
    name     = "F1"
  }
}

単にメッセージを受け取るだけであれば上記の定義だけで大丈夫です!skuはF1(無料)を選べるのは、自分のサブスクリプションに他にF1のiothubがいない時だけなので注意してください。

またIoTHubを使ったことのある方はお気づきかもしれませんが、terraformではIoTHub内のデバイスを定義する項目がありませんでした。

そのため、”構築”セクションでazコマンドにより作成しています。

Terraform Registry

Function App周り

以下の4つを定義します

  • AzureStorageAccount
    Functionのトリガーの管理やログを保存するのに必要
    参考:

    Storage considerations for Azure Functions

  • AzureServicePlan
    課金プラン

  • AzureFunctionApp
    Function本体

  • ApplicationInsights
    アプリケーションログを見るためのリソース

上3つのterraform


# randomなidを生成するためのリソース
resource "random_id" "randomId" {
  keepers = {
    # Generate a new ID only when a new resource group is defined
    resource_group = azurerm_resource_group.rg.name
  }

  byte_length = 8
}

# AzureStorageAccount
resource "azurerm_storage_account" "mystorageaccount" {
  name                        = "st${random_id.randomId.hex}"
  resource_group_name         = azurerm_resource_group.rg.name
  location                     = var.resource_group_location
  account_tier                = "Standard"
  account_replication_type    = "LRS"

  tags = {
    environment = "Terraform Demo"
  }
}

resource "azurerm_service_plan" "myAppservicePlan" {
  name                = "my-app-service-plan-${random_id.randomId.hex}"
  resource_group_name = azurerm_resource_group.rg.name
  location            = azurerm_resource_group.rg.location
  os_type             = "Linux"
  sku_name            = "S1"
}

resource "azurerm_application_insights" "myAppInsight" {
  name                = "tf-test-appinsights"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  application_type    = "Node.JS"
}

Azure Function

resource "azurerm_function_app" "myFunction" {
  name                       = "my-iothub-triggered-function"
  location                   = azurerm_resource_group.rg.location
  resource_group_name        = azurerm_resource_group.rg.name
  app_service_plan_id        = azurerm_service_plan.myAppservicePlan.id
  storage_account_name       = azurerm_storage_account.mystorageaccount.name
  storage_account_access_key = azurerm_storage_account.mystorageaccount.primary_access_key
  os_type                    = "linux"
  version                    = "~4"

  app_settings = {
    APPLICATIONINSIGHTS_CONNECTION_STRING = azurerm_application_insights.myAppInsight.connection_string
    APPINSIGHTS_INSTRUMENTATIONKEY = azurerm_application_insights.myAppInsight.instrumentation_key
    eventHubCompatibleEndpoint = "Endpoint=${azurerm_iothub.my-iot-hub.event_hub_events_endpoint};SharedAccessKeyName=${azurerm_iothub.my-iot-hub.shared_access_policy[0].key_name};SharedAccessKey=${azurerm_iothub.my-iot-hub.shared_access_policy[0].primary_key};EntityPath=${azurerm_iothub.my-iot-hub.event_hub_events_path}"
    FUNCTIONS_WORKER_RUNTIME ="node" 
    WEBSITE_RUN_FROM_PACKAGE =1
  }

  site_config {
    linux_fx_version = "node|14"
  }
}

実はazurerm_function_appは型落ちしたリソースで、最新のリソースはazurerm_linux_function_appです.

なぜわざわざこのようなことをしているかというと、azurerm_linux_function_appでリソースを作成した際に,azure functionのコードのデプロイがうまくいかないという問題が発生したためです。自分の何らかの設定ミスなのかバグなのか定かではないですが、現状うまくいくことが確認できたazurerm_function_appで構成しています.もし原因ご存知の方がいたら共有いただきたいです!

  • azurerm_function_app

https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/function_app#attributes-reference

  • azurerm_linux_function_app

https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_function_app

忘れてはいけないポイントは、app_settingsブロックでeventHubCompatibleEndpointをきちんと定義することです。この値はIoTHubとの連携を担う値で、Functionコードから参照されます。Functionコードのfunction.jsonと同じ名前になるようにしてください。

おわりに

いかがでしたでしょうか。UI操作より圧倒的なスピードで動作確認できたのではないでしょうか。

次はAzure専用のbicepも試してみたいと思います。