AzureのログをさくっとElastic Cloudに送る方法

# この記事について
AzureのログをElastic Stackに送りたいけど、具体的にどうすればいいのか？　
基本的にはElastic Agentを使うことになりますが、どちらかというとログの転送のためのAzure側の設定が大変なので、そこをTerraformでさくっと作ってみます。

他のクラウド版の記事と含め最終的にはこのように3つのクラウドからログを集めることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109197/012d3e8d-48bd-1ea1-54ca-9c5ed1abd0e1.png)

# 何のログを今回Elastic Cloudに送るか?
今回は以下の2つについてElastic Stackに転送してみます。

監査ログ （アクティビティログ）
アプリケーションログ （Azure Spring Appsのログ）

# アーキテクチャー概要
今回のアーキテクチャーはこうです。

```
P                     P               T                  T
┌────────────────┐   ┌───────────┐   ┌──────────────┐   ┌────────────────┐                    
│Azure Spring App│   │Diagnostic │   │Log Analytics │   │   app logs     │                    
│  <<service>>   │──▶│settings   │──▶│data export   │──▶│ <<event hub>>  │──┐   M              
└────────────────┘   └───────────┘   └──────────────┘   └────────────────┘  │   ┌────────────┐
                                                                            │   │  Elastic   │
 P                    M                  T                                  ├──▶│   Agent    │
┌────────────────┐   ┌──────────────┐   ┌────────────────┐                  │   └────────────┘
│ Azure Monitor  │   │  Diagnostic  │   │  activitylogs  │                  │                 
│  <<service>>   ├──▶│   settings   │──▶│ <<event hub>>  │----------------──┘                 
└────────────────┘   └──────────────┘   └────────────────┘                              
  ```
P ... 事前に設定してある前提
G ... マニュアルで構成　（Azure MonitorのアクティビティログをエクスポートするCLI/Terraformは現在まだない模様）
T ... この記事のTerraformスクリプトで構成

元ネタは以下の公式ページにある図とのアーキテクチャーですが、今回はAzure Log Analyticsからデータを取るという形もやってみました。Log AnalyticsのData Exportというのを使うと、Event Hubにエクスポートできます。
https://docs.elastic.co/en/integrations/azure


# 手順

Step1. 以下のTerraformスクリプトを作ります。
```Terraform:main.tf
terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~>3.0"
    }
  }
}

variable namespace {}
variable location {}
variable table_names { type = list }
variable log-analytics-workspace-resource-id {}
variable log-analytics-resource-group-name {}

provider "azurerm" {
  features {}
}

resource "azurerm_resource_group" "azure-log-to-elastic" {
  name     = "${var.namespace}elastic"
  location = var.location
}

resource "azurerm_storage_account" "azure-log-to-elastic" {
  name                     = "${var.namespace}elastic"
  resource_group_name      = azurerm_resource_group.azure-log-to-elastic.name
  location                 = azurerm_resource_group.azure-log-to-elastic.location
  account_tier             = "Standard"
  account_replication_type = "LRS"
}

output "azurerm_storage_account-name" {
  value = azurerm_storage_account.azure-log-to-elastic.name
}

resource "azurerm_eventhub_namespace" "azure-log-to-elastic" {
  name                = "${var.namespace}elastic"
  location            = azurerm_resource_group.azure-log-to-elastic.location
  resource_group_name = azurerm_resource_group.azure-log-to-elastic.name
  sku                 = "Standard"
  capacity            = 1
}

output "event_hub_connection_string_azure_to_elastic" {
  value = azurerm_eventhub_namespace.azure-log-to-elastic.default_primary_connection_string
  sensitive = true
}

resource "azurerm_eventhub" "activity-logs" {
  name                = "${var.namespace}_activity_logs"
  namespace_name      = azurerm_eventhub_namespace.azure-log-to-elastic.name
  resource_group_name = azurerm_resource_group.azure-log-to-elastic.name
  partition_count     = 2
  message_retention   = 1
}

output "azurerm_eventhub-activity-logs" {
  value = azurerm_eventhub.activity-logs.name
}

# =========== Log Analytics to Elastic ==============

resource "azurerm_eventhub" "springapplog" {
  name                = "${var.namespace}-springapplog"
  namespace_name      = azurerm_eventhub_namespace.azure-log-to-elastic.name
  resource_group_name = azurerm_resource_group.azure-log-to-elastic.name
  partition_count     = 2
  message_retention   = 1
}

output "azurerm_eventhub-springapplog" {
  value = azurerm_eventhub.springapplog.name
}

resource "azurerm_log_analytics_data_export_rule" "springapplog-to-elastic" {
  name                    = "${var.namespace}-springapplog-to-elastic"
  resource_group_name     = var.log-analytics-resource-group-name
  workspace_resource_id   = var.log-analytics-workspace-resource-id
  destination_resource_id = azurerm_eventhub.springapplog.id
  table_names             = var.table_names
  enabled                 = true
}
```

