使用 Clientset 獲取 Kubernetes 資源對象
?
本節(jié)主要講解 Kubernetes 核心的資源類型 Scheme 的定義以及如何使用 Clientset 來獲取集群資源對象。
介紹
當(dāng)我們操作資源和 apiserver 進行通信的時候,需要根據(jù)資源對象類型的 Group、Version、Kind 以及規(guī)范定義、編解碼等內(nèi)容構(gòu)成 Scheme 類型,然后 Clientset 對象就可以來訪問和操作這些資源類型了,Scheme 的定義主要在 api 子項目之中,源碼倉庫地址: https://github.com/kubernetes/api ,被同步到 Kubernetes 源碼的 staging/src/k8s.io/api 之下。
主要就是各種資源對象的原始結(jié)構(gòu)體定義,比如查看 apps/v1 目錄下面的定義:
$ tree staging/src/k8s.io/api/apps/v1staging/src/k8s.io/api/apps/v1├── BUILD├── doc.go├── generated.pb.go├── generated.proto├── register.go├── types.go├── types_swagger_doc_generated.go└── zz_generated.deepcopy.go0 directories, 8 files
types.go 文件
其中 types.go 文件里面就是 apps/v1 這個 GroupVersion 下面所有的資源對象的定義,有 Deployment、DaemonSet、StatefulSet、ReplicaSet 等幾個資源對象,比如 Deployment 的結(jié)構(gòu)體定義如下所示:

