由於 Kubernetes 控制器是主動調和(Reconciliation)資源過程的程式,它會透過與 API 伺服器溝通,以監視叢集的資源狀態,並依據 API 物件的當前狀態,嘗試將其推向預期狀態。而本系列文章是說明如何採用官方 API client 函式庫來編寫 Kubernetes 自定義控制器。因此需要在開發之前,先了解會使用到的函式庫與工具等等。
Kubernetes 組織在 GitHub 上,維護了許多可以使用的程式函式庫,如: api、client 與 api-machinery 等等都被用於不同的功能實現。而要使用這些函式庫只需要以k8s.io/..
方式,在 Go 語言的專案下導入即可。在接下來個小部分中,我將介紹一些會用於開發自定義控制器的 API 相關函式庫。
這部分包含以下:
API Machinery 是定義 API 級別的 Scheme、類型(Typing)、編碼(Encoding)、解碼(Decoding)、驗證(Validate)、類型轉換與相關工具等等功能。當我們要實現一個新的 API 資源時,就必須透過 API Machinery 來註冊 Scheme,另外 API Machinery 也定義了 TypeMeta、ObjectMeta、ListMeta、 Labels 與 Selector 等等物件,而這些物件幾乎在每個 Kubernetes API 資源中都會使用到,比如下面 YAML 所示。
apiVersion: v1 # TypeMeta
kind: Pod # TypeMeta
metadata: # ObjectMeta
name: memory-demo
namespace: mem-example
labels: # Labels
tt: xx
spec:
containers:
- name: memory-demo-ctr
image: polinux/stress
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "150M", "--vm-hang", "1"]
API 主要提供 Kubernetes 原生的 API 資源類型的 Scheme,這包含 Namespace、Pod 等等。該函式庫也提供了每個 API 資源類型,當前所支援的版本,如:v1、v1beta1。而每種 API 資源都依功能取向被群組化,如下圖所示。
gengo 主要用於透過 Go 語言檔案產生各種系統與 API 所需的文件,比如說 Protobuf。而該專案也包含了 Set、Deep-copy、Defaulter 等等產生器(Generator),這些會被用於產生客製化 Client 函式庫。
大家在看 Kubernetes 源碼時,一定會看到這樣一段註解
// Code generated by xxx. DO NOT EDIT.
。事實上 Kubernetes 有許多程式碼是基於該專案產生出來的,因為 Kubernetes 有很多 API 資源類型,若每一種都寫套維護的話,會非常複雜,因此 Kubernetes 定義了一套標準(Interface 與 Scheme 等等)來維護,並透過 Generator 來產生一些程式碼。
Code Generator 是基於 gengo 開發的程式碼產生器,主要用來實現產生 Kubernetes-style API types 的 Client、Deep-copy、Informer、Lister 等等功能的程式碼。這是因為 Go 語言中沒有泛型(Generic)概念,因此不同的 API 資源類型,若都要寫一次上述這些功能的話,會有大量重複的程式碼,因此 Kubernetes 採用定義好類型結構
後,再透過該專案提供的工具產生相關程式碼。下面舉個例子。
其他語言的 Generator 可以參考 gen。
假設要實作一個 LINE Bot 的 API 資源,並產生 Client 程式時,我們必需先定義結構在 Go 檔案中。然後接著用註解
方式,在程式碼標示物件結構要產生程式碼。比範例會產生 Bot 物件的 client 程式碼跟 Deep-copy 方法:
package v1alpha1
import (
"github.com/line/line-bot-sdk-go/linebot"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type Bot struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata"`
Spec BotSpec `json:"spec"`
Status BotStatus `json:"status,omitempty"`
}
type BotExposeType string
const (
NgrokExpose BotExposeType = "Ngrok"
IngressExpose BotExposeType = "Ingress"
LoadBalancerExpose BotExposeType = "LoadBalancer"
)
type BotExpose struct {
Type BotExposeType `json:"type"`
DomainName string `json:"domainName"`
LoadBalanceIPs []string `json:"loadBalanceIPs,omitempty"`
NgrokToken string `json:"ngrokToken"`
}
type BotSpec struct {
Selector *metav1.LabelSelector `json:"selector"`
ChannelSecretName string `json:"channelSecretName"`
Expose BotExpose `json:"expose"`
Version string `json:"version"`
LogLevel int `json:"logLevel"`
}
type BotPhase string
const (
BotPending BotPhase = "Pending"
BotActive BotPhase = "Active"
BotFailed BotPhase = "Failed"
BotTerminating BotPhase = "Terminating"
)
type BotStatus struct {
Phase BotPhase `json:"phase"`
Reason string `json:"reason,omitempty"`
LastUpdateTime metav1.Time `json:"lastUpdateTime"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type BotList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []Bot `json:"items"`
}
當完成定義與註解描述後,我們會以這樣的目錄方式放在開發專案中,其中types.go
就是上述檔案。
pkg/apis
└── line
├── register.go
└── v1alpha1
├── doc.go
├── register.go
├── types.go
└── zz_generated.deepcopy.go
其他檔案會在後續開發時,詳細說明。
接著利用 code-generator 工具來指向 API 物件結構位置,以讓 code-generator 解析,並產生對應的程式碼。下面是執行腳本範例:
#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail
SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
CODEGEN_PKG=${CODEGEN_PKG:-$(cd "${SCRIPT_ROOT}"; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo ../code-generator)}
bash "${CODEGEN_PKG}"/generate-groups.sh "deepcopy,client,informer,lister" \
github.com/kairen/line-bot-operator/pkg/generated \
github.com/kairen/line-bot-operator/pkg/apis \
"line:v1alpha1" \
--output-base "$(dirname ${BASH_SOURCE})/../../../../" \
--go-header-file ${SCRIPT_ROOT}/hack/boilerplate.go.txt
這邊
boilerplate.go.txt
為 Go 檔案的 License 內容。用於在產生程式碼時,自動塞在檔案內容的頭。
當完成後,我們會在指定輸出的目錄看到產生的程式碼,如
pkg/generated
├── clientset
│ └── versioned
│ ├── clientset.go
│ ├── doc.go
│ ├── fake
│ │ ├── clientset_generated.go
│ │ ├── doc.go
│ │ └── register.go
│ ├── scheme
│ │ ├── doc.go
│ │ └── register.go
│ └── typed
│ └── line
│ └── v1alpha1
│ ├── bot.go
│ ├── doc.go
│ ├── event.go
│ ├── eventbinding.go
│ ├── fake
│ │ ├── doc.go
│ │ ├── fake_bot.go
│ │ ├── fake_event.go
│ │ ├── fake_eventbinding.go
│ │ └── fake_line_client.go
│ ├── generated_expansion.go
│ └── line_client.go
├── informers
│ └── externalversions
│ ├── factory.go
│ ├── generic.go
│ ├── internalinterfaces
│ │ └── factory_interfaces.go
│ └── line
│ ├── interface.go
│ └── v1alpha1
│ ├── bot.go
│ ├── event.go
│ ├── eventbinding.go
│ └── interface.go
└── listers
└── line
└── v1alpha1
├── bot.go
├── event.go
├── eventbinding.go
└── expansion_generated.go
如此一來,我們就能在開發時,使用程式碼來操作自定義資源的 CRUD。
今天主要初步了解 Kubernetes GitHub 組織上關於 API 的函式庫,在開發 Kubernetes 自定義控制器時,有可能因為跟原本 Kubernetes 的功能整合,因此會很頻繁地使用到這些函式庫。然而對這些函式庫有出不了的話,對於後續在自定義資源實作時,也能比較清楚 Kubernetes 的一些設計架構。