kind: APIService
metadata:
labels:
k8s-app: metrics-server
name: v1beta1.metrics.k8s.io
spec:
# k8s會轉發所有發送到 /apis/metrics.k8s.io/v1beta1/ 的請求
group: metrics.k8s.io
version: v1beta1
# 發送到 /apis/metrics.k8s.io/v1beta1/ 的請求會被轉發給這個 service
service:
name: metrics-server
namespace: kube-system

而GVK(metrics.k8s.io/v1beta1)下的資源定義是在service背后的服務中定義,代碼地址:https://github.com/kubernetes/metrics/blob/master/pkg/apis/metrics/types.go#L31,你甚至可以暫時理解為和k8s的內建類型(Pod、Deployment)的定義模式一致,沒有CRD實體。

參閱 https://kubernetes.io/docs/tasks/debug/debug-cluster/resource-metrics-pipeline/ 官方文檔的例子,發送到 /apis/metrics.k8s.io/v1beta1 的請求被轉發給以AA方式注冊到K8S的MetricsServer服務

$ kubectl get --raw "/apis/metrics.k8s.io/v1beta1/nodes/minikube" | jq '.'
{
"kind": "NodeMetrics",
"apiVersion": "metrics.k8s.io/v1beta1",
"metadata": {
"name": "minikube",
"selfLink": "/apis/metrics.k8s.io/v1beta1/nodes/minikube",
"creationTimestamp": "2022-01-27T18:48:43Z"
},
"timestamp": "2022-01-27T18:48:33Z",
"window": "30s",
"usage": {
"cpu": "487558164n",
"memory": "732212Ki"
}
}

其中對 metrics.k8s.io/v1beta1 下的node資源的CURD請求將會交由對象 nodeMetrics 處理,核心代碼在 https://github.com/kubernetes-sigs/metrics-server/blob/master/pkg/api/node.go ,從下面的代碼可以看到,nodeMetrics 只要實現 k8s.io/apiserver/pkg/registry/rest 包下的幾個關鍵接口,就可以為資源對象 node 賦予GET(實現rest.Getter接口)、LIST(實現rest.Lister接口),類似的,實現了 rest.Creater 接口就可以為資源對象實現CREAT功能,具體細節在下篇文章解釋,大家有個大概的概念就行。

var _ rest.KindProvider = &nodeMetrics{}
var _ rest.Storage = &nodeMetrics{}
var _ rest.Getter = &nodeMetrics{}
var _ rest.Lister = &nodeMetrics{}
var _ rest.Scoper = &nodeMetrics{}
var _ rest.TableConvertor = &nodeMetrics{}
var _ rest.SingularNameProvider = &nodeMetrics{}

既然K8S把請求轉發到 APIService 指定的 service 中,那么這個service背后的服務需要以K8S的規范來提供API服務,這時候我們的 k8s.io/apiserver登場了,你可以基于 k8s.io/apiserver 包啟動一個基于K8S規范的APIServer服務,對外提供的API對象只要實現 k8s.io/apiserver 中提供的接口,就能創建一個符合K8S規范的資源對象,本文不會告訴你如何使用 k8s.io/apiserver開發一個 APIServer,我會在下一篇文章(文章已經寫完了,排版好之后就會發布到公眾號上)中從源碼層面告訴你如何開發一個符合K8S規范的APIServer

下面我們對照著k8s源碼來學習k8s是如何實現把請求 代理 到APIService后的服務這個動作,請認真閱讀這段代碼,在后面的例子中會頻繁出現類似的處理邏輯

代碼地址:https://github.com/kubernetes/kube-aggregator/blob/5544326a401d11ff3c58aac83b0c60c30e129ae9/pkg/apiserver/handler_proxy.go#L100-L171

整體的流程大概是:

  1. 根據APIService的配置解析出用戶自定義服務的地址
  2. 構造新的請求,把發送給Kube-APIServer請求的目標地址修改為AA的SVC地址
  3. 基于APIService中的服務信息,構造代理Handler

??重點,第三步中 proxy.NewUpgradeAwareHandler ?創建一個協議升級感知的Handler,因為k8s中的請求中有很多協議升級的場景(例如 pod exec 用到的spdy),NewUpgradeAwareHandler 方法會幫你處理好協議升級的場景,后面會經常看到NewUpgradeAwareHandler這個方法的使用

