天天看點

使用client-go實作對K8S叢集資源得CRUD操作及源碼分析

1、client-go 介紹

client-go 是一種能夠與 Kubernetes 叢集通信的用戶端,通過它可以對 Kubernetes 叢集中各資源類型進行 CRUD 操作,它有三大 client 類,分别為:Clientset、DynamicClient、RESTClient。通過它,我們可以很友善的對 Kubernetes 叢集 API 進行自定義開發,來滿足個性化需求。

2、client-go 安裝

client-go 安裝很簡單,前提是本機已經安裝并配置好了 Go 環境,安裝之前,我們需要先檢視下其版本針對 k8s 版本 相容性清單,針對自己本機安裝的 k8s 版本選擇對應的 client-go 版本,當然也可以預設選擇最新版本,來相容所有。

client-go 安裝方式有多種,比如 go get、Godep、Glide 方式。如果我們本地沒有安裝 Godep 和 Glide 依賴管理工具的話,可以使用最簡單的 go get 下載下傳安裝。

$ go get k8s.io/client-go/...
           

執行該指令将會自動将 k8s.io/client-go 下載下傳到本機 $GOPATH,預設下載下傳的源碼中隻包含了大部分依賴,并将其放在 k8s.io/client-go/vendor 路徑,但是如果想成功運作的話,還需要另外兩個依賴庫 k8s.io/client-go/vendor 和 glog,是以還需要接着執行如下指令。

$ go get -u k8s.io/apimachinery/...
           

說明一下,為什麼要使用 -u 參數來拉取最新的該依賴庫呢?那是因為最新的 client-go 庫隻能保證跟最新的 apimachinery 庫一起運作。其他幾種安裝方式,可以參考 這裡 來執行,這裡就不在示範了。

3、在 k8s 叢集外運作用戶端操作資源示例

好了,本機 client-go 已經安裝完畢,而且本機 Minikube 運作的 k8s 叢集也已經運作起來了,接下來,我們簡單示範下如果通過 client-go 來在 k8s 叢集外運作用戶端來操作各資源類型。

建立 main.go 檔案如下:

package main

import (
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"time"

	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd"
)

func main() {
	// 配置 k8s 叢集外 kubeconfig 配置檔案,預設位置 $HOME/.kube/config
	var kubeconfig *string
	if 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()

	//在 kubeconfig 中使用目前上下文環境,config 擷取支援 url 和 path 方式
	config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
	if err != nil {
		panic(err.Error())
	}

	// 根據指定的 config 建立一個新的 clientset
	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		panic(err.Error())
	}
	for {
		// 通過實作 clientset 的 CoreV1Interface 接口清單中的 PodsGetter 接口方法 Pods(namespace string) 傳回 PodInterface
		// PodInterface 接口擁有操作 Pod 資源的方法,例如 Create、Update、Get、List 等方法
		// 注意:Pods() 方法中 namespace 不指定則擷取 Cluster 所有 Pod 清單
		pods, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{})
		if err != nil {
			panic(err.Error())
		}
		fmt.Printf("There are %d pods in the k8s cluster\n", len(pods.Items))

		// 擷取指定 namespace 中的 Pod 清單資訊
		namespace := "kubeless"
		pods, err = clientset.CoreV1().Pods(namespace).List(metav1.ListOptions{})
		if err != nil {
			panic(err)
		}
		fmt.Printf("\nThere are %d pods in namespaces %s\n", len(pods.Items), namespace)
		for _, pod := range pods.Items {
			fmt.Printf("Name: %s, Status: %s, CreateTime: %s\n", pod.ObjectMeta.Name, pod.Status.Phase, pod.ObjectMeta.CreationTimestamp)
		}

		// 擷取指定 namespaces 和 podName 的詳細資訊,使用 error handle 方式處理錯誤資訊
		namespace = "kubeless"
		podName := "get-java-5ff45cd65d-2frkx"
		pod, err := clientset.CoreV1().Pods(namespace).Get(podName, metav1.GetOptions{})
		if errors.IsNotFound(err) {
			fmt.Printf("Pod %s in namespace %s not found\n", podName, namespace)
		} else if statusError, isStatus := err.(*errors.StatusError); isStatus {
			fmt.Printf("Error getting pod %s in namespace %s: %v\n",
				podName, namespace, statusError.ErrStatus.Message)
		} else if err != nil {
			panic(err.Error())
		} else {
			fmt.Printf("\nFound pod %s in namespace %s\n", podName, namespace)
			maps := map[string]interface{}{
				"Name":        pod.ObjectMeta.Name,
				"Namespaces":  pod.ObjectMeta.Namespace,
				"NodeName":    pod.Spec.NodeName,
				"Annotations": pod.ObjectMeta.Annotations,
				"Labels":      pod.ObjectMeta.Labels,
				"SelfLink":    pod.ObjectMeta.SelfLink,
				"Uid":         pod.ObjectMeta.UID,
				"Status":      pod.Status.Phase,
				"IP":          pod.Status.PodIP,
				"Image":       pod.Spec.Containers[0].Image,
			}
			prettyPrint(maps)
		}

		time.Sleep(10 * time.Second)
	}
}

