K8S中Golang的编程技巧
一、K8S层级函数接口风格:函数的链式调用k8s对象有层级关系的逻辑,一般一个接口有一个同名(首字母小写)的实现对象。函数返回接口不返回具体的类型主要是更关心对象需对外暴露的接口不关心对象的内部构造。函数调用形式为链式调用。// 创建pod informerpodInformer := informerFactory.Core().V1().Pods().Informer()//内部接口// 代码
·
一、K8S层级函数接口风格:函数的链式调用
k8s对象有层级关系的逻辑,一般一个接口有一个同名(首字母小写)的实现对象。函数返回接口不返回具体的类型主要是更关心对象需对外暴露的接口不关心对象的内部构造。函数调用形式为链式调用。
// 创建pod informer
podInformer := informerFactory.Core().V1().Pods().Informer()
//内部接口
// 代码源自client-go/informers/factory.go
func (f *sharedInformerFactory) Core() core.Interface {
// 调用了内核包里面的New()函数,详情见下文分析
return core.New(f, f.namespace, f.tweakListOptions)
}
// 代码源自client-go/informers/core/interface.go
// Interface又是一个被玩坏的名字,如果没有报名,根本不知道干啥的
type Interface interface {
V1() v1.Interface // 只有V1一个版本
}
// 这个是Interface的实现类,从名字上没任何关联吧?其实开发者命名也是挺有意思的,Interface定义的是接口
// 供外部使用,group也有意义,因为Core确实是内核Informer的分组
type group struct {
// 需要工厂对象的指针
factory internalinterfaces.SharedInformerFactory
// 这两个变量决定了Core这个分组对于SharedInformerFactory来说只有以下两个选项
namespace string
tweakListOptions internalinterfaces.TweakListOptionsFunc
}
// 构造Interface的接口
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
// 代码也挺简单的,不多说了
return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
}
// 实现V1()这个接口的函数
func (g *group) V1() v1.Interface {
// 通过调用v1包的New()函数实现的,下面会有相应代码的分析
return v1.New(g.factory, g.namespace, g.tweakListOptions)
}
// 代码源自client-go/informers/core/v1/interface.go
// 上面我们已经说过了version是v1.Interface的实现
func (v *version) Pods() PodInformer {
// 返回了podInformer的对象,说明podInformer是PodInformer 实现类
return &podInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
// 代码源自client-go/informers/core/v1/pod.go
// PodInformer定义了两个接口,分别为Informer()和Lister(),Informer()用来获取SharedIndexInformer对象
// Lister()用来获取PodLister对象,这个后面会有说明,当前可以不用关心
type PodInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1.PodLister
}
// PodInformer的实现类,参数都是上面层层传递下来的,这里不说了
type podInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// 实现了PodInformer.Informer()接口函数
func (f *podInformer) Informer() cache.SharedIndexInformer {
// 此处调用了工厂实现了Informer的创建
return f.factory.InformerFor(&corev1.Pod{}, f.defaultInformer)
}
二、利用闭包函数对资源进行封装
利用匿名函数进行函数封装调用:
// 代码源自client-go/informers/factory.go
// 这个是SharedInformerFactory构造函数的选项,是一个函数指针,传入的是工厂指针,返回也是工厂指针
// 很明显,选项函数直接修改工厂对象,然后把修改的对象返回就可以了
type SharedInformerOption func(*sharedInformerFactory) *sharedInformerFactory
// 把每个对象类型的同步周期这个参数转换为SharedInformerOption类型
// WithNamespace limits the SharedInformerFactory to the specified namespace.
func WithNamespace(namespace string) SharedInformerOption {
return func(factory *sharedInformerFactory) *sharedInformerFactory {
factory.namespace = namespace
return factory
}
}
//构造函数
func NewFilteredSharedInformerFactory(client kubernetes.Interface, defaultResync time.Duration, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerFactory {
// 最终是调用NewSharedInformerFactoryWithOptions()实现的,无非选项是2个
// WithNamespace()和WithTweakListOptions()会在后文讲解
return NewSharedInformerFactoryWithOptions(client, defaultResync, WithNamespace(namespace), WithTweakListOptions(tweakListOptions))
}
三、良好的对象封装函数
对资源结构体中的特定成员字段提供了特定的封装函数setter和getter,这对于成员变量多的结构体尤为方便与明确,在对结构体赋值修改的时候也保证的安全性(不影响其它字段),调用含义也明确。
// 代码源自:k8s.io/apimachinery/pkg/apis/meta/v1/types.go
// 同样来自apimachinery仓库,ObjectMeta是xxx.yaml中metadata字段,平时我们填写的metadata
type ObjectMeta struct {
// 对象的名字
Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"`
// 如果Name为空,系统这为该对象生成一个唯一的名字。
GenerateName string `json:"generateName,omitempty" protobuf:"bytes,2,opt,name=generateName"`
// 命名空间,在平时学习、调试的时候很少用,但是在发布的时候会经常用。
Namespace string `json:"namespace,omitempty" protobuf:"bytes,3,opt,name=namespace"`
// 对象的URL,由系统生成。
SelfLink string `json:"selfLink,omitempty" protobuf:"bytes,4,opt,name=selfLink"`
// 对象的唯一ID,由系统生成。
UID types.UID `json:"uid,omitempty" protobuf:"bytes,5,opt,name=uid,casttype=k8s.io/kubernetes/pkg/types.UID"`
// 资源版本,这是一个非常有意思且变量,版本可以理解为对象在时间轴上的一个时间节点,代表着对象最后
// 一次更新的时刻。如果说Name是在Namespace空间下唯一,那么ResourceVersion则是同名、同类型
// 对象时间下唯一。因为同名对象在不同时间可能会更新、删除再添加,在比较两个对象谁比较新的情况
// 非常有用,比如Watch。
ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,6,opt,name=resourceVersion"`
// 笔者很少关注这个值,所以也不太了解是干什么的,读者感兴趣可以自己了解一下。
Generation int64 `json:"generation,omitempty" protobuf:"varint,7,opt,name=generation"`
// 对象创建时间,由系统生成。
CreationTimestamp Time `json:"creationTimestamp,omitempty" protobuf:"bytes,8,opt,name=creationTimestamp"`
// 对象删除时间,指针类型说明是可选的,当指针不为空的时候说明对象被删除了,也是由系统生成
DeletionTimestamp *Time `json:"deletionTimestamp,omitempty" protobuf:"bytes,9,opt,name=deletionTimestamp"`
// 对象被删除前允许优雅结束的时间,单位为秒。
DeletionGracePeriodSeconds *int64 `json:"deletionGracePeriodSeconds,omitempty" protobuf:"varint,10,opt,name=deletionGracePeriodSeconds"`
// 对象标签,这个是我们经常用的,不用多解释了
Labels map[string]string `json:"labels,omitempty" protobuf:"bytes,11,rep,name=labels"`
// 批注,这个和标签很像,但是用法不同,比如可以用来做配置。
Annotations map[string]string `json:"annotations,omitempty" protobuf:"bytes,12,rep,name=annotations"`
// 该对象依赖的对象类表,如果这些依赖对象全部被删除了,那么该对象也会被回收。如果该对象对象被
// 某一controller管理,那么类表中有一条就是指向这个controller的。例如Deployment对象的
// OwnerReferences有一条就是指向DeploymentController的。
OwnerReferences []OwnerReference `json:"ownerReferences,omitempty" patchStrategy:"merge" patchMergeKey:"uid" protobuf:"bytes,13,rep,name=ownerReferences"`
// 下面这几个变量笔者没有了解过,等笔者知道了再来更新文章吧。
Finalizers []string `json:"finalizers,omitempty" patchStrategy:"merge" protobuf:"bytes,14,rep,name=finalizers"`
ClusterName string `json:"clusterName,omitempty" protobuf:"bytes,15,opt,name=clusterName"`
ManagedFields []ManagedFieldsEntry `json:"managedFields,omitempty" protobuf:"bytes,17,rep,name=managedFields"`
}
// 代码源自k8s.io/apimachinery/pkg/apis/meta/v1/meta.go,GetObjectMeta是MetaAccessor
// 的接口函数,这个函数说明了ObjectMeta实现了MetaAccessor。
func (obj *ObjectMeta) GetObjectMeta() Object { return obj }
// 下面所有的函数是接口Object的ObjectMeta实现,可以看出来基本就是setter/getter方法。纳尼?
// 又一个Object(这个Object是metav1.Object,本章节简写为Object)?Object是API对象公共属性
// (meta信息)的抽象,下面的函数是Object所有函数的实现,因为功能比较简单,笔者就不一一注释了。
func (meta *ObjectMeta) GetNamespace() string { return meta.Namespace }
func (meta *ObjectMeta) SetNamespace(namespace string) { meta.Namespace = namespace }
func (meta *ObjectMeta) GetName() string { return meta.Name }
func (meta *ObjectMeta) SetName(name string) { meta.Name = name }
func (meta *ObjectMeta) GetGenerateName() string { return meta.GenerateName }
func (meta *ObjectMeta) SetGenerateName(generateName string) { meta.GenerateName = generateName }
func (meta *ObjectMeta) GetUID() types.UID { return meta.UID }
func (meta *ObjectMeta) SetUID(uid types.UID) { meta.UID = uid }
func (meta *ObjectMeta) GetResourceVersion() string { return meta.ResourceVersion }
func (meta *ObjectMeta) SetResourceVersion(version string) { meta.ResourceVersion = version }
func (meta *ObjectMeta) GetGeneration() int64 { return meta.Generation }
func (meta *ObjectMeta) SetGeneration(generation int64) { meta.Generation = generation }
func (meta *ObjectMeta) GetSelfLink() string { return meta.SelfLink }
func (meta *ObjectMeta) SetSelfLink(selfLink string) { meta.SelfLink = selfLink }
func (meta *ObjectMeta) GetCreationTimestamp() Time { return meta.CreationTimestamp }
func (meta *ObjectMeta) SetCreationTimestamp(creationTimestamp Time) {
meta.CreationTimestamp = creationTimestamp
}
func (meta *ObjectMeta) GetDeletionTimestamp() *Time { return meta.DeletionTimestamp }
func (meta *ObjectMeta) SetDeletionTimestamp(deletionTimestamp *Time) {
meta.DeletionTimestamp = deletionTimestamp
}
func (meta *ObjectMeta) GetDeletionGracePeriodSeconds() *int64 { return meta.DeletionGracePeriodSeconds }
func (meta *ObjectMeta) SetDeletionGracePeriodSeconds(deletionGracePeriodSeconds *int64) {
meta.DeletionGracePeriodSeconds = deletionGracePeriodSeconds
}
func (meta *ObjectMeta) GetLabels() map[string]string { return meta.Labels }
func (meta *ObjectMeta) SetLabels(labels map[string]string) { meta.Labels = labels }
func (meta *ObjectMeta) GetAnnotations() map[string]string { return meta.Annotations }
func (meta *ObjectMeta) SetAnnotations(annotations map[string]string) { meta.Annotations = annotations }
func (meta *ObjectMeta) GetFinalizers() []string { return meta.Finalizers }
func (meta *ObjectMeta) SetFinalizers(finalizers []string) { meta.Finalizers = finalizers }
func (meta *ObjectMeta) GetOwnerReferences() []OwnerReference { return meta.OwnerReferences }
func (meta *ObjectMeta) SetOwnerReferences(references []OwnerReference) {
meta.OwnerReferences = references
}
func (meta *ObjectMeta) GetClusterName() string { return meta.ClusterName }
func (meta *ObjectMeta) SetClusterName(clusterName string) { meta.ClusterName = clusterName }
func (meta *ObjectMeta) GetManagedFields() []ManagedFieldsEntry { return meta.ManagedFields }
func (meta *ObjectMeta) SetManagedFields(managedFields []ManagedFieldsEntry) {
meta.ManagedFields = managedFields
四、并发协程优雅的退出
// Run begins processing items, and will continue until a value is sent down stopCh.
// It's an error to call Run more than once.
// Run blocks; call via go.
func (c *controller) Run(stopCh <-chan struct{}) {
defer utilruntime.HandleCrash()
go func() {
<-stopCh
// 创建一个协程,如果收到系统退出的信号就关闭队列,相当于在这里析构的队列
c.config.Queue.Close()
}()
r := NewReflector(
c.config.ListerWatcher,
c.config.ObjectType,
c.config.Queue,
c.config.FullResyncPeriod,
)
...
var wg wait.Group
defer wg.Wait()
wg.StartWithChannel(stopCh, r.Run)
//只要没有收到退出信号就会周期的执行传入的函数
wait.Until(c.processLoop, time.Second, stopCh)
}
// 代码源自client-go/tools/cache/reflector.go
func (r *Reflector) Run(stopCh <-chan struct{}) {
// func Until(f func(), period time.Duration, stopCh <-chan struct{})是下面函数的声明
// 这里面我们不用关心wait.Until是如何实现的,只要知道他调用函数f会被每period周期执行一次
// 意思就是f()函数执行完毕再等period时间后在执行一次,也就是r.ListAndWatch()会被周期性的调用
wait.Until(func() {
if err := r.ListAndWatch(stopCh); err != nil {
utilruntime.HandleError(err)
}
}, r.period, stopCh)
}
// 代码源自client-go/tools/cache/reflector.go
func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
// 前面已经列举了全量对象,接下来就是watch的逻辑了
for {
// 如果有退出信号就立刻返回,否则就会往下走,因为有default.
select {
case <-stopCh:
return nil
default:
}
...//逻辑处理
}
}
StartWithChannel函数的实现, StartWithChannel()会启动协程执行Reflector.Run(),同时接收到stopCh信号就会退出协程
package wait
// Group allows to start a group of goroutines and wait for their completion.
type Group struct {
wg sync.WaitGroup
}
// StartWithChannel starts f in a new goroutine in the group.
// stopCh is passed to f as an argument. f should stop when stopCh is available.
func (g *Group) StartWithChannel(stopCh <-chan struct{}, f func(stopCh <-chan struct{})) {
g.Start(func() {
f(stopCh)
})
}
// Start starts f in a new goroutine in the group.
func (g *Group) Start(f func()) {
g.wg.Add(1)
go func() {
defer g.wg.Done()
f()
}()
}
func (g *Group) Wait() {
g.wg.Wait()
}
更多推荐
已为社区贡献4条内容
所有评论(0)