iT邦幫忙

2024 iThome 鐵人賽

DAY 19
2
DevOps

後 Grafana 時代的自我修養系列 第 19

後 Grafana 時代的第十九天 - Gafana IaC 實戰 - Alerting

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20241003/20149562eIMZxyBUUH.png

前言

現在,我們將進入到 Grafana 的告警系統的 IaC 實作部分。這部份是 Grafana 中相對複雜的部份,需要對告警系統有一定的了解,才能夠更好地進行實作。 並且一直都還沒有人對這部份的資源做過 IaC 的實作(如果有的話,請告訴我讓我參考!),因為 Alerting 也是 Grafana 所有資源中最後期才加入當中的,並且自成體系相當複雜。在過程中,也遇到不少需要取捨以及現實如何將在 UI 上操作的習慣,轉換成能夠完全 IaC 的設計,並且要兼具高效以及操作的彈性,否則就失去了 IaC 的意義了。

Terraform Grafana Provider 對應的 Grafana Alerting 資源

Grafana Alerting 的資源相當多,我們必須對於所有的告警相關資源有所了解,才能夠更好地進行實作:

Grafana Alerting Resource Terraform Grafana Provider Resource
Alert rules grafana_rule_group
Contact points grafana_contact_point
Notification templates grafana_message_template
Notification policy tree grafana_notification_policy
Mute timings grafana_mute_timing

Grafana Alerting 之間的資源關係

在瞭解了所列出的資源後,我們可以粗略的畫出所有資源之間的關係圖:

https://ithelp.ithome.com.tw/upload/images/20241003/20149562FLWzb4Zvyp.png

從中我們可以知道,其實所有的資源都是環環相扣。更可以說所有資源都圍繞著 Alert Rule 和 Notification Policy 為中心搭建而成。

Folder 和 Rule Group:

Folder 是用來組織和管理告警規則組的容器。一個 folder 可以包含多個 rule_group,這種結構使得告警規則的管理變得更加有條理和系統化。通過使用 folder,管理員可以更容易地組織、查找和維護大量的告警規則。

Rule Group 和 Rule:

Rule Group 是一組相關告警規則的集合。每個 rule_group 可以包含多個 rule,並且為這些規則設置統一的評估頻率。這種分組方式使得管理員可以更有效地管理和監控相似類型或相關聯的告警規則。

Rule 和 Datasource、Dashboard、Panel:

Rule 定義了具體的告警條件和邏輯。它可以從一個或多個 datasource 查詢資料,根據預設的條件判斷是否觸發告警。同時,rule 可以與特定的儀表板(dashboard)和面板(panel)關聯,使得告警資訊可以直觀地在 Grafana 界面上呈現。

Rule 和 Notification Policy、Contact Point:

Rule 定義了告警的具體條件和觸發邏輯,在發送告警通知時提供了靈活的路由選擇。當告警被觸發時,系統可以採用兩種方式之一來發送通知。一種是直接將告警發送到與 Rule 關聯的特定 Contact Point,這種方式簡單直接,適用於固定的通知需求。另一種是通過 Notification Policy 進行路由,系統會根據 Rule 的標籤(labels)匹配 Notification Policy 中定義的規則,然後決定使用哪個 Contact Point 來發送通知。這種方法提供了更複雜和動態的路由邏輯,能夠根據不同的情況靈活地選擇通知方式。管理員可以根據具體需求和場景,選擇最適合的通知路由方式,從而實現高效且有針對性的告警管理。

Notification Policy 和 Contact Point、Rule、Mute Timing:

Notification Policy 定義了告警通知的整體策略。它可以使用多個 contact point 來指定不同的通知方式和接收者,匹配多個 rule 來決定哪些告警需要通知,並配置多個 mute timing 來設置靜音時間段。這種靈活的策略配置使得告警通知可以更精確地達到目標接收者。

Contact Point 和 Message Template:

Contact Point 定義了告警通知的具體接收方式和接收者。每個 contact point 可以使用多個 message template 來自定義通知的格式和內容。這種設計使得告警通知可以根據不同的場景和需求,以最適合的方式呈現給接收者。

這些資源和它們之間的關係使得我們可以靈活地配置和管理 Grafana 中的告警系統,實現高效的告警通知和管理。

實戰演練

讓我們深入探討如何使用 Terraform 來管理這些資源。我們將從設計全局資源結構開始,然後逐步實現各個組件。

設計全局資源結構