func prettyPrint(maps map[string]interface{}) {
	lens := 0
	for k, _ := range maps {
		if lens <= len(k) {
			lens = len(k)
		}
	}
	for key, values := range maps {
		spaces := lens - len(key)
		v := ""
		for i := 0; i < spaces; i++ {
			v += " "
		}
		fmt.Printf("%s: %s%v\n", key, v, values)
	}
}

func homeDir() string {
	if h := os.Getenv("HOME"); h != "" {
		return h
	}
	return os.Getenv("USERPROFILE") // windows
}
           

簡單說明一下,該示例主要示範如何在 k8s 叢集外操作 Pod 資源類型,包括擷取叢集所有 Pod 清單數量,擷取指定 Namespace 中的 Pod 清單資訊,擷取指定 Namespace 和 Pod Name 的詳細資訊。代碼裡面關鍵步驟簡單添加了一些注釋,詳細的代碼調用過程,下邊 client-go 源碼分析裡面會講到。這裡要提一下的是,這種方式擷取 k8s 叢集配置的方式為通過讀取 kubeconfig 配置檔案,預設位置 $HOME/.kube/config,來跟 k8s 建立連接配接,進而來操作其各個資源類型。運作一下,看下效果如何。

$ go run main.go
There are 30 pods in the k8s cluster

There are 3 pods in namespaces kubeless
Name: get-java-5ff45cd65d-2frkx, Status: Running, CreateTime: 2018-08-23 10:36:37 +0800 CST
Name: kubeless-controller-manager-5d7894857d-h4hr9, Status: Running, CreateTime: 2018-08-22 17:01:59 +0800 CST
Name: ui-5b87d84d96-vkmz7, Status: Running, CreateTime: 2018-08-23 15:13:25 +0800 CST

Found pod get-java-5ff45cd65d-2frkx in namespace kubeless
Status:      Running
Image:       kubeless/[email protected]:debf9502545f4c0e955eb60fabb45748c5d98ed9365c4a508c07f38fc7fefaac
Namespaces:  kubeless
NodeName:    minikube
Uid:         5bd5cfce-a67d-11e8-862b-080027c7f5ce
SelfLink:    /api/v1/namespaces/kubeless/pods/get-java-5ff45cd65d-2frkx
IP:          172.17.0.5
Name:        get-java-5ff45cd65d-2frkx
Annotations: map[prometheus.io/path:/metrics prometheus.io/port:8080 prometheus.io/scrape:true]
Labels:      map[created-by:kubeless function:get-java pod-template-hash:1990178218]
           

可以成功擷取到資源資訊,我們可以通過 kubectl 用戶端工具來驗證一下吧!

