经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Go语言 » 查看文章
如何自定义Kubernetes资源
来源:cnblogs  作者:圣骑士wind  时间:2020/12/21 15:10:14  对本文有异议

目前最流行的微服务架构非Springboot+Kubernetes+Istio莫属, 然而随着越来越多的微服务被拆分出来, 不但Deploy过程boilerplate的配置越来越多, 且繁琐易错, 维护成本也逐渐增高, 那么是时候采用k8s提供的扩展自定义资源的方法, 将重复的template抽到后面, 从而简化Deploy配置的数量与复杂度.

Tips:
一个基础的k8s微服务应该由几部分组成, 首先Deployment负责App的部署, Service负责端口的暴露, ServiceAccount负责赋予Pod相应的Identity, ServiceRole+RoleBinding负责api的访问权限控制, VirtualService负责路由

如果我们通过自定义资源的方式, 将每个微服务App的共有配置封装起来, 暴露出可变部分供各个应用配置, 如image, public/private api, mesh内访问权限等, 并设置mandatory/required的字段与validation pattern, 这样每一个App只需要一个配置文件, 就可以完成统一部署.

下面我们可以首先看一下k8s提供的扩展的两种方法.

  • 通过Aggregated Apiserver
  • 通过Custom Resource Defination

这两种最大的区别就是, 前者需要自己实现一个用户自定义的Apiserver, 而后者是被kube-apiserver内的extension apiserver module所处理.

二者都需要通过自定义Controller来处理资源的配置.

创建CRD

虽然看似AA的开放程度更高, 但实际上通过CRD来定义自定义资源更成熟且方便. 通过官方文档, 我们可以看到CRD的定义规则, 如下面我们定义的一个资源Beer:

  1. apiVersion: apiextensions.k8s.io/v1
  2. kind: CustomResourceDefinition
  3. metadata:
  4. name: beer.weyoung.io
  5. spec:
  6. group: weyoung.io
  7. versions:
  8. - name: v1alpha1
  9. storage: true
  10. served: true
  11. schema:
  12. openAPIV3Schema:
  13. type: object
  14. properties:
  15. spec:
  16. type: object
  17. properties:
  18. BeerName:
  19. description: This is a custom field of beer
  20. pattern: ^[a-z]+$
  21. type: string
  22. required:
  23. - beerName
  24. required:
  25. - spec
  26. type: object
  27. names:
  28. kind: Beer
  29. listKind: BeerList
  30. singular: beer
  31. plural: beers
  32. scope: Namespaced

这里我们添加了一个自定义字段beerName, 指定了pattern, 并且设置了spec与其下的beerName死required字段. 其余关键字可以查阅官方文档.

在apply该配置后, 通过kubectl get crd即可获得已经存在的资源.

  1. > kubectl get crd
  2. NAME CREATED AT
  3. beers.weyoung.io 2020-12-12T17:28:34Z

有了CRD之后, 我们就可以apply自定义的资源了:

  1. apiVersion: weyoung.io/v1alpha1
  2. kind: Beer
  3. metadata:
  4. name: test-beer
  5. spec:
  6. beerName: abc

这里大家可以试验一下required与pattern的作用是否生效.

这里有个比较关键的点, CRD中未声明的资源依旧可以被成功添加到Beer资源里, 并成功apply&configured, 但是不具备validation的效果, 这也可能是早期版本的crd把schema关键字换做validation的原因吧.

创建Controller

当我们实现了自己的CRD, 并且apply了根据CRD定义的自定义资源Beer后, 这仅仅是存在etcd的静态资源, 还需要controller来根据Beer的变化创建相应资源, 让Beer动起来.

首先可以先了解一下Controller的工作原理.

从图中可以看出, Informer模块内的Reflactor会监听k8sapi, 根据自己注册的资源类型, 轮询的将所有资源的最新状态存入队列, Indexer会对资源进行index, 生成key与namespace/name的映射关系.

通过Informer的监听方法, 可以从队列依次读取, 放进WorkQueue中供controller消费, 而controller可以通过Lister API由namespace/name获取到对应的自定义资源, 并做增删改查操作.

看似有很多模块在其中, 不过k8s已经帮我们实现了大部分(Informer, Lister等), 即它的client-go library. 我们只需要用对应的api来实现自己的controller部分就可以了.

具体的逻辑, k8s官方提供了一个sample-controller来供大家学习, 本人通过对这个sample的踩坑, 总结了一些需要注意的地方会在之后highlight出来.

总体来讲, 自定义自己的controller需要三个步骤:

  • 定义资源Type与注册
  • 生成Informer相应代码
  • 编写与调用Controller

当然在这之前, 需要先添加依赖.

  • apimachinery 负责client与k8sapi之间通信编解码等
  • client-go 调用k8s cluster相关api
  • code-generator 根据定义的type生成相关代码, 包括informer, client

