countとか大してわかってないしリストからループするのどうやればいいのと何年か思ってたのが以下サイトにより最近大体解決した気がするので備忘録です。
https://stackoverflow.com/questions/62264013/terraform-failing-with-invalid-for-each-argument-the-given-for-each-argument
以下のバージョンでの実行です。
Terraform: 1.04
azurerm: 2.70
これで何が嬉しいかというと、画面から数十のアラート登録の必要がなくリストを与えてコマンド打つため、ミスがたぶん減る気がするのとBGデプロイやDRで切り替えなどの際にたぶん早くおわるあたり。
ここには監視アラートのリソースしか書かいてなくてPaaSのリソースはdataリソース取ってくる作りになっています。(Insightを書いただけで力尽きた)
resourceから始まるのがその実物を作成し管理できるやつ。dataから始まるテンプレートは、リソースにつけられた名前でそのデータを取ってきてくれる。それを他のリソースで書いておいたリソースidを取り出したりします。
webtest,applicationInsight
- 変数スキーマ
variable "appinsight" {
default = {
insight_name = ""
resource_group_name = ""
}
}
variable "webtests" {
type = list(object({
webtest_name = string
id = string
timeout = string
guid = string
url = string
encoding = string
statuscode = string
duration = string
}))
default = [
{
webtest_name = ""
id = ""
timeout = ""
guid = ""
url = ""
encoding = ""
statuscode = ""
duration = ""
}
]
}
variable "params" {
description = "監視対象リソース以外のパラメータを定義"
default = {
log_analytics_ws_name = ""
log_retention_days = ""
env_tag = ""
appservices = {
threshold_cpu = ""
threshold_memory = ""
threshold_response_sec = ""
}
}
}
- 変数実体
appinsight = {
insight_name = "hoge-app-insight-prod"
resource_group_name = "rg-hoge-prod"
}
webtests = [
{
webtest_name = "www-hoge-jp"
id = "ee5adbc8-0edb-46e1-xxxx-3ebbff62aa2c"
timeout = "120"
guid = "a99b7dc5-e9a9-fb5f-xxxx-1769d4333cc2"
url = "https://www.hoge.jp/"
encoding = "utf-8"
statuscode = "200"
duration = "4000"
},
{
webtest_name = "admin-hoge-jp"
id = "ee5adbc8-0edb-46e1-xxxx-3ebbff62aa2c"
timeout = "120"
guid = "a99b7dc5-e9a9-fb5f-xxxx-1769d4333cc2"
url = "https://admin.hoge.jp/"
encoding = "utf-8"
statuscode = "200"
duration = "2000"
}
]
params = {
log_analytics_ws_name = ""
log_retention_days = ""
env_tag = "hoge"
insight = {
failed_location_count = 2
webtest_tag_key = "hidden-link:/subscriptions/xxxxead0-xxx-xxx-xxx-3b8aa531xxxx/resourceGroups/rg-hoge-prod/providers/microsoft.insights/components/hoge-app-insight-prod"
webtest_tag_value = "Resource"
}
}
- テンプレートに読み込まれる外部ファイル
可用性監視用
<WebTest Name="${name}" Id="${id}" Enabled="True" CssProjectStructure="" CssIteration="" Timeout="${timeout}" WorkItemIds=""
xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010" Description="" CredentialUserName="" CredentialPassword="" PreAuthenticate="True" Proxy="default" StopOnError="False" RecordedResultFile="" ResultsLocale="">
<Items>
<Request Method="GET" Guid="${guid}" Version="1.1" Url="${url}" ThinkTime="0" Timeout="${timeout}" ParseDependentRequests="True" FollowRedirects="True" RecordResult="True" Cache="False" ResponseTimeGoal="0" Encoding="${encoding}" ExpectedHttpStatusCode="${statuscode}" ExpectedResponseUrl="" ReportingName="" IgnoreHttpStatusCode="False" />
</Items>
</WebTest>
クエリログ監視用(ヒアドキュメント風にも書けるけど外部ファイルにしたほうが変数を埋め込むことが可能で便利)
https://docs.microsoft.com/ja-jp/azure/azure-monitor/essentials/app-insights-metrics
https://docs.microsoft.com/ja-jp/azure/azure-monitor/alerts/alerts-log-query
//レスポンスコード 5分 200以外 異常閾値の状態を2連続で検知(severity1)
requests
| where timestamp > ago(10m)
| where resultCode != 200 and resultCode != "302"
| where url == "${url}"
| summarize count() by bin(timestamp, 5m)
ステータスコードのあたりの絞り込みは以下でもよさそう
| where tolong(resultCode) >= 400
//レスポンスタイム 5分 接続にduration秒以上 異常閾値の状態を2連続で検知(severity2)
requests
| where timestamp > ago(10m)
| where url == "${url}"
| where duration > ${duration}
| summarize avgRequestDuration=avg(duration) by bin(timestamp, 5m)
- テンプレート
## Application Insights and url alerts
# insight data source
data "azurerm_application_insights" "app_insights" {
name = var.appinsight.insight_name
resource_group_name = data.azurerm_resource_group.rg1.name
}
# webtest(url監視)
resource "azurerm_application_insights_web_test" "webtest" {
for_each = { for x in var.webtests: x.webtest_name => x }
name = each.value.webtest_name
resource_group_name = data.azurerm_resource_group.rg1.name
location = data.azurerm_resource_group.rg1.location
application_insights_id = data.azurerm_application_insights.app_insights.id
kind = "ping"
frequency = 300
timeout = 60
enabled = true
geo_locations = ["apac-sg-sin-azr", "apac-jp-kaw-edge","apac-hk-hkn-azr"]
# https://docs.microsoft.com/en-us/azure/azure-monitor/app/monitor-web-app-availability#location-population-tags
configuration = templatefile("./webtest.xml", {
name = each.value.webtest_name
id = each.value.id
timeout = each.value.timeout
guid = each.value.guid
url = each.value.url
encoding = each.value.encoding
statuscode = each.value.statuscode
})
# https://docs.microsoft.com/en-us/rest/api/application-insights/web-tests/create-or-update
# tagsは初回はコメントインして2回目に設定する
tags = {
"${var.params.insight.webtest_tag_key}" = "${var.params.insight.webtest_tag_value}"
}
lifecycle {
ignore_changes = [
enabled,
]
}
}
# failed location
resource "azurerm_monitor_metric_alert" "failed_location" {
for_each = azurerm_application_insights_web_test.webtest
name = "[ ${each.value.name} ] url test failed location"
resource_group_name = data.azurerm_resource_group.rg1.name
scopes = [each.value.id,data.azurerm_application_insights.app_insights.id]
description = "${var.params.insight.failed_location_count}箇所以上からの接続失敗を検知しました。確認してください。"
auto_mitigate = true
frequency = "PT5M"
severity = 1
application_insights_web_test_location_availability_criteria {
web_test_id = each.value.id
component_id = data.azurerm_application_insights.app_insights.id
failed_location_count = var.params.insight.failed_location_count
}
tags = {}
action {
action_group_id = azurerm_monitor_action_group.hook1.id
webhook_properties = {}
}
lifecycle {
ignore_changes = [
enabled,
]
}
}
#例外エラー
resource "azurerm_monitor_metric_alert" "exception_count" {
name = "[ ${data.azurerm_application_insights.app_insights.name} ] Insights exceptions Alerts"
resource_group_name = data.azurerm_resource_group.rg1.name
scopes = [data.azurerm_application_insights.app_insights.id]
description = "${data.azurerm_application_insights.app_insights.name} Insights exceptions Alert"
enabled = true
severity = 2
auto_mitigate = true
tags = {}
criteria {
metric_namespace = "microsoft.insights/components"
metric_name = "exceptions/count"
aggregation = "Count"
operator = "GreaterThanOrEqual"
threshold = 5
}
action {
action_group_id = azurerm_monitor_action_group.hook1.id
webhook_properties = {}
}
lifecycle {
ignore_changes = [
enabled,
]
}
}
#トレースログ
#https://docs.microsoft.com/en-us/dotnet/api/microsoft.applicationinsights.datacontracts.severitylevel?view=azure-dotnet
resource "azurerm_monitor_scheduled_query_rules_alert" "trace_error" {
#name = format("[ %s ] log-error-traces", data.azurerm_log_analytics_workspace.log.name)
name = format("[ %s ] log-error-traces", data.azurerm_application_insights.app_insights.name)
resource_group_name = data.azurerm_resource_group.rg1.name
location = data.azurerm_resource_group.rg1.location
# data_source_id = data.azurerm_log_analytics_workspace.log.id
data_source_id = data.azurerm_application_insights.app_insights.id
description = "tracesログにErrorレベルのログがでています。"
severity = 2
enabled = true
# Count all requests with server error result code grouped into 5-minute bins
query = <<-QUERY
traces
|where severityLevel >= 3
QUERY
frequency = 5 ##min
time_window = 5
trigger {
operator = "GreaterThan"
threshold = 4
}
action {
action_group = [azurerm_monitor_action_group.hook1.id]
}
lifecycle {
ignore_changes = [
enabled,
]
}
}
# URL監視 レスポンスタイム
resource "azurerm_monitor_scheduled_query_rules_alert" "url_responsetime" {
for_each = { for x in var.webtests: x.webtest_name => x }
name = format("[ %s ] url test responsetime", each.value.webtest_name)
resource_group_name = data.azurerm_resource_group.rg1.name
location = data.azurerm_resource_group.rg1.location
# data_source_id = data.azurerm_log_analytics_workspace.log.id
data_source_id = data.azurerm_application_insights.app_insights.id
description = "${each.value.url}の応答に${each.value.duration}ミリ秒以上かかっています"
severity = 2
enabled = true
query = templatefile("./query_responsetime.txt", {
url = each.value.url
duration = each.value.duration
})
frequency = 5 ##min
time_window = 10
trigger {
operator = "GreaterThan"
threshold = 1
}
action {
action_group = [azurerm_monitor_action_group.hook1.id]
}
lifecycle {
ignore_changes = [
enabled,
]
}
}
# URL監視 レスポンスコード
resource "azurerm_monitor_scheduled_query_rules_alert" "url_responsecode" {
for_each = { for x in var.webtests: x.webtest_name => x }
name = format("[ %s ] url test responsecode", each.value.webtest_name)
resource_group_name = data.azurerm_resource_group.rg1.name
location = data.azurerm_resource_group.rg1.location
# data_source_id = data.azurerm_log_analytics_workspace.log.id
data_source_id = data.azurerm_application_insights.app_insights.id
description = "${each.value.url}の応答コードに200と302以外があります"
severity = 1
enabled = true
query = templatefile("./query_responsecode.txt", {
url = each.value.url
})
frequency = 5 ##min
time_window = 10
trigger {
operator = "GreaterThan"
threshold = 1
}
action {
action_group = [azurerm_monitor_action_group.hook1.id]
}
lifecycle {
ignore_changes = [
enabled,
]
}
}
#メモリーリーク
#例外数
#セキュリティ
# →以下手動対応
# smart detect
# ※まだこのリソースで定義できるdetector_typeがデフォで入るFailureAnomaliesDetectorのみ
# ※一つのインサイトに重複して2つ同じdetector_typeは設定できない
# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/monitor_smart_detector_alert_rule
# 手動の場合はLink先のとおり, 「インサイトリソース>スマート検出>アラートに移行する(プレビュー)>移行」とすると以下の5つのアラートが自動登録される
# RequestPerformanceDegradationDetector 応答待機時間の劣化
# DependencyPerformanceDegradationDetector 依存関係の期間の低下
# ExceptionVolumeChangedDetector 例外数の異常な上昇
# TraceSeverityDetector 潜在的なセキュリティの問題の検出
# MemoryLeakDetector 潜在的なメモリ リークの検出
# https://docs.microsoft.com/ja-jp/azure/azure-monitor/alerts/alerts-smart-detections-migration
#resource "azurerm_monitor_smart_detector_alert_rule" "example" {
# name = "test-smart-detector-alert-rule"
# resource_group_name = data.azurerm_resource_group.rg1.name
# severity = "Sev0"
# scope_resource_ids = [data.azurerm_application_insights.app_insights.id]
# frequency = "PT1M"
# detector_type = "FailureAnomaliesDetector"
# #detector_type = "MemoryLeakDetector"
#
# action_group {
# ids = [data.azurerm_monitor_action_group.data1.id]
# }
#}
普通のメトリックアラートとログアラートとWebtestとデータリソース取ってくるあたりをループ処理で。
一応解説すると、以下のあたりで、var.webtestsに入ってるリストをforループでxに入れていて、コロンの後ろではインデックスとしてvar.webtestsの中身のどれかの変数名一個をインデックスみたいに指定してるらしい。
each.value.webtest_nameでその中身が取り出せて、each.key.webtest_nameでキーにあたる変数名が取り出せる。
for_each = { for x in var.webtests: x.webtest_name => x }
メモ
- ループで取ってきたデータリソースを使用する際は、そのリソースをパラメータ無で指定すると、
中身で同じようにeach.valueが使える - データリソースの変数指定で使えるのはデータリソースがoutputとして出すマニュアルに載ってるパラメータのみで初回にデータリソースを検索してくるために指定した変数が受け継がれるとかは特にない気がする。
- countとfor_eachは一つのリソースのうちで一つしか使うことができない。どっちも使うのも片方2つとかも無理です。
- for_each2つじゃないと無理そうになった場合、idをdataリソースからとってくるのをあきらめていくつかの変数を組み合わせる方式にすると1つですむこともあるかも。
scopes = [each.value.id]
↓
scopes = ["/subscriptions/${var.subscription_id}/resourceGroups/${each.value.resource_group_name}/providers/Microsoft.Web/sites/${each.value.appservice_name}"]
- もし変数を途中で編集したいというのがある場合localvaluesでどうこうすることができなくもないらしいけどそんなに複雑なことが可能かは知らない。
https://www.mpon.me/entry/2018/03/30/200000 - for_eachつかっているとcountで環境毎の切り替えとかはできないためworkspaceを分けるとかディレクトリを別にするとかの構造から考える必要がある
https://www.terraform.io/docs/language/settings/backends/remote.html#workspaces - なんでだかわからないがWebtestで指定するxmlの中身に書いてるIDは全部同じでも何の問題もなく登録可能だった。
- Webtest(可用性テスト)のタグが、戻ってきたやつは次にPlanうつと差分わかるのでそれを後から反映するため初回はタグのあたりをコメントに入れる必要がある。これもなんだかわからないが全部一緒だった。
- 2年前くらいはazurerm_monitor_metric_alertくらいしかなかった気がするので更新超ありがとうございます、という気持ち。
参考
terraform関連
https://stackoverflow.com/questions/62264013/terraform-failing-with-invalid-for-each-argument-the-given-for-each-argument
https://www.mpon.me/entry/2018/03/30/200000
https://qiita.com/VA_nakatsu/items/3df4136988e3d49e3cc2
https://dev.classmethod.jp/articles/how-to-use-terraform-workspace/
https://qiita.com/naomichi-y/items/4501331d114b4ef9d584
https://ngyuki.hatenablog.com/entry/2021/03/15/185207
https://tech-blog.cloud-config.jp/2020-01-10-terraform-appservice-backup-and-log/
https://www.terraform.io/docs/language/settings/backends/remote.html#workspaces
Azure関連
https://docs.microsoft.com/en-us/azure/azure-monitor/essentials/metrics-supported
https://resources.azure.com/