# 擷取 kubeless 指令空間下所有 pod
$ kubectl get pods -n kubeless
NAME                                           READY     STATUS    RESTARTS   AGE
get-java-5ff45cd65d-2frkx                      1/1       Running   2          98d
kubeless-controller-manager-5d7894857d-h4hr9   3/3       Running   18         98d
ui-5b87d84d96-vkmz7                            2/2       Running   5          97d

# 擷取 kubeless 指令空間下名稱為 get-java-5ff45cd65d-2frkx 的 Pod 的資訊
$ kubectl get pod/get-java-5ff45cd65d-2frkx -o yaml -n kubeless
apiVersion: v1
kind: Pod
metadata:
  annotations:
    prometheus.io/path: /metrics
    prometheus.io/port: "8080"
    prometheus.io/scrape: "true"
  creationTimestamp: 2018-08-23T02:36:37Z
  generateName: get-java-5ff45cd65d-
  labels:
    created-by: kubeless
    function: get-java
    pod-template-hash: "1990178218"
  name: get-java-5ff45cd65d-2frkx
  namespace: kubeless
  ownerReferences:
  - apiVersion: extensions/v1beta1
    blockOwnerDeletion: true
    controller: true
    kind: ReplicaSet
    name: get-java-5ff45cd65d
    uid: 5bd1e6c3-a67d-11e8-862b-080027c7f5ce
  resourceVersion: "1284918"
  selfLink: /api/v1/namespaces/kubeless/pods/get-java-5ff45cd65d-2frkx
  uid: 5bd5cfce-a67d-11e8-862b-080027c7f5ce
spec:
  containers:
  - env:
    - name: FUNC_HANDLER
      value: foo
      ......
           

可以看到,兩種方式擷取的資訊是一緻的。

4、在 k8s 叢集内運作用戶端操作資源示例

接下來,我們示範下如何在 k8s 叢集内運作用戶端操作資源類型。既然是在 k8s 叢集内運作,那麼就需要将編寫的代碼放到鏡像内,然後在 k8s 叢集内以 Pod 方式運作該鏡像容器,來驗證一下了。建立 main.go代碼如下:

package main

import (
	"fmt"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
)

func main() {
	// 通過叢集内部配置建立 k8s 配置資訊,通過 KUBERNETES_SERVICE_HOST 和 KUBERNETES_SERVICE_PORT 環境變量方式擷取
	// 若叢集使用 TLS 認證方式,則預設讀取叢集内部 tokenFile 和 CAFile
	// tokenFile  = "/var/run/secrets/kubernetes.io/serviceaccount/token"
	// rootCAFile = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
	config, err := rest.InClusterConfig()
	if err != nil {
		panic(err.Error())
	}

	// 根據指定的 config 建立一個新的 clientset
	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		panic(err.Error())
	}
	for {
		// 通過實作 clientset 的 CoreV1Interface 接口清單中的 PodsGetter 接口方法 Pods(namespace string) 傳回 PodInterface
		// PodInterface 接口擁有操作 Pod 資源的方法,例如 Create、Update、Get、List 等方法
		// 注意:Pods() 方法中 namespace 不指定則擷取 Cluster 所有 Pod 清單
		pods, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{})
		if err != nil {
			panic(err.Error())
		}
		fmt.Printf("There are %d pods in the k8s cluster\n", len(pods.Items))

		// 擷取指定 namespace 中的 Pod 清單資訊
		namespce := "kubeless"
		pods, err = clientset.CoreV1().Pods(namespce).List(metav1.ListOptions{})
		if err != nil {
			panic(err)
		}
		fmt.Printf("\nThere are %d pods in namespaces %s\n", len(pods.Items), namespce)
		for _, pod := range pods.Items {
			fmt.Printf("Name: %s, Status: %s, CreateTime: %s\n", pod.ObjectMeta.Name, pod.Status.Phase, pod.ObjectMeta.CreationTimestamp)
		}

		// 擷取所有的 Namespaces 清單資訊
		ns, err := clientset.CoreV1().Namespaces().List(metav1.ListOptions{})
		if err != nil {
			panic(err)
		}
		nss := ns.Items
		fmt.Printf("\nThere are %d namespaces in cluster\n", len(nss))
		for _, ns := range nss {
			fmt.Printf("Name: %s, Status: %s, CreateTime: %s\n", ns.ObjectMeta.Name, ns.Status.Phase, ns.CreationTimestamp)
		}

		time.Sleep(10 * time.Second)
	}
}
           

