生活随笔
收集整理的這篇文章主要介紹了
client-go实战之三:Clientset
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
歡迎訪問我的GitHub
這里分類和匯總了欣宸的全部原創(含配套源碼):https://github.com/zq2599/blog_demos
系列文章鏈接
client-go實戰之一:準備工作 client-go實戰之二:RESTClient client-go實戰之三:Clientset client-go實戰之四:dynamicClient 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
. DiscoveryClientadmissionregistrationV1
* admissionregistrationv1
. AdmissionregistrationV1ClientadmissionregistrationV1beta1
* admissionregistrationv1beta1
. AdmissionregistrationV1beta1ClientinternalV1alpha1
* internalv1alpha1
. InternalV1alpha1ClientappsV1
* appsv1
. AppsV1ClientappsV1beta1
* appsv1beta1
. AppsV1beta1ClientappsV1beta2
* appsv1beta2
. AppsV1beta2ClientauthenticationV1
* 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,就執行以下操作:
新建名為test-clientset的namespace 新建一個deployment,namespace為test-clientset,鏡像用tomcat,副本數為2 新建一個service,namespace為test-clientset,類型是NodePort
如果operate參數等于clean,就刪除create操作中創建的service、deployment、namespace等資源: 以上需求使用Clientset客戶端實現,完成后咱們用瀏覽器訪問來驗證tomcat是否正常;
源碼下載
本篇實戰中的源碼可在GitHub下載到,地址和鏈接信息如下表所示(https://github.com/zq2599/blog_demos):
名稱鏈接備注 項目主頁 https://github.com/zq2599/blog_demos 該項目在GitHub上的主頁 git倉庫地址(https) https://github.com/zq2599/blog_demos.git 該項目源碼的倉庫地址,https協議 git倉庫地址(ssh) git@github.com: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/api@v0.20.0
go get k8s.io/client-go@v0.20.0
新建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 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" ) } operate
:= flag
. String ( "operate" , "create" , "operate type : create or clean" ) flag
. Parse ( ) config
, err
:= clientcmd
. BuildConfigFromFlags ( "" , * kubeconfig
) if err
!= nil { panic ( err
. Error ( ) ) } clientset
, err
:= kubernetes
. NewForConfig ( config
) if err
!= nil { panic ( err
. Error ( ) ) } fmt
. Printf ( "operation is %v\n" , * operate
) if "clean" == * operate
{ clean ( clientset
) } else { createNamespace ( clientset
) createDeployment ( clientset
) createService ( clientset
) }
}
func clean ( clientset
* kubernetes
. Clientset
) { emptyDeleteOptions
:= metav1
. DeleteOptions
{ } if err
:= clientset
. CoreV1 ( ) . Services ( NAMESPACE
) . Delete ( context
. TODO ( ) , SERVICE_NAME
, emptyDeleteOptions
) ; err
!= nil { panic ( err
. Error ( ) ) } if err
:= clientset
. AppsV1 ( ) . Deployments ( NAMESPACE
) . Delete ( context
. TODO ( ) , DEPLOYMENT_NAME
, emptyDeleteOptions
) ; err
!= nil { panic ( err
. Error ( ) ) } if err
:= clientset
. CoreV1 ( ) . Namespaces ( ) . Delete ( context
. TODO ( ) , NAMESPACE
, emptyDeleteOptions
) ; err
!= nil { panic ( err
. Error ( ) ) }
}
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 ( ) )
}
func createService ( clientset
* kubernetes
. Clientset
) { 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 ( ) )
}
func createDeployment ( clientset
* kubernetes
. Clientset
) { 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 ~
]
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 ~
]
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的資源了;
你不孤單,欣宸原創一路相伴
Java系列 Spring系列 Docker系列 kubernetes系列 數據庫+中間件系列 DevOps系列
歡迎關注公眾號:程序員欣宸
微信搜索「程序員欣宸」,我是欣宸,期待與您一同暢游Java世界…
總結
以上是生活随笔 為你收集整理的client-go实战之三:Clientset 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。