func (r *proxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
value := r.handlingInfo.Load()
if value == nil {
r.localDelegate.ServeHTTP(w, req)
return
}
handlingInfo := value.(proxyHandlingInfo)
...
// write a new location based on the existing request pointed at the target service
location := &url.URL{}
location.Scheme = "https"
// 根據APIService的配置解析出用戶自定義服務的地址
rloc, err := r.serviceResolver.ResolveEndpoint(handlingInfo.serviceNamespace, handlingInfo.serviceName, handlingInfo.servicePort)
if err != nil {
klog.Errorf("error resolving %s/%s: %v", handlingInfo.serviceNamespace, handlingInfo.serviceName, err)
proxyError(w, req, "service unavailable", http.StatusServiceUnavailable)
return
}
// 構造新的請求,把發送給Kube-APIServer請求的目標地址修改為AA的SVC地址
location.Host = rloc.Host
location.Path = req.URL.Path
location.RawQuery = req.URL.Query().Encode()

// 對原始請求深拷貝構建新的轉發請求
newReq, cancelFn := apiserverproxyutil.NewRequestForProxy(location, req)
defer cancelFn()

if handlingInfo.proxyRoundTripper == nil {
proxyError(w, req, "", http.StatusNotFound)
return
}

proxyRoundTripper := handlingInfo.proxyRoundTripper
upgrade := httpstream.IsUpgradeRequest(req)

proxyRoundTripper = transport.NewAuthProxyRoundTripper(user.GetName(), user.GetGroups(), user.GetExtra(), proxyRoundTripper)

// If we are upgrading, then the upgrade path tries to use this request with the TLS config we provide, but it does
// NOT use the proxyRoundTripper. It's a direct dial that bypasses the proxyRoundTripper. This means that we have to
// attach the "correct" user headers to the request ahead of time.
if upgrade {
transport.SetAuthProxyHeaders(newReq, user.GetName(), user.GetGroups(), user.GetExtra())
}
// !!重點,創建一個協議升級感知的Handler,因為k8s中的請求中有很多協議升級的場景(例如 pod exec 用到的spdy),
// NewUpgradeAwareHandler 方法會幫你處理好協議升級的場景,后面會經常看到NewUpgradeAwareHandler這個方法
// 的使用
// 基于APIService中的服務信息,構造代理Handler
handler := proxy.NewUpgradeAwareHandler(location, proxyRoundTripper, true, upgrade, &responder{w: w})
if r.rejectForwardingRedirects {
handler.RejectForwardingRedirects = true
}
utilflowcontrol.RequestDelegated(req.Context())
// 這里請求就轉發到了用戶啟動的服務上,
handler.ServeHTTP(w, newReq)
}

關于AA的原理介紹就結束了,讀者可以基于AA的原理繼續看下面的開源項目是如何做定制化開發

代理網關

Cluster-Gateway

一切的源頭都要從 https://github.com/oam-dev/cluster-gateway 這個項目開始,該項目作為 KubeVela 多集群功能的核心模塊,管控組件對不同集群的請求交由 cluster-gateway 組件代理,我們先通過這個組件來了解市面上的一些實現K8S-API代理的開源組件的實現原理

下面展示了Cluster-Gateway的設計圖,我們把項目中關于 Cluster-Gateway 的關鍵設計提煉為4點:

  1. 組件是基于 apiserver-aggregation[1] 的方式提供擴展API
  2. 基于AA模式抽象出 ClusterGateway 資源用來代表不同集群
  3. 受到 “service/proxy”, “pod/proxy” 這類子資源的設計影響,也為 ClusterGateway 設計了proxy子資源實現請求代理的核心邏輯
  4. hub集群存儲能夠訪問被納管集群的訪問憑證,你可以理解為kubeconfig,cluster-gateway使用這些kubeconfig完成請求的代理轉發

首先我們通過介紹 KubeVela 中的多集群應用管理功能的設計和需求背景來了解 Cluster-Gateway 在多集群管理中發揮的作用。

下圖展示了KubeVela的一個多集群應用是如何完成資源多集群的分發,其中多集群應用的配置是存放在Hub集群中,并被被Hub集群的 KubeVela 控制器管理,根據應用的配置中的多集群調度策略,選擇不同的集群下下發資源配置。

