目录

梗概

实操

总结


梗概

第一章中主要演示前端通过请求后端 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 发送到前端,即可完成列表的更新,不需要使用者手动刷新页面。

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