由 TypeMeta、ObjectMeta、DeploymentSpec 以及 DeploymentStatus 4個屬性組成,和我們使用 YAML 文件定義的 Deployment 資源對象也是對應(yīng)的。
apiVersion: apps/v1kind: Deploymentmetadata:name: nginx-deploynamespace: defaultspec:selector:matchLabels:app: nginxtemplate:metadata:labels:app: nginxspec:containers:name: nginximage: nginxports:containerPort: 80
其中 apiVersion 與 kind 就是 TypeMeta 屬性,metadata 屬性就是 ObjectMeta,spec 屬性就是 DeploymentSpec,當(dāng)資源部署過后也會包含一個 status 的屬性,也就是 DeploymentStatus ,這樣就完整的描述了一個資源對象的模型。
zz_generated.deepcopy.go 文件
上面定義的規(guī)范在 Kubernetes 中稱為資源類型 Scheme,此外zz_generated.deepcopy.go 文件是由 deepcopy-gen 工具創(chuàng)建的定義各資源類型 DeepCopyObject() 方法的文件,所有注冊到 Scheme 的資源類型都要實現(xiàn) runtime.Object 接口:
// staging/src/k8s.io/apimachinery/pkg/runtime/interface.gotype Object interface {GetObjectKind() schema.ObjectKindDeepCopyObject() Object}
而所有的資源類型都包含一個 TypeMeta 類型,而該類型實現(xiàn)了 GetObjectKind() 方法,所以各資源類型只需要實現(xiàn) DeepCopyObject() 方法即可:
// staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/meta.gofunc (obj *TypeMeta) GetObjectKind() schema.ObjectKind { return obj }
各個資源類型的 DeepCopyObject() 方法也不是手動定義,而是使用 deepcopy-gen 工具命令統(tǒng)一自動生成的,該工具會讀取 types.go 文件中的 +k8s:deepcopy-gen 注釋,以 Deployment 為例:
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object// Deployment enables declarative updates for Pods and ReplicaSets.type Deployment struct {......}
然后將自動生成的代碼保存到 zz_generated.deepcopy.go 文件中。
register.go 文件
register.go 文件的主要作用是定義 AddToScheme 函數(shù),將各種資源類型注冊到 Clientset 使用的 ?Scheme 對象中去,由于每個資源自動生成了 DeepCopyObject() 方法,這樣資源就實現(xiàn)了 runtime.Object 接口,所以可以注冊到 Scheme 中去了。
// staging/src/k8s.io/api/apps/v1/register.govar (// TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api.// localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes.SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)localSchemeBuilder = &SchemeBuilder// 對外暴露的 AddToScheme 方法用于注冊該 Group/Verion 下的所有資源類型AddToScheme = localSchemeBuilder.AddToScheme)// staging/src/k8s.io/client-go/kubernetes/scheme/register.go// 新建一個 Scheme,將各類資源對象都添加到該 Schemevar Scheme = runtime.NewScheme()// 為 Scheme 中的所有類型創(chuàng)建一個編解碼工廠var Codecs = serializer.NewCodecFactory(Scheme)// 為 Scheme 中的所有類型創(chuàng)建一個參數(shù)編解碼工廠var ParameterCodec = runtime.NewParameterCodec(Scheme)// 將各 k8s.io/api// 目錄下資源類型的 AddToScheme() 方法注冊到 SchemeBuilder 中 var localSchemeBuilder = runtime.SchemeBuilder{......appsv1.AddToScheme,appsv1beta1.AddToScheme,appsv1beta2.AddToScheme,......}var AddToScheme = localSchemeBuilder.AddToSchemefunc init() {v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"})// 調(diào)用 SchemeBuilder 中各資源對象的 AddToScheme() 方法,將它們注冊到到 Scheme 對象utilruntime.Must(AddToScheme(Scheme))}
將各類資源類型注冊到全局的 Scheme 對象中,這樣 Clientset 就可以識別和使用它們了,那么我們應(yīng)該如何使用 Clientset 呢?
示例
首先我們來看下如何通過 Clientset 來獲取資源對象,我們這里來創(chuàng)建一個 Clientset 對象,然后通過該對象來獲取默認命名空間之下的 Deployments 列表,代碼如下所示:
package mainimport ("flag""fmt""os""path/filepath"metav1 "k8s.io/apimachinery/pkg/apis/meta/v1""k8s.io/client-go/kubernetes""k8s.io/client-go/rest""k8s.io/client-go/tools/clientcmd")func main() {var err errorvar config *rest.Configvar kubeconfig *stringif home := homeDir(); home != "" {kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")} else {kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")}flag.Parse()// 使用 ServiceAccount 創(chuàng)建集群配置(InCluster模式)if config, err = rest.InClusterConfig(); err != nil {// 使用 KubeConfig 文件創(chuàng)建集群配置if config, err = clientcmd.BuildConfigFromFlags("", *kubeconfig); err != nil {panic(err.Error())}}// 創(chuàng)建 clientsetclientset, err := kubernetes.NewForConfig(config)if err != nil {panic(err.Error())}// 使用 clientsent 獲取 Deploymentsdeployments, err := clientset.AppsV1().Deployments("default").List(metav1.ListOptions{})if err != nil {panic(err)}for idx, deploy := range deployments.Items {fmt.Printf("%d -> %s\n", idx+1, deploy.Name)}}func homeDir() string {if h := os.Getenv("HOME"); h != "" {return h}return os.Getenv("USERPROFILE") // windows}
上面的代碼運行可以獲得 default 命名空間之下的 Deployments:
go run main.go1 -> details-v12 -> el-gitlab-listener3 -> nginx4 -> productpage-v15 -> ratings-v16 -> reviews-v17 -> reviews-v28 -> reviews-v3
這是一個非常典型的訪問 Kubernetes 集群資源的方式,通過 client-go 提供的 Clientset 對象來獲取資源數(shù)據(jù),主要有以下三個步驟:
使用 kubeconfig 文件或者 ServiceAccount(InCluster 模式)來創(chuàng)建訪問 Kubernetes API 的 Restful 配置參數(shù),也就是代碼中的
rest.Config對象
使用 rest.Config 參數(shù)創(chuàng)建 Clientset 對象,這一步非常簡單,直接調(diào)用
kubernetes.NewForConfig(config)即可初始化
然后是 Clientset 對象的方法去獲取各個 Group 下面的對應(yīng)資源對象進行 CRUD 操作
Clientset 對象
上面我們了解了如何使用 Clientset 對象來獲取集群資源,接下來我們來分析下 Clientset 對象的實現(xiàn)。
上面我們使用的 Clientset 實際上是對各種資源類型的 Clientset 的一次封裝:
// staging/src/k8s.io/client-go/kubernetes/clientset.go// NewForConfig 使用給定的 config 創(chuàng)建一個新的 Clientsetfunc NewForConfig(c *rest.Config) (*Clientset, error) {configShallowCopy := *cif configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 {configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst)}var cs Clientsetvar err errorcs.admissionregistrationV1beta1, err = admissionregistrationv1beta1.NewForConfig(&configShallowCopy)if err != nil {return nil, err}// 將其他 Group 和版本的資源的 RESTClient 封裝到全局的 Clientset 對象中cs.appsV1, err = appsv1.NewForConfig(&configShallowCopy)if err != nil {return nil, err}......cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy)if err != nil {return nil, err}return &cs, nil}
上面的 NewForConfig 函數(shù)里面就是將其他的各種資源的 RESTClient 封裝到了全局的 Clientset 中,這樣當(dāng)我們需要訪問某個資源的時候只需要使用 Clientset 里面包裝的屬性即可,比如 clientset.CoreV1() 就是訪問 Core 這個 Group 下面 v1 這個版本的 RESTClient。這些局部的 RESTClient 都定義在 staging/src/k8s.io/client-go/typed/ 文件中,比如 staging/src/k8s.io/client-go/kubernetes/typed/apps/v1/apps_client.go 這個文件中就是定義的 apps 這個 Group 下面的 v1 版本的 RESTClient,這里同樣以 Deployment 為例:
// staging/src/k8s.io/client-go/kubernetes/typed/apps/v1/apps_client.go// NewForConfig 根據(jù) rest.Config 創(chuàng)建一個 AppsV1Clientfunc NewForConfig(c *rest.Config) (*AppsV1Client, error) {config := *c// 為 rest.Config 設(shè)置資源對象默認的參數(shù)if err := setConfigDefaults(&config); err != nil {return nil, err}// 實例化 AppsV1Client 的 RestClientclient, err := rest.RESTClientFor(&config)if err != nil {return nil, err}return &AppsV1Client{client}, nil}func setConfigDefaults(config *rest.Config) error {// 資源對象的 GroupVersiongv := v1.SchemeGroupVersionconfig.GroupVersion = &gv// 資源對象的 root pathconfig.APIPath = "/apis"// 使用注冊的資源類型 Scheme 對請求和響應(yīng)進行編解碼,Scheme 就是前文中分析的資源類型的規(guī)范config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}if config.UserAgent == "" {config.UserAgent = rest.DefaultKubernetesUserAgent()}return nil}func (c *AppsV1Client) Deployments(namespace string) DeploymentInterface {return newDeployments(c, namespace)}// staging/src/k8s.io/client-go/kubernetes/typed/apps/v1/deployment.go// deployments 實現(xiàn)了 DeploymentInterface 接口type deployments struct {client rest.Interfacens string}// newDeployments 實例化 deployments 對象func newDeployments(c *AppsV1Client, namespace string) *deployments {return &deployments{client: c.RESTClient(),ns: namespace,}}
通過上面代碼我們就可以很清晰的知道可以通過 clientset.AppsV1().Deployments("default")來獲取一個 deployments 對象,然后該對象下面定義了 deployments 對象的 CRUD 操作,比如我們調(diào)用的 List() 函數(shù):
// staging/src/k8s.io/client-go/kubernetes/typed/apps/v1/deployment.gofunc (c *deployments) List(opts metav1.ListOptions) (result *v1.DeploymentList, err error) {var timeout time.Durationif opts.TimeoutSeconds != nil {timeout = time.Duration(*opts.TimeoutSeconds) * time.Second}result = &v1.DeploymentList{}err = c.client.Get().Namespace(c.ns).Resource("deployments").VersionedParams(&opts, scheme.ParameterCodec).Timeout(timeout).Do().Into(result)return}
從上面代碼可以看出最終是通過 c.client 去發(fā)起的請求,也就是局部的 restClient 初始化的函數(shù)中通過 rest.RESTClientFor(&config) 創(chuàng)建的對象,也就是將 rest.Config 對象轉(zhuǎn)換成一個 Restful 的 Client 對象用于網(wǎng)絡(luò)操作:
// staging/src/k8s.io/client-go/rest/config.go// RESTClientFor 返回一個滿足客戶端 Config 對象上的屬性的 RESTClient 對象。// 注意在初始化客戶端的時候,RESTClient 可能需要一些可選的屬性。func RESTClientFor(config *Config) (*RESTClient, error) {if config.GroupVersion == nil {return nil, fmt.Errorf("GroupVersion is required when initializing a RESTClient")}if config.NegotiatedSerializer == nil {return nil, fmt.Errorf("NegotiatedSerializer is required when initializing a RESTClient")}qps := config.QPSif config.QPS == 0.0 {qps = DefaultQPS}burst := config.Burstif config.Burst == 0 {burst = DefaultBurst}baseURL, versionedAPIPath, err := defaultServerUrlFor(config)if err != nil {return nil, err}transport, err := TransportFor(config)if err != nil {return nil, err}// 初始化一個 HTTP Client 對象var httpClient *http.Clientif transport != http.DefaultTransport {httpClient = &http.Client{Transport: transport}if config.Timeout > 0 {httpClient.Timeout = config.Timeout}}return NewRESTClient(baseURL, versionedAPIPath, config.ContentConfig, qps, burst, config.RateLimiter, httpClient)}
到這里我們就知道了 Clientset 是基于 RESTClient 的,RESTClient 是底層的用于網(wǎng)絡(luò)請求的對象,可以直接通過 RESTClient 提供的 RESTful 方法如 Get()、Put()、Post()、Delete() 等和 APIServer 進行交互:
同時支持 JSON 和 protobuf 兩種序列化方式
支持所有原生資源
但實際上除了常用的 CRUD 操作之外,我們還可以進行 Watch 操作,可以監(jiān)聽資源對象的增、刪、改、查操作,這樣我們就可以根據(jù)自己的業(yè)務(wù)邏輯去處理這些數(shù)據(jù)了,但是實際上也并不建議這樣使用,因為往往由于集群中的資源較多,我們需要自己在客戶端去維護一套緩存,而這個維護成本也是非常大的,為此 client-go 也提供了自己的實現(xiàn)機制,那就是 Informers。Informers 是這個事件接口和帶索引查找功能的內(nèi)存緩存的組合,這樣也是目前最常用的用法。Informers 第一次被調(diào)用的時候會首先在客戶端調(diào)用 List 來獲取全量的對象集合,然后通過 Watch 來獲取增量的對象更新緩存,這個我們后續(xù)在講解。
K8S進階訓(xùn)練營,點擊下方圖片了解詳情