講到這里,請大家思考一個這樣的問題:KubeVela是如何在控制器內完成把應用配置下發到不同的集群內?

聰明的你可能會想到在控制器內管理多個不同的集群的 client,需要向特定集群下發配置的時候切換到指定的集群client,這種方法會帶來很多問題:

  1. 1. 首先維護多個集群的client的成本就不必多說,
  2. 2. 當有新集群納管到多集群的時候,又如何自動化的注冊新的client,
  3. 3. 假設被納管集群因為網絡環境的特殊性不能直接通過管控集群直接訪問,如何處理

KubeVela給出了一個相對優雅的解決方案,在管控集群中安裝以AA模式部署的Cluster-Gateway,所有發往納管集群的請求都會被轉為請求管控集群 clustergateway/proxy 子資源的請求,所有的請求鏈路都在管控集群內完成,KubeVela控制器只要持有管控集群的KubeConfig和Client即可,具體如何實現請求的轉換,涉及到client-go的一個黑魔法,一會會詳細介紹。

請讀者注意下圖中標紅部分的請求路徑的轉換,

  1. 首先KubeVela控制器請求cluster0集群創建deploy,該請求是直接發往管控集群的APIServer,經過一些客戶端的黑魔法,該請求會被封裝為對 clustergateway/proxy 子資源的請求,其中對deploy資源進行操作的API路徑(/apis/apps/v1/deployments)會被附加在 clustergateway/proxy 后,所以最終請求被轉為 clustergateway/proxy/apis/apps/v1/deployments
  2. 因為clustergateway資源對象是通過AA的模式注冊到管控集群,該請求便會被轉到Cluster-Gateway,Cluster-Gateway提取出放在proxy資源后的正常請求,把封裝后的請求轉為對cluster0集群的Kube-APIServer的正常請求

大家再回去看看上面貼出的 Cluster-Gateway 的原理圖,是不是就清晰很多,下面統一回答遺留的幾個問題:

1.kubevela是如何在只持有管控集群的client的情況下完成請求路徑的轉換?

完成請求路徑轉換的核心代碼如下,代碼地址:https://github.com/kubevela/kubevela/blob/fdcdf659d89eb81e9b381794b2c72c5e7a6d1e85/pkg/cmd/factory.go#L97

func NewDefaultFactory(cfg *rest.Config) Factory {
copiedCfg := *cfg
copiedCfg.RateLimiter = DefaultRateLimiter
copiedCfg.Wrap(multicluster.NewTransportWrapper())
return &defaultFactory{cfg: &copiedCfg}
}

關鍵點是調用了rest.Config的Wrap方法來構造 rest.Config 對象,這里你可以把 multicluster.NewTransportWrapper() 理解為一個 Middleware,所有client的請求都會經過這個中間層處理發往 APIServer

 // RoundTrip is the main function for the re-write API path logic
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
cluster := t.getClusterFor(req)
If !IsLocal(cluster) {
req = req.Clone(req.Context())
req.URL.Path = formatProxyURL(cluster, req.URL.Path)
}
return t.delegate.RoundTrip(req)
}

經常使用client-go的同學知道,在對資源進行CURD的時候,調用的方法都會傳遞 context,kubevela在這個ctx里注入了想要請求的集群信息,在 Middleware 處理 http.Request 的時候,可以通過 http.Request 的 Context方法獲取到client-go傳遞的 ctx,并提取集群信息,用來構建轉換路徑,感興趣的同學可以看下 t.getClusterFor(req) 里的實現。

2.cluster-gateway是如何完成請求的代理?

cluster-gateway 抽象出了 clustergateways/proxy 子資源實現了 Connecter 接口(關于這個接口的細節會在下一篇文章討論,如果不理解也沒關系),用來代理發往被納管集群的請求,以pod/proxy為例,這個子資源也是實現了 Connecter 接口用來代理發往Pod內進程的請求,這里思路做一些轉換你可以把被納管的集群當成Pod進程,這里也是簡單做了一層代理,只是后面的服務變成了k8s。

