iT邦幫忙

2024 iThome 鐵人賽

DAY 13
2
DevOps

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

後 Grafana 時代的第十三天 - Gafana IaC 工具 - Jsonnet 介紹

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20240927/20149562YHOfxEiEBt.png

前言

還記得我們在前一個章節中提到的 Grafana as Code 我個人理想組合是 Terraform 和 Jsonnet 嗎?在一開始也提到透過高可讀性的 JSON 設定檔,我們可以輕鬆設置幾乎所有 Grafana 的內部資源,這看似是一個美好的解決方案。

然而,隨著團隊和業務規模的擴大,Dashboard 的數量越來越多,我們逐漸發現其中的設定開始變得重複、冗長,並且難以維護和管理。每次修改設定時,都可能涉及到大量的複製與修改,這不僅影響效率,還增加了出錯的風險。在這樣的背景下,Jsonnet 作為一種進階的設定管理工具,成為了 Grafana 團隊應對這一挑戰的終極解決方案。

接下來,我們將深入探討大家熟悉的 JSON 和 Jsonnet 之間的具體差異,並聊聊為何 Jsonnet 能夠在 Grafana 生態系統中占據如此重要的一席之地。

Jsonnet 是什麼

https://ithelp.ithome.com.tw/upload/images/20240927/20149562IMNTLGH7Om.png

Jsonnet 是一種強大而靈活且圖靈具備的資料模板程式語言,它是 JSON 的更多進階功能的擴充版本,旨在幫助使用者生成複雜的 JSON 資料。在探討 Jsonnet 的特性之前,我們需要理解為什麼需要這樣一種語言。

在現代軟體開發中,設定管理變得越來越重要。隨著系統規模的擴大和複雜度的提升,我們需要一種既能保持簡潔易讀,又能處理複雜邏輯的設置語言。JSON 作為一種廣泛使用的資料交換格式,雖然簡單直觀,但在處理大型、複雜的設定時顯得力不從心。這就是 Jsonnet 誕生的背景。

Jsonnet 提供了許多 JSON 所不具備的特性:

  • 變數和引用:Jsonnet 允許定義變數並在設定檔中多次引用,大大減少了重複程式碼。
  • 函數:可以定義和使用函數,實現程式碼的模組化和重用。
  • 繼承和覆蓋:Jsonnet 支持對象繼承,可以基於現有對象創建新對象,並覆蓋特定字段。
  • 導入:可以導入其他 Jsonnet 設定檔,促進了程式碼的組織和重用。
  • 條件語句和循環:支持 if-then-else 結構和 for 循環,增加了設定的靈活性。
  • 計算能力:可以進行數學運算和字符串操作,使設定更加動態。
  • 錯誤處理:提供了錯誤處理機制,有助於調試和錯誤定位。

這些特性使得 Jsonnet 成為一種既保留了 JSON 簡潔性,又具備程式語言靈活性的強大工具。它的主要目標是減少重複程式碼,提高可維護性,並使大型設定檔更易於管理。

https://ithelp.ithome.com.tw/upload/images/20240927/201495629MMXLQM3id.png

JSON 的應用場景及缺陷

在深入探討 Jsonnet 的優勢之前,我們有必要回顧一下 JSON 的應用場景及其侷限性。JSON(JavaScript Object Notation)作為一種輕量級的資料交換格式,自誕生以來就因其簡潔性和易用性而廣受歡迎。

它在許多領域都有廣泛的應用,包括但不限於:

  • API 傳輸:JSON 是 RESTful API 中最常用的資料格式,用於客戶端和服務器之間的資料交換。
  • 設定檔文件:許多應用程序使用 JSON 格式的設定檔,因為它易於人類閱讀和編輯。
  • 資料存儲:一些 NoSQL 資料庫如 MongoDB 使用 JSON 格式存儲資料。
  • 前端開發:在 JavaScript 中,JSON 是一種原生的資料格式,廣泛用於資料處理和存儲。

JSON 的優點顯而易見:

  • 簡單易讀:JSON 的語法簡單,結構清晰,人類可以輕鬆閱讀和編寫。
  • 輕量級:相比 XML,JSON 更加輕量,資料傳輸效率更高。
  • 跨平台兼容性好:幾乎所有現代編程語言都支持 JSON,使其成為理想的資料交換格式。

