iT邦幫忙

2024 iThome 鐵人賽

DAY 15
2
DevOps

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

後 Grafana 時代的第十五天 - Gafana IaC 實戰 - 使用 Terraform 動態產生 Grafana 資源

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20240929/20149562Fq0hMHyEsw.png

前言

Terraform 是一個功能強大的基礎設施即程式碼(Infrastructure as Code, IaC)工具,可用於自動建立、維護和刪除雲端資源。就像開發人員使用 GitHub 等儲存庫儲存程式碼一樣,Terraform 為組織的雲端基礎設施提供了版本控制系統的優勢。這意味著我們可以追蹤基礎設施的變更歷史,輕鬆回復到先前的狀態,並協作管理複雜的基礎設施設定。

Terraform 提供了多種鼓勵程式設計最佳實踐的功能,例如 for_each 和 dynamic 區塊。這些功能不僅可以簡化我們的程式碼,還可以提高其可讀性和可維護性。在本章節中,我們將深入探討如何使用這兩個非常有用的功能來有效管理大規模的 Grafana 環境。透過學習這些技巧,我們將能夠撰寫更加靈活、可擴展的 Terraform 設定,從而更好地應對複雜的基礎設施需求。

透過 Terraform 實現全域動態生成 Grafana 資源

https://ithelp.ithome.com.tw/upload/images/20240929/201495620iVjFxOtxL.png

Terraform 提倡的最佳實踐中,強調了 Terraform 資源程式碼的可重複使用性。所謂的可重複使用性的實現重點就在於如何將所有重要參數的依賴反轉,透過類似環境變數的方式在最初渲染前將所有依賴注入。這種方法被稱為依賴注入(Dependency Injection),它是一種設計模式,用於減少程式碼之間的耦合度。

透過這種方式,我們的程式碼將會變成高度可重複使用。我們透過不寫死參數來保持一個資源的彈性,並且利用陣列(Array)或映射(Map)的迭代來動態產生資源,實現簡潔且高度效率的 Terraform 最佳實踐。這種方法不僅可以減少重複程式碼,還可以使 Terraform 設定更加靈活,能夠輕鬆適應不同的環境和需求。

如何使用 for_each 參數

for_each 是 Terraform 中一個強大的元參數(meta-argument),它允許我們基於一個映射或一組字串來建立多個相似的資源實例。這個功能特別適用於需要建立多個相似但略有不同的資源的情況。

使用 for_each 的主要優點包括:

  1. 程式碼簡潔:使用一個資源區塊替代多個幾乎相同的資源區塊。
  2. 更好的可維護性:當需要更改時,只需要修改一個地方。
  3. 動態性:基於變數或資料來源動態建立資源,而不是硬編碼資源數量。

讓我們看一個使用 for_each 的例子:

locals {
  folders = [
    "folder A",
    "folder B",
    "folder C"
  ]
}

resource "grafana_folder" "test_folders" {
  for_each = toset(local.folders)
  title = "Terraform Test Folder - ${each.value}"
}

實際執行指令運行:

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_folder.test_folders["folder A"] will be created
  + resource "grafana_folder" "test_folders" {
      + id    = (known after apply)
      + title = "Terraform Test Folder - folder A"
      + uid   = (known after apply)
      + url   = (known after apply)
    }

  # grafana_folder.test_folders["folder B"] will be created
  + resource "grafana_folder" "test_folders" {
      + id    = (known after apply)
      + title = "Terraform Test Folder - folder B"
      + uid   = (known after apply)
      + url   = (known after apply)
    }

  # grafana_folder.test_folders["folder C"] will be created
  + resource "grafana_folder" "test_folders" {
      + id    = (known after apply)
      + title = "Terraform Test Folder - folder C"
      + uid   = (known after apply)
      + url   = (known after apply)
    }

Plan: 3 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_folder.test_folders["folder A"]: Creating...
grafana_folder.test_folders["folder C"]: Creating...
grafana_folder.test_folders["folder B"]: Creating...
grafana_folder.test_folders["folder A"]: Creation complete after 0s [id=17]
grafana_folder.test_folders["folder B"]: Creation complete after 0s [id=18]
grafana_folder.test_folders["folder C"]: Creation complete after 0s [id=19]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