// Connecter is a storage object that responds to a connection request.
type Connecter interface {
// Connect returns an http.Handler that will handle the request/response for a given API invocation.
// The provided responder may be used for common API responses. The responder will write both status
// code and body, so the ServeHTTP method should exit after invoking the responder. The Handler will
// be used for a single API request and then discarded. The Responder is guaranteed to write to the
// same http.ResponseWriter passed to ServeHTTP.
Connect(ctx context.Context, id string, options runtime.Object, r Responder) (http.Handler, error)

// NewConnectOptions returns an empty options object that will be used to pass
// options to the Connect method. If nil, then a nil options object is passed to
// Connect. It may return a bool and a string. If true, the value of the request
// path below the object will be included as the named string in the serialization
// of the runtime object.
NewConnectOptions() (runtime.Object, bool, string)

// ConnectMethods returns the list of HTTP methods handled by Connect
ConnectMethods() []string
}

我們來看下 clustergateways/proxy 在 Connect 方法中是如何完成的代理,代碼地址:https://github.com/oam-dev/cluster-gateway/blob/48259e095bb66d5a98b4203def038b359e408391/pkg/apis/cluster/v1alpha1/clustergateway_proxy.go#L157

return &proxyHandler{
parentName: id,
path: proxyOpts.Path,
impersonate: proxyOpts.Impersonate,
clusterGateway: clusterGateway,
responder: r,
finishFunc: func(code int) {
metrics.RecordProxiedRequestsByResource(proxyReqInfo.Resource, proxyReqInfo.Verb, code)
metrics.RecordProxiedRequestsByCluster(id, code)
metrics.RecordProxiedRequestsDuration(proxyReqInfo.Resource, proxyReqInfo.Verb, id, code, time.Since(ts))
},
}, nil

Connect方法最終返回了一個實現了http.Handler 接口的 proxyHandler 對象,在這個對象的ServerHTTP方法內完成請求的代理 ,下面列出了ServerHTTP方法的部分實現,請讀者回顧一開始我們分析 AA 模式是如何實現請求的代理,和這里的代理功能實現原理基本一致,正如我之前提到的,后面你還會見到類似的代碼。唯一的不同點是AA是通過APIService對象里的信息構造轉發請求,cluster-gateway則是通過存在secret里的納管集群的kubeconfig構造轉發請求。

下面是通過restconfig構造請求的核心代碼,代碼地址:https://github.com/oam-dev/cluster-gateway/blob/48259e095bb66d5a98b4203def038b359e408391/pkg/apis/cluster/v1alpha1/clustergateway_proxy.go#L222

....
// 該方法返回一個http.RoundTripper對象,該對象會根據提供的
// rest.Config定義的身份驗證信息和k8s-apiserver交互
rt, err := restclient.TransportFor(cfg)
if err != nil {
responsewriters.InternalError(writer, request, errors.Wrapf(err, "failed creating cluster proxy client %s", cluster.Name))
return
}
// 和前面AA的構造代理Handler所調用的NewUpgradeAwareHandler是同一方法
proxy := apiproxy.NewUpgradeAwareHandler(
&url.URL{
Scheme: urlAddr.Scheme,
Path: newReq.URL.Path,
Host: urlAddr.Host,
RawQuery: request.URL.RawQuery,
},
rt,
false,
false,
nil)
...
proxy.UpgradeTransport = apiproxy.NewUpgradeRequestRoundTripper(
upgrading,
RoundTripperFunc(func(req *http.Request) (*http.Response, error) {
newReq := utilnet.CloneRequest(req)
return upgrader.RoundTrip(newReq)
}))
proxy.Transport = rt
proxy.FlushInterval = defaultFlushInterval
proxy.Responder = ErrorResponderFunc(func(w http.ResponseWriter, req *http.Request, err error) {
p.responder.Error(err)
})
proxy.ServeHTTP(writer, newReq)

3.如何實現權限管理?

在cluster-gateway這種多集群管理模式下權限管理是必須要實現的能力,cluster-gateway在轉發請求的時候使用的RestConfig在管控集群是具有admin權限的,因此在轉發請求的時候不能無腦轉發,需要在轉發時做到權限收斂。cluster-gateway權限控制的功能是基于k8s的角色扮演機制 https://kubernetes.io/docs/reference/access-authn-authz/authentication/#user-impersonation 詳細的原理請讀者自己深入研究。

        
// rest.Config 的 Impersonate用于填充你想扮演的角色信息,比如你是admin的用戶
// 但是你可以扮演 其他只有讀權限的用戶,這樣cluster-gateway轉發的請求也只有只讀權限
// Impersonate is the configuration that RESTClient will use for impersonation.
Impersonate ImpersonationConfig

