kubernetes的 Aggregated API是什么呢?它是允许k8s的开发人员编写一个自己的服务,可以把这个服务注册到k8s的api里面,这样,就像k8s自己的api一样,你的服务只要运行在k8s集群里面,k8s 的Aggregate通过service名称就可以转发到你写的service里面去了。
这个设计理念:
第一是增加了api的扩展性,这样k8s的开发人员就可以编写自己的API服务器来公开他们想要的API。集群管理员应该能够使用这些服务,而不需要对核心库存储库进行任何更改。
第二是丰富了APIs,核心kubernetes团队阻止了很多新的API提案。通过允许开发人员将他们的API作为单独的服务器公开,并使集群管理员能够在不对核心库存储库进行任何更改的情况下使用它们,这样就无须社区繁杂的审查了
第三是开发分阶段实验性API的地方,新的API可以在单独的聚集服务器中开发,当它稳定之后,那么把它们封装起来安装到其他集群就很容易了。
第四是确保新API遵循kubernetes约定:如果没有这里提出的机制,社区成员可能会被迫推出自己的东西,这可能会或可能不遵循kubernetes约定。

一句话阐述就是:

Aggregator for Kubernetes-style API servers: dynamic registration, discovery summarization, secure proxy

动态注册、发现汇总、和安全代理。好了基本概念说清楚了,下面就说说实现。
如果你已经已经阅读我上一篇blog就知道,proxy的巨大作用。下面看看这个聚合api的神奇之处。
先看怎么使用,然后再看源代码

apiVersion: apiregistration.k8s.io/v1beta1
kind: APIService
metadata:
  name: v1alpha1.custom-metrics.metrics.k8s.io
spec:
  insecureSkipTLSVerify: true
  group: custom-metrics.metrics.k8s.io
  groupPriorityMinimum: 1000
  versionPriority: 15
  service:
    name: api
    namespace: custom-metrics
  version: v1alpha1

上面定义了资源类型为APIService,service名称为api,空间为custom-metrics的一个资源聚合接口。
下面带大家从源代码的角度来看你
pkg/apiserver/apiservice_controller.go
和k8s其它controller一样,watch变化分发到add、update和delete方法这套原理在此就不赘述了,如果有兴趣可以看我之前写的blog。

apiServiceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
        AddFunc:    c.addAPIService,
        UpdateFunc: c.updateAPIService,
        DeleteFunc: c.deleteAPIService,
    })

    serviceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
        AddFunc:    c.addService,
        UpdateFunc: c.updateService,
        DeleteFunc: c.deleteService,
    })

主要监听两种资源apiService和service,分别看看

