歡迎訪問我的GitHub

https://github.com/zq2599/blog_demos

內容:所有原創文章分類彙總及配套原始碼,涉及Java、Docker、Kubernetes、DevOPS等;

系列文章連結

  1. client-go實戰之一:準備工作
  2. client-go實戰之二:RESTClient
  3. client-go實戰之三:Clientset
  4. client-go實戰之四:dynamicClient
  5. client-go實戰之五:DiscoveryClient

關於Clientset

  • 本篇是《client-go實戰》系列的第三篇,前文學習了最基礎的客戶端Restclient,儘管咱們實戰的需求很簡單(獲取指定namespace下所有pod的資訊),但還是寫了不少程式碼,如下圖,各種設定太麻煩,例如api的path、Group、Version、返回的資料結構、編解碼工具等:

  • 如果業務程式碼中,需要操作kubernetes資源的程式碼都寫成上圖的樣子,相信您是難以忍受的,應該會做一些封裝以簡化程式碼,不過client-go已經給出了簡化版客戶端,就省去了咱們自己動手的麻煩,也就是本文的主題:Clientset

本篇概覽

  • 本篇目標是學習如何使用Clientset,因此毫無難度,咱們先來個原始碼速讀,快速搞清楚Clientset到底是啥,然後確認需求,最後快速編碼和驗證就完事兒了;

原始碼速度

  • 之所以是速讀而非精度,是因為Clientset內容簡單容易理解,快速掌握其原理即可用於實戰;
  • Clientset原始碼閱讀的切入點就是其名字中的set,這是個集合,裡面有很多東西,看一下Clientset資料結構的原始碼(限於篇幅只展示了一部分):