大概的實現方式是,在通過restconfig構造代理server的時候,配置好 Impersonate 信息,其實最終轉發請求的時候,是把角色扮演信息注入在請求的Header中,完成角色扮演信息的傳遞

Impersonate-User: songyang
Impersonate-Group: developers

當管控集群接收到轉發的請求的時候,會用header中指定的角色去校驗授權

4.當管控面和被納管集群之間的網絡不通的場景下是如何完成代理的?

cluster-gateway使用了ANP的能力通過隧道打通Hub集群和被納管集群之間的網絡,這部分實踐不多,便不再多說,感興趣的同學可以翻閱 https://kubernetes.io/docs/tasks/extend-kubernetes/setup-konnectivity/文檔 和開源項目 https://github.com/kubernetes-sigs/apiserver-network-proxy

關于cluster-gateway的介紹到此為止,本文不會太多聚焦到代碼上,感興趣的讀者建議下載代碼閱讀,總結下來cluster-gateway實現了很優雅的集群請求轉發,巧妙的通過AA的方式為Hub集群支持多集群管理能力,是一種在k8s整個框架下的api增強。希望大家了解到原理后,能夠舉一反三擴寬k8s下增強能力開發的思路。

市面上有很多在API測做增強的開源項目,https://github.com/oam-dev/cluster-gateway 似乎是我見到最早開源的(僅表個人看法),而且開源前已經在螞蟻內部生產環境使用了很久。

KubeGateway

KubeGateway[2] 是字節跳動在2年前開源出來的項目,當時一同開源的還有 KubeZoo[3],這2個項目都是在 K8S-API 層面實現了功能增強,后面的文章會對 https://github.com/kubewharf/kubezoo 進行深入的分析。

KubeGateway 定位是為字節內部的 kube-apiserver 的 HTTP2 流量專門設計并定制的七層負載均衡代理,可以根據七層流量特性完成定制化的請求轉發,比如對pod的讀請求轉發到某個apiserver,把寫請求轉發到另一個apiserver完成讀寫分離,或者限制pod的list的請求速率,限制某個用戶的請求。

在介紹原理之前,讀者可以根據上文已經了解到的知識背景自己思考一下如何設計出這樣一個服務,我們一起來思考下:

這只是一個代理,但是需要承接所有用戶的請求,用戶需要對這層代理無感知,你會說我直接使用常見的7層代理,但是注意這個代理是面向k8s-apiserver的代理,需要適配k8s的所有請求特征,并不是一個簡單的7層代理轉發就能完成的,比如

  1. 1. 用戶信息是從kubeconfig的用戶憑證里獲取到,需要解析下token或者用戶證書
  2. 2. k8s api里很多verb語句,比如 watch,list動作需要從七層的請求信息中分析出

所以聰明的你,可能會想到我們可以用之前AA的思路,基于k8s.io/apiserver 這個構建一個7層代理,利用這個包天然能夠兼容k8s的api,通過使用這個包下的增強能力,可以輕松的實現提取用戶信息,解析k8s請求的功能,用戶對某個集群的請求可以使用我們前面提到的proxy的能力轉發到對應的k8s-apiserver上。

沒錯,KubeGateway的整體設計和我們分析的思路基本差不多。這個項目的搭建也是基于k8s.io/apiserver 這個庫,不過字節應該是對部分功能做了增強,自己fork了一版,具體增加的代碼細節就不再深究。

下面的官方的設計圖,本文稍微做了一些注解,client端請求的域名中會帶有請求的集群信息,以clusterA.k8s.kubegateway.io為例,表示這次請求的面向后面上游的Cluster A集群,KubeGateway會通過解析域名來判斷該請求是轉發給ClusterA的kube-apiserver,在轉發請求的過程中會經過基本的鑒權和授權認證,其實就是把用戶信息封裝向被代理集群發起 Authentication Request (TokenReview) 和 Authorization Request (SubjectReview) 來驗證用戶身份和授權,完成鑒權和授權之后就到了我們老朋友出馬的時候,把請求代理到目標集群。

