1. 程式人生 > 其它 >golang 使用模板新增檔案_自己動手寫一個 Kubernetes YAML 模板化工具

golang 使用模板新增檔案_自己動手寫一個 Kubernetes YAML 模板化工具

技術標籤:golang 使用模板新增檔案

(給Go開發大全加星標)

來源:陽明

https://www.qikqiak.com/post/code-k8s-yaml-templating/

我們在使用 Kubernetes 編寫資源清單檔案的時候,往往會使用類似於Helm或者Kustomize這樣的工具來進行模板化處理,一來是提高了資源清單的靈活性,另一方面也確實降低了我們安裝複雜的 Kubernetes 應用的門檻。本文我們嘗試自己使用 Golang 來實現一個 YAML 資源清單檔案模板化的方案。

Golang 的模板化

Golang 中有一個支援模板文字檔案的標準庫text/template

,這個庫允許我們執行函式、賦值等操作,並可以執行一些邏輯來替換一些源文字中的模板值,我們可以從檔案中讀取這些文字,也可以從一個字串去進行解析。由於我們想要模板化 YAML 檔案,所以會從檔案中去讀取,這樣我們就可以用如下所示的程式碼來進行處理:

package templatesimport (    "bytes"    "path/filepath"    "text/template"    ...)func Read(filePath string) ([]byte, error) {    tmpl, err := template.New(filepath.Base(filePath)).    Funcs(availableFunctions).    ParseFiles(filePath)    if err != nil {        return nil, err    }    var buf bytes.Buffer    if err := tmpl.Execute(&buf, availableData); err != nil {        return nil, err    }    return buf.Bytes(), nil}

上面的程式碼讀取一個位於 filePath 的檔案,並將其作為模板,使用availableFunctions中的函式和availableData中的資料來填充所有的模板值。比如我們讀取的是一個 ConfigMap 的 YAML 檔案。

apiVersion: v1kind: ConfigMapmetadata:  name: my-configmap  namespace: {{ .Namespace }}  labels:    app: myappdata:  USER: admin  PASSWORD: {{ GeneratePassword }}

然後我們把availableData

availableFunctions定義成如下所示的程式碼。

var availableData = map[string]string{    "Namespace": "my-namespace",}var availableFunctions = template.FuncMap{    "GeneratePassword": GeneratePasswordFunc,}func GeneratePasswordFunc() (string, error) {...}

這樣上面定義的 Read 函式呼叫後的輸出結果如下所示。

apiVersion: v1kind: ConfigMapmetadata:  name: my-configmap  namespace: my-namespace  labels:    app: myappdata:  USER: admin  PASSWORD: s0m3p455w0rd # 依賴你的 GeneratePassword 函式

在程式中使用 YAML

當我們使用 kubectl 這樣的 CLI 工具的時候,在 Kubernetes 中使用 YAML 非常簡單:

kubectlcreate-fmyfile.yaml

但是如果要我們自己去編寫程式碼來應用 YAML 檔案的話,一般情況下會去使用client-go這個客戶端工具包,但是 client-go 是針對靜態型別的,而 YAML 檔案中是沒有對應的資訊的,但是我們還可以通過下面兩種方案來解決這個問題。

  • 使用 YAML 中的Kind和Version 反序列化為靜態型別,然後使用它的型別化 REST 客戶端進行通訊。

  • 使用 Discovery 功能,Discovery 允許我們動態地查詢給定型別的 REST 客戶端,而不是通過靜態型別去訪問,下面我們就使用這種方式來進行演示。

首先我們需要像往常一樣與 APIServer 通訊建立一個 ClientSet 物件,如果我們從一個可以使用 kubectl 的系統執行程式碼,就意味著有一個可用的kubeconfig檔案可以使用,通常這個檔案為$HOME/.kube/config檔案,如下所示:

import (    "k8s.io/client-go/tools/clientcmd"    "k8s.io/client-go/kubernetes")...// 使用本地 ~/.kube/config 建立配置kubeConfigPath := os.ExpandEnv("$HOME/.kube/config")config, err := clientcmd.BuildConfigFromFlags("", kubeConfigPath)if err != nil {    log.Fatal(err)}// 使用上面的配置獲取連線c, err := kubernetes.NewForConfig(config)if err != nil {    log.Fatal(err)}

