目录
梗概
实操
总结
第一章中主要演示前端通过请求后端 api 展示 deployment 列表;
本章依旧以 deployment 为例,借助 client-go 的 informer 机制,将 deployment 存入本地维护的一个缓存 map。并在添加、更新、删除时自动触发 handler,通过 websocket 通知到前端并重新渲染。
首先定义 map,key 为 namespace,value 为该 namespace 下的 deployment 实体列表:
type DeploymentMap struct {
data sync.Map // [key string] []*v1.Deployment key=>namespace
}
//添加
func (this *DeploymentMap) Add(dep *v1.Deployment) {
if list, ok := this.data.Load(dep.Namespace); ok {
list = append(list.([]*v1.Deployment), dep)
this.data.Store(dep.Namespace, list)
} else {
this.data.Store(dep.Namespace, []*v1.Deployment{dep})
}
}
//更新
func (this *DeploymentMap) Update(dep *v1.Deployment) error {
if list, ok := this.data.Load(dep.Namespace); ok {
for i, range_dep := range list.([]*v1.Deployment) {
if range_dep.Name == dep.Name {
list.([]*v1.Deployment)[i] = dep
}
}
return nil
}
return fmt.Errorf("deployment-%s not found", dep.Name)
}
// 删除
func (this *DeploymentMap) Delete(dep *v1.Deployment) {
if list, ok := this.data.Load(dep.Namespace); ok {
for i, range_dep := range list.([]*v1.Deployment) {
if range_dep.Name == dep.Name {
newList := append(list.([]*v1.Deployment)[:i], list.([]*v1.Deployment)[i+1:]...)
this.data.Store(dep.Namespace, newList)
break
}
}
}
}
然后是获得某 namespace 所有 deployment 的方法,期望列表是有序的,所以我们通过对deployment 切片定义成结构体(其实就是起别名),并且为该结构体实现 sort.Sort 所需要三种方法(Len、Less、Swap),来对返回的 deployment 切片结果进行默认排序。
type deployItems []*v1.Deployment
func (this deployItems) Len() int {
return len(this)
}
func (this deployItems) Less(i, j int) bool {
return this[i].CreationTimestamp.Time.Before(this[j].CreationTimestamp.Time)
}
func (this deployItems) Swap(i, j int) {
this[i], this[j] = this[j], this[i]
}
func (this *DeploymentMap) ListByNS(ns string) ([]*v1.Deployment, error) {
if list, ok := this.data.Load(ns); ok {
ret := list.([]*v1.Deployment)
sort.Sort(deployItems(ret))
return ret, nil
}
return nil, fmt.Errorf("record not found")
}
有了本地缓存 map,我们就可以通过加入 deployment 对应类型的 informer 去 watch 资源的变化,并且通过 handler 来对 map 中的内容进行增删改。
初始化 informer:
func InitInformer() informers.SharedInformerFactory {
fact := informers.NewSharedInformerFactory(this.InitClient(), 0)
depInformer := fact.Apps().V1().Deployments()
depInformer.Informer().AddEventHandler(this.DepHandler)
fact.Start(wait.NeverStop)
return fact
}
informer-handlers:
type DepHandler struct {
DepMap *DeploymentMap `inject:"-"`
}
func (this *DepHandler) OnAdd(obj interface{}) {
this.DepMap.Add(obj.(*v1.Deployment))
ns := obj.(*v1.Deployment).Namespace
}
func (this *DepHandler) OnUpdate(oldObj, newObj interface{}) {
err := this.DepMap.Update(newObj.(*v1.Deployment))
if err != nil {
log.Println(err)
}
}
func (this *DepHandler) OnDelete(obj interface{}) {
if d, ok := obj.(*v1.Deployment); ok {
this.DepMap.Delete(d)
}
ns := obj.(*v1.Deployment).Namespace
}
这里只是做一个思路的展示,因为诸如或者容器日志或者远程 shell 这类复杂的操作,往往通过多个资源的组合(namespace、deployment、pod..)来获取最终操作的对象,我们不可能去多次请求api,一是延迟问题,二是 apiserver 的压力问题(这个影响比较小)。
至此,就完成后端 api 对 deployment 资源的 watch 机制,当其发生变化时,会自动更新到本地缓存维护的一个 map 中。接下来,我们只需要把对应的数据通过 websocket 发送到前端,即可完成列表的更新,不需要使用者手动刷新页面。