理解kubernetes tools/cache包: part 1

想知道如何编写k8s controller(例如我的案例),在java中,我们必须知道事件的真实来源。如果k8s可以被看作是一个分布式消息传递系统,你想要获得的是消息的specifications状态,那么“消息”来自何处呢?
在任意时刻,kubectl创建应用或者是删除某些内容,都需要发布消息。这些消息包括kubernetes想要创建,更新或删除的资源。这些消息由3部分组成:kind,表示消息的是什么类型;specification,描述了你想要的资源创建,更改或删除的状态;status,表示了实际运行状况的状态。通常,如果你正在编辑yaml文件并使用kubectl,那么你只需要关注specification因为只有kubernetes本身才能告诉你yaml运行的具体情况。
那么你需要向谁发送specification消息呢?答案是:api server。
我们以不同的方式看待它。在略高的级别,它也是一个消息代理,kubectl POST、PUT、PATCH或DELETE一个resource,api server接收它并将这个操作有效的广播给其它想对该操作响应的listeners。想要监听这些specification消息,你需要用到k8s的监听机制。
K8s提供了watch接口(但是实际上这种做法是不推荐使用的),当你使用后,k8s将返回一个WatchEvents流,描述该类型资源的Events。如果你愿意,消息channel可以根据kind过滤消息。K8s的watch是建立在etcd之上并继承了他们的概念属性。(更多特性可以查看文章kubernetes Events Can be Complicated)。
因此,要编写一个消息监听器来响应特定类型资源的specification消息,我们所要做的只是实现一个合适的k8s监听器,难道不是吗?答案是,不完全正确。虽然监听器比之前的要轻巧,但他们并不完全轻巧,更重要的是,你可能会对所有的specification变化做出相应,你可能会进入一个逻辑事件流中。因此,你首先要列出所要处理的事,然后再开始建立你的监听机制。
为了编写一个好的k8s controllers,有必要深入研究tools/cache包,它是用go语言实现的一个框架。这个包必须列出给定类型的所有资源,例如pod,deployment,然后监听它们。这个组合会给你提供对象的逻辑集合和对它们的修改。
果然,如果你足够深入,你最终会看到listwatch.go文件,在该文件里,你会看到listerWatcher类型,什么如下:

// ListerWatcher is any object that knows how to perform an initial list and start a watch on a resource.
type ListerWatcher interface {
    // List should return a list type object; the Items field will be extracted, and the
    // ResourceVersion field will be used to start the watch in the right place.
    List(options metav1.ListOptions) (runtime.Object, error)
    // Watch should begin a watch at the specified version.
    Watch(options metav1.ListOptions) (watch.Interface, error)
}

我们想找到这个类型的实现以支持由k8s客户端或http客户端等,因此,k8s apiserver应该要满足这样的List()函数。看,这里有个函数

// NewListWatchFromClient creates a new ListWatch from the specified client, resource, namespace and field selector.
func NewListWatchFromClient(c Getter, resource string, namespace string, fieldSelector fields.Selector) *ListWatch {
    listFunc := func(options metav1.ListOptions) (runtime.Object, error) {
        options.FieldSelector = fieldSelector.String()
        return c.Get().
            Namespace(namespace).
            Resource(resource).
            VersionedParams(&options, metav1.ParameterCodec).
            Do().
            Get()
    }
    watchFunc := func(options metav1.ListOptions) (watch.Interface, error) {
        options.Watch = true
        options.FieldSelector = fieldSelector.String()
        return c.Get().
            Namespace(namespace).
            Resource(resource).
            VersionedParams(&options, metav1.ParameterCodec).
            Watch()
    }
    return &ListWatch{ListFunc: listFunc, WatchFunc: watchFunc}
}

你可以在源码中进一步挖掘,你将会发现对c的调用实际上是在调用k8s客户端。
因此我们需要一个组件能够对指定类型list和watch。还要注意它能够fieldSelector,用来根据过滤消息流。不幸的是,fieldSelector现在还非常原始并且大部分还没文档介绍,但总比没有好。
假设我们的k8s client支持ListerWatcher,我们现在有指定类型的k8s逻辑集合以及与这类资源有关的一定数量的消息,我们应该将这些东西存储在哪里呢,放在Store里怎样?

