3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[Azure x IoT] Azure IoTHubで受け取ったメッセージをDBに保存しよう[Terraform編]

Last updated at Posted at 2022-04-01

はじめに

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

本記事のゴール

本記事ではIoTHubで受信したメッセージをRDBに保存できるようにします.

image.png

AzureFunctionからAzureDatabaseへのアクセスについてはVnet統合という機能を使います。

本記事の内容は筆者の次の4つの記事を組み合わせた内容になります。IotHubは使わないけど、部分的に参考にしたいという方は以下を参考にしてください。

コード

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

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

構築方法

以下はレポジトリの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.秘密鍵を取得

terraform output -raw tls_private_key > secret_key.pem

6.VMに接続(PUBLIC_IPはterraformのコンソール出力 or Azure Portalで確認できます)

ssh -i path/to/secret_key.pem azureuser@VM_PUBLIC_IP

7.DBに接続

mysql -h terraform-db.mysql.database.azure.com -u test_db_admin -p --ssl-mode=REQUIRED --ssl-ca=DigiCertGlobalRootCA.crt.pem

8.テーブルを作成

CREATE TABLE
    test_table (
        id INTEGER AUTO_INCREMENT PRIMARY KEY,
        message VARCHAR(255),
        created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
    );

9.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"

10.確認

メッセージを送信後、テーブルにレコードが追加されていれば成功です!

image.png

解説

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

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

Azure Key Vault周り

data "azurerm_client_config" "current" {}

resource "azurerm_key_vault" "myKeyVault" {
  name                = "terra-vault-2022"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  tenant_id           = data.azurerm_client_config.current.tenant_id
  sku_name            = "standard"
  access_policy {
    tenant_id = data.azurerm_client_config.current.tenant_id
    object_id = data.azurerm_client_config.current.object_id

    secret_permissions = [
      "List",
      "Set",
      "Get",
      "Delete",
      "Purge"
    ]
  }
}

resource "azurerm_key_vault_secret" "example" {
  name         = "DB-PASSWORD"
  value        = var.db_password
  key_vault_id = azurerm_key_vault.myKeyVault.id
}

resource "azurerm_key_vault_access_policy" "example" {
  key_vault_id = azurerm_key_vault.myKeyVault.id
  tenant_id    = data.azurerm_client_config.current.tenant_id
  object_id    = azurerm_function_app.myFunction.identity[0].principal_id

  secret_permissions = [
    "Get",
  ]
}

データベースのパスワードを格納するのに用います

Function App周り

以下の5つを定義します

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

    Storage considerations for Azure Functions

  • AzureServicePlan課金プラン

  • AzureFunctionAppFunction本体

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

  • Vnet統合設定用のリソース

上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とVnet統合設定

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
    myEventHubReadConnectionAppSetting = "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" #Without this you won't be able to publish function
    WEBSITE_RUN_FROM_PACKAGE =1
    DB_HOST = "${azurerm_mysql_flexible_server.database_server.name}.mysql.database.azure.com"
    DB_USER_NAME = azurerm_mysql_flexible_server.database_server.administrator_login
    DB_DATABASE_NAME = var.db_name
    DB_PASSWORD = "@Microsoft.KeyVault(SecretUri=https://${azurerm_key_vault.myKeyVault.name}.vault.azure.net/secrets/DB-PASSWORD/${azurerm_key_vault_secret.example.version})"
  }

  site_config {
    linux_fx_version = "node|14"
  }

  identity {
    type = "SystemAssigned"
  }
}

resource "azurerm_app_service_virtual_network_swift_connection" "function_vnet_integration" {
  app_service_id = azurerm_function_app.myFunction.id
  subnet_id      = azurerm_subnet.subnet-for-vent-integration.id
}

