-
client-go是kubernetes官方提供的go語言的客戶端庫,go應用使用該庫可以訪問kubernetes的API Server,這樣我們就能通過編程來對kubernetes資源進行增刪改查操作;
-
除了提供豐富的API用於操作kubernetes資源,client-go還為controller和operator提供了重要支持,如下圖,client-go的informer機制可以將controller關注的資源變化及時帶給此controller,使controller能夠及時響應變化:
- 官方倉庫:https://github.com/kubernetes/client-go
- 通過client-go提供的客戶端對象與kubernetes的API Server進行交互,而client-go提供了以下四種客戶端對象
-
RESTClient:這是最基礎的客戶端對象,僅對HTTPRequest進行了封裝,實現RESTFul風格API,這個對象的使用並不方便,因為很多參數都要使用者來設置,於是client-go基於RESTClient又實現了三種新的客戶端對象;
-
ClientSet:把Resource和Version也封裝成方法了,用起來更簡單直接,一個資源是一個客戶端,多個資源就對應了多個客戶端,所以ClientSet就是多個客戶端的集合了,這樣就好理解了,不過ClientSet只能訪問內置資源,訪問不了自定義資源;
-
DynamicClient:可以訪問內置資源和自定義資源,個人感覺有點像java的集合操作,拿出的內容是Object類型,按實際情況自己去做強制轉換,當然了也會有強轉失敗的風險;
-
DiscoveryClient:用於發現kubernetes的API Server支持的Group、Version、Resources等信息;
進入demo 實戰
- 本次實戰的目錄結構
.
├── cmd
│ └── root.go
├── config
│ ├── config.go
│ └── config.yaml
├── go.mod
├── go.sum
├── main.go
├── pkg
│ └── tool.go
└── service
└── demo.go
4 directories, 8 files
調用鏈:main.go --> root.go --> config.go --> namespace.go --> total.go
本次demo 重點演示ClientSet
客戶端對象
代碼如下:
main.go
主程序入口
/*
* @Author: zisefeizhu
* @Description: student operator
* @File: main.go
* @Version: 1.0.0
* @Date: 2021/8/301 13:37
*/
package main
import "operator/cmd"
func main() {
//入口
cmd.Execute()
}
cmd
root.go
程序初始化操作
package cmd
import (
"fmt"
"operator/config"
"operator/service"
"os"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
var (
cfgFile string
serverPort int
)
var rootCmd = &cobra.Command{
Use: "operator",
Short: "Learning Project Operator",
Long: "Learning project Operator from zisefeizhu",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("啟動參數: ", args)
httpServer()
},
}
func init() {
logrus.Infoln("init root.go...")
cobra.OnInitialize(initConifg)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $CURRENT_DIR/config/config.yaml)")
rootCmd.Flags().IntVarP(&serverPort, "port", "p", 9002, "port on which the server will listen")
}
// 初始化配置
func initConifg() {
config.Loader(cfgFile) // cfgFile string
service.Namespace()
}
func httpServer() {
logrus.Infoln("server start...")
defer func() {
logrus.Infoln("server exit..")
}()
}
// Execute rootCmd
func Execute() {
if err := rootCmd.Execute(); err != nil {
logrus.Fatalln(err)
os.Exit(1)
}
}
config
config.yaml
動態配置 對應k8s的configmap
# DeploymentMethod 部署方式 1為k8s集群外部 0為k8s集群內部
DeploymentMethod: 1
config.go
目前主要是對k8s的配置文件處理
package config
import (
"flag"
"fmt"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"path/filepath"
"os"
"strings"
)
// Loader 加載配置文件
func Loader(cfgFile string) {
if cfgFile == "" {
path, _ := os.Getwd()
cfgFile = path + "/config/config.yaml"
fmt.Println(cfgFile)
}
viper.SetConfigFile(cfgFile) //用來指定配置文件的名稱
viper.SetEnvPrefix("ENV") //SetEnvPrefix會設置一個環境變量的前綴名
viper.AutomaticEnv() //會獲取所有的環境變量,同時如果設置過了前綴則會自動補全前綴名
replacer := strings.NewReplacer(".", "_") //NewReplacer() 使用提供的多組old、new字符串對創建並返回一個*Replacer
viper.SetEnvKeyReplacer(replacer)
if err := viper.ReadInConfig(); err != nil {
fmt.Printf("config file error: %s\n", err)
os.Exit(1)
}
}
var (
// 1. 聲明三個變量
err error
config *rest.Config
kubeconfig *string
)
// homeDir 2.定義一個函數用來在操作系統中獲取目錄路徑
func homeDir() string {
if h := os.Getenv("HOME");h != ""{
return h
}
return os.Getenv("USERPROFILE") //windows
}
// DeployAndKuExternal 部署與k8s外部
func DeployAndKuExternal() *kubernetes.Clientset {
// 3. 在k8s的環境中kubectl配置文件一般放在用戶目錄的.kube文件中
if home := homeDir(); home != ""{
kubeconfig = flag.String("kubeconfig",filepath.Join(home,".kube","config"),"(可選)kubeconfig 文件的絕對路徑")
fmt.Println("kubeConfig", *kubeconfig)
}else {
kubeconfig = flag.String("kubeconfig","","kubeconfig 文件的絕對路徑")
fmt.Println(kubeconfig)
fmt.Println("##################")
}
flag.Parse()
// 4.使用Kubeconfig文件配置集群Config對象
if config,err = clientcmd.BuildConfigFromFlags("",*kubeconfig); err != nil {
panic(err.Error())
}
// 5.在獲取到使用Kubeonfig文件配置的Config對象之后,創建Clientset對象,並對其進行操作
// 已經獲得了rest.Config對象
// 創建Clientset對象
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err.Error())
}
return clientset
}
// DeployAndKuInternal 部署與k8s內部
func DeployAndKuInternal() *kubernetes.Clientset {
// 創建集群內部的config
config, err := rest.InClusterConfig();if err != nil {
panic(err.Error())
}
// 創建clientsSt對象
clientsSt, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err.Error())
}
return clientsSt
}
// KubeConfig k8s的config加載
func KubeConfig() *kubernetes.Clientset {
var clientsSt *kubernetes.Clientset
switch choose := viper.GetInt("DeploymentMethod"); choose {
case 1 :
clientsSt = DeployAndKuExternal()
case 0 :
clientsSt = DeployAndKuInternal()
}
return clientsSt
}
service
demo.go
以名稱空間、deployment、service 為例 學習增刪改查
package service
/*
參考文章:
https://github.com/kubernetes/client-go
https://blog.csdn.net/boling_cavalry/article/details/113487087?spm=1001.2014.3001.5501
*/
import (
"context"
"fmt"
"github.com/sirupsen/logrus"
appsV1 "k8s.io/api/apps/v1"
coreV1 "k8s.io/api/core/v1"
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"operator/config"
"operator/pkg"
"time"
)
func Namespace() {
clientSet := config.KubeConfig()
// 1. namespace 列表
fmt.Println("namespace list: ")
namespaceClient := clientSet.CoreV1().Namespaces()
namespaceResult, err := namespaceClient.List(context.TODO(), metaV1.ListOptions{}); if err != nil {
logrus.Fatal(err)
}
now := time.Now()
namespaces := []string{}
fmt.Println("namespace: ")
for _, namespace := range namespaceResult.Items {
namespaces = append(namespaces, namespace.Name)
fmt.Println(namespace.Name, now.Sub(namespace.CreationTimestamp.Time))
}
fmt.Println("namespaces\t", namespaces)
// 2. namespace 創建
fmt.Println("namespace create: ")
namespace := &coreV1.Namespace{
ObjectMeta: metaV1.ObjectMeta{
Name: "test",
},
}
namespace, err = namespaceClient.Create(context.TODO(), namespace, metaV1.CreateOptions{}); if err != nil {
logrus.Println(err)
} else {
fmt.Println(namespace.Status)
}
// 2. deployment 列表
fmt.Println("deployment list: ")
for _, namespace := range namespaces {
deploymentClient := clientSet.AppsV1().Deployments(namespace)
deploymentResult, err := deploymentClient.List(context.TODO(), metaV1.ListOptions{}); if err != nil {
logrus.Fatal(err)
}else {
for _, deployment := range deploymentResult.Items {
fmt.Println(deployment.Name, deployment.Namespace,*deployment.Spec.Replicas)
}
}
}
// 3. deployment 創建
fmt.Println("deployments create: ")
deploymentClient := clientSet.AppsV1().Deployments("test")
deployment := &appsV1.Deployment{
ObjectMeta: metaV1.ObjectMeta{
Name: "test-dev-nginx",
Labels: map[string]string{
"app": "nginx",
"env": "test",
"by": "zisefeizhu",
"version": "v0.1.0",
},
},
Spec: appsV1.DeploymentSpec{
Replicas: pkg.Int32Ptr(3),
Selector: &metaV1.LabelSelector{
MatchLabels: map[string]string{
"app": "nginx",
"env": "test",
"by": "zisefeizhu",
"version": "v0.1.0",
},
},
Template: coreV1.PodTemplateSpec{
ObjectMeta: metaV1.ObjectMeta{
Labels: map[string]string{
"app": "nginx",
"env": "test",
"by": "zisefeizhu",
"version": "v0.1.0",
},
},
Spec: coreV1.PodSpec{
Containers: []coreV1.Container{
{
Name: "nginx",
Image: "nginx:latest",
Ports: []coreV1.ContainerPort{
{
Name: "http",
ContainerPort: 80,
Protocol: coreV1.ProtocolTCP,
},
},
},
},
},
},
},
}
fmt.Println("create deployment: ")
deployment, err = deploymentClient.Create(context.TODO(), deployment, metaV1.CreateOptions{}); if err != nil {
logrus.Println(err)
} else {
fmt.Println(deployment.Status.Conditions)
}
// 4。 deployment 修改
fmt.Println("deployment update: ")
deployment, err = deploymentClient.Get(context.TODO(), "test-dev-nginx", metaV1.GetOptions{})
if *deployment.Spec.Replicas > 3 {
deployment.Spec.Replicas = pkg.Int32Ptr(1)
} else {
deployment.Spec.Replicas = pkg.Int32Ptr(*deployment.Spec.Replicas + 1)
}
// 1 => nginx:1.19.1
// 2 => nginx:1.19.2
// 3 => nginx:1.19.3
// 3 => nginx:1.19.4
deployment.Spec.Template.Spec.Containers[0].Image = fmt.Sprintf("nginx:1.19.%d", *deployment.Spec.Replicas); if err != nil {
logrus.Println(err)
}
deployment, err = deploymentClient.Update(context.TODO(), deployment, metaV1.UpdateOptions{}); if err != nil {
logrus.Println(err)
}else {
fmt.Println(deployment.Status)
}
// 5. service 列表
fmt.Println("services list: ")
for _, namespace := range namespaces {
serviceClient := clientSet.CoreV1().Services(namespace)
serviceResult, err := serviceClient.List(context.TODO(), metaV1.ListOptions{}); if err != nil {
logrus.Println(err)
}else {
for _, service := range serviceResult.Items {
fmt.Println(service.Name, service.Namespace, service.Labels, service.Spec.Selector, service.Spec.Type, service.Spec.ClusterIP, service.Spec.Ports, service.CreationTimestamp)
}
}
}
// 6. service 創建
fmt.Println("services create: ")
serviceClient := clientSet.CoreV1().Services("test")
service := &coreV1.Service{
ObjectMeta: metaV1.ObjectMeta{
Name: "test-dev-nginx",
Labels: map[string]string{
"app": "nginx",
"env": "test",
"by": "zisefeizhu",
"version": "v0.1.0",
},
},
Spec: coreV1.ServiceSpec{
Selector: map[string]string{
"app": "nginx",
"env": "test",
"by": "zisefeizhu",
"version": "v0.1.0",
},
Type: coreV1.ServiceTypeNodePort,
Ports: []coreV1.ServicePort{
{
Name: "http",
Port: 80,
Protocol: coreV1.ProtocolTCP,
},
},
},
}
service, err = serviceClient.Create(context.TODO(), service, metaV1.CreateOptions{})
if err != nil {
logrus.Println(err)
} else {
fmt.Println(service.Status)
}
// 7. service 修改
fmt.Println("services update: ")
service, err = serviceClient.Get(context.TODO(), "test-dev-nginx", metaV1.GetOptions{}); if err != nil {
logrus.Println(err)
}
if service.Spec.Type == coreV1.ServiceTypeNodePort {
service.Spec.Ports[0].NodePort = 30900
}
service, err = serviceClient.Update(context.TODO(), service, metaV1.UpdateOptions{}); if err != nil {
logrus.Println(err)
}else {
fmt.Println(service.Spec.ClusterIP)
}
// 8. deployment 刪除
fmt.Println("deployment delete: ")
err = deploymentClient.Delete(context.TODO(), "test-dev-nginx", metaV1.DeleteOptions{}); if err != nil {
logrus.Println(err)
}
// 補充邏輯 判斷deployment刪除完畢 --> 再刪除service
// 9. service 刪除
fmt.Println("service delete: ")
err = serviceClient.Delete(context.TODO(),"test-dev-nginx", metaV1.DeleteOptions{}); if err != nil {
logrus.Println(err)
}
// 補充邏輯 判斷所有資源均被刪除完畢后 --> 再刪除namespace
// 10. namespace 刪除
fmt.Println("namespace delete: ")
err = namespaceClient.Delete(context.TODO(),"test", metaV1.DeleteOptions{}); if err != nil {
logrus.Println(err)
}
}
pkg
tool.go
工具包
package pkg
func Int32Ptr(n int32) *int32 {
return &n
}
演示
請自行測試
本demo 環境
- k8s 1.18.3
- client.go k8s.io/client-go@v0.18.3