然而,隨著系統複雜度的增加,JSON 也暴露出了一些明顯的缺陷:

  • 不支持註釋:JSON 不允許添加註釋,這在複雜設定中是一個重大缺陷,因為無法解釋設定的意圖和用途。
  • 缺乏變數和函數:JSON 是一種純資料格式,不支持變數定義和函數調用,這導致在大型設定中經常出現大量重複程式碼。
  • 難以維護大型設定檔:由於缺乏模組化和重用機制,大型 JSON 設定檔往往變得冗長且難以維護。
  • 不支持計算或邏輯操作:JSON 不能進行動態計算或條件判斷,所有值都必須是靜態的。
  • 缺乏錯誤處理機制:在複雜的設定中,錯誤定位和調試變得非常困難。
  • 不支持引用:無法在設定檔內部或跨設定檔引用其他部分的資料,增加了重複的風險。

這些限制在小型專案中可能不太明顯,但在大規模系統中,特別是在需要頻繁更新和維護的場景下,它們會成為嚴重的障礙。這就是為什麼我們需要一種更強大的工具來處理複雜的設定需求,而 Jsonnet 正是為解決這些問題而生的。

Jsonnet 語法入門

Jsonnet 是一種基於 JSON 的編程語言,提供了更多靈活性和可讀性。它支援註解、引用、邏輯和算術運算、數組和物件的深入操作,並能通過模組化來提高可維護性。以下是 Jsonnet 的一些常見使用方式及範例:

安裝

MacOs:

$ brew install jsonnet

Golang:

$ go get github.com/google/go-jsonnet/cmd/jsonnet

基本指令

將簡單的表達式保存至檔案並執行:

$ echo '{key: 1 + 2}' > test.jsonnet
$ jsonnet test.jsonnet
{
   "key": 3
}

也可以直接使用 -e 選項來運行簡單的程式碼:

$ jsonnet -e '{key: 1 + 2}'
{
   "key": 3
}

格式化 Jsonnet 原始碼:

$ jsonnet fmt test.jsonnet

基本語法

註解(支援單行和多行註解):

/* 
     multiple line comment
 */
{ // single line comment
    key1: {
        "user": [
            {kind: "person", value: 1.0},
        ]
    }
}

引用(使用 self 引用當前對象,$ 引用根對象):

{
    example: {
        data: "sample",
        reference: self.data
    },
    rootExample: {
        data: $.example.data
    }
}

資料運算(支援邏輯和算術運算):

{
    num: 4,
    multiplied: 2 * self.num,
    message: "The value is " + self.multiplied + ".",
    concatenatedArray: [1, 2, 3] + [4, 5], // Array
    isEqual: 1 == 2
}

陣列和物件的深入操作:

{
    nums: [1, 2, 3],
    squared: [x * x for x in self.nums if x >= 2],
    fields: {["field" + x]: x for x in self.nums},
    obj: {["prefix" + "suffix"]: 3},
}

函數:

// Function
{
    calculate(size, values)::
      if std.length(values) == 0 then
        error "No data provided"
      else [
            {kind: i, result: size / std.length(values)}
            for i in values
      ],
    identity:: function(x) x,
}

local Person(name='Alice') = {
  name: name,
  greeting: 'Hello ' + name + '!',
};
{
  person1: Person(),
  person2: Person('Bob'),
}

變數:

// Variable
{
    local myData = "sample",
    data: {
        example: myData
    }
}

物件導向繼承:

{
    base: {
        value: "baseValue",
    },
    derived: self["base"] + {
        additional: "extraValue"
    }
}

Jsonnet 實務範例

為了更好地理解 Jsonnet 如何解決 JSON 的局限性,讓我們通過一個具體的例子來展示 Jsonnet 的強大之處。假設我們正在為一個監控系統創建 Grafana Dashboard 設定。使用傳統的 JSON,我們可能會這樣寫:

{
  "dashboard": {
    "title": "My Dashboard",
    "panels": [
      {
        "title": "CPU Usage",
        "type": "graph",
        "datasource": "Prometheus",
        "targets": [
          {
            "expr": "avg(rate(node_cpu_seconds_total{mode=\"user\"}[5m])) * 100",
            "legendFormat": "CPU Usage"
          }
        ]
      },
      {
        "title": "Memory Usage",
        "type": "graph",
        "datasource": "Prometheus",
        "targets": [
          {
            "expr": "(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes * 100",
            "legendFormat": "Memory Usage"
          }
        ]
      }
    ]
  }
}

這個 JSON 設定雖然直觀,但如果我們需要添加更多類似的 Panel,就會導致大量的重複程式碼。現在,讓我們看看如何使用 Jsonnet 來改進這個設定:

{
  local dashboard = {
    title: "My Dashboard",
    panels: [],
  },

  local createPanel(title, metric) = {
    title: title,
    type: "graph",
    datasource: "Prometheus",
    targets: [
      {
        expr: metric,
        legendFormat: title,
      },
    ],
  },

  dashboard: dashboard {
    panels: [
      createPanel("CPU Usage", 'avg(rate(node_cpu_seconds_total{mode="user"}[5m])) * 100'),
      createPanel("Memory Usage", '(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes * 100'),
    ],
  },
}

這個 Jsonnet 設定展示了幾個關鍵的改進:

  1. 變數使用:我們定義了 dashboard 變數作為基礎模板。
  2. 函數定義:createPanel 函數封裝了 Panel 的通用結構,大大減少了重複程式碼。
  3. 動態生成:我們可以輕鬆地通過調用 createPanel 函數來添加新的 Panel,而不需要複製粘貼大塊程式碼。
  4. 可讀性提升:程式碼結構更清晰,邏輯更容易理解。
  5. 可維護性提升:如果需要修改所有面板的共同屬性,我們只需要修改 createPanel 函數,而不是逐個修改每個 Panel。

這個簡單的例子展示了 Jsonnet 如何通過引入程式語言的特性來增強 JSON 的表達能力。在實際應用中,Jsonnet 的優勢會更加明顯,特別是在處理大型、複雜的設定時。

Jsonnet 在監控領域的地位

Jsonnet 在監控領域,特別是在 Grafana 生態系統中,已經成為一個不可或缺的工具。它的應用主要體現在兩個方面:Prometheus Monitoring Mixins 和 Grafonnet。讓我們深入探討這兩個領域,看看 Jsonnet 如何革新了監控設定的管理方式。

Jsonnet 和 Prometheus Monitoring Mixins

Prometheus Monitoring Mixins 是一種使用 Jsonnet 來定義可重用監控組件的方法。這個概念最初由 Grafana Labs 提出,旨在解決大規模監控系統中的設定管理問題。Mixins 允許團隊創建、共享和組合監控設定,包括告警規則、記錄規則和 Grafana Dashboard。

Mixins 的核心思想是:

  1. 模組化:將監控設定分解為可重用的組件。
  2. 可組合性:允許使用者輕鬆組合不同的 Mixins 來構建完整的監控解決方案。
  3. 可定制性:提供覆蓋機制,允許使用者根據需求調整預定義的設定。

例如,一個典型的 Prometheus Mixin 可能包含以下部分:

{
  prometheusAlerts+:: {
    groups+: [
      {
        name: 'example-alerts',
        rules: [
          {
            alert: 'HighErrorRate',
            expr: 'rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) > 0.1',
            'for': '10m',
            labels: {
              severity: 'warning',
            },
            annotations: {
              summary: 'High error rate detected',
              description: 'Error rate is above 10% for the last 10 minutes',
            },
          },
        ],
      },
    ],
  },
}

這個例子定義了一個簡單的告警規則。使用 Jsonnet,我們可以輕鬆地將這個規則與其他 Mixins 組合,或者根據需要進行覆蓋和定制。

Mixins 的優勢在於:

  • 標準化:促進了監控最佳實踐的共享和採用。
  • 效率提升:減少了從頭開始構建監控設定的需求。
  • 一致性:確保跨團隊和專案的監控設定保持一致。
  • 版本控制:Mixins 可以像程式碼一樣進行版本控制和審查。

