Kubernetes API 聚合开发汇总
2. Kubernetes API 聚合開發
自定義資源實際上是為了擴展 kubernetes 的 API,向 kubenetes API 中增加新類型,可以使用以下三種方式:
- 修改 kubenetes 的源碼,顯然難度比較高,也不太合適
- 創建自定義 API server 并聚合到 API 中
- 創建自定義資源(CRD)
2.1 CRD存在的問題
2.2 自定義API server相比CRD的優勢
2.3 使用自定義API server前先考慮以下幾點
2.4 Kubernetes APIServer 和 自定義APIServer的關系
2.4.1 Kubernetes 的 Aggregated API是什么?
1.1 概述
Aggregated(聚合的)API server 是為了將原來的 API server 這個巨石(monolithic)應用給拆分開,為了方便用戶開發自己的 API server 集成進來,而不用直接修改 Kubernetes 官方倉庫的代碼,這樣一來也能將 API server 解耦,方便用戶使用實驗特性,簡而言之,它是允許k8s的開發人員編寫一個自己的服務,可以把這個服務注冊到k8s的api里面,這樣,就像k8s自己的api一樣,自定義的服務只要運行在k8s集群里面,k8s 的Aggregate通過service名稱就可以轉發到我們自定義的service里面去了。這些 API server 可以跟 kube-apiserver 無縫銜接,使用 kubectl 也可以管理它們。
在 1.7+ 版本及以后,聚合層apiserver和 kube-apiserver 一起運行。在擴展資源被注冊前,聚合層不執行任何操,要注冊其 API,用戶必需添加一個 APIService 對象,該對象需在 Kubernetes API 中聲明 URL 路徑,聚合層將發送到該 API 路徑(e.g. /apis/myextension.mycompany.io/v1/…)的所有對象代理到注冊的 APIService。
通常,通過在集群中的一個 Pod 中運行一個 extension-apiserver 來實現 APIService。如果已添加的資源需要主動管理,這個 extension-apiserver 通常需要和一個或多個controller配對。
1.2 設計理念
- api的擴展性:這樣k8s的開發人員就可以編寫自己的API服務器來公開他們想要的API。集群管理員應該能夠使用這些服務,而不需要對核心庫存儲庫進行任何更改。
- 豐富了APIs:核心kubernetes團隊阻止了很多新的API提案。通過允許開發人員將他們的API作為單獨的服務器公開,并使集群管理員能夠在不對核心庫存儲庫進行任何更改的情況下使用它們,這樣就無須社區繁雜的審查了。
- 開發分階段實驗性API的地方:新的API可以在單獨的聚集服務器中開發,當它穩定之后,那么把它們封裝起來安裝到其他集群就很容易了。
- 確保新API遵循kubernetes約定:如果沒有這里提出的機制,社區成員可能會被迫推出自己的東西,這可能會或可能不遵循kubernetes約定。
1.3 Kubernetes Aggregated原理解析
我們說的自定義API其實就是和Metrics Server的實現方式一樣,都是通過注冊API的形式來完成和Kubernetes的集成的,也就是在API Server增加原本沒有的API。不過添加API還可以通過CRD的方式完成,不過我們這里直說聚合方式??聪聢D:
Kube-Aggregator類似于一個七層負載均衡,將來自用戶的請求攔截轉發給其他服務器,并且負責整個 APIServer 的 Discovery 功能。
通過APIServices對象關聯到某個Service來進行請求的轉發,其關聯的Service類型進一步決定了請求轉發形式。Aggregator包括一個GenericAPIServer和維護自身狀態的Controller。其中 GenericAPIServer主要處理apiregistration.k8s.io組下的APIService資源請求。
主要controller包括:
- apiserviceRegistrationController:負責APIServices中資源的注冊與刪除;
- availableConditionController:維護APIServices的可用狀態,包括其引用Service是否可用等;
- autoRegistrationController:用于保持API中存在的一組特定的APIServices;
- crdRegistrationController:負責將CRD GroupVersions自動注冊到APIServices中;
- openAPIAggregationController:將APIServices資源的變化同步至提供的OpenAPI文檔;
假設有兩個路由分別訪問API,實際上我們訪問API的時候訪問的是一個aggregator的代理層,下面橙色的都是可用的服務后端。我們訪問上圖中的2個URL其實是被代理到不同的后端,在這個機制下你可以添加更多的后端,比如舉例說說Custome-metrics-apiserver綠色線條的路徑。
注冊自定義API文件
該文件的主要作用就是向Api server注冊一個api,此API名稱是關聯到一個service名稱上。
apiVersion: apiregistration.k8s.io/v1 kind: APIService metadata:name: v1beta1.custom.metrics.k8s.iolabels:api: custom-metrics-apiserverapiserver: "true" spec:version: v1beta1 #API版本group: custom.metrics.k8s.io #API所屬的組groupPriorityMinimum: 2000service:name: custom-metrics-apiserver #自定義API所關聯的service名稱,當訪問這個自定義API后轉發到哪個service處理,就根據這個service名稱選擇namespace: defaultversionPriority: 10caBundle: "LS0tLS1CRUdJTiBDRVJUSUZJQ0"上面定義了資源類型為APIService,service名稱為custom-metrics-apiserver,空間為default的一個資源聚合接口。
下面帶大家從源代碼的角度來看,代碼路徑:
staging/src/k8s.io/kube-aggregator/pkg/apiserver/apiservice_controller.go ,和k8s其它controller一樣,watch變化分發到add、update和delete方法
主要監聽兩種資源apiService和service,路徑:
staging/src/k8s.io/kube-aggregator/pkg/apiserver/apiserver.go
func (s *APIAggregator) AddAPIService(apiService *v1.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 respondif 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 handlerproxyHandler := &proxyHandler{localDelegate: s.delegateHandler,proxyCurrentCertKeyContent: s.proxyCurrentCertKeyContent,proxyTransport: s.proxyTransport,serviceResolver: s.serviceResolver,egressSelector: s.egressSelector,}proxyHandler.updateAPIService(apiService)if s.openAPIAggregationController != nil {s.openAPIAggregationController.AddAPIService(proxyHandler, apiService)}s.proxyHandlers[apiService.Name] = proxyHandlers.GenericAPIServer.Handler.NonGoRestfulMux.Handle(proxyPath, proxyHandler)s.GenericAPIServer.Handler.NonGoRestfulMux.UnlistedHandlePrefix(proxyPath+"/", proxyHandler)// if we're dealing with the legacy group, we're done hereif 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 endpointgroupPath := "/apis/" + apiService.Spec.GroupgroupDiscoveryHandler := &apiGroupHandler{codecs: aggregatorscheme.Codecs,groupName: apiService.Spec.Group,lister: s.lister,delegate: s.delegateHandler,}// aggregation is protecteds.GenericAPIServer.Handler.NonGoRestfulMux.Handle(groupPath, groupDiscoveryHandler)s.GenericAPIServer.Handler.NonGoRestfulMux.UnlistedHandle(groupPath+"/", groupDiscoveryHandler)s.handledGroups.Insert(apiService.Spec.Group)return nil }結合上面的源碼:
proxyPath := "/apis/" + apiService.Spec.Group + "/" + apiService.Spec.Version例子就是/apis/custom-metrics.k8s.io/v1beta1,而處理方法請求的handle就是
// register the proxy handlerproxyHandler := &proxyHandler{localDelegate: s.delegateHandler,proxyCurrentCertKeyContent: s.proxyCurrentCertKeyContent,proxyTransport: s.proxyTransport,serviceResolver: s.serviceResolver,egressSelector: s.egressSelector,}proxyHandler.updateAPIService(apiService)updateAPIService就是更新這個proxy的后端service,路徑:
staging/src/k8s.io/kube-aggregator/pkg/apiserver/handler_proxy.go
func (r *proxyHandler) updateAPIService(apiService *apiregistrationv1api.APIService) {if apiService.Spec.Service == nil {r.handlingInfo.Store(proxyHandlingInfo{local: true})return}proxyClientCert, proxyClientKey := r.proxyCurrentCertKeyContent()newInfo := proxyHandlingInfo{name: apiService.Name,restConfig: &restclient.Config{TLSClientConfig: restclient.TLSClientConfig{Insecure: apiService.Spec.InsecureSkipTLSVerify,ServerName: apiService.Spec.Service.Name + "." + apiService.Spec.Service.Namespace + ".svc",CertData: proxyClientCert,KeyData: proxyClientKey,CAData: apiService.Spec.CABundle,},},serviceName: apiService.Spec.Service.Name,serviceNamespace: apiService.Spec.Service.Namespace,servicePort: *apiService.Spec.Service.Port,serviceAvailable: apiregistrationv1apihelper.IsAPIServiceConditionTrue(apiService, apiregistrationv1api.Available),}if r.egressSelector != nil {networkContext := egressselector.Cluster.AsNetworkContext()var egressDialer utilnet.DialFuncegressDialer, err := r.egressSelector.Lookup(networkContext)if err != nil {klog.Warning(err.Error())} else {newInfo.restConfig.Dial = egressDialer}} else if r.proxyTransport != nil && r.proxyTransport.DialContext != nil {newInfo.restConfig.Dial = r.proxyTransport.DialContext}newInfo.proxyRoundTripper, newInfo.transportBuildingError = restclient.TransportFor(newInfo.restConfig)if newInfo.transportBuildingError != nil {klog.Warning(newInfo.transportBuildingError.Error())}r.handlingInfo.Store(newInfo) }上述源碼中restConfig就是調用service的客戶端參數,其中
ServerName: apiService.Spec.Service.Name + "." + apiService.Spec.Service.Namespace + ".svc"指的就是具體的service。
2.4.2 認證流程
工作方式
與自定義資源定義(CRD)不同,除標準的 Kubernetes kube-apiserver 外,Aggregation API 還涉及另一個服務器:Extension apiserver。Kubernetes kube-apiserver 將需要與自定義的 Extension apiserver 通信,并且自定義的 Extension apiserver 也需要與 Kubernetes kube-apiserver 通信。為了確保此通信的安全,Kubernetes kube-apiserver 使用 x509 證書向 Extension apiserver 認證。具體流程如下:
Kubernetes kube-apiserver 認證和授權
假設我們已經在 Kubernetes kube-apiserver 注冊了 Extension apiserver。
當用戶請求訪問 path ,Kubernetes kube-apiserver 使用它的標準認證和授權配置來對用戶認證,以及對特定 path 的鑒權,到目前為止,所有內容都是標準的 Kubernetes API 請求,認證與鑒權,接下來 Kubernetes kube-apiserver 現在準備將請求發送到 Extension apiserver。
Kubernetes kube-apiserver認證時,認證接受會將認證信息刪除,處理邏輯如下:
- 1.通過context獲取user信息
- 2.構造請求,刪除requestheader信息,通過user重新填充
- 3.通過proxyRoundTripper轉發請求
代碼路徑:
staging/src/k8s.io/kube-aggregator/pkg/apiserver/handler_proxy.go
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)if handlingInfo.local {if r.localDelegate == nil {http.Error(w, "", http.StatusNotFound)return}r.localDelegate.ServeHTTP(w, req)return}if !handlingInfo.serviceAvailable {proxyError(w, req, "service unavailable", http.StatusServiceUnavailable)return}if handlingInfo.transportBuildingError != nil {proxyError(w, req, handlingInfo.transportBuildingError.Error(), http.StatusInternalServerError)return}// 通過context獲取useruser, ok := genericapirequest.UserFrom(req.Context())if !ok {proxyError(w, req, "missing user", http.StatusInternalServerError)return}// write a new location based on the existing request pointed at the target service// 構造請求url,通過apiservice配置的service/namespace隨機得到某個endpoint后端location := &url.URL{}location.Scheme = "https"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}location.Host = rloc.Hostlocation.Path = req.URL.Pathlocation.RawQuery = req.URL.Query().Encode()newReq, cancelFn := newRequestForProxy(location, req)defer cancelFn()if handlingInfo.proxyRoundTripper == nil {proxyError(w, req, "", http.StatusNotFound)return}// we need to wrap the roundtripper in another roundtripper which will apply the front proxy headersproxyRoundTripper, upgrade, err := maybeWrapForConnectionUpgrades(handlingInfo.restConfig, handlingInfo.proxyRoundTripper, req)if err != nil {proxyError(w, req, err.Error(), http.StatusInternalServerError)return}// 包裹請求信息,將user信息放到header中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 roundtripper. Its a direct call that bypasses the round tripper. This means that we have to// attach the "correct" user headers to the request ahead of time. After the initial upgrade, we'll be back// at the roundtripper flow, so we only have to muck with this request, but we do have to do it.if upgrade {transport.SetAuthProxyHeaders(newReq, user.GetName(), user.GetGroups(), user.GetExtra())}// 調用后端handler := proxy.NewUpgradeAwareHandler(location, proxyRoundTripper, true, upgrade, &responder{w: w})handler.ServeHTTP(w, newReq) }根據擴展apiserver找到后端時通過service獲取對應endpoint列表,隨機選擇某個endpoint、實現如下,源碼路徑:
staging/src/k8s.io/apiserver/pkg/util/proxy/proxy.go
func ResolveEndpoint(services listersv1.ServiceLister, endpoints listersv1.EndpointsLister, namespace, id string, port int32) (*url.URL, error) {svc, err := services.Services(namespace).Get(id)if err != nil {return nil, err}svcPort, err := findServicePort(svc, port)if err != nil {return nil, err}switch {case svc.Spec.Type == v1.ServiceTypeClusterIP, svc.Spec.Type == v1.ServiceTypeLoadBalancer, svc.Spec.Type == v1.ServiceTypeNodePort:// these are finedefault:return nil, fmt.Errorf("unsupported service type %q", svc.Spec.Type)}eps, err := endpoints.Endpoints(namespace).Get(svc.Name)if err != nil {return nil, err}if len(eps.Subsets) == 0 {return nil, errors.NewServiceUnavailable(fmt.Sprintf("no endpoints available for service %q", svc.Name))}// Pick a random Subset to start searching from.ssSeed := rand.Intn(len(eps.Subsets))// Find a Subset that has the port.for ssi := 0; ssi < len(eps.Subsets); ssi++ {ss := &eps.Subsets[(ssSeed+ssi)%len(eps.Subsets)]if len(ss.Addresses) == 0 {continue}for i := range ss.Ports {if ss.Ports[i].Name == svcPort.Name {// Pick a random address.ip := ss.Addresses[rand.Intn(len(ss.Addresses))].IPport := int(ss.Ports[i].Port)return &url.URL{Scheme: "https",Host: net.JoinHostPort(ip, strconv.Itoa(port)),}, nil}}}return nil, errors.NewServiceUnavailable(fmt.Sprintf("no endpoints available for service %q", id)) }ProxyRoundTripper創建路徑:
staging/src/k8s.io/client-go/transport/round_trippers.go
func NewAuthProxyRoundTripper(username string, groups []string, extra map[string][]string, rt http.RoundTripper) http.RoundTripper {return &authProxyRoundTripper{username: username,groups: groups,extra: extra,rt: rt,} }func (rt *authProxyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {req = utilnet.CloneRequest(req)// 設置user信息SetAuthProxyHeaders(req, rt.username, rt.groups, rt.extra)return rt.rt.RoundTrip(req) }// SetAuthProxyHeaders stomps the auth proxy header fields. It mutates its argument. func SetAuthProxyHeaders(req *http.Request, username string, groups []string, extra map[string][]string) {// 清除原始url的requestheader信息req.Header.Del("X-Remote-User")req.Header.Del("X-Remote-Group")for key := range req.Header {if strings.HasPrefix(strings.ToLower(key), strings.ToLower("X-Remote-Extra-")) {req.Header.Del(key)}}// 通過user重新填充信息req.Header.Set("X-Remote-User", username)for _, group := range groups {req.Header.Add("X-Remote-Group", group)}for key, values := range extra {for _, value := range values {req.Header.Add("X-Remote-Extra-"+headerKeyEscape(key), value)}} }Kubernetes kube-apiserver 代理請求
Kubernetes kube-apiserver 現在將請求發送或代理到注冊以處理該請求的 Extension apiserver。為此,它需要了解幾件事:
簡而言之,就是 Kubernetes kube-apiserver 已經認證和鑒權用戶的請求,怎么將這些信息傳遞給 Extension apiserver,為提供這兩條信息,我們必須使用若干啟動參數來配置 Kubernetes apiserver。
Kubernetes kube-apiserver 客戶端認證
Kubernetes kube-apiserver 通過 TLS 連接到 Extension apiserver,并使用客戶端證書認證,這里 Kubernetes kube-apiserver (aggregator or proxy) 是 Extension apiserver 的客戶端。必須在啟動時使用提供的參數向 Kubernetes kube-apiserver 提供以下內容:
- 通過 --proxy-client-key-file 指定簽名私鑰文件
- 通過 --proxy-client-cert-file 指定驗簽證書文件
- 通過 --requestheader-client-ca-file 簽署客戶端證書文件的 CA 證書
- 通過 --requestheader-allowed-names 在簽署的客戶證書中有效的公用名(CN)
Kubernetes kube-apiserver 將使用由 –proxy-client-*-file 指示的文件來通過 Extension apiserver 驗證。為了使合規的 Extension apiserver 能夠將該請求視為有效,必須滿足以下條件:
使用這些選項啟動時,Kubernetes kube-apiserver 將:
保存原始請求用戶名和組信息
當 Kubernetes kube-apiserver 將請求代理到 Extension apiserver 時,它將向 Extension apiserver 通知原始請求已成功通過其驗證的用戶名和組。它在其代理請求的 http 標頭中提供這些。您必須將要使用的標頭名稱告知 Kubernetes kube-apiserver。
- 通過--requestheader-username-headers 標明用來保存用戶名的頭部
- 通過--requestheader-group-headers 標明用來保存 group 的頭部
- 通過--requestheader-extra-headers-prefix 標明用來保存拓展信息前綴的頭部
這些標頭名稱也放置在extension-apiserver-authentication 的 configmap 中,因此 Extension apiserver 可以檢索和使用它們。
Extension apiserver 認證
Extension apiserver 在收到來自 Kubernetes kube-apiserver 的代理請求后,必須驗證該請求確實來自有效的身份驗證代理,該認證代理由 Kubernetes kube-apiserver 履行。Extension apiserver 通過以下方式對其認證:
- 客戶端 CA 證書 --requestheader-client-ca-file。
- 允許名稱(CN)列表 --requestheader-allowed-names。
- 用戶名,組和其他信息的頭部。
- 由其證書與檢索到的 CA 證書匹配的 CA 簽名。
- 在允許的 CN 列表中有一個 CN,除非列表為空,在這種情況下允許所有 CN。
- 從適當的頭部中提取用戶名和組。
如果以上均通過,則該請求是來自合法認證代理(在本例中為 Kubernetes kube-apiserver)的有效代理請求。
為了具有檢索 configmap 的權限,Extension apiserver 需要適當的角色。在 kube-system 名字空間中有一個默認角色extension-apiserver-authentication-reader 可用于設置。
Extension apiserver 執行
如果SubjectAccessReview通過,則擴展 apiserver 執行請求。
2.4.3 部署過程
安裝cfssl
wget https://pkg.cfssl.org/R1.2/cfssl_linux-amd64 -O /usr/local/bin/cfssl wget https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64 -O /usr/local/bin/cfssljson wget https://pkg.cfssl.org/R1.2/cfssl-certinfo_linux-amd64 -O /usr/local/bin/cfssl-certinfo cd /usr/local/bin/ chmod +x cfssl cfssljson cfssl-certinfo創建CA
CA 配置文件
$ cat > aggregator-ca-config.json <<EOF {"signing": {"default": {"expiry": "87600h"},"profiles": {"aggregator": {"usages": ["signing","key encipherment","server auth","client auth"],"expiry": "87600h"}}} } EOF- profiles : 可以定義多個 profiles,分別指定不同的過期時間、使用場景等參數;后續在簽名證書時使用某個 profile。
- signing :表示該證書可用于簽名其它證書;生成的 aggregator-ca.pem 證書中 CA=TRUE。
- server auth :表示 Client 可以用該 CA 對 Server 提供的證書進行驗證。
- client auth :表示 Server 可以用該 CA 對 Client 提供的證書進行驗證。
創建CA證書簽名請求
$ cat > aggregator-ca-csr.json <<EOF {"CN": "aggregator","key": {"algo": "rsa","size": 2048},"names": [{"C": "CN","ST": "Shanghai","L": "Shanghai","O": "k8s","OU": "wzlinux"}],"ca": {"expiry": "87600h"} }- “CN” :Common Name,kube-apiserver 從證書中提取該字段作為請求的用戶名 (User Name);瀏覽器使用該字段驗證網站是否合法。
- “O” :Organization,kube-apiserver 從證書中提取該字段作為請求用戶所屬的組 (Group);
生成CA證書和私鑰
cfssl gencert -initca aggregator-ca-csr.json | cfssljson -bare aggregator-ca創建Kubernetes證書
創建aggregator證書簽名請求
$ cat > aggregator-csr.json <<EOF {"CN": "aggregator","hosts": ["127.0.0.1","172.18.0.101","172.18.0.102","172.18.0.103","10.96.0.1","kubernetes","kubernetes.default","kubernetes.default.svc","kubernetes.default.svc.cluster","kubernetes.default.svc.cluster.local"],"key": {"algo": "rsa","size": 2048},"names": [{"C": "CN","ST": "Shanghai","L": "Shanghai","O": "k8s","OU": "wzlinux"}] }如果 hosts 字段不為空則需要指定授權使用該證書的 IP 或域名列表,由于該證書后續kubernetes master 集群使用,所以上面指定kubernetes master 集群的主機 IP 和 kubernetes 服務的服務 IP(一般是 kube-apiserver 指定的 service-cluster-ip-range 網段的第一個 IP,如 10.96.0.1)。
生成aggreagtor證書和私鑰
cfssl gencert -ca=aggregator-ca.pem -ca-key=aggregator-ca-key.pem -config=aggregator-ca-config.json -profile=aggregator aggregator-csr.json | cfssljson -bare aggregator分發證書
將生成的證書和秘鑰文件(后綴名為.pem)拷貝到 Master 節點的 /etc/kubernetes/pki 目錄下備用。
開啟聚合層API
kube-apiserver 增加以下啟動配置:
--requestheader-client-ca-file=/etc/kubernetes/pki/aggregator-ca.pem --requestheader-allowed-names=aggregator --requestheader-extra-headers-prefix=X-Remote-Extra- --requestheader-group-headers=X-Remote-Group --requestheader-username-headers=X-Remote-User --proxy-client-cert-file=/etc/kubernetes/pki/aggregator.pem --proxy-client-key-file=/etc/kubernetes/pki/aggregator-key.pem前面創建的證書的 CN 字段的值必須和參數 –requestheader-allowed-names 指定的值 aggregator 相同。
重啟 kube-apiserver:
$ systemctl daemon-reload $ systemctl restart kube-apiserver如果 kube-proxy 沒有在 Master 上面運行,kube-proxy 還需要添加配置:
--enable-aggregator-routing=true2.5 實現自定義API(聚合)服務
API聚合這個方式實現相對復雜一點,但靈活度很高,基本業務上的大部分需求都可以滿足。
2.5.1 工具介紹
雖然官方給了一個sample-apiserver,我們可以照著實現自己的Aggregated APIServer。但完全手工編寫還是太費勁了,這里使用官方推薦的工具apiserver-builder幫助快速創建項目骨架。
apiserver-builder構建AA方案的API接口服務的原理還是比較清晰的,總之就是kubernetes里最常見的控制器模式,這里就不具體介紹了,官方文檔既有文字又有圖片講得還是挺細致的,強烈推薦大家多看看,學習一下。
apiserver-builder這個工具與kubebuilder和operator-sdk非常相似,他們都依賴一個底層庫controller-gen,apiserver-builder該工具生成的工程與kubebuilder生成的工程也非常相似,其中一個不同的地方就是kubebuilder因為不需要自定義apiserver,因此,apiserver-builder生成的工程會有一個自定義apiserver,控制器部分的邏輯兩者都一樣,都是通過調協方法實現自定義資源狀態的維護。
2.5.2 API聚合服務開發
用例源碼下載
git clone https://github.com/kubernetes/sample-apiserver.git源碼編譯
cd sample-apiserver 編譯二進制文件: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o artifacts/simple-image/kube-sample-apiserver 編譯docker鏡像: docker build -t kube-sample-apiserver:latest ./artifacts/simple-image集群中部署
踩坑:
artifacts/example/deployment.yaml文件中需要修改鏡像及版本,要與集群中對應;
artifacts/example下所有文件中涉及的命名空間統一修改為default,auth-reader.yaml中metadata下的命名空間為kube-system保持不變。
訪問測試
查看sercet
kubectl get sa apiserver -o json查看sercet里面的tocken,并將該tocken拷貝到test.log的文件中后續訪問是作為參數傳入
kubectl describe secret apiserver查看角色
kubectl get clusterrole查看角色綁定
kubectl get clusterrolebindingREST接口訪問
curl -k -H "Authorization: Bearer $(cat test.log)" https://10.131.180.168:6443/apis/wardle.example.com/v1alpha1/flunders2.5.3 apiserver-builder實現API聚合服務開發
工具使用當前最新版本V2.0beta
初始化項目
apiserver-boot init repo --domain example.com創建一個非命名空間范圍的api-resource
apiserver-boot create group version resource --group demo --version v1beta1 --non-namespaced=true --kind Foo創建Foo這個api-resource的子資源
apiserver-boot create subresource --subresource bar --group demo --version v1beta1 --kind Foo生成上述創建的api-resource類型的相關代碼,包括deepcopy接口實現代碼、versioned/unversioned類型轉換代碼、api-resource類型注冊代碼、api-resource類型的Controller代碼、api-resource類型的AdmissionController代碼
解決工程依賴問題并生成腳手架代碼
go mod init go mod vendor make generate可以直接在本地將etcd, apiserver, controller運行起來
apiserver-boot run local上述這樣操作之后,就可以訪問我們的APIServer了,如下面的命令:
curl -k https://127.0.0.1:9443/apis/demo.example.com/v1beta1/foos當然也可以新建一個yaml文件,然后用kubectl命令直接對api-resource進行操作:
創建Foo資源的yaml
如果在apiserver的main方法里補上一些代碼,以開啟swagger-ui,還能更方便地看到這些API接口:
func main() {version := "v0"server.StartApiServer("/registry/example.com", apis.GetAllApiBuilders(), openapi.GetOpenAPIDefinitions, "Api", version, func(apiServerConfig *apiserver.Config) error {...apiServerConfig.RecommendedConfig.EnableSwaggerUI = trueapiServerConfig.RecommendedConfig.SwaggerConfig = genericapiserver.DefaultSwaggerConfig()return nil}) }然后瀏覽器訪問https://127.0.0.1:9443/swagger-ui/就可以在swagger的Web頁面上看到創建出來的所有API接口
定制API接口服務
像上面這樣創建的API接口,接口是都有了,但接口沒有啥意義,一般要根據實際情況定義api-resource的spec、status等結構體。
type Foo struct {metav1.TypeMeta `json:",inline"`metav1.ObjectMeta `json:"metadata,omitempty"`Spec FooSpec `json:"spec,omitempty"`Status FooStatus `json:"status,omitempty"` }// FooSpec defines the desired state of Foo type FooSpec struct { }// FooStatus defines the observed state of Foo type FooStatus struct { }定制Controller
默認生成的api-resource的Reconcile邏輯如下:
// Reconcile reads that state of the cluster for a Foo object and makes changes based on the state read // and what is in the Foo.Spec // TODO(user): Modify this Reconcile function to implement your Controller logic. The scaffolding writes // a Deployment as an example // +kubebuilder:rbac:groups=demo.jeremyxu2010.me,resources=foos,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=demo.jeremyxu2010.me,resources=foos/status,verbs=get;update;patch func (r *ReconcileFoo) Reconcile(request reconcile.Request) (reconcile.Result, error) {// Fetch the Foo instanceinstance := &demov1beta1.Foo{}err := r.Get(context.TODO(), request.NamespacedName, instance)if err != nil {if errors.IsNotFound(err) {// Object not found, return. Created objects are automatically garbage collected.// For additional cleanup logic use finalizers.return reconcile.Result{}, nil}// Error reading the object - requeue the request.return reconcile.Result{}, err}return reconcile.Result{}, nil }可以參考:operator-sdk-samples
打包部署
程序寫好后,通過以下命令即可生成容器鏡像及kubernetes的部署manifest文件:
生成二進制文件
apiserver-boot build executables生成容器鏡像
apiserver-boot build container --image demo/foo-apiserver:latest生成kubernetes的部署manifest文件,可直接在kubernetes里apply即完成部署
apiserver-boot build config --name foo-apiserver --namespace default --image demo/foo-apiserver:latest觀察生成的kubernetes部署manifest文件config/apiserver.yaml,可以發現最終會創建一個Deployment,一個Service和一個APIService類型的kubernetes資源,同時APIService的caBundle及apiserver的TLS證書也配置妥當了。
總結
以上是生活随笔為你收集整理的Kubernetes API 聚合开发汇总的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Kubernetes CRD开发汇总
- 下一篇: Kubernetes Controlle