Step2. 以下の変数ファイルを作成します。値は例なので、適宜変えてください。
```Terraform:terraform.tfvars
namespace="test1204"
location="Japan East"
table_names = ["AppPlatformIngressLogs"]
log-analytics-resource-group-name="nobu-test_group"
log-analytics-workspace-resource-id="/subscriptions/xxx 省略 xxxx/resourceGroups/nobu-test_group/providers/Microsoft.OperationalInsights/workspaces/defaultworkspace-1cf2751a-e85c-4e4a-af37-cf8d9f00b985-lpqjiw7n"
```

Step3. terraform applyで反映します。その際以下のoutput値が出力されるので、これを後ほどのElastic Agentの設定で使います。（値は今回における例です）
```
azurerm_eventhub-springapplog = "test1204_activity_logs"
azurerm_eventhub-activity-logs = "test1204-springapplog"
azurerm_storage_account-name = "test1204elastic"
event_hub_connection_string_azure_to_elastic = "Endpoint=sb://test1204elastic.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=xxx 省略 xxx"
```

Step4. AzureモニターのアクティビティログをEvent Hubにエクスポートする設定

Azureモニターのアクティビティログのエクスポートを選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109197/ace04649-f5bd-b2f6-cf28-02063ccc34d1.png)

診断設定を作成し、転送するログカテゴリを選び、イベントハブは上のTerraformで作成したものを選びます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109197/c3ce48b6-4c3a-02fe-9ac2-5a643e894b04.png)

Step5. Azure Log AnalyticsからEvent Hubにエクスポートする設定

既に構成されてログを受け取っているLog Analyticsワークスペースを開き、データエクスポートを選択し、ルールを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109197/9d2a01e4-4b04-ceb1-c311-42c3886194be.png)

今回はテーブルは１つ、Springアプリケーションへのアクセスログ(AppPlatformIngressLogs)を転送に指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109197/d1061b50-e4c4-d83b-cb28-1bdb910b0f59.png)

とここまで書いて気づきましたが、このStep5はTerraformに含まれていたので、手動でやる必要はありません。

***
ここまでで、Event HubにアクティビティログとSpringのアプリケーションログが送られている状態となっています。
***
Step6. Elastic AgentのVirtual Machinesへのインストール
この手順は詳しくは書きませんが、普通にVMにElastic AgentをインストールしてElastic Cloudに接続してください。Agentは1台だけでいいです。

Step7. Azure Log integrationの設定

さきほどのElastic Agentに割り当ててあるAgent Policyに対して以下の2つのIntegrationを作ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109197/9f22400c-1e88-50ad-44a8-d3ec9dc57b3f.png)

Springアプリケーションのログは、Collect events from Event Hubを指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109197/91855d94-9937-337a-8db3-11dd764e6a79.png)

アクティビティログはCollect Azure Activity Logs from Event Hubを指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109197/e5fa1d55-8293-912b-c4a4-9dc8913e85a7.png)

# 結果 

### アクティビティログ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109197/926931d2-9bef-9533-cd6c-5e30c05c0712.png)

上記と同じ情報をElasticで確認できました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109197/06985cbb-ebfa-47fb-c458-6f9e3a2060be.png)

```:ES|QL
from logs-* 
| where event.dataset == "azure.activitylogs"
| keep azure.activitylogs.operation_name, azure.activitylogs.result_type, azure.activitylogs.event_category, @timestamp, 
azure.subscription_id, azure.activitylogs.identity.claims_initiated_by_user.name, azure.resource.provider, azure.resource.id
| sort @timestamp desc
| limit 100
```

### アプリケーションログ (Spring アクセスログ）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109197/04ad7f02-b289-0969-bb45-c3c6d66d1862.png)

アプリケーションログのような汎用的なログはElastic側はそのスキーマについて知らないので、デフォルトだとこのようにmessageフィールドに情報が全部入ってました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109197/77d21e7b-93cc-b58f-07b2-e67c2aba7c5c.png)

ES|QLを使い、ログを見る時にGROKを使うとパースすることができます。（全部のGROK文かくのが大変なので、今回は２列だけ）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109197/9a64f4e2-9ba0-4c76-51ee-461bdb193f5b.png)

```:ES|QL
from logs-* 
| where event.dataset == "azure.eventhub"
| grok message "\\{%{GREEDYDATA:head}\"Host\":\"%{DATA:Host}\"%{GREEDYDATA:middle}\"TimeLocal\":\"%{DATA:TimeLocal}\"%{GREEDYDATA:tail}\\}"
| keep TimeLocal, Host
| sort TimeLocal desc
| limit 100
```

その他、Ingest Pipelineを作り、データをインデックスする過程で、messageのJSON文字列をフィールドに分割させることができます（こちらの方がES|QLで都度パースするより読み込み負荷が低く、推奨です）
Ingest Pipelineの名前をlogs-(指定したネームスペース).eventhub@customとすると、自動的にそれがインデックス時の処理に使われる仕組みになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109197/6f12633f-4763-39d2-513f-7f17de2e6727.png)

そうすれば、Pipeline追加以降のデータに関してはこのようにGROKを検索時にやらなくても表示できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/109197/7f2382a1-0b5c-afda-f086-0f1239637b96.png)


# おわり
続編として、Elastic AgentのところもTerraform化したいですね。