这里需要注意的是依赖版本应该最新, 并且一致, 因为在尝试多次之后, 发现新老版本之间的变化非常大, 添加了很多新的module与功能, 而且如果不一致, 也会导致代码生成, 与k8sapi之间通信等各种的异常.

当我们通过go init custom-k8s-controller初始化新的module后, 可以添加最新依赖:

  1. go get apimachinery@master
  2. go get client-go@master
  3. go get code-generator@master

定义Type与注册

首先需要在工程目录创建一个存放自己api module的地方, 如api, 并在之下创建beercontroller, 放置我们自定义资源相关的代码, 版本号可以自己定义, v1/v1alphav1.

  • types.go
  1. package v1alpha1
  2. import (
  3. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  4. )
  5. // +genclient
  6. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
  7. //Beer is a custom kubenetes resourec
  8. type Beer struct {
  9. metav1.TypeMeta `json:",inline"`
  10. metav1.ObjectMeta `json:"metadata,omitempty"`
  11. Spec BeerSpec `json:"spec"`
  12. }
  13. //BeerSpec is the spec of Beer
  14. type BeerSpec struct {
  15. BeerName string `json:"BeerName"`
  16. }
  17. // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
  18. //BeerList is a list of Beer
  19. type BeerList struct {
  20. metav1.TypeMeta `json:",inline"`
  21. metav1.ListMeta `json:"metadata"`
  22. Items []Beer `json:"items"`
  23. }

这里定义了Beer的成员BeerName, 且有两行给代码生成器的注释也很关键, 不能缺失.

  • register.go
  1. ...
  2. var (
  3. // SchemeBuilder initializes a scheme builder
  4. SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
  5. // AddToScheme is a global function that registers this API group & version to a scheme
  6. AddToScheme = SchemeBuilder.AddToScheme
  7. )
  8. func addKnownTypes(scheme *runtime.Scheme) error {
  9. scheme.AddKnownTypes(
  10. SchemeGroupVersion,
  11. &Beer{},
  12. &BeerList{},
  13. )
  14. // register the type in the scheme
  15. metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
  16. return nil
  17. }
  18. ...

这里省略了一些, 完整代码可参考官方sample. 这里主要的逻辑是将我们定义的资源注册上去, 当然由于还没有运行generator, AddKnownTypes会因为我们的Beer没有Deepcopy而报错.

生成代码

之前讲过需要实现一个完整的自定义k8s资源需要很多东西, 但实际上除了controller之外的都可以生成出来, 这里就使用到了code-generator里面的generate-groups.sh.

由于要使用该脚本, 这里我们可以添加vendor来管理我们的之前添加的依赖. 在工程的根目录输出git mod vendor来创建vendor目录, 这时所有之前添加过的依赖 (go.mod)都会出现在这个文件夹下供我们项目使用.

关于如何调用generator的逻辑可以参考官方sample, 这里我们需要给vendor整体(-R)赋予可执行权限, 否则在generate的过程会提示权限问题.

关于generate-groups.sh的使用需要注意的是它其中的2,3,4参数, 分别为输出目录, 输入目录(我们自定义资源的module所在的目录, 即之前创建的api文件夹), 自定义资源名:版本.

具体参数设置也可以参考:

  1. ../vendor/k8s.io/code-generator/generate-groups.sh "deepcopy,client,informer,lister" custom-k8s-controller/generated custom-k8s-controller/api beercontroller:v1alpha1 --go-header-file $(pwd)/boilerplate.go.txt --output-base $(pwd)/../../

在执行脚本过后, 它会在types.go旁生成deepcopy代码, 并会在我们指定的generated文件夹下生成informer, lister等代码.

编写与调用Controller

现在万事俱备只欠实现最后的自定义资源处理逻辑, 就是之前提到的controller.

首先我们需要定义controller的成员:

  1. type Controller struct {
  2. // kubeclientset is a standard kubernetes clientset
  3. kubeclientset kubernetes.Interface
  4. // beerclientset is a clientset for our own API group
  5. beerclientset clientset.Interface
  6. deploymentsLister appslisters.DeploymentLister
  7. deploymentsSynced cache.InformerSynced
  8. beersLister listers.beerLister
  9. beersSynced cache.InformerSynced
  10. // workqueue is a rate limited work queue. This is used to queue work to be
  11. // processed instead of performing it as soon as a change happens. This
  12. // means we can ensure we only process a fixed amount of resources at a
  13. // time, and makes it easy to ensure we are never processing the same item
  14. // simultaneously in two different workers.
  15. workqueue workqueue.RateLimitingInterface
  16. // recorder is an event recorder for recording Event resources to the
  17. // Kubernetes API.
  18. recorder record.EventRecorder
  19. }

其中kubeclientset可以调用k8s的CRUD api, 例如创建更新deployment; beerclientset可以调用自定义资源的CRUD; deploymentsLister与beersLister可以通过namespace/name获得对应资源; workqueue用来同步与限流多个自定义资源处理worker; recorder用来publish在资源处理过程中的事件, 该事件可以在kubectl describe里面看到.

