6.2.2 List-Watch简介
顾名思义,List-Watch分为List和Watch两种能力,其中List基于HTTP短连接实现,通过调用服务的ListAPI进行查询;Watch基于HTTP长连接实现,通过调用服务的watch接口来监听资源变更事件。在这里需要着重关注Watch的能力,它基于HTTP的分块传输编码(chunkedtransferencoding),允许服务器为动态生成的内容维持HTTP的长连接。通常长连接需要服务器在开始发送消息体前发送Content-Length消息头字段,但是动态生成的内容在内容创建完之前是不可知的,使用分块传输编码可以把数据分解成一系列数据块并以一个或多个块发送,这样服务器可以发送数据而不需要预先知道发送内容的总大小。所以,当客户端向APIServer调用watch接口时,APIServer会在返回的响应中设置Transfer-Encoding?=?chunked,表示采用分块传输编码。这样客户端便会和APIServer保持一个长连接并等待下一个数据块的到来。而这些数据块其实就是事件信息,这些事件信息包含事件类型和对应的对象定义,如代码清单6-2所示。
代码清单6-2Watch中的事件信息:
{"type":"ADDED", "object":{"kind":"Pod","apiVersion":"v1",...}}
{"type":"ADDED", "object":{"kind":"Pod","apiVersion":"v1",...}}
{"type":"MODIFIED", "object":{"kind":"Pod","apiVersion":"v1",...}}
{"type":"DELETED", "object":{"kind":"Pod","apiVersion":"v1",...}}
这样通过watch接口客户端可以实时从APIServer接收最新的事件,用户只需要针对不同的事件做相应的处理即可,而用户也可以通过处理这些事件来完成稳定性测试需要的监控能力。接下来,通过代码清单6-3来看一下如何通过代码来调用watch接口。
代码清单6-3Go语言中的watch接口:
watchPods := func(namespace string, k8s *kubernetes.Clientset) (watch.
Interface, error) {
return k8s.CoreV1().Pods(namespace).Watch(context.Background(),
metav1.ListOptions{})
}
podWatcher, err := watchPods(watcher.Namespace, watcher.K8s)
if err != nil {
log.Errorf("watch pod of namespace %s failed, err:%s", watcher.Namespace, err)
watcher.handleK8sErr(err)
}
for {
event, ok := <-podWatcher.ResultChan()
if !ok || event.Object == nil {
log.Info("the channel or Watcher is closed")
podWatcher, err = watchPods(watcher.Namespace, watcher.K8s)
if err != nil {
watcher.handleK8sErr(err)
time.Sleep(time.Minute * 5)
}
continue
}
…
在代码清单6-3中,需要注意以下几点。
·需要监控哪个对象就调用该对象的Watch函数。
· Watch函数会返回一个channel,所有的事件都会被保存在这个channel中,用户需要使用循环的方式不停地从channel中获取对应的事件。
· 每过一段时间channel就会关闭,需要重新调用Watch函数,所以在代码清单6-3中一开始就封装了一个watchPods函数。在后面的循环中每次都要判断channel是否为关闭状态,如果关闭了就需要重新调用封装好的watchPods函数来转换channel状态。
从channel中取出的事件包含事件类型和事件所对应的对象定义,如代码清单6-4所示。
代码清单6-4Event源码:
// Event表示一个被监控对象的独立事件
type Event struct {
Type EventType
// * 如果事件类型是ADDED(增加)或MODIFIED(修改),那么Object表示该事件所属对象的最新状态
// * 如果事件类型是DELETED(删除),那么Object表示该事件包含对象在删除前的状态
Object runtime.Object
}
// EventType定义了事件所有可能的类型
type EventType string
const (
Added EventType = "ADDED"
Modified EventType = "MODIFIED"
Deleted EventType = "DELETED"
Bookmark EventType = "BOOKMARK"
Error EventType = "ERROR"
)
用户可以判断事件的类型以选择不同的逻辑,同时也可以将事件中的对象类型转换为具体的K8s对象,如代码清单6-5所示。
代码清单6-5事件的类型转换:
if event.Type == watch.Error || event.Type == watch.Modified || event.Type
== watch.Deleted {
pod, _ := event.Object.(*corev1.Pod)
}
通过代码清单6-5中的方式可以获取到当前事件中所包含的对象,由此可以从中获取到该对象的各种信息以判断当前是否有异常情况出现并抓取具体的异常信息,而这些构成了本章需要的监控组件的基础。代码清单6-6所示是Python的调用watch接口的方式。
代码清单6-6Python调用watch接口:
from kubernetes import client, config, watch
# 配置可以直接在配置类中设置,也可以使用助手实用程序设置
config.load_kube_config()
v1 = client.CoreV1Api()
count = 10
w = watch.Watch()
for event in w.stream(v1.list_namespace, _request_timeout = 60):
print("Event: %s %s" % (event['type'], event['object'].metadata.name))
count -= 1
if not count:
w.stop()
print("Ended.")
6.2.3 小结
List-Watch机制是K8s中非常著名的设计,其高性能的表现也让众多高级API和开源项目选择K8s,例如控制器并没有直接调用List-Watch最原始的能力,而是通过一个利用List-Watch能力的名为informer的高级API完成调用。最近开源的kubebuilder项目则是做了更高级的封装来帮助用户简化控制器的代码,但不管后续出现多少高级API,它们的底层一定是利用List-Watch机制来完成对象查询的。所以,了解List-Watch的原理对于后续的学习至关重要。