実は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と同じ名前になるようにしてください。

  • app_settingsブロックでDB_PASSWORDにKeyVaultの値を参照させるため、システム割り当てマネージド IDを有効にする
    以下の部分で有効にしています。

    identity {
        type = "SystemAssigned"
      }
    

    DB_PASSWORDの値には@Microsoft.KeyVault(SecretUri=xxx)を定義することでAzureKeyVaultに保存された値を読み出すことができます。
    以前[Azure] Azure FunctionsからAzure Key Vaultの値を参照するで紹介した内容です。

  • azurerm_app_service_virtual_network_swift_connectionでVnet統合を有効化
    このリソースでvnet統合用のsubnetとfunctionの紐付けを行います
    この紐付けができていないとFunctionからDBへの書き込みができないので重要です!

DB周り

subnetやらvnet周りは少し省略しています。

# データベースサーバー
resource "azurerm_mysql_flexible_server" "database_server" {
  name                = "terraform-db"
  resource_group_name = azurerm_resource_group.rg.name
  location = var.resource_group_location
  administrator_login    = var.db_user_name
  administrator_password = var.db_password
  backup_retention_days  = 7
  delegated_subnet_id = azurerm_subnet.db_subnet.id
  private_dns_zone_id = azurerm_private_dns_zone.dns_zone.id
  zone = 1
  version    = "8.0.21"
  sku_name = "B_Standard_B1s"
  depends_on = [azurerm_private_dns_zone_virtual_network_link.pdz_vent_link]
}

# mysqlデータベース
resource "azurerm_mysql_flexible_database" "database" {
  name                = var.db_name
  resource_group_name = azurerm_resource_group.rg.name
  server_name         = azurerm_mysql_flexible_server.database_server.name
  charset             = "utf8"
  collation           = "utf8_unicode_ci"
}

上記2つでデータベースサーバーとデータベースを定義できます。特段説明することもないです

踏み台サーバー

storageAccount,nicなどは省略してます。

resource "azurerm_linux_virtual_machine" "myterraformvm" {
  name                  = "myBastion"
  resource_group_name         = azurerm_resource_group.rg.name
  location                     = var.resource_group_location
  network_interface_ids = [azurerm_network_interface.bastion_nic.id]
  size                  = "Standard_B1s"

  os_disk {
    name              = "myOsDisk"
    caching           = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }

  source_image_reference {
    publisher = "Canonical"
    offer     = "UbuntuServer"
    sku       = "18.04-LTS"
    version   = "latest"
  }

  computer_name  = "mybastion"
  admin_username = "azureuser"
  disable_password_authentication = true

  admin_ssh_key {
    username       = "azureuser"
    public_key     = tls_private_key.bastion_ssh.public_key_openssh
  }

  boot_diagnostics {
    storage_account_uri = azurerm_storage_account.mystorageaccount.primary_blob_endpoint
  }
}

resource "tls_private_key" "bastion_ssh" {
  algorithm = "RSA"
  rsa_bits = 4096
}

resource "azurerm_virtual_machine_extension" "myextension" {
  name                 = "mybastionExtension"
  virtual_machine_id   = azurerm_linux_virtual_machine.myterraformvm.id
  publisher            = "Microsoft.Azure.Extensions"
  type                 = "CustomScript"
  type_handler_version = "2.0"

  settings = <<SETTINGS
    {
        "script": "${base64encode(file(var.script_file))}"
    }
  SETTINGS
}

ポイント

  • tls_private_keyでssh鍵ペアを作成
    上記リソースでssh鍵ペアを作成できます。
    リソースのデプロイ後は以下のようにすればファイルに書き出せます

    terraform output -raw tls_private_key > secret_key.pem
    
  • azurerm_virtual_machine_extensionでVMのデプロイ完了後にVM内で実行する処理を定義
    上記リソースを使うことで、デプロイ後にVM内でshellscriptを実行するようにできます。
    今回は、mysqlクライアントのインストールや
    コマンドを実行することもできます。

おわりに

いかがでしたでしょうか。UI操作より圧倒的なスピードで動作確認できたのではないでしょうか。次はAzure専用のbicepも試してみたいと思います。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?