接着我们可以创建构造函数将clientset, informer传入构建自己的controller对象, 并通过informer的AddEventHandler添加监听, 将回调的beer资源通过indexer(cache)的MetaNamespaceKeyFunc方法转换为key, 加入workqueue队列.

当然有队列必然有死循环去读取这个队列, 这也是我们启动整个controller的入口, 我们需要从workqueue中pop交由下游处理, 在处理成功后调用forget方法清除queue, 否则会重新进行处理.

处理资源是我们controller的核心逻辑, 这里我们可以再次通过indexer(cache)通过key反转回namespace/name, 然后通过listers查找对应的资源, 比如beer或者deployment, 通过对比自定义资源与背后k8s资源的区别, 对k8s资源进行CRUD.

  1. func (c *Controller) syncHandler(key string) error {
  2. // Convert the namespace/name string into a distinct namespace and name
  3. namespace, name, err := cache.SplitMetaNamespaceKey(key)
  4. if err != nil {
  5. utilruntime.HandleError(fmt.Errorf("invalid resource key: %s", key))
  6. return nil
  7. }
  8. // Get the beer resource with this namespace/name
  9. beer, err := c.beersLister.beers(namespace).Get(name)
  10. if err != nil {
  11. // The beer resource may no longer exist, in which case we stop
  12. // processing.
  13. klog.Infof("beer %s is deleted ", name)
  14. if errors.IsNotFound(err) {
  15. utilruntime.HandleError(fmt.Errorf("beer '%s' in work queue no longer exists", key))
  16. return nil
  17. }
  18. return err
  19. }
  20. deploymentName := beer.Spec.beerName
  21. if deploymentName == "" {
  22. // We choose to absorb the error here as the worker would requeue the
  23. // resource otherwise. Instead, the next time the resource is updated
  24. // the resource will be queued again.
  25. utilruntime.HandleError(fmt.Errorf("%s: deployment name must be specified", key))
  26. return nil
  27. }
  28. deployment, err := c.deploymentsLister.Deployments(beer.Namespace).Get(beer.Name)
  29. // If the resource doesn't exist, we'll create it
  30. if errors.IsNotFound(err) {
  31. deployment, err = c.kubeclientset.AppsV1().Deployments(beer.Namespace).Create(context.TODO(), newDeployment(beer), metav1.CreateOptions{})
  32. }
  33. // If an error occurs during Get/Create, we'll requeue the item so we can
  34. // attempt processing again later. This could have been caused by a
  35. // temporary network failure, or any other transient reason.
  36. if err != nil {
  37. return err
  38. }
  39. // ***********************
  40. // Handle custom CRUD here
  41. // ***********************
  42. c.recorder.Event(beer, corev1.EventTypeNormal, SuccessSynced, MessageResourceSynced)
  43. return nil
  44. }

整个syncHanlder函数分为四个部分:

  • 通过index做key->namespace/name
  • 通过lister查找beer资源
    • 如果不存在, 说明这是一次删除操作, 直接返回
  • 通过lister获取deployment
    • 如果不存在, 通过beer中相关字段创建对应的deployment, 或者其他k8s资源
  • 否则, 说明资源已经存在, 则对比beer与实际资源的区别, 判断是否需要对相应的资源, 如deployment进行更新. 这里举例子

当然在整个过程中也可以通过recorder来广播event, 这样在apply自定义资源后, 可以获取一定信息, 方便查看或者debug问题原因.

controller完整的逻辑存在大量boilerplate的代码, 具体也可以参考官方的sample进行学习.

最后还有一点需要注意的是, 在我们创建并启动自己的controller时, 即本次custom-k8s-controller的入口main package内, 除了调用构造以及启动函数, 一定要记得传入的Informer需要调用Start进行启动, 否则无法获得资源变化的回调. 当然这里也涉及到需要传入一个stopSignal可以让监听中断, 这一个可以直接copy官方代码.

总结

k8s已经提供了一个非常详尽的demo来实现自定义资源并通过controller进行配置, 但是对于没有go开发经验, 有不少由于版本, 依赖, 权限造成的小问题, 希望文章中highlight的点能提供一定帮助~

最后通过不懈努力, 我们可以通过一个自定义的Beer资源来deploy pod了. 由于这条路的打通, 我们可以继续添加CRD的schema, 并在controller内进行解析, 简化deploy过程, 统一基于k8s的微服务基础架构. 比如对于任意一个通过Beer资源deploy的app, 我们都通过istio sidecar来做ingress/outgress, 对于image的完整仓库地址, 我们也在后面通过不同部署(apply)环境来组合不同的地址, 还有大量缺省值的公共配置, 都不需要再重复的写在多个yaml中了.

Reference

原文链接:http://www.cnblogs.com/mengdd/p/custom-k8s-resource.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号