簡單說下,該示例主要示範如何在 k8s 叢集内操作 Pod 和 Namespaces 資源類型,包括擷取叢集所有 Pod 清單數量,擷取指定 Namespace 中的 Pod 清單資訊,擷取叢集内所有 Namespace 清單資訊。這裡,該方式擷取 k8s 叢集配置的方式跟上邊方式不同,它通過叢集内部建立的 k8s 配置資訊,通過 KUBERNETES_SERVICE_HOST 和 KUBERNETES_SERVICE_PORT 環境變量方式擷取,來跟 k8s 建立連接配接,進而來操作其各個資源類型。如果 k8s 開啟了 TLS 認證方式,那麼預設讀取叢集内部指定位置的 tokenFile 和 CAFile。

那麼,編譯一下,看下是否通過。

$ cd <code_path>
$ GOOS=linux go build -o ./app .
           

接下來,在同級目錄建立一個 Dockerfile 檔案如下:

FROM debian
COPY ./app /app
ENTRYPOINT /app
           

說明一下,這裡 app 為上邊代碼編譯後可以直接運作的二進制檔案,将該檔案添加到鏡像内,最後運作該檔案即可。接下來,我們需要 Build 鏡像并上傳到鏡像倉庫,來提供拉取。注意:這裡因為我們本地使用 Minikube 運作 k8s 叢集,那麼可以不需要上傳鏡像到倉庫,直接建構到本地,然後在啟動該鏡像時,指定拉取政策為 --image-pull-policy=Never,即可從本地直接使用鏡像。

$ eval $(minikube docker-env)
$ docker build -t client-go/in-cluster:1.0 .
Sending build context to Docker daemon  32.76MB
Step 1/3 : FROM debian
 ---> be2868bebaba
Step 2/3 : COPY ./app /app
 ---> 0f424ab04f5c
Step 3/3 : ENTRYPOINT /app
 ---> Running in ce12b6e4d7fc
Removing intermediate container ce12b6e4d7fc
 ---> c6ce75b50123
Successfully built c6ce75b50123
Successfully tagged client-go/in-cluster:1.0

$ docker images|head -1;docker images|grep client-go
REPOSITORY                TAG         IMAGE ID            CREATED              SIZE
client-go/in-cluster      1.0         c6ce75b50123        About a minute ago   133MB
           

因為本機 k8s 預設開啟了 RBAC 認證的,是以需要建立一個 clusterrolebinding 來賦予 default 賬戶 view 權限。

$ kubectl create clusterrolebinding default-view --clusterrole=view --serviceaccount=default:default
clusterrolebinding.rbac.authorization.k8s.io "default-view" created
           

最後,在 Pod 中運作該鏡像即可,這裡可以使用 yaml 方式來建立,簡單些直接使用 kubectl run 指令來建立。

$ kubectl run --rm -i client-go-in-cluster-demo --image=client-go/in-cluster:1.0 --image-pull-policy=Never
If you don't see a command prompt, try pressing enter.
There are 30 pods in the k8s cluster

There are 3 pods in namespaces kubeless
Name: get-java-5ff45cd65d-2frkx, Status: Running, CreateTime: 2018-08-23 02:36:37 +0000 UTC
Name: kubeless-controller-manager-5d7894857d-h4hr9, Status: Running, CreateTime: 2018-08-22 09:01:59 +0000 UTC
Name: ui-5b87d84d96-vkmz7, Status: Running, CreateTime: 2018-08-23 07:13:25 +0000 UTC

