解析kubernetes Aggregated API Servers

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。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
"In aggregated query without GROUP BY"是一个错误信息,表示在一个聚合查询中没有使用GROUP BY子句。在SQL中,当使用聚合函数(如SUM、COUNT、AVG等)时,如果SELECT列表中还包含其他非聚合的列,则需要使用GROUP BY子句来指定按照哪些列进行分组。 这个错误信息的意思是,SELECT列表中的表达式1包含了非聚合的列'base_table.user_id',而在当前的sql_mode设置中,只允许在聚合查询中使用GROUP BY来指定分组。因此,需要修改查询语句,将非聚合的列添加到GROUP BY子句中,或者使用聚合函数对该列进行处理。 请注意,具体的修复方法可能因查询语句的具体情况而有所不同,可以根据错误信息中提供的具体查询语句进行调整。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [In aggregated query without GROUP BY是什么错误](https://blog.csdn.net/weixin_44126489/article/details/124429951)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [MYSQL||报错:In aggregated query without GROUP BY, expression #1 of SELECT list contains ...](https://blog.csdn.net/Inochigohan/article/details/122380585)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柳清风09

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值