在這個例子中,我們定義了一個 locals 區塊,其中包含一個名為 folders 的列表。然後我們使用 for_each 元參數來建立多個 grafana_folder 資源,每個資源對應於 folders 列表中的一個元素,如果日後需要新增 Folder 只需要在列表中新增即可。

https://ithelp.ithome.com.tw/upload/images/20240929/20149562Xs20tUv8B1.png

如何使用 dynamic blocks 功能

dynamic 區塊也是 Terraform 中一個關鍵的功能,它允許我們動態生成設定區塊。這個功能特別適用於需要根據變數或資料來源生成多個相似但略有不同的設定區塊的情況。

讓我們看一個使用 dynamic 區塊的例子:

locals {
  team_permissions = [
    "team A",
    "team B",
    "team C",
  ]
}

resource "grafana_folder" "test_folder" {
  title = "Folder Title"
}

resource "grafana_team" "teams" {
  for_each = toset(local.team_permissions)
  name = each.value
}

resource "grafana_folder_permission" "foldersPermission" {
  folder_uid = grafana_folder.test_folder.uid
  dynamic "permissions" {
    for_each = toset(local.team_permissions)
    content {
      team_id    = grafana_team.teams[permissions.value].id
      permission = "View"
    }
  }
}

實際執行指令運行:

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_folder.test_folder will be created
  + resource "grafana_folder" "test_folder" {
      + id    = (known after apply)
      + title = "Folder Title"
      + uid   = (known after apply)
      + url   = (known after apply)
    }

  # grafana_folder_permission.foldersPermission will be created
  + resource "grafana_folder_permission" "foldersPermission" {
      + folder_uid = (known after apply)
      + id         = (known after apply)

      + permissions {
          + permission = "View"
          + team_id    = (known after apply)
          + user_id    = 0
            # (1 unchanged attribute hidden)
        }
      + permissions {
          + permission = "View"
          + team_id    = (known after apply)
          + user_id    = 0
            # (1 unchanged attribute hidden)
        }
      + permissions {
          + permission = "View"
          + team_id    = (known after apply)
          + user_id    = 0
            # (1 unchanged attribute hidden)
        }
    }

  # grafana_team.teams["team A"] will be created
  + resource "grafana_team" "teams" {
      + id      = (known after apply)
      + name    = "team A"
      + team_id = (known after apply)
    }

  # grafana_team.teams["team B"] will be created
  + resource "grafana_team" "teams" {
      + id      = (known after apply)
      + name    = "team B"
      + team_id = (known after apply)
    }

  # grafana_team.teams["team C"] will be created
  + resource "grafana_team" "teams" {
      + id      = (known after apply)
      + name    = "team C"
      + team_id = (known after apply)
    }

Plan: 5 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_team.teams["team C"]: Creating...
grafana_team.teams["team A"]: Creating...
grafana_folder.test_folder: Creating...
grafana_team.teams["team B"]: Creating...
grafana_team.teams["team B"]: Creation complete after 0s [id=1]
grafana_team.teams["team A"]: Creation complete after 0s [id=2]
grafana_team.teams["team C"]: Creation complete after 0s [id=3]
grafana_folder.test_folder: Creation complete after 0s [id=20]
grafana_folder_permission.foldersPermission: Creating...
grafana_folder_permission.foldersPermission: Creation complete after 0s [id=edx69qm9h49vke]

Apply complete! Resources: 5 added, 0 changed, 0 destroyed.

這裡的操作實現了使特定的 Team 能夠對 Folder(team A、team B、team C) 擁有 View 的權限。在這個例子中我們同樣定義了 locals 區塊,其中包含一個名為 team_permissions 的列表。最關鍵的地方在於我們使用 dynamic block 來為 grafana_folder_permission 資源建立多個 permissions 設定區塊,每個區塊對應於 teams 列表中的一個元素。
這種結構提供了一種靈活且可擴展的方式來管理多個團隊的 Folder 權限,同時保持代碼的簡潔性和可維護性。

https://ithelp.ithome.com.tw/upload/images/20240929/20149562wkz9GJgeKU.png

實戰演練

現在,讓我們將 for_each 和 dynamic 的使用場景結合起來,建立一個 Team 與 Folder 相互依賴的進階資源結構:

locals {
  teams = [
    "team A",
    "team B",
    "team C",
  ]
  folders = {
    "folder A" = {permission = ["team A", "team B"]},
    "folder B" = {permission = ["team B", "team C"]},
    "folder C" = {permission = ["team C"]}
  }
}

