k8s编程operator系列:
client-go是一个golang的client,我们可以通过client-go与K8S apiServer进行交互,对k8s集群中资源对象,包括内置资源(例如:Pod、Deployment、Service等)和CRD进行增删改查等操作。
client-go地址
: https://github.com/kubernetes/client-go
目录结构:
kubernetes:
该包包含了可以访问kubernetes集群的api,通过这些API可以与apiServer进行通信,对集群的资源对象进行增删改查。比如,我们可以通过kubectl来创建一个deployment,同样的,我们可以使用kubernetes中的clientset来创建一个Deployment。discovery:
该包用于发现Kubernetes apiServer支持的API。dynamic:
该包包含一个动态客户端,可以对任意Kubernetes API对象执行通用操作。plugin/pkg/client/auth:
该包包含可选的身份验证插件,用于从外部源获取凭证。transport:
该包用于设置认证并启动连接。informers:
每种k8s资源的informer实现。listers:
为每一个k8s资源提供list功能,将数据缓存到本地,然后get和list时从本地获取,减轻apiServer的压力。tools:
提供常用工具,例如SharedInformer、Reflector、DeltaFifo已经Indexers等。提供client查询和缓存机制,主要子目录为cache。util:
提供常用方法。例如WorkQueue工作队列,Certificate证书管理等。
kubernetes API是通过http协议以Restful的形式提供的,同时支持json和protobuf的数据格式。protobuf是为了方便集群内部调用而支持的,我们自己平时调用kubernetes接口,一般都是使用json格式。
在kubernetes API中一般使用GVR或GVK来区分特定的资源,根据不同的分组、版本和资源,进行URL的定义。有了分组和多版本的支持,即使是在后续的版本中,需要去掉资源对象的某些字段或者重构API资源,也可以保证版本之间的兼容性。
kubernetes API的分组可以分为无组名资源
和有组名资源
,无组名资源
也被称为核心资源组
,core group。在后面的client-go中可以看到,无组名资源都在core下面。
有组名资源组:
无组名资源组:
GVR/GVK的含义:
G(Group组):
资源组,包含一组资源操作的集合,比如apps下面有deployment、demonset等。
V(Version版本):
资源版本,用于区分不同API的稳定程度和兼容性,比如v1、v1alpha1等。
R(Resources资源):
资源信息,用于区别不同的资源API,k8s中有很多资源,比如pod、deployment、ingress等。
K(Kind类别):
资源对象的类型,每个资源对象都需要Kind来区分它自身代表的资源类型。
一般在调用接口时,我们只需要知道GVR即可,通过GVR操作对应的资源对象。
通过GVR组成Restful API的请求路径,例如下面的url可以获取pod信息:
Get /api/v1/namespaces/{namespace}/pods/{name}
# 通过kubectl来开启一个proxy
[root@master .kube]# kubectl proxy
Starting to serve on 127.0.0.1:8001
# 通过proxy来访问k8s的资源对象
# 获取dev下的pods
curl http://127.0.0.1:8001/api/v1/namespaces/dev/pods
通过GVK信息可以获取要读取的资源对象的GVR,进而构建Restful API请求获取对应的资源。这种GVK和GVR的映射叫做RESTMapper。
RESTMapper主要作用是在ListerWatcher时,根据Scheme定义的类型GVK解析出GVR,向APIServer发起HTTP请求获取资源,然后watch。
client-go中提供了四种与k8s apiServer交互的客户端,分别是 Rest Client、ClientSet、 Discovery Client和Dynamic Client:
RestClient:
最基础的client,底层是对标准库的net/http的封装,下面的client都是对rest client的封装。ClientSet:
基于Rest Client进行了封装,通过clientset可以更加方便地操作K8S地资源对象。DiscoveryClient:
发现客户端,负责发现apiServer支持地资源组、资源版本和资源信息,相当于使用kubectl api-resourcesDynamicClient:
动态客户端,可以对任意的K8S资源对象进行操作,包括CRD.
使用
:go get go get k8s.io/client-go
或者 go get k8s.io/[email protected]
talk is cheap,show me the code,
先来一段代码来看rest client怎么使用,然后在对其中的内容进行详解:
package main
import (
"context"
"fmt"
v1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"log"
)
func main() {
// 1. 构造访问config的配置,从文件中加载,将 home目录下的 .kube/config拷贝到当前./conf/下
config, err := clientcmd.BuildConfigFromFlags("", "./conf/config")
if err != nil {
panic(err)
}
config.GroupVersion = &v1.SchemeGroupVersion
config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
config.APIPath = "/api"
// 2. 创建rest client
client, err := rest.RESTClientFor(config)
if err != nil {
panic(err)
}
// 3. 查找命名空间dev下的pod
var podList v1.PodList
err = client.Get().Namespace("dev").Resource("pods").Do(context.Background()).Into(&podList)
if err != nil {
log.Printf("get pods error:%v\n", err)
return
}
fmt.Println("dev pod count:", len(podList.Items))
for _, pod := range podList.Items {
fmt.Printf("name: %s\n", pod.Name)
}
}
运行结果:
rest client的使用步骤如下:
1、创建config,创建config的方法有两种:
(1) 使用clientcmd.BuildConfigFromFlags从配置文件中读取,读取的正是home目录下.kube/config中的内容。因此,需要将该文件拷贝到程序目录下。
(2) 使用rest.InClusterConfig(),该函数返回一个配置对象,它使用kubernetes提供给pods的服务账户。这个API是为运行在k8s的pod中的服务而设计的。如果你写的应用将来要跑在pod中,那么就可以使用这个方式。如果没有运行在kubernetes环境中的进程调用,它将会返回ErrNotInCluster。当我们的程序要在 pod中运行时,我们需要给其创建一个account,然后k8s会将需要的信息放入pod中,我们的程序就可以读取到了。
注意:使用restclient时还要自己设置config的GroupVersion和NegotiatedSerializer字段,如果不设置,会直接panic。同时也要设置APIPath,否则就查询不到 对应的资源。
2、使用config来创建restclient
3、使用restclient来对资源对象进行操作,比如查找指定命名空间下的pod等
BuildConfigFromFlags的源码如下:
如果两个字符串都为空字符串,那么将会尝试使用InClusterConfig来读取。否则,就从指定的config文件中读取
func BuildConfigFromFlags(masterUrl, kubeconfigPath string) (*restclient.Config, error) {
// 如果kubeconfigPath和masterUrl都为空字符串,那么将尝试使用InClusterConfig来读取配置
if kubeconfigPath == "" && masterUrl == "" {
klog.Warning("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)
}
// 否则从指定的config文件中读取
return NewNonInteractiveDeferredLoadingClientConfig(
&ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},
&ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterUrl}}).ClientConfig()
}
InClusterConfig的源码如下:
InClusterConfig会从文件中读取token和ca,并从环境变量中取得集群入口的host和port
func InClusterConfig() (*Config, error) {
// 如果再pod中运行,k8s会将指定账户的toeknFile和rootCAFile放入pod中的指定文件中,因此我们的程序就可以去读取token和ca文件
const (
tokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token"
rootCAFile = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
)
// 从环境变量中读取host和port
host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT")
if len(host) == 0 || len(port) == 0 {
return nil, ErrNotInCluster
}
token, err := ioutil.ReadFile(tokenFile)
if err != nil {
return nil, err
}
tlsClientConfig := TLSClientConfig{}
if _, err := certutil.NewPool(rootCAFile); err != nil {
klog.Errorf("Expected to load root CA config from %s, but got err: %v", rootCAFile, err)
} else {
tlsClientConfig.CAFile = rootCAFile
}
return &Config{
// TODO: switch to using cluster DNS.
Host: "https://" + net.JoinHostPort(host, port),
TLSClientConfig: tlsClientConfig,
BearerToken: string(token),
BearerTokenFile: tokenFile,
}, nil
}
RESTClientFor的源码如下:
在RESTClientFor中会对config的GroupVersion和NegotiatedSerializer进行检查,如果为nil,将会panic,因此我们需要收到设置这两个字段的值。
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")
}
// Validate config.Host before constructing the transport/client so we can fail fast.
// ServerURL will be obtained later in RESTClientForConfigAndClient()
_, _, err := defaultServerUrlFor(config)
if err != nil {
return nil, err
}
httpClient, err := HTTPClientFor(config)
if err != nil {
return nil, err
}
return RESTClientForConfigAndClient(config, httpClient)
}
如果不知道怎么设置这两个字段的值,我们可以搜索该方法调用的地方来查看官方是怎么设置的:
package main
import (
"context"
"fmt"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"log"
)
func main() {
// 1. 构造访问config的配置,从文件中加载,将 home目录下的 .kube/config拷贝到当前./conf/下
config, err := clientcmd.BuildConfigFromFlags("", "./conf/config")
if err != nil {
panic(err)
}
// 2. 创建clientset
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err)
}
// 3.对资源对象进行操作
podList, err := clientset.CoreV1().Pods("dev").List(context.Background(), v1.ListOptions{})
if err != nil {
log.Printf("list pods error:%v\n", err)
return
}
fmt.Println("dev pod count:", len(podList.Items))
for _, pod := range podList.Items {
fmt.Printf("name: %s\n", pod.Name)
}
}
可以看到,使用clientset要简单的多,而且我们无需自己为config中的字段进行赋值了,因为clientset对各种资源对象的操作进行了封装。使用步骤如下:
1、构造config配置
2、使用kubernetes的NewForConfig和config来创建clientset
3、使用clientset对资源对象进行操作
接下来我们来创建一个nginx的pod:
package main
import (
"context"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"log"
)
func main() {
// 1. 构造访问config的配置,从文件中加载,将 home目录下的 .kube/config拷贝到当前./conf/下
config, err := clientcmd.BuildConfigFromFlags("", "./conf/config")
if err != nil {
panic(err)
}
// 2. 创建clientset
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err)
}
// 3.创建一个pod对象并填上字段
pod := corev1.Pod{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Pod",
},
ObjectMeta: metav1.ObjectMeta{
Name: "nginx",
Namespace: "default",
Labels: map[string]string{
"run": "nginx",
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Image: "nginx",
Name: "nginx-container",
Ports: []corev1.ContainerPort{
{
ContainerPort: 80,
},
},
},
},
},
}
// 4. 创建pod
_, err = clientset.CoreV1().Pods("default").Create(context.Background(), &pod, metav1.CreateOptions{})
if err != nil {
log.Printf("create pod error:%v\n", err)
return
}
log.Printf("create pod success\n")
}
运行程序,可以看到,pod已经被创建。但是由于我的集群只启动了master,master默认是有污点的,因此该pod调度不上去,就是Pending状态。
可以看到,上面的创建pod对象并设置字段非常麻烦,下面将介绍从文件模板中构造一个k8s的pod资源对象:
# 使用该命令生成一个创建pod的yaml
[root@master .kube]# kubectl run nginx --image=nginx --port=80 --dry-run=client -o yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: nginx
name: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
在项目中创建一个template的文件夹,并将创建一个pod.yaml文件,内容如下:
{{.Name}}为模板语法,可以将程序中结构的Name字段渲染到模板中的对应位置
apiVersion: v1
kind: Pod
metadata:
name: {{.Name}}
namespace: {{.Namespace}}
labels:
run: pod
spec:
containers:
- image: {{.Image}}
name: {{.ContainerName}}
ports:
- containerPort: 80
然后我们可以在程序中读入yaml模板文件,并对模板进行实例化,然后解析到pod对象中,再来创建pod,代码如下:
package main
import (
"bytes"
"context"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"log"
"text/template"
)
func main() {
// 1. 构造访问config的配置,从文件中加载,将 home目录下的 .kube/config拷贝到当前./conf/下
config, err := clientcmd.BuildConfigFromFlags("", "./conf/config")
if err != nil {
panic(err)
}
// 2. 创建clientset
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err)
}
// 3. 从模板文件中构造pod
spec := PodSpec{
Name: "nginx-pod-demo",
Image: "nginx",
Namespace: "default",
ContainerName: "nginx",
}
var pod corev1.Pod
tmpl, err := ParseTemplate("./template/pod.yaml", &spec)
if err != nil {
panic(err)
}
err = yaml.Unmarshal(tmpl, &pod)
if err != nil {
panic(err)
}
// 4. 创建pod
_, err = clientset.CoreV1().Pods("default").Create(context.Background(), &pod, metav1.CreateOptions{})
if err != nil {
log.Printf("create pod error:%v\n", err)
return
}
log.Printf("create pod success\n")
}
type PodSpec struct {
Name string `json:"name"`
Image string `json:"image"`
Namespace string `json:"namespace"`
ContainerName string `json:"container_name"`
}
func ParseTemplate(name string, item *PodSpec) ([]byte, error) {
tmpl, err := template.ParseFiles(name)
if err != nil {
return nil, err
}
buf := bytes.Buffer{}
err = tmpl.Execute(&buf, item)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
运行代码后,也成功创建了一个pod。
clientset的结构:
可以看到,ClientSet为一个结构体,里面包含了各种操作各个GVR的client。其中我们的pod在corev1下
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
authenticationV1beta1 *authenticationv1beta1.AuthenticationV1beta1Client
authorizationV1 *authorizationv1.AuthorizationV1Client
authorizationV1beta1 *authorizationv1beta1.AuthorizationV1beta1Client
autoscalingV1 *autoscalingv1.AutoscalingV1Client
autoscalingV2 *autoscalingv2.AutoscalingV2Client
autoscalingV2beta1 *autoscalingv2beta1.AutoscalingV2beta1Client
autoscalingV2beta2 *autoscalingv2beta2.AutoscalingV2beta2Client
batchV1 *batchv1.BatchV1Client
batchV1beta1 *batchv1beta1.BatchV1beta1Client
certificatesV1 *certificatesv1.CertificatesV1Client
certificatesV1beta1 *certificatesv1beta1.CertificatesV1beta1Client
coordinationV1beta1 *coordinationv1beta1.CoordinationV1beta1Client
coordinationV1 *coordinationv1.CoordinationV1Client
coreV1 *corev1.CoreV1Client
discoveryV1 *discoveryv1.DiscoveryV1Client
discoveryV1beta1 *discoveryv1beta1.DiscoveryV1beta1Client
eventsV1 *eventsv1.EventsV1Client
eventsV1beta1 *eventsv1beta1.EventsV1beta1Client
extensionsV1beta1 *extensionsv1beta1.ExtensionsV1beta1Client
flowcontrolV1alpha1 *flowcontrolv1alpha1.FlowcontrolV1alpha1Client
flowcontrolV1beta1 *flowcontrolv1beta1.FlowcontrolV1beta1Client
flowcontrolV1beta2 *flowcontrolv1beta2.FlowcontrolV1beta2Client
networkingV1 *networkingv1.NetworkingV1Client
networkingV1alpha1 *networkingv1alpha1.NetworkingV1alpha1Client
networkingV1beta1 *networkingv1beta1.NetworkingV1beta1Client
nodeV1 *nodev1.NodeV1Client
nodeV1alpha1 *nodev1alpha1.NodeV1alpha1Client
nodeV1beta1 *nodev1beta1.NodeV1beta1Client
policyV1 *policyv1.PolicyV1Client
policyV1beta1 *policyv1beta1.PolicyV1beta1Client
rbacV1 *rbacv1.RbacV1Client
rbacV1beta1 *rbacv1beta1.RbacV1beta1Client
rbacV1alpha1 *rbacv1alpha1.RbacV1alpha1Client
schedulingV1alpha1 *schedulingv1alpha1.SchedulingV1alpha1Client
schedulingV1beta1 *schedulingv1beta1.SchedulingV1beta1Client
schedulingV1 *schedulingv1.SchedulingV1Client
storageV1beta1 *storagev1beta1.StorageV1beta1Client
storageV1 *storagev1.StorageV1Client
storageV1alpha1 *storagev1alpha1.StorageV1alpha1Client
}
创建clientset:
首先会创建http.Client,该client用于与apiServer进行通信。然后,创建出各个GVR的client。
// 利用config来创建clientset
func NewForConfig(c *rest.Config) (*Clientset, error) {
configShallowCopy := *c
if configShallowCopy.UserAgent == "" {
configShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent()
}
// 创建net/http.Client,该client将在各个GVR的client中共享
httpClient, err := rest.HTTPClientFor(&configShallowCopy)
if err != nil {
return nil, err
}
// 创建各个GVR的client
return NewForConfigAndClient(&configShallowCopy, httpClient)
}
func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) {
configShallowCopy := *c
if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 {
if configShallowCopy.Burst <= 0 {
return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0")
}
configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst)
}
var cs Clientset
var err error
// 创建各种GVR的client
...
cs.appsV1beta1, err = appsv1beta1.NewForConfigAndClient(&configShallowCopy, httpClient)
if err != nil {
return nil, err
}
...
cs.coreV1, err = corev1.NewForConfigAndClient(&configShallowCopy, httpClient)
if err != nil {
return nil, err
}
cs.discoveryV1, err = discoveryv1.NewForConfigAndClient(&configShallowCopy, httpClient)
if err != nil {
return nil, err
}
cs.discoveryV1beta1, err = discoveryv1beta1.NewForConfigAndClient(&configShallowCopy, httpClient)
if err != nil {
return nil, err
}
...
cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient)
if err != nil {
return nil, err
}
return &cs, nil
}
在创建各个GVR的client的时候会调用NewForConfigAndClient,而在NewForConfigAndClient中又会调用RESTClientForConfigAndClient来创建rest client
同时在NewForConfigAndClient中会调用setConfigDefaults来为config的几个字段赋值。
// 以CoreV1Client为例,可以看到其中包含了restClient
type CoreV1Client struct {
restClient rest.Interface
}
func NewForConfigAndClient(c *rest.Config, h *http.Client) (*CoreV1Client, error) {
config := *c
// 为config中的字段设置默认值
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
// 创建restClient
client, err := rest.RESTClientForConfigAndClient(&config, h)
if err != nil {
return nil, err
}
return &CoreV1Client{client}, nil
}
// 在restClient中我们需要手动为config中的GroupVersion、NegotiatedSerializer和APIPath设置值,
// 但是使用clientSet我们无需自己设置,因为在创建各个GVR的client时会自动指定不同的值
func setConfigDefaults(config *rest.Config) error {
gv := v1.SchemeGroupVersion
config.GroupVersion = &gv
config.APIPath = "/api"
config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
return nil
}
接下来将使用client-go,实现一个deployment的创建、更新和删除操作。代码依据官方的例子修改而来:
package main
import (
"bufio"
"context"
"fmt"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
appsresv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/retry"
"k8s.io/klog/v2"
"os"
)
func main() {
// 1、创建配置文件
config, err := clientcmd.BuildConfigFromFlags("", "./conf/config")
if err != nil {
panic(err)
}
// 2、创建clientset
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err)
}
deployClient := clientset.AppsV1().Deployments(corev1.NamespaceDefault)
// 3、创建deployment
CreateDeploy(deployClient)
prompt()
// 4、更新deployment
UpdateDeploy(deployClient)
prompt()
// 5、查询deployment
ListDeploy(deployClient)
prompt()
// 6、删除deployment
DeleteDeploy(deployClient)
}
func CreateDeploy(client appsresv1.DeploymentInterface) {
klog.Info("CreateDeploy...........")
replicas := int32(2)
deploy := appsv1.Deployment{
TypeMeta: v1.TypeMeta{
Kind: "Deployment",
APIVersion: "apps/v1",
},
ObjectMeta: v1.ObjectMeta{
Name: "deploy-nginx-demo",
Namespace: corev1.NamespaceDefault,
},
Spec: appsv1.DeploymentSpec{
Replicas: &replicas,
Selector: &v1.LabelSelector{
MatchLabels: map[string]string{
"app": "nginx",
},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: v1.ObjectMeta{
Name: "nginx",
Labels: map[string]string{
"app": "nginx",
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "web",
Image: "nginx:1.12",
Ports: []corev1.ContainerPort{
{
Protocol: corev1.ProtocolTCP,
ContainerPort: 80,
},
},
},
},
},
},
},
}
dep, err := client.Create(context.Background(), &deploy, v1.CreateOptions{})
if err != nil {
klog.Errorf("create deployment error:%v", err)
return
}
klog.Infof("create deployment success, name:%s", dep.Name)
}
func UpdateDeploy(client appsresv1.DeploymentInterface) {
klog.Info("UpdateDeploy...........")
// 当有多个客户端对同一个资源进行操作时,可能会发生错误。使用RetryOnConflict来重试,重试相关参数由DefaultRetry来提供
err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
// 查询要更新的deploy
deploy, err := client.Get(context.Background(), "deploy-nginx-demo", v1.GetOptions{})
if err != nil {
klog.Errorf("can't get deployment, err:%v", err)
return nil
}
// 修改参数后进行更新
replicas := int32(1)
deploy.Spec.Replicas = &replicas
deploy.Spec.Template.Spec.Containers[0].Image = "nginx:1.13"
_, err = client.Update(context.Background(), deploy, v1.UpdateOptions{})
if err != nil {
klog.Errorf("update deployment error, err:%v", err)
}
return err
})
if err != nil {
klog.Errorf("update deployment error, err:%v", err)
} else {
klog.Infof("update deployment success")
}
}
func ListDeploy(client appsresv1.DeploymentInterface) {
klog.Info("ListDeploy...........")
deplist, err := client.List(context.Background(), v1.ListOptions{})
if err != nil {
klog.Errorf("list deployment error, err:%v", err)
return
}
for _, dep := range deplist.Items {
klog.Infof("deploy name:%s, replicas:%d, container image:%s", dep.Name, *dep.Spec.Replicas, dep.Spec.Template.Spec.Containers[0].Image)
}
}
func DeleteDeploy(client appsresv1.DeploymentInterface) {
klog.Info("DeleteDeploy...........")
// 删除策略
deletePolicy := v1.DeletePropagationForeground
err := client.Delete(context.Background(), "deploy-nginx-demo", v1.DeleteOptions{PropagationPolicy: &deletePolicy})
if err != nil {
klog.Errorf("delete deployment error, err:%v", err)
} else {
klog.Info("delete deployment success")
}
}
func prompt() {
fmt.Printf("-> Press Return key to continue.")
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
break
}
if err := scanner.Err(); err != nil {
panic(err)
}
fmt.Println()
}