func (s *APIAggregator) AddAPIService(apiService *apiregistration.APIService) error {
    // if the proxyHandler already exists, it needs to be updated. The aggregation bits do not
    // since they are wired against listers because they require multiple resources to respond
    if proxyHandler, exists := s.proxyHandlers[apiService.Name]; exists {
        proxyHandler.updateAPIService(apiService)
        if s.openAPIAggregationController != nil {
            s.openAPIAggregationController.UpdateAPIService(proxyHandler, apiService)
        }
        return nil
    }

    proxyPath := "/apis/" + apiService.Spec.Group + "/" + apiService.Spec.Version
    // v1. is a special case for the legacy API.  It proxies to a wider set of endpoints.
    if apiService.Name == legacyAPIServiceName {
        proxyPath = "/api"
    }

    // register the proxy handler
    proxyHandler := &proxyHandler{
        contextMapper:   s.contextMapper,
        localDelegate:   s.delegateHandler,
        proxyClientCert: s.proxyClientCert,
        proxyClientKey:  s.proxyClientKey,
        proxyTransport:  s.proxyTransport,
        serviceResolver: s.serviceResolver,
    }
    proxyHandler.updateAPIService(apiService)
    if s.openAPIAggregationController != nil {
        s.openAPIAggregationController.AddAPIService(proxyHandler, apiService)
    }
    s.proxyHandlers[apiService.Name] = proxyHandler
    s.GenericAPIServer.Handler.NonGoRestfulMux.Handle(proxyPath, proxyHandler)
    s.GenericAPIServer.Handler.NonGoRestfulMux.UnlistedHandlePrefix(proxyPath+"/", proxyHandler)

    // if we're dealing with the legacy group, we're done here
    if apiService.Name == legacyAPIServiceName {
        return nil
    }

    // if we've already registered the path with the handler, we don't want to do it again.
    if s.handledGroups.Has(apiService.Spec.Group) {
        return nil
    }

    // it's time to register the group aggregation endpoint
    groupPath := "/apis/" + apiService.Spec.Group
    groupDiscoveryHandler := &apiGroupHandler{
        codecs:        Codecs,
        groupName:     apiService.Spec.Group,
        lister:        s.lister,
        delegate:      s.delegateHandler,
        contextMapper: s.contextMapper,
    }
    // aggregation is protected
    s.GenericAPIServer.Handler.NonGoRestfulMux.Handle(groupPath, groupDiscoveryHandler)
    s.GenericAPIServer.Handler.NonGoRestfulMux.UnlistedHandle(groupPath+"/", groupDiscoveryHandler)
    s.handledGroups.Insert(apiService.Spec.Group)
    return nil
}

上面path是

proxyPath := "/apis/" + apiService.Spec.Group + "/" + apiService.Spec.Version

结合上面的例子就是/apis/custom-metrics.metrics.k8s.io/v1alpha1.
而处理方法请求的handle就是

proxyHandler := &proxyHandler{
        contextMapper:   s.contextMapper,
        localDelegate:   s.delegateHandler,
        proxyClientCert: s.proxyClientCert,
        proxyClientKey:  s.proxyClientKey,
        proxyTransport:  s.proxyTransport,
        serviceResolver: s.serviceResolver,
    }
    proxyHandler.updateAPIService(apiService)

上面的updateAPIService就是更新这个proxy的后端service

func (r *proxyHandler) updateAPIService(apiService *apiregistrationapi.APIService) {
    if apiService.Spec.Service == nil {
        r.handlingInfo.Store(proxyHandlingInfo{local: true})
        return
    }

    newInfo := proxyHandlingInfo{
        restConfig: &restclient.Config{
            TLSClientConfig: restclient.TLSClientConfig{
                Insecure:   apiService.Spec.InsecureSkipTLSVerify,
                ServerName: apiService.Spec.Service.Name + "." + apiService.Spec.Service.Namespace + ".svc",
                CertData:   r.proxyClientCert,
                KeyData:    r.proxyClientKey,
                CAData:     apiService.Spec.CABundle,
            },
        },
        serviceName:      apiService.Spec.Service.Name,
        serviceNamespace: apiService.Spec.Service.Namespace,
    }
    newInfo.proxyRoundTripper, newInfo.transportBuildingError = restclient.TransportFor(newInfo.restConfig)
    if newInfo.transportBuildingError == nil && r.proxyTransport.Dial != nil {
        switch transport := newInfo.proxyRoundTripper.(type) {
        case *http.Transport:
            transport.Dial = r.proxyTransport.Dial
        default:
            newInfo.transportBuildingError = fmt.Errorf("unable to set dialer for %s/%s as rest transport is of type %T", apiService.Spec.Service.Namespace, apiService.Spec.Service.Name, newInfo.proxyRoundTripper)
            glog.Warning(newInfo.transportBuildingError.Error())
        }
    }
    r.handlingInfo.Store(newInfo)
}

这个restConfig就是调用service的客户端参数,其中

ServerName: apiService.Spec.Service.Name + "." + apiService.Spec.Service.Namespace + ".svc",

就是具体的service。
而上面watch service的变化就是为了动态更新这个apiservice后端handler所用的service。