resource "grafana_team" "teams" {
  for_each = toset(local.teams)
  name     = each.key
}

resource "grafana_folder" "folders" {
  for_each = local.folders
  title    = each.key
}

resource "grafana_folder_permission" "folder_permissions" {
  for_each   = local.folders
  folder_uid = grafana_folder.folders[each.key].uid

  dynamic "permissions" {
    for_each = toset(each.value.permission)
    content {
      team_id    = grafana_team.teams[permissions.value].id
      permission = "View"
    }
  }
}

實際執行指令運行:

terraform plan
---
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_folder.folders["folder A"] will be created
  + resource "grafana_folder" "folders" {
			//...
    }

  # grafana_folder.folders["folder B"] will be created
  + resource "grafana_folder" "folders" {
			//...
    }

  # grafana_folder.folders["folder C"] will be created
  + resource "grafana_folder" "folders" {
			//...
    }

  # grafana_folder_permission.folder_permissions["folder A"] will be created
  + resource "grafana_folder_permission" "folder_permissions" {
 			//...
      + permissions {
          + permission = "View"
          + team_id    = (known after apply)
          + user_id    = 0
            # (1 unchanged attribute hidden)
        }
      + permissions {
          + permission = "View"
          + team_id    = (known after apply)
          + user_id    = 0
            # (1 unchanged attribute hidden)
        }
    }

  # grafana_folder_permission.folder_permissions["folder B"] will be created
  + resource "grafana_folder_permission" "folder_permissions" {
			//...
      + permissions {
          + permission = "View"
          + team_id    = (known after apply)
          + user_id    = 0
            # (1 unchanged attribute hidden)
        }
      + permissions {
          + permission = "View"
          + team_id    = (known after apply)
          + user_id    = 0
            # (1 unchanged attribute hidden)
        }
    }

  # grafana_folder_permission.folder_permissions["folder C"] will be created
  + resource "grafana_folder_permission" "folder_permissions" {
			//...
      + permissions {
          + permission = "View"
          + team_id    = (known after apply)
          + user_id    = 0
            # (1 unchanged attribute hidden)
        }
    }

  # grafana_team.teams["team A"] will be created
  + resource "grafana_team" "teams" {
			//...
    }

  # grafana_team.teams["team B"] will be created
  + resource "grafana_team" "teams" {
			//...
    }

  # grafana_team.teams["team C"] will be created
  + resource "grafana_team" "teams" {
			//...
    }

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

在這個範例中,我們巧妙地利用了頂層設定管理的方式來建立 Grafana 各資源之間的依賴關係,實現了團隊與資料夾權限的動態配置。透過統一管理的 locals 配置,我們將團隊與資料夾的權限關聯起來,並使用 for_each 和 dynamic 區塊來動態生成所有 Grafana 資源。這樣的結構不僅增強了資源配置的靈活性,還確保了各資源之間的自動化關聯和同步。

接下來能夠在 localhost:3000/dashboards 中,看到我們建立出來的三個 Folder:

https://ithelp.ithome.com.tw/upload/images/20240929/20149562cbSEqbRdDE.png

並且驗證 Folder A 是否配置了正確的權限:

https://ithelp.ithome.com.tw/upload/images/20240929/20149562ydznOXhGeD.png

結論

透過使用 for_each 和 dynamic 區塊,我們能夠在管理大規模 Grafana 環境時保持高效。這些功能不僅簡化了程式碼結構,使其更具條理性,還顯著提升了可讀性和可維護性。更重要的是,它們賦予了 Terraform 設定極大的靈活性,讓我們能夠輕鬆應對不同的環境變化和需求,無論是擴展新資源還是動態調整現有配置,都能快速適應。

這些方法為我們進一步設計更高階、抽象的配置管理架構打下了堅實基礎。隨著這些技術的運用,我們將能夠創建一個更智能、更自動化的基礎設施管理系統。讓我們繼續深入探索,揭開更多這些進階技術背後的精妙之處吧。


上一篇
後 Grafana 時代的第十四天 - Gafana IaC 工具 - Terraform 與 Grafana Provider 介紹
下一篇
後 Grafana 時代的第十六天 - Gafana IaC 實戰 - Organization、Team、User
系列文
後 Grafana 時代的自我修養31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言