ClientSet相當於和 K8S 叢集通訊的閘道器,使用它我們可以獲取物件來給我們提供發現介面。對於我們想要實現的功能,需要能夠查詢給定資源的型別,並與該型別的 REST 客戶端進行通訊,所以我們分別需要一個 Discovery REST mapper 和一個動態的 REST 介面,程式碼如下所示:

import (    "k8s.io/client-go/restmapper"    "k8s.io/client-go/dynamic")...// 獲取支援的資源型別列表resources, err := restmapper.GetAPIGroupResources(c.Discovery())if err != nil {    log.Fatal(err)}// 建立 'Discovery REST Mapper',獲取查詢的資源的型別mapper:= restmapper.NewDiscoveryRESTMapper(resourcesAvailable)// 獲取 'Dynamic REST Interface',獲取一個指定資源型別的 REST 介面dynamicREST, err := dynamic.NewForConfig(config)if err != nil {    log.Fatal(err)}

接下來我們去查詢 YAML 檔案中所代表的物件型別,並得到一個支援它的 REST 客戶端是不是就可以去操作這個資源物件了?

首先呼叫前面的 Read 函式讀取並執行一個模板:

finalYAML, err := templates.Read(myFilePath)if err != nil {    log.Fatal(err)}

為了使用我們的DiscoveryREST mapper和動態 REST 介面,我們需要將 YAML 檔案的內容 decode 成一個runtime.Objects物件。

首先將 YAML 檔案內容根據---進行分割(一個 YAML 檔案中可能有多個資源物件):

objectsInYAML := bytes.Split(yamlBytes, []byte("---"))if len(objectsInYAML) == 0 {    return nil, nil}

然後在每個片段上使用 k8s.io 的反序列化功能輸出得到 runtime.Object 物件,以及一個持有 Group、Version 和 Kind 資訊的結構體。

import(    "k8s.io/apimachinery/pkg/runtime/serializer/yaml")...for _, objectInYAML := range objectsInYAML {    runtimeObject, groupVersionAndKind, err :=     yaml.        NewDecodingSerializer(unstructured.UnstructuredJSONScheme).        Decode(objectInYAML.Raw, nil, nil)    if err != nil {        log.Fatal(err)    }...

現在我們可以回頭去使用我們的 RESTMapper,通過上面得到的 GVK 來獲取一個對映:

// 查詢 Group/Version/Kind 的 REST 對映mapping, err := d.mapper.RESTMapping(groupVersionAndKind.GroupKind(), groupVersionAndKind.Version)if err != nil {    log.Fatal(err)}

有了資源型別,我們就可以使用前面的動態 REST 介面獲取特定資源物件的客戶端了:

unstructuredObj := runtimeObject.(*unstructured.Unstructured)var resourceREST dynamic.ResourceInterface// 需要為 namespace 範圍內的資源提供不同的介面if mapping.Scope.Name() == meta.RESTScopeNameNamespace {    if unstructuredObj.GetNamespace() == "" {        unstructuredObj.SetNamespace("default")    }    resourceREST =     d.      dynamicREST.      Resource(mapping.Resource).      Namespace(unstructuredObj.GetNamespace())} else {    resourceREST = d.dynamicREST.Resource(mapping.Resource)}

到這裡我們就可以在 Kubernetes 中使用得到的 client 物件來執行建立刪除等操作了!

import (    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1")// 建立物件_, err = resourceREST.Create(unstructuredObj, metav1.CreateOptions{})if err != nil {    log.Fatal(err)}// 刪除物件prop := metav1.DeletePropagationForegrounderr = resourceREST.Delete(unstructuredObj.GetName(),    &metav1.DeleteOptions{       PropagationPolicy: &prop,    })if err != nil {   log.Fatal(err)}

到這裡我們就使用 Golang 完成了一個輕量級的 YAML 模板處理工具了。

- EOF -

推薦閱讀(點選標題可開啟)

1、Go 語言層出不窮的安全問題

2、如何在 Go 中做依賴注入?

3、英雄聯盟的大廠開發商是如何玩轉 Go 的?

如果覺得本文不錯,歡迎轉發推薦給更多人。

420a0d3fc0ea6001572249496fb2f591.png

分享、點贊和在看

支援我們分享更多好文章,謝謝!