開發自定義控制器前,需要先了解的東西 Part1

前言

由於 Kubernetes 控制器是主動調和(Reconciliation)資源過程的程式,它會透過與 API 伺服器溝通,以監視叢集的資源狀態,並依據 API 物件的當前狀態,嘗試將其推向預期狀態。而本系列文章是說明如何採用官方 API client 函式庫來編寫 Kubernetes 自定義控制器。因此需要在開發之前,先了解會使用到的函式庫與工具等等。

Kubernetes 組織在 GitHub 上,維護了許多可以使用的程式函式庫,如: api、client 與 api-machinery 等等都被用於不同的功能實現。而要使用這些函式庫只需要以k8s.io/..方式,在 Go 語言的專案下導入即可。在接下來個小部分中,我將介紹一些會用於開發自定義控制器的 API 相關函式庫。

這部分包含以下:

  • API Machinery
  • API
  • gengo
  • code-generator

apimachinery

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

API 主要提供 Kubernetes 原生的 API 資源類型的 Scheme,這包含 Namespace、Pod 等等。該函式庫也提供了每個 API 資源類型,當前所支援的版本,如:v1、v1beta1。而每種 API 資源都依功能取向被群組化,如下圖所示。

Kubernetes API Resources

gengo

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

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。

TODO: 補全部 Generator 細節與設定

結語

今天主要初步了解 Kubernetes GitHub 組織上關於 API 的函式庫,在開發 Kubernetes 自定義控制器時,有可能因為跟原本 Kubernetes 的功能整合,因此會很頻繁地使用到這些函式庫。然而對這些函式庫有出不了的話,對於後續在自定義資源實作時,也能比較清楚 Kubernetes 的一些設計架構。

Reference

Share Comments