type Clientset struct {
*discovery.DiscoveryClient
admissionregistrationV1 *admissionregistrationv1.AdmissionregistrationV1Client
admissionregistrationV1beta1 *admissionregistrationv1beta1.AdmissionregistrationV1beta1Client
internalV1alpha1 *internalv1alpha1.InternalV1alpha1Client
appsV1 *appsv1.AppsV1Client
appsV1beta1 *appsv1beta1.AppsV1beta1Client
appsV1beta2 *appsv1beta2.AppsV1beta2Client
authenticationV1 *authenticationv1.AuthenticationV1Client
...
  • 如果您還沒有理解上述原始碼的含義,再請看下圖,左邊是kubernetes的Group、Version資訊,右邊依舊是Clientset資料結構的原始碼,通過箭頭可見:kubernetes的Group和Version的每個組合,都對應Clientset資料結構的一個欄位:

  • Clientset果然名副其實,是所有Group和Version組合物件的集合,不過Group和Version組合物件到底是啥呢?以appsV1欄位為例,去看看其型別appsv1.AppsV1Client,如下圖,AppsV1Client只有一欄位,就是咱們熟悉的restClient,所以RESTClient是Clientset的基礎,這話沒毛病,另外注意紅框2中的Deployments方法,返回的是DeploymentInterface介面實現:

  • 順藤摸瓜去看DeploymentInterface,開啟deployment.go檔案後真相大白,介面定義的和實現一目瞭然:

  • 挑一個介面實現的程式碼看看,就選新建deployment的方法吧,如下,和我們使用RESTClient編碼差不多:
func (c *deployments) Create(ctx context.Context, deployment *v1.Deployment, opts metav1.CreateOptions) (result *v1.Deployment, err error) {
result = &v1.Deployment{}
err = c.client.Post().
Namespace(c.ns).
Resource("deployments").
VersionedParams(&opts, scheme.ParameterCodec).
Body(deployment).
Do(ctx).
Into(result)
return
}
  • 至此,您對Clientset應該心裡有數了:其實就是把我們使用RESTClient操作資源的程式碼按照Group和Version分類再封裝而已,這不像技術活,更像體力活---所以,這種體力活是誰做的呢?如下圖紅框所示,原始碼中已經註明這些程式碼是工具client-gen自動生成的:

  • 至此,Clientset的原始碼速度就算完成了,咱們已經知道了Clientset的內幕,接下來開始嘗試使用它;

需求確認

  • 本次編碼實戰的需求如下:
  • 寫一段程式碼,檢查使用者輸入的operate引數,該引數預設是create,也可以接受clean;
  • 如果operate引數等於create,就執行以下操作:
  1. 新建名為test-clientset的namespace
  2. 新建一個deployment,namespace為test-clientset,映象用tomcat,副本數為2
  3. 新建一個service,namespace為test-clientset,型別是NodePort
  • 如果operate引數等於clean,就刪除create操作中建立的service、deployment、namespace等資源:
  • 以上需求使用Clientset客戶端實現,完成後咱們用瀏覽器訪問來驗證tomcat是否正常;

原始碼下載

名稱 連結 備註
專案主頁 https://github.com/zq2599/blog_demos 該專案在GitHub上的主頁
git倉庫地址(https) https://github.com/zq2599/blog_demos.git 該專案原始碼的倉庫地址,https協議
git倉庫地址(ssh) [email protected]:zq2599/blog_demos.git 該專案原始碼的倉庫地址,ssh協議
  • 這個git專案中有多個資料夾,client-go相關的應用在client-go-tutorials資料夾下,如下圖紅框所示:

  • client-go-tutorials資料夾下有多個子資料夾,本篇對應的原始碼在clientsetdemo目錄下,如下圖紅框所示:

編碼

  • 新建資料夾restclientdemo,在裡面執行以下命令,新建module:
go mod init clientsetdemo
  • 新增k8s.io/api和k8s.io/client-go這兩個依賴,注意版本要匹配kubernetes環境:
go get k8s.io/[email protected]
go get k8s.io/[email protected]
  • 新建main.go,內容如下,已經都添加了詳細的註釋,就不贅述了:
package main

import (
"context"
"flag"
"fmt"
appsv1 "k8s.io/api/apps/v1"
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"
"k8s.io/utils/pointer"
"path/filepath"
) const (
NAMESPACE = "test-clientset"
DEPLOYMENT_NAME = "client-test-deployment"
SERVICE_NAME = "client-test-service"
) func main() { var kubeconfig *string // home是家目錄,如果能取得家目錄的值,就可以用來做預設值
if home:=homedir.HomeDir(); home != "" {
// 如果輸入了kubeconfig引數,該引數的值就是kubeconfig檔案的絕對路徑,
// 如果沒有輸入kubeconfig引數,就用預設路徑~/.kube/config
kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
} else {
// 如果取不到當前使用者的家目錄,就沒辦法設定kubeconfig的預設目錄了,只能從入參中取
kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
} // 獲取使用者輸入的操作型別,預設是create,還可以輸入clean,用於清理所有資源
operate := flag.String("operate", "create", "operate type : create or clean") flag.Parse() // 從本機載入kubeconfig配置檔案,因此第一個引數為空字串
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig) // kubeconfig載入失敗就直接退出了
if err != nil {
panic(err.Error())
} // 例項化clientset物件
clientset, err := kubernetes.NewForConfig(config) if err!= nil {
panic(err.Error())
} fmt.Printf("operation is %v\n", *operate) // 如果要執行清理操作
if "clean"==*operate {
clean(clientset)
} else {
// 建立namespace
createNamespace(clientset) // 建立deployment
createDeployment(clientset) // 建立service
createService(clientset)
}
} // 清理本次實戰建立的所有資源
func clean(clientset *kubernetes.Clientset) {
emptyDeleteOptions := metav1.DeleteOptions{} // 刪除service
if err := clientset.CoreV1().Services(NAMESPACE).Delete(context.TODO(), SERVICE_NAME, emptyDeleteOptions) ; err != nil {
panic(err.Error())
} // 刪除deployment
if err := clientset.AppsV1().Deployments(NAMESPACE).Delete(context.TODO(), DEPLOYMENT_NAME, emptyDeleteOptions) ; err != nil {
panic(err.Error())
} // 刪除namespace
if err := clientset.CoreV1().Namespaces().Delete(context.TODO(), NAMESPACE, emptyDeleteOptions) ; err != nil {
panic(err.Error())
}
} // 新建namespace
func createNamespace(clientset *kubernetes.Clientset) {
namespaceClient := clientset.CoreV1().Namespaces() namespace := &apiv1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: NAMESPACE,
},
} result, err := namespaceClient.Create(context.TODO(), namespace, metav1.CreateOptions{}) if err!=nil {
panic(err.Error())
} fmt.Printf("Create namespace %s \n", result.GetName())
} // 新建service
func createService(clientset *kubernetes.Clientset) {
// 得到service的客戶端
serviceClient := clientset.CoreV1().Services(NAMESPACE) // 例項化一個數據結構
service := &apiv1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: SERVICE_NAME,
},
Spec: apiv1.ServiceSpec{
Ports: []apiv1.ServicePort{{
Name: "http",
Port: 8080,
NodePort: 30080,
},
},
Selector: map[string]string{
"app" : "tomcat",
},
Type: apiv1.ServiceTypeNodePort,
},
} result, err := serviceClient.Create(context.TODO(), service, metav1.CreateOptions{}) if err!=nil {
panic(err.Error())
} fmt.Printf("Create service %s \n", result.GetName())
} // 新建deployment
func createDeployment(clientset *kubernetes.Clientset) {
// 得到deployment的客戶端
deploymentClient := clientset.
AppsV1().
Deployments(NAMESPACE) // 例項化一個數據結構
deployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: DEPLOYMENT_NAME,
},
Spec: appsv1.DeploymentSpec{
Replicas: pointer.Int32Ptr(2),
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app" : "tomcat",
},
}, Template: apiv1.PodTemplateSpec{
ObjectMeta:metav1.ObjectMeta{
Labels: map[string]string{
"app" : "tomcat",
},
},
Spec: apiv1.PodSpec{
Containers: []apiv1.Container{
{
Name: "tomcat",
Image: "tomcat:8.0.18-jre8",
ImagePullPolicy: "IfNotPresent",
Ports: []apiv1.ContainerPort{
{
Name: "http",
Protocol: apiv1.ProtocolSCTP,
ContainerPort: 8080,
},
},
},
},
},
},
},
} result, err := deploymentClient.Create(context.TODO(), deployment, metav1.CreateOptions{}) if err!=nil {
panic(err.Error())
} fmt.Printf("Create deployment %s \n", result.GetName())
}