關于如何基于k8s.io/apiserver這個庫搭建一個符合k8s規范的apiserver我們會放在下一篇文章分析,這里大家只要知道基于這個庫就能實現非常神奇的功能。

下面我們簡單的講解KubeGateway的核心代碼,第一部分是KubeGateway是如何一步步完成請求的解析,鑒權授權和代理轉發

代碼地址:https://github.com/kubewharf/kubegateway/blob/43b7830e9973f45971655a92564c88e0c9b4caed/cmd/kube-gateway/app/proxy.go#L139

buildProxyHandlerChainFunc 會把每一步驟像洋蔥一樣一層一層封裝起來,用戶請求會從最下面的handler一層一層的被解析,有幾個關鍵的handler:

  1. WithRequestInfo 會把用戶請求解析為k8s標準的請求,并存儲在ctx中,包括請求的資源(pods),請求動作(list、watch),所在命名空間(ns)等
  2. WithAuthentication和WithFailedAuthenticationAudit完成用戶身份認證和授權
  3. 最后WithDispatcher完成請求的轉發到不同的集群apiserver中
func buildProxyHandlerChainFunc(clusterManager clusters.Manager, enableAccessLog bool) func(apiHandler http.Handler, c *genericapiserver.Config) http.Handler {
return func(apiHandler http.Handler, c *genericapiserver.Config) http.Handler {
// new gateway handler chain
handler := gatewayfilters.WithDispatcher(apiHandler, proxydispatcher.NewDispatcher(clusterManager, enableAccessLog))
// without impersonation log
handler = gatewayfilters.WithNoLoggingImpersonation(handler, c.Authorization.Authorizer, c.Serializer)
// new gateway handler chain, add impersonator userInfo
handler = gatewayfilters.WithImpersonator(handler)
handler = genericapifilters.WithAudit(handler, c.AuditBackend, c.AuditPolicyChecker, c.LongRunningFunc)
failedHandler := genericapifilters.Unauthorized(c.Serializer, c.Authentication.SupportsBasicAuth)
failedHandler = genericapifilters.WithFailedAuthenticationAudit(failedHandler, c.AuditBackend, c.AuditPolicyChecker)
handler = genericapifilters.WithAuthentication(handler, c.Authentication.Authenticator, failedHandler, c.Authentication.APIAudiences)
handler = genericfilters.WithCORS(handler, c.CorsAllowedOriginList, nil, nil, nil, "true")
// disabel timeout, let upstream cluster handle it
// handler = gatewayfilters.WithTimeoutForNonLongRunningRequests(handler, c.LongRunningFunc, c.RequestTimeout)
handler = genericfilters.WithWaitGroup(handler, c.LongRunningFunc, c.HandlerChainWaitGroup)
// new gateway handler chain
handler = gatewayfilters.WithPreProcessingMetrics(handler)
handler = gatewayfilters.WithExtraRequestInfo(handler, &request.ExtraRequestInfoFactory{})
handler = gatewayfilters.WithTerminationMetrics(handler)
handler = gatewayfilters.WithRequestInfo(handler, c.RequestInfoResolver)
if c.SecureServing != nil && !c.SecureServing.DisableHTTP2 && c.GoawayChance > 0 {
handler = genericfilters.WithProbabilisticGoaway(handler, c.GoawayChance)
}
handler = genericapifilters.WithCacheControl(handler)
handler = gatewayfilters.WithNoLoggingPanicRecovery(handler)
return handler
}
}

第二部分我們看下我們的老朋友是如何完成請求的代理:

代碼地址:https://github.com/kubewharf/kubegateway/blob/43b7830e9973f45971655a92564c88e0c9b4caed/pkg/gateway/proxy/dispatcher/dispatcher.go#L53

  1. 1. 從 endpointPicker 選擇需要代理的kube-apiserver endpoint
  2. 2. 基于選出的endpoint構造新的代理請求
  3. 3. 基于被代理的集群的訪問憑證,構造代理Handler,再細看下 NewUpgradeAwareHandler 函數的實現,果然見到了親切的 proxy.NewUpgradeAwareHandler 函數
