DiscoveryController用于暴露CRD的版本信息和某个特定版本支持的Resource列表
一个简单的例子
- kubectl创建一个CRD
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
# name must match the spec fields below, and be in the form: <plural>.<group>
name: crontabs.stable.example.com
spec:
# group name to use for REST API: /apis/<group>/<version>
group: stable.example.com
# list of versions supported by this CustomResourceDefinition
versions:
- name: v1
# Each version can be enabled/disabled by Served flag.
served: true
# One and only one version must be marked as the storage version.
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
cronSpec:
type: string
image:
type: string
replicas:
type: integer
# either Namespaced or Cluster
scope: Namespaced
names:
# plural name to be used in the URL: /apis/<group>/<version>/<plural>
plural: crontabs
# singular name to be used as an alias on the CLI and for display
singular: crontab
# kind is normally the CamelCased singular type. Your resource manifests use this.
kind: CronTab
# shortNames allow shorter string to match your resource on the CLI
shortNames:
- ct
- DiscoveryController发现了这个CRD, 并暴露CR RESTful接口:
$ GET /apis/stable.example.com/v1
crontabs.stable.example.com
$ GET /apis/stable.example.com/
v1
v1beta1
- kubectl创建一个CR, kubectl 实际调用了CR RESTful接口创建CR.
apiVersion: "stable.example.com/v1"
kind: CronTab
metadata:
name: my-new-cron-object
spec:
cronSpec: "* * * * */5"
image: my-awesome-cron-image
DiscoveryController 定义
type DiscoveryController struct {
versionHandler *versionDiscoveryHandler
groupHandler *groupDiscoveryHandler
crdLister listers.CustomResourceDefinitionLister
crdsSynced cache.InformerSynced
// To allow injection for testing.
syncFn func(version schema.GroupVersion) error
queue workqueue.RateLimitingInterface
}
func NewDiscoveryController(crdInformer informers.CustomResourceDefinitionInformer, versionHandler *versionDiscoveryHandler, groupHandler *groupDiscoveryHandler) *DiscoveryController {
c := &DiscoveryController{
versionHandler: versionHandler,
groupHandler: groupHandler,
crdLister: crdInformer.Lister(),
crdsSynced: crdInformer.Informer().HasSynced,
// workqueue
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "DiscoveryController"),
}
// 监听CRD, 注册ResourceEventHandler
crdInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: c.addCustomResourceDefinition,
UpdateFunc: c.updateCustomResourceDefinition,
DeleteFunc: c.deleteCustomResourceDefinition,
})
c.syncFn = c.sync
return c
}
此时还是拿出那张万能小图:
ResourceEventHandler
func (c *DiscoveryController) enqueue(obj *apiextensionsv1.CustomResourceDefinition) {
for _, v := range obj.Spec.Versions {
// 实际入队的是GroupVersion对象
// schema.GroupVersion{Group: "stable.example.com", Version: "v1"}
c.queue.Add(schema.GroupVersion{Group: obj.Spec.Group, Version: v.Name})
}
}
// 创建CRD时触发执行
func (c *DiscoveryController) addCustomResourceDefinition(obj interface{}) {
castObj := obj.(*apiextensionsv1.CustomResourceDefinition)
klog.V(4).Infof("Adding customresourcedefinition %s", castObj.Name)
c.enqueue(castObj)
}
Sync方法
- Established状态
简单来说, Established状态即命名冲突等检测完全通过,可以提供使用的状态。
// Established means that the resource has become active. A resource is established when all names are
// accepted without a conflict for the first time. A resource stays established until deleted, even during
// a later NamesAccepted due to changed names. Note that not all names can be changed.
Established CustomResourceDefinitionConditionType = "Established"
- sync
func (c *DiscoveryController) sync(version schema.GroupVersion) error {
// 数组版本的已发现的GroupVersion
apiVersionsForDiscovery := []metav1.GroupVersionForDiscovery{}
// 存储CRD所描述的CR和CR subresource
apiResourcesForDiscovery := []metav1.APIResource{}
// 存储已发现GroupVersion
versionsForDiscoveryMap := map[metav1.GroupVersion]bool{}
// 列出所有CRD
crds, err := c.crdLister.List(labels.Everything())
if err != nil {
return err
}
foundVersion := false
foundGroup := false
for _, crd := range crds {
// 只对状态为apiextensionsv1.Established的CRD注册RESTful接口
if !apiextensionshelpers.IsCRDConditionTrue(crd, apiextensionsv1.Established) {
continue
}
// Group不相等则跳过
if crd.Spec.Group != version.Group {
continue
}
// Group相等, 寻找相等Version
foundThisVersion := false
var storageVersionHash string
for _, v := range crd.Spec.Versions {
// 如果这个版本并没有激活则跳过
if !v.Served {
continue
}
// If there is any Served version, that means the group should show up in discovery
foundGroup = true
// 如果之前没有发现这个版本,则标记为发现
gv := metav1.GroupVersion{Group: crd.Spec.Group, Version: v.Name}
if !versionsForDiscoveryMap[gv] {
versionsForDiscoveryMap[gv] = true
//
apiVersionsForDiscovery = append(apiVersionsForDiscovery, metav1.GroupVersionForDiscovery{
GroupVersion: crd.Spec.Group + "/" + v.Name,
Version: v.Name,
})
}
// 找到参数version的精确匹配版本
if v.Name == version.Version {
foundThisVersion = true
}
if v.Storage {
storageVersionHash = discovery.StorageVersionHash(gv.Group, gv.Version, crd.Spec.Names.Kind)
}
}
if !foundThisVersion {
continue
}
// 找到精确匹配的GroupVersion,则开始注册
foundVersion = true
verbs := metav1.Verbs([]string{"delete", "deletecollection", "get", "list", "patch", "create", "update", "watch"})
// if we're terminating we don't allow some verbs
if apiextensionshelpers.IsCRDConditionTrue(crd, apiextensionsv1.Terminating) {
verbs = metav1.Verbs([]string{"delete", "deletecollection", "get", "list", "watch"})
}
// 构造CR
apiResourcesForDiscovery = append(apiResourcesForDiscovery, metav1.APIResource{
Name: crd.Status.AcceptedNames.Plural,
SingularName: crd.Status.AcceptedNames.Singular,
Namespaced: crd.Spec.Scope == apiextensionsv1.NamespaceScoped,
Kind: crd.Status.AcceptedNames.Kind,
Verbs: verbs,
ShortNames: crd.Status.AcceptedNames.ShortNames,
Categories: crd.Status.AcceptedNames.Categories,
StorageVersionHash: storageVersionHash,
})
// 构造CR subresource
subresources, err := apiextensionshelpers.GetSubresourcesForVersion(crd, version.Version)
if err != nil {
return err
}
if subresources != nil && subresources.Status != nil {
apiResourcesForDiscovery = append(apiResourcesForDiscovery, metav1.APIResource{
Name: crd.Status.AcceptedNames.Plural + "/status",
Namespaced: crd.Spec.Scope == apiextensionsv1.NamespaceScoped,
Kind: crd.Status.AcceptedNames.Kind,
Verbs: metav1.Verbs([]string{"get", "patch", "update"}),
})
}
if subresources != nil && subresources.Scale != nil {
apiResourcesForDiscovery = append(apiResourcesForDiscovery, metav1.APIResource{
Group: autoscaling.GroupName,
Version: "v1",
Kind: "Scale",
Name: crd.Status.AcceptedNames.Plural + "/scale",
Namespaced: crd.Spec.Scope == apiextensionsv1.NamespaceScoped,
Verbs: metav1.Verbs([]string{"get", "patch", "update"}),
})
}
}
// Group注册的RESTful接口主要包括Group下获取支持的所有版本
// 没有找到Group,则删除对应Group注册的RESTful接口
if !foundGroup {
c.groupHandler.unsetDiscovery(version.Group)
c.versionHandler.unsetDiscovery(version)
return nil
}
sortGroupDiscoveryByKubeAwareVersion(apiVersionsForDiscovery)
// 如果找到Group,则注册对应Group的RESTful接口
apiGroup := metav1.APIGroup{
Name: version.Group,
Versions: apiVersionsForDiscovery,
// the preferred versions for a group is the first item in
// apiVersionsForDiscovery after it put in the right ordered
PreferredVersion: apiVersionsForDiscovery[0],
}
c.groupHandler.setDiscovery(version.Group, discovery.NewAPIGroupHandler(Codecs, apiGroup))
// GroupVersion注册的RESTful接口,主要包括GroupVersion下支持的所有CR
// 没有找到匹配的GroupVersion,说明Version已被删除,取消GroupVersion下的RESTful接口
if !foundVersion {
c.versionHandler.unsetDiscovery(version)
return nil
}
// 找到匹配的GroupVersion,注册GroupVersion下的RESTful接口
c.versionHandler.setDiscovery(version, discovery.NewAPIVersionHandler(Codecs, version, discovery.APIResourceListerFunc(func() []metav1.APIResource {
return apiResourcesForDiscovery
})))
return nil
}