系列文章
- Terraform 系列文章
- Grafana 系列文章
概述
前文 Grafana 系列 - Grafana Terraform Provider 基礎 介紹了使用 Grafana Terraform Provider 創建 Datasource.
現在有這么一個現實需求:
有大量的同型別 (type) 的 datasource 需要批量添加,而且這些 datasource 的基本資訊是以 json 的格式已經存在,
需要對 json 進行決議/精簡/重構等操作并將 json 作為 Terraform 的 datasource.
Json 的格式可能類似于這樣:
[
{
"env_name": "dev",
"prom_url": "http://dev-prom.example.com",
"es_url": "http://dev-es.example.com:9200",
"jaeger_url": "http://dev-jaeger.example.com"
},
{
"env_name": "test",
"prom_url": "http://test-prom.example.com",
"es_url": "http://test-es.example.com:9200",
"jaeger_url": "http://test-jaeger.example.com"
}
]
??Notes:
舉一反三,后面的解決方案也適用于其他任意 Json 格式,
該如何實作???
解決方案
通過 Terraform 的 locals jsondecode for 回圈 和 for_each 實作,
具體如下:
- 構造一個 local 變數
- local 變數從 .json 檔案中讀取并內容并通過
jsondecode+file將 json 檔案解碼為 object - 使用
for回圈,將 object 根據當前需求調整,將例子中env_name作為 key, 將其他作為 value - 批量創建資源時,通過
for_each, 進行批量創建,
基本概念
locals
locals 為 運算式 指定一個名稱,所以你可以在一個模塊中多次使用這個名稱,而不用重復運算式,
如果你熟悉傳統的編程語言,把 Terraform 模塊比作函式定義可能會很有用:
- variables(輸入變數) 就像函式的引數,
- outputs(輸出值) 就像函式的回傳值,
locals就像一個函式的臨時本地變數(區域值),
一旦宣告了一個本地值,你可以在 運算式 中以local.<NAME>的形式參考它,
本地值有助于避免在配置中多次重復相同的值或運算式,只有在一個單一的值或結果被用于許多地方的情況下,才可以適度地使用本地值,能夠在一個中心位置輕松地改變數值是本地值的關鍵優勢,
file 函式
file讀取指定路徑下的檔案內容,并將其作為 string 回傳,
> file("${path.module}/hello.txt")
Hello World
jsondecode 函式
jsondecode將一個給定的 string 解釋為 JSON,回傳該字串的解碼結果,
該函式以如下方式將 JSON 值映射到 Terraform 語言 type:
| JSON type | Terraform type |
|---|---|
| String | string |
| Number | number |
| Boolean | bool |
| Object | object(...)的屬性型別根據此表確定 |
| Array | tuple(...)的元素型別根據此表確定 |
| Null | Terraform 語言的 null值 |
Terraform 語言的自動型別轉換規則意味著你通常不需要擔心一個給定的值到底會產生什么型別,只需以直觀的方式使用結果即可,
> jsondecode("{\"hello\": \"world\"}")
{
"hello" = "world"
}
> jsondecode("true")
true
jsonencode執行相反的操作,將一個 string 編碼為 JSON,
for 運算式
一個for運算式通過轉換另一個復雜型別的值來創建一個復雜型別的值,輸入值中的每個元素可以對應于結果中的一個或零個值,并且可以使用一個任意的運算式來將每個輸入元素轉化為輸出元素,
例如,如果var.list是一個字串的串列,那么下面的運算式將產生一個全大寫字母的字串的元組:
[for s in var.list : upper(s)]
這個for運算式遍歷了var.list中的每個元素,然后評估運算式upper(s),將s設定為每個相應的元素,然后它用所有執行該運算式的結果按相同的順序建立一個新的元組值,
一個for運算式的輸入(在in關鍵字之后給出)可以是一個串列,一個集合,一個元組,一個 map,或者一個物件 (object),
上面的例子顯示了一個只有一個臨時符號s的for運算式,但是一個for運算式可以選擇宣告一對臨時符號,以便也使用每個專案的鍵或索引:
[for k, v in var.map : length(k) + length(v)]
對于 map 或物件型別,像上面那樣,k符號是指當前元素的鍵或屬性名稱,你也可以對串列和 map 使用雙符號形式,在這種情況下,額外的符號是每個元素的索引,從 0 開始,常規的符號名稱是i或idx,除非選擇一個很有幫助的更具體的名稱:
[for i, v in var.list : "${i} is ${v}"]
索引或關鍵符號總是可選的,如果你在for關鍵字后面只指定一個符號,那么這個符號將總是代表輸入集合的每個元素的值,
for運算式周圍的括號的型別決定了它產生的結果的型別,
上面的例子使用[和],產生一個元組,如果你用{和}代替,結果是一個物件,你必須提供兩個結果運算式,用=>符號分開:
{for s in var.list : s => upper(s)}
這個運算式產生一個物件,其屬性是來自var.list的原始元素,其相應的值是大寫版本,例如,產生的值可能如下:
{
foo = "FOO"
bar = "BAR"
baz = "BAZ"
}
單獨的for運算式只能產生一個物件值或一個元組值,但 Terraform 的自動型別轉換規則意味著你通常可以在期望使用串列、map 和集合 (set) 的地方使用其結果,
一個 for 運算式也可以包括一個可選的 if 子句來過濾源集合中的元素,產生一個比源值更少元素的值:
[for s in var.list : upper(s) if s != ""]
在for運算式中過濾集合的一個常見原因是根據一些標準將一個源集合分成兩個獨立的集合,例如,如果輸入的var.users是一個物件的映射,其中每個物件都有一個屬性is_admin,那么你可能希望產生包含管理員和非管理員物件的單獨映射:
variable "users" {
type = map(object({
is_admin = bool
}))
}
locals {
admin_users = {
for name, user in var.users : name => user
if user.is_admin
}
regular_users = {
for name, user in var.users : name => user
if !user.is_admin
}
}
因為for運算式可以從無序型別(map、物件、集合 set)轉換為有序型別(串列、元祖),Terraform 必須為無序集合的元素選擇一個隱含的排序,
對于 map 和物件,Terraform 通過鍵或屬性名稱對元素進行排序,使用詞法排序,
對于字串的集合,Terraform 按其值排序,使用詞法排序,
for運算式機制是為了在運算式中從其他集合值中構建集合值,然后你可以將其分配給期待復雜值的單個資源引數,
for_each 元引數
默認情況下,一個 資源塊 配置一個真實的基礎設施物件(同樣,一個 模塊塊 將一個子模塊的內容納入一次配置),然而,有時你想管理幾個類似的物件(比如一個固定的計算實體池),而不需要為每個物件單獨寫一個塊,Terraform 有兩種方法可以做到這一點: count 和 for_each,
如果一個資源或模塊塊包括一個for_each引數,其值是一個 map 或字串集合,Terraform 為該 map 或字串集合的每個成員創建一個實體,
版本說明: for_each是在 Terraform 0.12.6 中添加的,Terraform 0.13 中增加了對for_each 的模塊支持;以前的版本只能在資源中使用它,
注意:一個特定的資源或模塊塊不能同時使用count和for_each,
for_each是 Terraform 語言定義的一個元引數,它可以與模塊和每一種資源型別一起使用,
for_each 元引數接受一個 map 或字串集合,并為該 map 或字串集合的每個專案創建一個實體,每個實體都有一個獨特的基礎設施物件與之相關聯,每個實體都在應用配置時被單獨創建、更新或銷毀,
Map:
resource "azurerm_resource_group" "rg" {
for_each = {
a_group = "eastus"
another_group = "westus2"
}
name = each.key
location = each.value
}
字串集合:
resource "aws_iam_user" "the-accounts" {
for_each = toset( ["Todd", "James", "Alice", "Dottie"] )
name = each.key
}
在設定了for_each 的區塊中,運算式中還有一個each物件,所以你可以修改每個實體的配置,這個物件有兩個屬性:
each.key- 這個實體對應的 map 鍵(或集合成員),each.value- 該實體對應的 map 值,(如果提供了一個集合,這與each.key相同,)
當 for_each 被設定時,Terraform 區分了區塊本身和與之相關的多個資源或模塊實體,實體由提供給for_each的值中的一個 map 鍵(或集合成員)來識別,
<TYPE>.<NAME>或module.<NAME>(例如,azurerm_resource_group.rg) 代表這個塊,<TYPE>.<NAME>[<KEY>]或module.<NAME>[<KEY>](例如,azurerm_resource_group.rg["a_group"],azurerm_resource_group.rg["another_group"], etc.) 代表獨立的實體
這與沒有count或for_each的資源和模塊不同,它們可以在沒有索引或鍵的情況下被參考,
String & Template
字串是 Terraform 中最復雜的一種文字表達,也是最常用的一種,
Terraform 同時支持字串的引號語法和 heredoc 語法,這兩種語法都支持用于插值和操作文本的模板序列,
帶引號的字串是一系列由雙引號字符(")劃定的字符,
有兩個不使用反斜線的特殊轉義序列:
| Sequence | Replacement |
|---|---|
$${ |
字面意思是${,不會開始一個插值序列, |
%%{ |
字面意思是%{,不會開始一個模板指令序列, |
${ ... }序列是一個插值,它評估標記之間給出的運算式,如果有必要,將結果轉換為字串,然后將其插入到最終的字串中:
"Hello, ${var.name}!"
在上面的例子中,命名的物件var.name被訪問,其值被插入到字串中,產生的結果類似 "Hello, Juan!",
%{ ... } 序列是一個指令,它允許有條件的結果和對集合的迭代,類似于條件和for運算式,
以下指令被支持:
-
%{if <BOOL>}/%{else}/%{endif}指令根據一個 bool 運算式的值在兩個模板之間進行選擇:"Hello, %{ if var.name != "" }${var.name}%{ else }unnamed%{ endif }!"else部分可以省略,在這種情況下,如果條件運算式回傳false,結果就是一個空字串, -
%{for <NAME> in <COLLECTION>}/%{endfor}指令在給定的集合或結構值的元素上進行迭代,對每個元素評估一次給定的模板,將結果串聯起來:<<EOT %{ for ip in aws_instance.example.*.private_ip } server ${ip} %{ endfor } EOT
實戰
需求:
有大量的同型別 (type) 的 datasource 需要批量添加,而且這些 datasource 的基本資訊是以 json 的格式已經存在,
需要對 json 進行決議/精簡/重構等操作并將 json 作為 Terraform 的 datasource.
Json 的格式可能類似于這樣:
[
{
"env_name": "dev",
"prom_url": "http://dev-prom.example.com",
"es_url": "http://dev-es.example.com:9200",
"jaeger_url": "http://dev-jaeger.example.com"
},
{
"env_name": "test",
"prom_url": "http://test-prom.example.com",
"es_url": "http://test-es.example.com:9200",
"jaeger_url": "http://test-jaeger.example.com"
}
]
解決方案:
- 構造一個 local 變數
- local 變數從 .json 檔案中讀取并內容并通過
jsondecode+file將 json 檔案解碼為 object - 使用
for回圈,將 object 根據當前需求調整,將例子中env作為 key, 將其他作為 value - 批量創建資源時,通過
for_each, 進行批量創建,
串起來, 最終如下:
locals {
# 將 json 檔案轉換為 物件
user_data = https://www.cnblogs.com/east4ming/archive/2023/06/25/jsondecode(file("${path.module}/env-details.json"))
# 構造一個 map
# key 是 env_name
# value 又是一個 map, 其 key 是 grafana datasource type, value 是 url
envs = { for env in local.user_data : env.env_name =>
{
prometheus = env.prom_url
# 利用 ${} 構造新的 url
jaeger = "${env.jaeger_url}/trace/"
es = env.es_url
}
}
}
resource "grafana_data_source" "prometheus" {
# 通過 for_each 迭代
for_each = local.envs
type = "prometheus"
name = "${each.key}_prom"
uid = "${each.key}_prom"
url = each.value.prometheus
json_data_encoded = jsonencode({
httpMethod = "POST"
})
}
resource "grafana_data_source" "jaeger" {
for_each = local.envs
type = "jaeger"
name = "${each.key}_jaeger"
uid = "${each.key}_jaeger"
url = each.value.jaeger
}
resource "grafana_data_source" "elasticsearch" {
for_each = local.envs
type = "elasticsearch"
name = "${each.key}_es"
uid = "${each.key}_es"
url = each.value.es
database_name = "[example.*-]YYYY.MM.DD"
json_data_encoded = jsonencode({
esVersion = "6.0.0"
interval = "Daily"
includeFrozen = false
maxConcurrentShardRequests = 256
timeField = "@timestamp"
logLevelField = "level"
logMessageField = "message"
})
}
完成??????
???參考檔案
- Overview - Configuration Language | Terraform | HashiCorp Developer
- Terraform: Using for-each in Terraform to iterate through local JSON (copyprogramming.com)
- automation - Iterate over Json using Terraform - Stack Overflow
- Using data returned by jsondecode and iterate over the results in a for_each loop - Terraform - HashiCorp Discuss
- How to Use Terraform's 'for_each', with Examples - The New Stack
三人行, 必有我師; 知識共享, 天下為公. 本文由東風微鳴技術博客 EWhisper.cn 撰寫.
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/555936.html
標籤:其他
上一篇:密碼學概念科普(加密演算法、數字簽名、散列函式、HMAC)
下一篇:返回列表