type Store interface {
    Add(obj interface{}) error
    Update(obj interface{}) error
    Delete(obj interface{}) error
    List() []interface{}
    ListKeys() []string
    Get(obj interface{}) (item interface{}, exists bool, err error)
    GetByKey(key string) (item interface{}, exists bool, err error)

    // Replace will delete the contents of the store, using instead the
    // given list. Store takes ownership of the list, you should not reference
    // it after calling this function.
    Replace([]interface{}, string) error
    Resync() error
}

Store似乎比较好用和通用(除了go语言之外没有任何类型的参数),如果我们要扩展ListerWatcher以接收可以转存其内容的Store,那将是一件好事。基于各种原因,tools/cache包创建了Reflector的概念,它将ListerWatcher和Store组合在一起,并将ListerWatcher List()函数的返回值存进Store,同时将WatchEvents关联到Store的添加,更新和删除
我们想一想,为什么要叫它为reflector。从某种角度来说,它将k8s消息通道中的内容反映(reflector)到缓存中。然后你现在k8s controller中使用了定类型资源的specifications和statuses做逻辑时,你可以查看cache而不是直接访问apiserver。
需要注意的是,目前这个子系统是独立运行的,如果你有一个k8s客户端支持ListerWatcher,Store和Reflector绑定在一起,你就有了一个在很多场景下都适用的组件。更重要的是,从理论上来说,通过Rflector清理空间,理解一个缓存时间我们想要去响应,并且我们已经深入研究它的其它机制和实现细节。
如果我们利用面向对象编程和一点Java的概念(毕竟,这个系列是关于推到k8s controller框架的java实现),我们可以省去ListerWatcher概念并将其折叠成Reflector概念去实现它的细节。如果我们这样做,我们的这些模型可能会如下所示:
这里写图片描述
这里,KubernetesResourceReflector是Reflector(假设的)的具体实现,设置为由T参数表示的特定类型资源。在构造它时给它一个kubernetesClient和Store实现它并run(),它复制k8s指定类型资源cache到Store中(我们都知道实际使用了某种机制,类似于ListerWatcher机制,但是终端用户并不关心这个机制的实际listening和watching,只是填充了缓存)。随着时间的推移,我们将极大地改进和重构这个模型。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
这段代码是用来设置环境变量的,具体解释如下: ``` export PATH=$PWD/subtools/kaldi/utils/:$KALDI_ROOT/tools/openfst/bin:$PWD:$PATH ``` - `export PATH=` 表示将后面的路径添加到环境变量 PATH 中。 - `$PWD/subtools/kaldi/utils/` 是一个路径,指的是当前工作目录下的 `subtools/kaldi/utils/` 目录。 - `$KALDI_ROOT/tools/openfst/bin` 是另一个路径,指的是 Kaldi 工具箱所依赖的 OpenFST 库的二进制文件目录。 - `$PWD` 表示当前工作目录。 - `$PATH` 是系统默认的环境变量,表示可执行文件的搜索路径。 所以整个命令的意思是将当前工作目录下的 `subtools/kaldi/utils/` 目录和 Kaldi 工具箱所依赖的 OpenFST 库的二进制文件目录添加到系统默认的环境变量 PATH 的前面,这样在执行命令时就可以直接调用这些路径下的可执行文件。 ``` [ ! -f $KALDI_ROOT/tools/config/common_path.sh ] && echo >&2 "The standard file $KALDI_ROOT/tools/config/common_path.sh is not present -> Exit!" && exit 1 ``` 这一行代码是用来判断 `$KALDI_ROOT/tools/config/common_path.sh` 是否存在,如果不存在则输出错误信息并退出。 ``` . $KALDI_ROOT/tools/config/common_path.sh ``` 这一行代码是用来执行 `$KALDI_ROOT/tools/config/common_path.sh` 脚本文件,该文件定义了一些环境变量和函数,供 Kaldi 工具箱使用。其中 `.` 表示在当前 shell 环境中执行脚本文件,这样设置的环境变量和函数会在当前 shell 环境中生效。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值