資料結構初始化的煩惱

  • 看過或者上述程式碼後您可能在煩惱:建立資源時,資料結構的欄位太多太複雜根本記不住,對應的程式碼不好寫,這裡分享一個我的做法,如下圖,我在開發的時候一共有兩個視窗,左側是官方的yaml示例,右側用了GoLand的分屏功能,分屏的左側是我寫程式碼的視窗,右側是資料結構定義,此時內容不會搞錯,資料結構也能對應上,寫起來就舒服多了:

驗證

  • 程式碼寫完後,執行go run main.go,即可建立namespace、deployment、service等資源;

  • 檢視kubernetes上資源是否成功建立:

[root@hedy ~]# kubectl get pods -n test-clientset
NAME READY STATUS RESTARTS AGE
client-test-deployment-7677cc9669-kd7l7 1/1 Running 0 178m
client-test-deployment-7677cc9669-kt5rv 1/1 Running 0 178m
[root@hedy ~]# kubectl get service -n test-clientset
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
client-test-service NodePort 10.109.189.151 <none> 8080:30080/TCP 178m
  • 瀏覽器訪問kubernetes伺服器的30080埠,可見熟悉的tomcat首頁:

  • 執行命令go run main.go -operate clean即可刪除剛才建立的所有資源;

  • 至此Clientset的學習和實戰就結束了,總得來說這是個大部分都是自動生成程式碼的客戶端,邏輯簡單容易理解,多用幾次熟練後,就能隨心所欲的操控kubernetes的資源了;

關於容器和映象的環境

如果您不想自己搭建kubernetes環境,推薦使用騰訊雲容器服務TKE:無需自建,即可在騰訊雲上使用穩定, 安全,高效,靈活擴充套件的 Kubernetes 容器平臺;

如果您希望自己的映象可以通過外網上傳和下載,推薦騰訊雲容器映象服務TCR:像資料加密儲存,大映象多節點快速分發,跨地域映象同步

你不孤單,欣宸原創一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 資料庫+中介軟體系列
  6. DevOps系列

歡迎關注公眾號:程式設計師欣宸

微信搜尋「程式設計師欣宸」,我是欣宸,期待與您一同暢遊Java世界...

https://github.com/zq2599/blog_demos