locals {
  alerting = {
    rule_groups = {
      "Trading Service - 1m" = {
        folder_name        = "Engineering"
        interval_seconds   = 60
        disable_provenance = true  # 允許在 UI 上修改
        rules = {
          TradingError = {
            condition         = "TradingErrorThreshold"
            no_data_state     = "NoData"
            exec_err_state    = "Error"
            for               = "5m"
            notification_settings = {
              contact_point   = "My contact point email"
              group_by        = []
              mute_timings    = ["No weekends"]
            }
            annotations       = {
              "Custom annotation name" = "Custom annotation context"
              __dashboardUid__         = "advgkpnyhc5j4a"
              __panelId__              = "11"
              description              = "Add Description"
              runbook_url              = "https://www.google.com"
              summary                  = "Add Summary"
            }
            labels            = {
              label_key = "label_value"
            }
            is_paused         = false
            data_file         = "${path.module}/rules_data/trading_error.json"
            disable_provenance = false  # 允許在 UI 上修改
          },
          # 可以在這裡添加更多規則
        }
      }
    }
    contact_points = {
      "My contact point email" = {
        email              = ["alice@example.com"]
        message            = "{{ template \"custom_email.message\" .}}"
        disable_provenance = true  # 不允許在 UI 上修改
      }
    }
    message_templates = {
      "Custom email message" = {
        template           = <<-EOT
          {{ define "custom_email.message" }}
          Lorem ipsum - Custom alert!
          {{ end }}
        EOT
        disable_provenance = true  # 不允許在 UI 上修改
      }
    }
    mute_timings = {
      "No weekends" = {
        intervals          = { weekdays = ["saturday", "sunday"] }
        disable_provenance = false  # 允許在 UI 上修改
      }
    }
  }
}

在上述的 Grafana 告警設定中,我們採用了一個簡潔且靈活的頂層架構設計。特別值得注意的是,我們將 Alert Rule 的詳細設定(data)單獨拆分出來,存儲在獨立的 JSON 文件中。這種設計方法有以下幾個優點:

  • 簡化主配置文件:將複雜的數據邏輯從主配置文件中分離出來,使主文件更加清晰易讀。
  • 提高靈活性:獨立的 JSON 文件可以更容易地進行修改和版本控制,而不會影響主配置。
  • 便於管理大量規則:當有多個告警規則時,分離數據可以更好地組織和管理這些規則。

這種方法類似於我們處理 Grafana dashboard JSON 的方式。對於像告警規則這樣可能包含大量細節且不固定的配置,很難設計出一個完全制式和動態的模板。因此,直接使用文件導入的形式是一個實用的解決方案。

Note: 我們想要進一步的模組化管理 JSON 檔內容的話,可以透過我們先前提過 Jsonnet 的語法來進行管理,進一步提升 JSON 的彈性和可維護性。

實現 Rule Group 資源

data "grafana_folder" "folders" {
  for_each = { for k, v in local.alerting.rule_groups : k => v.folder_name }
  title    = each.value
}

resource "grafana_rule_group" "rule_groups" {
  for_each           = local.alerting.rule_groups
  name               = each.key
  folder_uid         = data.grafana_folder.folders[each.key].uid
  interval_seconds   = each.value.interval_seconds
  disable_provenance = each.value.disable_provenance

  dynamic "rule" {
    for_each = each.value.rules
    content {
      name               = rule.key
      condition          = rule.value.condition
      no_data_state      = rule.value.no_data_state
      exec_err_state     = rule.value.exec_err_state
      for                = rule.value.for
      annotations        = rule.value.annotations
      labels             = rule.value.labels
      is_paused          = rule.value.is_paused

      dynamic "notification_settings" {
        for_each = rule.value.notification_settings != null ? [rule.value.notification_settings] : []
        content {
          contact_point = notification_settings.value.contact_point
          group_by      = notification_settings.value.group_by
          mute_timings  = notification_settings.value.mute_timings
        }
      }

      dynamic "data" {
        for_each = jsondecode(file(rule.value.data_file))
        content {
          ref_id = data.value.refId
          relative_time_range {
            from = data.value.relativeTimeRange.from
            to   = data.value.relativeTimeRange.to
          }
          datasource_uid = data.value.datasourceUid
          model          = jsonencode(data.value.model)
        }
      }
    }
  }
}

在這裡我們定義了 Rule Group 的資源,並且透過先前介紹的 dynamic block 來動態生成多個 Rule Group。

並且我們還有另一個小巧思在於,我們在 local 中指定的不是 folder_uid,而是 folder_name,並且在 data source 的部份,我們使用 data "grafana_folder" "folders" 的方式來動態的取得 folder_uid。這樣的設計將更符合我們在 UI 上的操作習慣,也讓我們在管理多個告警規則時更加方便。

實現 Notification Policy 、Contact Point 等相關資源

resource "grafana_contact_point" "contact_points" {
  for_each           = local.alerting.contact_points
  name               = each.key
  disable_provenance = each.value.disable_provenance

  email {
    addresses = each.value.email
    message   = each.value.message
  }
}

resource "grafana_message_template" "message_templates" {
  for_each           = local.alerting.message_templates
  name               = each.key
  template           = each.value.template
  disable_provenance = each.value.disable_provenance
}

resource "grafana_mute_timing" "mute_timings" {
  for_each           = local.alerting.mute_timings
  name               = each.key
  disable_provenance = each.value.disable_provenance

  intervals {
    weekdays = each.value.intervals.weekdays
  }
}