There are 8 namespaces in cluster
Name: default, Status: Active, CreateTime: 2018-08-07 09:17:15 +0000 UTC
Name: fission, Status: Active, CreateTime: 2018-09-06 08:19:32 +0000 UTC
Name: fission-builder, Status: Active, CreateTime: 2018-09-06 09:21:20 +0000 UTC
Name: fission-function, Status: Active, CreateTime: 2018-09-06 09:21:20 +0000 UTC
Name: kube-public, Status: Active, CreateTime: 2018-08-07 09:17:19 +0000 UTC
Name: kube-system, Status: Active, CreateTime: 2018-08-07 09:17:15 +0000 UTC
Name: kubeless, Status: Active, CreateTime: 2018-08-22 09:01:27 +0000 UTC
Name: monitoring, Status: Active, CreateTime: 2018-08-09 02:43:55 +0000 UTC
           

運作正常,簡單驗證一下吧!

$ kubectl get pods -n kubeless
NAME                                           READY     STATUS    RESTARTS   AGE
get-java-5ff45cd65d-2frkx                      1/1       Running   2          98d
kubeless-controller-manager-5d7894857d-h4hr9   3/3       Running   18         98d
ui-5b87d84d96-vkmz7                            2/2       Running   5          98d

$ kubectl get namespaces
NAME               STATUS    AGE
default            Active    113d
fission            Active    84d
fission-builder    Active    83d
fission-function   Active    83d
kube-public        Active    113d
kube-system        Active    113d
kubeless           Active    98d
monitoring         Active    112d
           

5、k8s 各資源對象 CRUD 操作示例

上邊示範了,在 k8s 叢集内外運作用戶端操作資源類型,但是僅僅是 Read 相關讀取操作,接下來簡單示範下如何進行 Create、Update、Delete 操作。建立 main.go 檔案如下:

package main

import (
	"flag"
	"fmt"
	apiv1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/util/homedir"
	"path/filepath"
)

func main() {
	// 配置 k8s 叢集外 kubeconfig 配置檔案,預設位置 $HOME/.kube/config
	var kubeconfig *string
	if home := homedir.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()

	//在 kubeconfig 中使用目前上下文環境,config 擷取支援 url 和 path 方式
	config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
	if err != nil {
		panic(err)
	}

	// 根據指定的 config 建立一個新的 clientset
	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		panic(err)
	}

	// 通過實作 clientset 的 CoreV1Interface 接口清單中的 NamespacesGetter 接口方法 Namespaces 傳回 NamespaceInterface
	// NamespaceInterface 接口擁有操作 Namespace 資源的方法,例如 Create、Update、Get、List 等方法
	name := "client-go-test"
	namespacesClient := clientset.CoreV1().Namespaces()
	namespace := &apiv1.Namespace{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
		Status: apiv1.NamespaceStatus{
			Phase: apiv1.NamespaceActive,
		},
	}

	// 建立一個新的 Namespaces
	fmt.Println("Creating Namespaces...")
	result, err := namespacesClient.Create(namespace)
	if err != nil {
		panic(err)
	}
	fmt.Printf("Created Namespaces %s on %s\n", result.ObjectMeta.Name, result.ObjectMeta.CreationTimestamp)

	// 擷取指定名稱的 Namespaces 資訊
	fmt.Println("Getting Namespaces...")
	result, err = namespacesClient.Get(name, metav1.GetOptions{})
	if err != nil {
		panic(err)
	}
	fmt.Printf("Name: %s, Status: %s, selfLink: %s, uid: %s\n",
		result.ObjectMeta.Name, result.Status.Phase, result.ObjectMeta.SelfLink, result.ObjectMeta.UID)

	// 删除指定名稱的 Namespaces 資訊
	fmt.Println("Deleting Namespaces...")
	deletePolicy := metav1.DeletePropagationForeground
	if err := namespacesClient.Delete(name, &metav1.DeleteOptions{
		PropagationPolicy: &deletePolicy,
	}); err != nil {
		panic(err)
	}
	fmt.Printf("Deleted Namespaces %s\n", name)
}
           