Jsonnet 和 Grafonnet

Grafonnet 是一個專門用於生成 Grafana Dashboard 的 Jsonnet 庫。它提供了一組進階抽象介面,使得創建和管理複雜的 Grafana Dashboard 變得更加簡單和系統化。

使用 Grafonnet,開發者可以用程式化的方式定義 Dashboard,從而實現更好的版本控制和自動化。以下是一個使用 Grafonnet 創建 Dashboard 的簡單例子:

local grafana = import 'grafonnet/grafana.libsonnet';
local dashboard = grafana.dashboard;
local row = grafana.row;
local prometheus = grafana.prometheus;
local graph = grafana.graphPanel;

dashboard.new(
  'My Dashboard',
  tags=['generated', 'jsonnet'],
  time_from='now-1h',
)
.addRow(
  row.new()
  .addPanel(
    graph.new(
      'HTTP Requests',
      datasource='Prometheus',
      span=12,
      format='short',
      min=0,
    )
    .addTarget(
      prometheus.target(
        'sum(rate(http_requests_total[5m])) by (status_code)',
        legendFormat='{{status_code}}',
      )
    )
  )
)

這個例子展示了如何使用 Grafonnet 創建一個包含 HTTP 請求圖表的 Dashboard。Grafonnet 的優勢包括:

  • 程式碼重用:可以定義通用的面板模板和函數,減少重複工作。
  • 版本控制:Dashboard 設定可以像程式碼一樣進行版本控制,方便追踪變更和協作。
  • 可編程性:可以使用 Jsonnet 的邏輯結構(如循環和條件語句)來動態生成 Dashboard 設定。
  • 模組化:可以將複雜的 Dashboard 拆分為多個小型、可管理的組件。
  • 一致性:通過使用統一的模板和函數,可以確保所有 Dashboard 保持一致的風格和結構。

Grafonnet 的使用不僅簡化了 Dashboard 的創建過程,還大大提高了維護效率。例如,如果需要在所有 Dashboard 中更新某個共同的元素(如資料源或時間範圍),只需修改一處程式碼,然後重新生成所有 Dashboard 即可。

Jsonnet 開發效率比較

我們可以想像團隊使用 Jsonnet 後的相關情況在渡過前期的學習取線後,我們所使用到的設定檔體積將會大幅精簡,每個經手開發的人員將會更了解現有資源,並且進一步的抽象復用,形成一個良性循環。

指標 使用前 使用後
設定檔大小 (KB) 500 300
維護時間 (小時/月) 20 10
錯誤率 (%) 5 1

結語

Jsonnet 在 Grafana 生態系統中的應用,特別是通過 Prometheus Monitoring Mixins 和 Grafonnet,極大地改善了監控設定的管理方式。它不僅解決了 JSON 的局限性,還為團隊提供了一種更加靈活、可維護的方法來處理複雜的監控設置。

隨著監控需求的不斷增長和複雜化,Jsonnet 的重要性只會越來越突出。它為 Grafana as Code 的實踐提供了強大的支持,使得大規模、高效率的監控設定管理成為可能。

對於任何希望提升監控效率和可維護性的團隊來說,掌握 Jsonnet 都是一項值得投資的技能。它不僅能夠解決當前的設定管理問題,還能為未來的擴展和優化奠定堅實的基礎。

在未來,我們可以期待看到更多基於 Jsonnet 的工具和庫的出現,進一步豐富 Grafana 生態系統。同時,隨著更多團隊採用這種方法,社區中的最佳實踐和共享資源也將不斷增加,使得高質量的監控設定變得更加容易實現和維護。

總的來說,Jsonnet 在 Grafana 和監控領域的應用,代表了設定管理的一次重要革新。它不僅解決了當前的痛點,還為未來的發展開闢了新的可能性。對於任何嚴肅對待監控的組織來說,深入了解和採用 Jsonnet 都是一個明智的選擇。


References:


上一篇
後 Grafana 時代的第十二天 - 探討 Grafana IaC 可行性方案
系列文
後 Grafana 時代的自我修養13
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言