在這裡我們定義了 Contact Point 、Message Template 和 Mute Timing 的資源,複雜度比起 Rule Group 來說簡單許多。

實際建立資源

terraform init
terraform apply
---
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # grafana_contact_point.contact_points["My contact point email"] will be created
  + resource "grafana_contact_point" "contact_points" {
      + disable_provenance = true
      + id                 = (known after apply)
      + name               = "My contact point email"

      + email {
          # At least one attribute in this block is (or was) sensitive,
          # so its contents will not be displayed.
        }
    }

  # grafana_message_template.message_templates["Custom email message"] will be created
  + resource "grafana_message_template" "message_templates" {
      + disable_provenance = true
      + id                 = (known after apply)
      + name               = "Custom email message"
      + template           = <<-EOT
            {{ define "custom_email.message" }}
            Lorem ipsum - Custom alert!
            {{ end }}
        EOT
    }

  # grafana_mute_timing.mute_timings["No weekends"] will be created
  + resource "grafana_mute_timing" "mute_timings" {
      + disable_provenance = false
      + id                 = (known after apply)
      + name               = "No weekends"

      + intervals {
          + weekdays = [
              + "saturday",
              + "sunday",
            ]
        }
    }

  # grafana_rule_group.rule_groups["Trading Service - 1m"] will be created
  + resource "grafana_rule_group" "rule_groups" {
      + disable_provenance = true
      + folder_uid         = "engineering"
      + id                 = (known after apply)
      + interval_seconds   = 60
      + name               = "Trading Service - 1m"

      + rule {
          + annotations    = {
              + "Custom annotation name" = "Custom annotation context"
              + "__dashboardUid__"       = "advgkpnyhc5j4a"
              + "__panelId__"            = "11"
              + "description"            = "Add Description"
              + "runbook_url"            = "https://www.google.com"
              + "summary"                = "Add Summary"
            }
          + condition      = "TradingErrorThreshold"
          + exec_err_state = "Error"
          + for            = "5m"
          + is_paused      = false
          + labels         = {
              + "label_key" = "label_value"
            }
          + name           = "TradingError"
          + no_data_state  = "NoData"
          + uid            = (known after apply)

          + data {
            // ...
          }
          + data {
            // ...
          }
          + data {
            // ...
          }

          + notification_settings {
              + contact_point = "My contact point email"
              + group_by      = []
              + mute_timings  = [
                  + "No weekends",
                ]
            }
        }
    }

Plan: 4 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

接著我們可以使用 Grafana 查看我們剛剛建立的資源:

  • 我們可以看到我們剛剛建立的 Contact Point 和 Message Template 和 Mute Timing 都已經建立成功:

https://ithelp.ithome.com.tw/upload/images/20241003/20149562EhF3nQvCfB.png
https://ithelp.ithome.com.tw/upload/images/20241003/201495621ekr0R9pJ4.png
https://ithelp.ithome.com.tw/upload/images/20241003/20149562TaNOT2PYrk.png

  • 我們也可以看到 disable_provenance 的設定,的確有讓我們在 UI 上無法修改到我們設定的內容:

https://ithelp.ithome.com.tw/upload/images/20241003/20149562otuunNilya.png

  • 我們也可以看到我們剛剛建立的 Rule Group 和 Rule 都已經建立成功,Folder 對於 Rule Group 在到 Rule 的階層關係,也如我們在 Terraform 中設計的一樣:

https://ithelp.ithome.com.tw/upload/images/20241003/20149562Z6GMn4Ay22.png

  • 關於 Rule 方面的設定細節,我們也可以在 Grafana 的 UI 上看到我們設計中的內容一致:

https://ithelp.ithome.com.tw/upload/images/20241003/20149562PnRDQffYo2.png

結語

到此,我們已經完成了幾乎 Grafana 上所有資源的 IaC 實作,包含 Dashboard、Alerting、Folder 和 Datasource。這也意味著我們已經完成了大部份在 Grafana 上的操作都可以轉換成 Terraform 的語法來進行實作。而詳細在團隊中如何實作 CI/CD 的流程,以及如何讓工程師們能夠順利地將 IaC 佈署在不同的環境中,就留待各位看官們自行發揮想像了。礙於篇幅,我們就不在這系列文中進行深入探討。

總之,透過使用 Terraform 來管理 Grafana 的 Alerting 資源,我們可以實現一個高度可配置、版本控制的告警系統。這種方法不僅提高了管理效率,還確保了跨環境的一致性和可重複性。期待能帶給各位讀者一些啟發,讓大家都能夠在各自的實務上,將 IaC 的思維應用到更多的地方。


References:


上一篇
後 Grafana 時代的第十八天 - Gafana IaC 實戰 - Dashboard、Folder
下一篇
後 Grafana 時代的第二十天 - 探討告警事件中心的重要性
系列文
後 Grafana 時代的自我修養30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言