該示例主要示範如何在 k8s 叢集内操作 Namespace 資源類型,包括建立一個新的 Namespace、擷取該 Namespace 的詳細資訊,删除該 Namespace。采用 k8s 叢集外運作用戶端操作資源方式來操作。運作結果如下:

$ go run main.go 
Creating Namespaces...
Created Namespaces client-go-test on 2018-11-29 17:38:25 +0800 CST
Getting Namespaces...
Name: client-go-test, Status: Active, selfLink: /api/v1/namespaces/client-go-test, uid: 84c55ca3-f3ba-11e8-9302-080027c7f5ce
Deleting Namespaces...
Deleted Namespaces client-go-test
           

這裡就不在驗證了,因為我們建立完了後又删除了,如果想驗證,可以按照官方的方法,鍵盤輸入來繼續執行,這樣就可以在每一步等待輸入的時候,去做驗證了。這裡隻是簡單的拿 Namespace 示範一下,使用 client-go 可以操作 k8s 各種資源類型,方法都大同小異,這裡就不在示範了。

6、client-go 源碼分析

最後,我們以 在 k8s 叢集外運作用戶端操作資源示例中的代碼為例,簡單分析一下 client-go 的底層執行過程,這裡涉及到幾個關鍵的對象:kubeconfig、restclient.Config、Clientset、CoreV1Interface、Pod 等。

6.1、kubeconfig

var kubeconfig *string
	if 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()
           

使用 client-go 在 k8s 叢集外操作資源,首先需要通過擷取 kubeconfig 配置檔案,來建立連接配接。預設路徑為 $HOME/.kube/config。config 檔案包含目前 kubernetes 叢集配置資訊,大緻如下:

$ cat $HOME/.kube/config
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: <token>
    server: https://127.0.0.1:8443
  name: 127-0-0-1:8443
contexts:
- context:
    cluster: 127-0-0-1:8443
    namespace: default
    user: system:admin/127-0-0-1:8443
  name: default/127-0-0-1:8443/system:admin
current-context: minikube
kind: Config
preferences: {}
users:
- name: system:admin/127-0-0-1:8443
  user:
    client-certificate-data: <token>
    client-key-data: <token>
           

6.2、restclient.Config

config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
           

接着在 kubeconfig 中使用目前上下文環境,config 擷取支援 url 和 path 方式,通過 BuildConfigFromFlags() 函數擷取 restclient.Config 對象,用來下邊根據該 config 對象建立 client 集合。

func BuildConfigFromFlags(masterUrl, kubeconfigPath string) (*restclient.Config, error) {
	if kubeconfigPath == "" && masterUrl == "" {
		klog.Warningf("Neither --kubeconfig nor --master was specified.  Using the inClusterConfig.  This might not work.")
		kubeconfig, err := restclient.InClusterConfig()
		if err == nil {
			return kubeconfig, nil
		}
		klog.Warning("error creating inClusterConfig, falling back to default config: ", err)
	}
	return NewNonInteractiveDeferredLoadingClientConfig(
		&ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},
		&ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}}).ClientConfig()
}
           

6.3、Clientset

clientset, err := kubernetes.NewForConfig(config)
           

接着根據擷取的 config 來建立一個 clientset 對象。通過調用 NewForConfig 函數建立 clientset 對象。NewForConfig函數具體實作就是初始化 clientset 中的每個 client,基本涵蓋了 k8s 内各種類型。

// NewForConfig creates a new Clientset for the given config.
func NewForConfig(c *rest.Config) (*Clientset, error) {
	configShallowCopy := *c
	if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 {
		configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst)
	}
	var cs Clientset
	var err error
	cs.admissionregistrationV1alpha1, err = admissionregistrationv1alpha1.NewForConfig(&configShallowCopy)
	if err != nil {
		return nil, err
	}
	......	
	cs.appsV1, err = appsv1.NewForConfig(&configShallowCopy)
	if err != nil {
		return nil, err
	}
	......
	cs.coreV1, err = corev1.NewForConfig(&configShallowCopy)
	if err != nil {
		return nil, err
	}
	......
	return &cs, nil
}
           

