k8s client-go快速入门教程及源代码阅读之静态客户端
client-go的客户端可以分为两类,一类称为静态客户端,另一类称为动态客户端,前者的好处在接口已经固定,在写代码的时候有很好的提示,坏处是无法访问非k8s内置资源,后者的好处是够灵活,但是需要使用者知道GVK/GVR等前置知识。两者各有好处,需要根据自己需要选择。
其实还有一个发现客户端
DiscoveryClient
,但是它是用于发现集群资源版本的,而不是操作资源的客户端。
虽然分为两类,但是,只是两者暴露的接口不同而已,两者底层都是使用的rest.RESTClient
对象,所以最终代码的深入都会到同一个地方,为了不造成文章的冗余,静态客户端和动态客户端的源码阅读部分都不会深入到发送请求,构造请求的层面,所以静态/动态客户端的源代码解读可能不够硬。
快速入门
在之前的文章已经贴过使用静态客户端的增删改查的代码,这里就不贴完整的代码了,还是以deployment对象/资源为例, 代码删减如下:
func main() {
var kubeconfig *string
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
clientset, err := kubernetes.NewForConfig(config)
deploymentsClient := clientset.AppsV1().Deployments(apiv1.NamespaceDefault)
deployment := &appsv1.Deployment{}
// Create Deployment
fmt.Println("Creating deployment...")
result, err := deploymentsClient.Create(context.TODO(), deployment, metav1.CreateOptions{})
fmt.Printf("Created deployment %q.\n", result.GetObjectMeta().GetName())
fmt.Println("Updating deployment...")
retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
result, getErr := deploymentsClient.Get(context.TODO(), "demo-deployment", metav1.GetOptions{})
result.Spec.Replicas = int32Ptr(1) // reduce replica count
result.Spec.Template.Spec.Containers[0].Image = "nginx:1.13" // change nginx version
_, updateErr := deploymentsClient.Update(context.TODO(), result, metav1.UpdateOptions{})
return updateErr
})
fmt.Println("Updated deployment...")
fmt.Printf("Listing deployments in namespace %q:\n", apiv1.NamespaceDefault)
list, err := deploymentsClient.List(context.TODO(), metav1.ListOptions{})
for _, d := range list.Items {
fmt.Printf(" * %s (%d replicas)\n", d.Name, *d.Spec.Replicas)
}
fmt.Println("Deleting deployment...")
deletePolicy := metav1.DeletePropagationForeground
if err := deploymentsClient.Delete(context.TODO(), "demo-deployment", metav1.DeleteOptions{
PropagationPolicy: &deletePolicy,
}); err != nil {
panic(err)
}
fmt.Println("Deleted deployment.")
}
完整版本可参考: https://github.com/kubernetes/client-go/tree/v0.20.2/examples/create-update-delete-deployment
客户端构造
静态客户端构造过程比较简单
clientset, err := kubernetes.NewForConfig(config)
deploymentsClient := clientset.AppsV1().Deployments(apiv1.NamespaceDefault)
因为k8s内置了非常多的资源,所以静态客户端是一个集合的概念,在这个集合的基础上可以继续选择自己所需资源的客户端。
代码的调用链还是比较简单的。
func NewForConfig(c *rest.Config) (*Clientset, error) {
var cs Clientset
var err error
cs.appsV1, err = appsv1.NewForConfig(&configShallowCopy)
// 省略其他客户端的配置代码
}
// appsv1.NewForConfig
func NewForConfig(c *rest.Config) (*AppsV1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
client, err := rest.RESTClientFor(&config)
if err != nil {
return nil, err
}
return &AppsV1Client{client}, nil
}
func (c *AppsV1Client) Deployments(namespace string) DeploymentInterface {
return newDeployments(c, namespace)
}
func newDeployments(c *AppsV1Client, namespace string) *deployments {
return &deployments{
client: c.RESTClient(),
ns: namespace,
}
}
还记得之前说的GVR么,这里的apps是G(roup), v1是V(ersion), Deployments是R(esource),通过GVR我们就能定位到我们要使用的客户端。因为apps是一个组(Group)的概念, 所以这个组下面还有DaemonSets
,StatefulSets
等资源。
总的来说,我们可以通过GVR定位到了我们所需资源的客户端,下面再看看客户端的常用接口的源代码。
获取 Get/List
两者的源代码如下:
func (c *deployments) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.Deployment, err error) {
result = &v1.Deployment{}
err = c.client.Get().
Namespace(c.ns).
Resource("deployments").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do(ctx).
Into(result)
return
}
func (c *deployments) List(ctx context.Context, opts metav1.ListOptions) (result *v1.DeploymentList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1.DeploymentList{}
err = c.client.Get().
Namespace(c.ns).
Resource("deployments").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do(ctx).
Into(result)
return
}
可以看到两者的区别不是太大,前者多了一个Name的参数,用于指定资源的名字,这样就能定位到唯一的资源个体了,另一个区别就是options
对象不一样。
值得注意的是所有资源的同类型操作,公用同一个Options对象,比如Get操作对应
metav1.GetOptions
如果使用了构造者的设计模式,对诸如Namespace
,Resource
等方法不会太陌生,这些方法基本上就是在restClient
上设置一个属性。
新增 Create
代码如下:
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
}
更新 Upadate/Patch
代码如下:
func (c *deployments) Update(ctx context.Context, deployment *v1.Deployment, opts metav1.UpdateOptions) (result *v1.Deployment, err error) {
result = &v1.Deployment{}
err = c.client.Put().
Namespace(c.ns).
Resource("deployments").
Name(deployment.Name).
VersionedParams(&opts, scheme.ParameterCodec).
Body(deployment).
Do(ctx).
Into(result)
return
}
func (c *deployments) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.Deployment, err error) {
result = &v1.Deployment{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("deployments").
Name(name).
SubResource(subresources...).
VersionedParams(&opts, scheme.ParameterCodec).
Body(data).
Do(ctx).
Into(result)
return
}
值得注意的是,我们常使用的kubectl apply
对于更新的操作其实用的是Patch
接口。
除了更新对象本身,很多资源还有子资源的说法,比如deployment
还可以了UpdateStatus
, UpdateScale
删除 Delete
代码如下:
func (c *deployments) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("deployments").
Name(name).
Body(&opts).
Do(ctx).
Error()
}
当然了,你也可以一次性删除多个deployment
对象, 可以使用DeleteCollection
方法。
总结
总的来说静态客户端的代码如果不深入到restClient
其实阅读起来没啥难度,比较千篇一律,而看起来千篇一律的一个重要的原因就是,这些静态客户端代码是通过代码生成的。
因为不会深入到请求构造的代码中,所以本文可能看起来比较水,这个也没啥办法,慢慢写,慢慢看吧。其实大多数资源还有一个重要的方法,那就是Watch
方法,这个主要用于k8s的Informer
, 等讲到Informer
在说吧。
kubernetes client-go源代码阅读之静态客户端
client-go的客户端可以分为两类,一类称为静态客户端,另一类称为动态客户端,前者的好处在接口已经固定,在写代码的时候有很好的提示,坏处是无法访问非k8s内置资源,后者的好处是够灵活,但是需要使用者知道GVK/GVR等前置知识。两者各有好处,需要根据自己需要选择。
其实还有一个发现客户端
DiscoveryClient
,但是它是用于发现集群资源版本的,而不是操作资源的客户端。
虽然分为两类,但是,只是两者暴露的接口不同而已,两者底层都是使用的rest.RESTClient
对象,所以最终代码的深入都会到同一个地方,为了不造成文章的冗余,静态客户端和动态客户端的源码阅读部分都不会深入到发送请求,构造请求的层面,所以静态/动态客户端的源代码解读可能不够硬。
快速入门
在之前的文章已经贴过使用静态客户端的增删改查的代码,这里就不贴完整的代码了,还是以deployment对象/资源为例, 代码删减如下:
func main() {
var kubeconfig *string
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
clientset, err := kubernetes.NewForConfig(config)
deploymentsClient := clientset.AppsV1().Deployments(apiv1.NamespaceDefault)
deployment := &appsv1.Deployment{}
// Create Deployment
fmt.Println("Creating deployment...")
result, err := deploymentsClient.Create(context.TODO(), deployment, metav1.CreateOptions{})
fmt.Printf("Created deployment %q.\n", result.GetObjectMeta().GetName())
fmt.Println("Updating deployment...")
retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
result, getErr := deploymentsClient.Get(context.TODO(), "demo-deployment", metav1.GetOptions{})
result.Spec.Replicas = int32Ptr(1) // reduce replica count
result.Spec.Template.Spec.Containers[0].Image = "nginx:1.13" // change nginx version
_, updateErr := deploymentsClient.Update(context.TODO(), result, metav1.UpdateOptions{})
return updateErr
})
fmt.Println("Updated deployment...")
fmt.Printf("Listing deployments in namespace %q:\n", apiv1.NamespaceDefault)
list, err := deploymentsClient.List(context.TODO(), metav1.ListOptions{})
for _, d := range list.Items {
fmt.Printf(" * %s (%d replicas)\n", d.Name, *d.Spec.Replicas)
}
fmt.Println("Deleting deployment...")
deletePolicy := metav1.DeletePropagationForeground
if err := deploymentsClient.Delete(context.TODO(), "demo-deployment", metav1.DeleteOptions{
PropagationPolicy: &deletePolicy,
}); err != nil {
panic(err)
}
fmt.Println("Deleted deployment.")
}
完整版本可参考: https://github.com/kubernetes/client-go/tree/v0.20.2/examples/create-update-delete-deployment
客户端构造
静态客户端构造过程比较简单
clientset, err := kubernetes.NewForConfig(config)
deploymentsClient := clientset.AppsV1().Deployments(apiv1.NamespaceDefault)
因为k8s内置了非常多的资源,所以静态客户端是一个集合的概念,在这个集合的基础上可以继续选择自己所需资源的客户端。
代码的调用链还是比较简单的。
func NewForConfig(c *rest.Config) (*Clientset, error) {
var cs Clientset
var err error
cs.appsV1, err = appsv1.NewForConfig(&configShallowCopy)
// 省略其他客户端的配置代码
}
// appsv1.NewForConfig
func NewForConfig(c *rest.Config) (*AppsV1Client, error) {
config := *c
if err := setConfigDefaults(&config); err != nil {
return nil, err
}
client, err := rest.RESTClientFor(&config)
if err != nil {
return nil, err
}
return &AppsV1Client{client}, nil
}
func (c *AppsV1Client) Deployments(namespace string) DeploymentInterface {
return newDeployments(c, namespace)
}
func newDeployments(c *AppsV1Client, namespace string) *deployments {
return &deployments{
client: c.RESTClient(),
ns: namespace,
}
}
还记得之前说的GVR么,这里的apps是G(roup), v1是V(ersion), Deployments是R(esource),通过GVR我们就能定位到我们要使用的客户端。因为apps是一个组(Group)的概念, 所以这个组下面还有DaemonSets
,StatefulSets
等资源。
总的来说,我们可以通过GVR定位到了我们所需资源的客户端,下面再看看客户端的常用接口的源代码。
获取 Get/List
两者的源代码如下:
func (c *deployments) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.Deployment, err error) {
result = &v1.Deployment{}
err = c.client.Get().
Namespace(c.ns).
Resource("deployments").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do(ctx).
Into(result)
return
}
func (c *deployments) List(ctx context.Context, opts metav1.ListOptions) (result *v1.DeploymentList, err error) {
var timeout time.Duration
if opts.TimeoutSeconds != nil {
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
}
result = &v1.DeploymentList{}
err = c.client.Get().
Namespace(c.ns).
Resource("deployments").
VersionedParams(&opts, scheme.ParameterCodec).
Timeout(timeout).
Do(ctx).
Into(result)
return
}
可以看到两者的区别不是太大,前者多了一个Name的参数,用于指定资源的名字,这样就能定位到唯一的资源个体了,另一个区别就是options
对象不一样。
值得注意的是所有资源的同类型操作,公用同一个Options对象,比如Get操作对应
metav1.GetOptions
如果使用了构造者的设计模式,对诸如Namespace
,Resource
等方法不会太陌生,这些方法基本上就是在restClient
上设置一个属性。
新增 Create
代码如下:
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
}
更新 Upadate/Patch
代码如下:
func (c *deployments) Update(ctx context.Context, deployment *v1.Deployment, opts metav1.UpdateOptions) (result *v1.Deployment, err error) {
result = &v1.Deployment{}
err = c.client.Put().
Namespace(c.ns).
Resource("deployments").
Name(deployment.Name).
VersionedParams(&opts, scheme.ParameterCodec).
Body(deployment).
Do(ctx).
Into(result)
return
}
func (c *deployments) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.Deployment, err error) {
result = &v1.Deployment{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("deployments").
Name(name).
SubResource(subresources...).
VersionedParams(&opts, scheme.ParameterCodec).
Body(data).
Do(ctx).
Into(result)
return
}
值得注意的是,我们常使用的kubectl apply
对于更新的操作其实用的是Patch
接口。
除了更新对象本身,很多资源还有子资源的说法,比如deployment
还可以了UpdateStatus
, UpdateScale
删除 Delete
代码如下:
func (c *deployments) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("deployments").
Name(name).
Body(&opts).
Do(ctx).
Error()
}
当然了,你也可以一次性删除多个deployment
对象, 可以使用DeleteCollection
方法。
总结
总的来说静态客户端的代码如果不深入到restClient
其实阅读起来没啥难度,比较千篇一律,而看起来千篇一律的一个重要的原因就是,这些静态客户端代码是通过代码生成的。
因为不会深入到请求构造的代码中,所以本文可能看起来比较水,这个也没啥办法,慢慢写,慢慢看吧。其实大多数资源还有一个重要的方法,那就是Watch
方法,这个主要用于k8s的Informer
, 等讲到Informer
在说吧。