// 1. 從 endpointPicker 選擇需要代理的kube-apiserver endpoint
endpoint, err := endpointPicker.Pop()
if err != nil {
    d.responseError(errors.NewServiceUnavailable(err.Error()), w, req, statusReasonNoReadyEndpoints)
    return
}

ep, err := url.Parse(endpoint.Endpoint)
if err != nil {
    d.responseError(errors.NewInternalError(err), w, req, statusReasonInvalidEndpoint)
    return
}

...
// 2. 基于選出的endpoint構造新的代理請求
location := &url.URL{}
location.Scheme = ep.Scheme
location.Host = ep.Host
location.Path = req.URL.Path
location.RawQuery = req.URL.Query().Encode()
...

rw := responsewriter.WrapForHTTP1Or2(delegate)
// 3. 基于被代理的訪問憑證構造 proxy handler
proxyHandler := NewUpgradeAwareHandler(location, endpoint.ProxyTransport, endpoint.PorxyUpgradeTransport, false, false, d, endpoint)
proxyHandler.ServeHTTP(rw, newReq)

// NewUpgradeAwareHandler creates a new proxy handler with a default flush interval. Responder is required for returning
// errors to the caller.
func NewUpgradeAwareHandler(location *url.URL, transport http.RoundTripper, upgradeTransport proxy.UpgradeRequestRoundTripper, wrapTransport, upgradeRequired bool, responder proxy.ErrorResponder, endpoint *clusters.EndpointInfo) *UpgradeAwareHandler {
    handler := proxy.NewUpgradeAwareHandler(location, transport, wrapTransport, upgradeRequired, responder)
    handler.UpgradeTransport = upgradeTransport
    return &UpgradeAwareHandler{
        UpgradeAwareHandler: handler,
        endpoint:            endpoint,
    }
}

這里簡單的介紹了 KubeGateway 的實現原理,不過這個組件的能力不僅如此,他很多靈活的基于7層請求的轉發限流等功能實現也很有意思,這些都依賴讀者自己研究了。

Karmada-Aggregated-APIServer

這里簡單介紹一下karmada多集群方案中的 karmada-aggregated-apiserver 組件,他是karmada push多集群管理模式下,管控面集群管理被納管集群的代理服務,他也是抽象了cluster和cluster/proxy的概念,和ocm的cluster-gateway采用同樣的思路來代理發往不同集群的請求,實現方式和我們上面講解的思路也大同小異,不過他加入了一些增強功能,可以支持http和socks5協議的代理,感興趣的同學也可以下載研究一下,代碼入口在 https://github.com/karmada-io/karmada/blob/master/cmd/aggregated-apiserver/main.go。

總結

通過對比幾個開源項目的實現,大家會發現這些項目的應用場景和多集群密切相關,cluster-gateway和karamda-aggregate-apiserver作為多集群代理,kubegateway則是支持為多個集群做負載均衡,等介紹完后面2類開源項目 自定義k8s-apiserver 和 多租k8s 后,大家會有更深刻的體會。系列文章的最后,會給大家介紹一種我司已經上線了的基于k8s.io/apiserver構建的服務,也非常有意思,另外本文關于一些實現細節沒有深究,這些都會放在下一篇文章內講解

參考文檔

https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/apiserver-aggregation/

https://www.zeng.dev/post/2023-k8s-apiserver-aggregation-internals/

引用鏈接

[1] apiserver-aggregation: https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/apiserver-aggregation/
[2] KubeGateway: https://github.com/kubewharf/kubegateway/blob/main/README.zh_CN.md
[3] KubeZoo: https://github.com/kubewharf/kubezoo

文章轉自微信公眾號@云原生AI百寶箱

上一篇:

LLM實戰?|?使用ChatGPT?API提取文本topic

下一篇:

AnalyticDB(ADB)+LLM:構建AIGC時代下企業專屬Chatbot
#你可能也喜歡這些API文章!

我們有何不同?

API服務商零注冊

多API并行試用

數據驅動選型,提升決策效率

查看全部API→
??

熱門場景實測,選對API

#AI文本生成大模型API

對比大模型API的內容創意新穎性、情感共鳴力、商業轉化潛力

25個渠道
一鍵對比試用API 限時免費

#AI深度推理大模型API

對比大模型API的邏輯推理準確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費