clientset 結構體定義如下:

type Clientset struct {
	*discovery.DiscoveryClient
	admissionregistrationV1alpha1 *admissionregistrationv1alpha1.AdmissionregistrationV1alpha1Client
	admissionregistrationV1beta1  *admissionregistrationv1beta1.AdmissionregistrationV1beta1Client
	appsV1beta1                   *appsv1beta1.AppsV1beta1Client
	appsV1beta2                   *appsv1beta2.AppsV1beta2Client
	appsV1                        *appsv1.AppsV1Client
	auditregistrationV1alpha1     *auditregistrationv1alpha1.AuditregistrationV1alpha1Client
	authenticationV1              *authenticationv1.AuthenticationV1Client
	authenticationV1beta1         *authenticationv1beta1.AuthenticationV1beta1Client
	authorizationV1               *authorizationv1.AuthorizationV1Client
	authorizationV1beta1          *authorizationv1beta1.AuthorizationV1beta1Client
	autoscalingV1                 *autoscalingv1.AutoscalingV1Client
	autoscalingV2beta1            *autoscalingv2beta1.AutoscalingV2beta1Client
	autoscalingV2beta2            *autoscalingv2beta2.AutoscalingV2beta2Client
	batchV1                       *batchv1.BatchV1Client
	batchV1beta1                  *batchv1beta1.BatchV1beta1Client
	batchV2alpha1                 *batchv2alpha1.BatchV2alpha1Client
	certificatesV1beta1           *certificatesv1beta1.CertificatesV1beta1Client
	coordinationV1beta1           *coordinationv1beta1.CoordinationV1beta1Client
	coreV1                        *corev1.CoreV1Client
	eventsV1beta1                 *eventsv1beta1.EventsV1beta1Client
	extensionsV1beta1             *extensionsv1beta1.ExtensionsV1beta1Client
	networkingV1                  *networkingv1.NetworkingV1Client
	policyV1beta1                 *policyv1beta1.PolicyV1beta1Client
	rbacV1                        *rbacv1.RbacV1Client
	rbacV1beta1                   *rbacv1beta1.RbacV1beta1Client
	rbacV1alpha1                  *rbacv1alpha1.RbacV1alpha1Client
	schedulingV1alpha1            *schedulingv1alpha1.SchedulingV1alpha1Client
	schedulingV1beta1             *schedulingv1beta1.SchedulingV1beta1Client
	settingsV1alpha1              *settingsv1alpha1.SettingsV1alpha1Client
	storageV1beta1                *storagev1beta1.StorageV1beta1Client
	storageV1                     *storagev1.StorageV1Client
	storageV1alpha1               *storagev1alpha1.StorageV1alpha1Client
}
           

6.4、CoreV1Interface

pods, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{})
           

接着通過實作 clientset 的 CoreV1Interface 接口清單中的 PodsGetter 接口方法 Pods(namespace string) 傳回 PodInterface。從上邊可以看到 clientset 包含很多種 client,我們來使用 CoreV1Client 來實作 CoreV1Interface 接口中各資源類型的 Getter 接口。因為這裡示範的是操作 Pod,那麼就需要實作 PodsGette 接口方法。

CoreV1Interface 接口定義如下:

type CoreV1Interface interface {
	RESTClient() rest.Interface
	ComponentStatusesGetter
	ConfigMapsGetter
	EndpointsGetter
	EventsGetter
	LimitRangesGetter
	NamespacesGetter
	NodesGetter
	PersistentVolumesGetter
	PersistentVolumeClaimsGetter
	PodsGetter
	PodTemplatesGetter
	ReplicationControllersGetter
	ResourceQuotasGetter
	SecretsGetter
	ServicesGetter
	ServiceAccountsGetter
}
           

PodsGetter 及 PodInterface 接口定義如下:

type PodsGetter interface {
	Pods(namespace string) PodInterface
}

// PodInterface has methods to work with Pod resources.
type PodInterface interface {
	Create(*v1.Pod) (*v1.Pod, error)
	Update(*v1.Pod) (*v1.Pod, error)
	UpdateStatus(*v1.Pod) (*v1.Pod, error)
	Delete(name string, options *metav1.DeleteOptions) error
	DeleteCollection(options *metav1.DeleteOptions, listOptions metav1.ListOptions) error
	Get(name string, options metav1.GetOptions) (*v1.Pod, error)
	List(opts metav1.ListOptions) (*v1.PodList, error)
	Watch(opts metav1.ListOptions) (watch.Interface, error)
	Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.Pod, err error)
	PodExpansion
}
           

從 PodsInterface 接口定義清單可以看到,裡面包含了 CRUD 各種操作,通過這些方法,就可以操作 Pod 資源對象了。例如,上邊我們調用了 List(opts metav1.ListOptions) 方法,傳回 PodList 對象,那麼看下 PodList 結構體如何定義的。

// PodList is a list of Pods.
type PodList struct {
	metav1.TypeMeta `json:",inline"`
	// Standard list metadata.
	metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
	// List of pods.
	Items []Pod `json:"items" protobuf:"bytes,2,rep,name=items"`
}
           

最後,我們就可以通過簡單的執行 len(pods.Items) 方法擷取叢集内所有 Pod 的數量了。

6.5、Pod

// 擷取指定 namespaces 和 podName 的詳細資訊,使用 error handle 方式處理錯誤資訊
	namespace = "kubeless"
	podName := "get-java-5ff45cd65d-2frkx"
	pod, err := clientset.CoreV1().Pods(namespace).Get(podName, metav1.GetOptions{})
	if errors.IsNotFound(err) {
		fmt.Printf("Pod %s in namespace %s not found\n", podName, namespace)
	} else if statusError, isStatus := err.(*errors.StatusError); isStatus {
		fmt.Printf("Error getting pod %s in namespace %s: %v\n",
			podName, namespace, statusError.ErrStatus.Message)
	} else if err != nil {
		panic(err.Error())
	} else {
		fmt.Printf("\nFound pod %s in namespace %s\n", podName, namespace)
		maps := map[string]interface{}{
			"Name":        pod.ObjectMeta.Name,
			"Namespaces":  pod.ObjectMeta.Namespace,
			"NodeName":    pod.Spec.NodeName,
			"Annotations": pod.ObjectMeta.Annotations,
			"Labels":      pod.ObjectMeta.Labels,
			"SelfLink":    pod.ObjectMeta.SelfLink,
			"Uid":         pod.ObjectMeta.UID,
			"Status":      pod.Status.Phase,
			"IP":          pod.Status.PodIP,
			"Image":       pod.Spec.Containers[0].Image,
		}
		prettyPrint(maps)
	}
           

第三個示例是擷取指定 Namespace 中指定名稱的 Pod 清單資訊,操作方法跟上述一緻,隻是最後調用了 Get(name string, options metav1.GetOptions) 方法來擷取該 Pod 的一系列資訊。Pod 有那些資訊可以擷取呢?看下 Pod 的結構體定義。

type Pod struct {
	metav1.TypeMeta `json:",inline"`
	// Standard object's metadata.
	metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`

	// Specification of the desired behavior of the pod.
	Spec PodSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`

	// Most recently observed status of the pod.
	Status PodStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}
           

Pod 資訊又包含了三大類型:metav1.ObjectMeta、Spec、Status,每個類型又包含了不同的屬性值,像 Name、Namespace、Labels、Annotations 等對象源資訊屬于 ObjectMeta 這一類,像 Volumes、Containers、Hostname、DNSConfig、RestartPolicy 等詳情資訊屬于 Spec 這一類,像 Phase、 HostIP、PodIP、InitContainerStatuses 等狀态資訊屬于 Status 這一類,我們在取屬性資訊時,需要找到與之比對的類型才行,可以分别參考這三大類的結構體定義代碼,這裡就不在貼出來了。

原文連結

繼續閱讀