大家都较熟悉之 Kubernetes API 分析
致力于讓深度學習和數據分析走進個人的開放平臺,已完成所有數據分析平臺部署于K8S。
?Kubernetes概覽
以下是 k8s 的整體架構,在 master 節點上主要是 kube-apiserver(整合了 kube-aggregator),還有 kube-scheduler,以及 kube-controller-manager,包括后端存儲 etcd。
其中 kube-apiserver 是一個比較關鍵的部分,而且前期寫得坑很多,導致這一部分雖然看起來是一個 API server 其實代碼很復雜,特別冗余,而且目前對 kube-apiserver 還要做拆分,能夠支持插入第三方的 apiserver,也就是又一個 aggregated apiserver 的 feature,也是和 kube-apiserver 和里面包的一層 genericserver 揉合在一起了,感覺一個大的系統 API server 越寫越挫是一個通病,還好現在 k8s 迷途知返正在調整。
kube-apiserver
Kube-apiserver 可以是認為在 generic server 上封裝的一層官方默認的 apiserver,有第三方需要的情況下,自己也可以在 generic server 上封裝一層加入到集成模式中,這里主要介紹 kube-apiserver 的結構。
2.1restful API
kube-apiserver 是一個 restful 服務,請求直接通過 HTTP 請求發送,例如創建一個 ubuntu 的 pod,用以下的 pod.yaml 文件。
apiVersion: v1 kind: Pod metadata:name: ubuntu1labels:name: ubuntu1 spec:containers:- name: ubuntu1image: ubuntucommand: [ "sleep" , "1d" ] 復制代碼
執行命令 kubectl create -f ./pod.yaml -v=8,可以看到對應的 POST 請求如下。
RequestBody : { "apiVersion" : "v1" , "kind" : "Pod" , "metadata" :{ "labels" :{ "name" : "ubuntu1" }, "name" : "ubuntu1" , "namespace" : "default" }, "spec" :{ "containers" :[{ "command" :[ "sleep" , "1d" ], "image" : "ubuntu" , "name" : "ubuntu1" }], "schedulerName" : "default-scheduler" }} curl -k -v -XPOST -H "Content-Type: application/json"-H "Accept: application/json"-H "User-Agent: kubectl/v1.7.5 (linux/amd64) kubernetes/17d7182"https: //localhost:6443/api/v1/namespaces/default/pods POST https: //localhost:6443/api/v1/namespaces/default/pods 201 Created in 6 milliseconds ResponseHeaders :Content - Type : application/jsonContent - Length : 1208Date : Wed , 18Oct201715 : 04 : 17GMT ResponseBody : { "kind" : "Pod" , "apiVersion" : "v1" , "metadata" :{ "name" : "ubuntu1" , "namespace" : "default" , "selfLink" : "/api/v1/namespaces/default/pods/ubuntu1" , "uid" : "9c9af581-b415-11e7-8033-024d1ba659e8" , "resourceVersion" : "486154" , "creationTimestamp" : "2017-10-18T15:04:17Z" , "labels" :{ "name" : "ubuntu1" }}, "spec" :{ "volumes" :[{ "name" : "default-token-p0980" , "secret" :{ "secretName" : "default-token-p0980" , "defaultMode" : 420 }}], "containers" :[{ "name" : "ubuntu1" , "image" : "ubuntu" , "command" :[ "sleep" , "1d" ], "resources" :{}, "volumeMounts" :[{ "name" : "default-token-p0980" , "readOnly" : true , "mountPath" : "/var/run/secrets/kubernetes.io/serviceaccount" }], "terminationMessagePath" : "/dev/termination-log" , "terminationMessagePolicy" : "File" , "imagePullPolicy" : "Always" }], "restartPolicy" : "Always" , "terminationGracePeriodSeconds" : 30 , "dnsPolicy" : "ClusterFirst" , "serviceAccountName" : "default" , "serviceAccount" : "default" , "securityContext" :{}, "schedulerName" : "default-scheduler" , "tolerations" :[{ "key" : "node.kubernetes.io/not-ready" , "operator" : "Exists" , "effect" : "NoExecute" , "tolerationSeconds" : 300 },{ "key" : "node.alpha.kubernetes.io/unreachable" , "operator" : "Exists" , "effect" : "NoExecute" , "tolerationSeconds" : 300 }]}, "status" :{ "phase" : "Pending" , "qosClass" : "BestEffort" }}復制代碼
從 url path 里面可以看到幾個劃分,path 的分類大概有下面這幾種。
路徑上整體分成 group, version, resource, 作為核心 API group 的 core(包括 pod, node 之類的 resource),不帶 group,直接接在 /api/ 后面,其他的 api group 則接在 /apis 后面。以 pod 為例,pod 對應的數據類型如下,這個數據結構和 POST 請求中的結構的參數是一致的。
如果是 job 的話則是在,pkg/apis/batch/v2alpha1/types.go,和 API 路徑是對應的。例子當中 kubectl 加上 level 大于 8 的 log 就會打印請求和相應的 body,可以看到 request body 和上面的數據結構是一致的。這個請求會發送到 apiserver 進行處理并且返回存儲之后的 pod。
2.2重要結構體
2.2.1、Config
父結構,主要的配置內容,其中有一個結構 RESTOptionsGetter genericregistry.RESTOptionsGetter 是和 API 初始化相關的,這個接口的實現是在 k8s.io/apiserver/pkg/server/options/etcd.go 中的 storageFactoryRestOptionsFactory 實現的,對應的實現函數是
func (f *storageFactoryRestOptionsFactory) GetRESTOptions (resource schema. GroupResource ) ( generic . RESTOptions , error) {storageConfig, err := f. StorageFactory . NewConfig (resource)iferr != nil{returngeneric . RESTOptions {}, fmt. Errorf ( "unable to find storage destination for %v, due to %v" , resource, err. Error ())}ret := generic . RESTOptions {StorageConfig : storageConfig,Decorator : generic . UndecoratedStorage ,DeleteCollectionWorkers : f. Options . DeleteCollectionWorkers ,EnableGarbageCollection : f. Options . EnableGarbageCollection ,ResourcePrefix : f. StorageFactory . ResourcePrefix (resource),}iff. Options . EnableWatchCache{sizes, err := ParseWatchCacheSizes (f. Options . WatchCacheSizes )iferr != nil{returngeneric . RESTOptions {}, err}cacheSize, ok := sizes[resource]if!ok {cacheSize = f. Options . DefaultWatchCacheSize}ret. Decorator= genericregistry. StorageWithCacher (cacheSize)}returnret, nil }2.2.2、APIGroupInfo 復制代碼2.2.2、APIGroupInfo
APIGroupInfo 主要定義了一個 API 組的相關信息,觀察一下 APIGroupInfo 是如何初始化的。 在 k8s.io/pkg/master/master.go 當中,每個 Resource 都要提供自己的 Provider,比如說 storagerest 就在 k8s.io/kubernetes/pkg/registry/storage/rest/storage_storage.go 定義了 NewRESTStorage 方法。而默認的 resource 的 legacy provider 單獨處理。
ifc. ExtraConfig . APIResourceConfigSource . AnyResourcesForVersionEnabled (apiv1. SchemeGroupVersion ) {legacyRESTStorageProvider := corerest. LegacyRESTStorageProvider {StorageFactory : c. ExtraConfig . StorageFactory ,ProxyTransport : c. ExtraConfig . ProxyTransport ,KubeletClientConfig : c. ExtraConfig . KubeletClientConfig ,EventTTL : c. ExtraConfig . EventTTL ,ServiceIPRange : c. ExtraConfig . ServiceIPRange ,ServiceNodePortRange : c. ExtraConfig . ServiceNodePortRange ,LoopbackClientConfig : c. GenericConfig . LoopbackClientConfig ,}m. InstallLegacyAPI (&c, c. GenericConfig . RESTOptionsGetter , legacyRESTStorageProvider)} 復制代碼
然后通過調用k8s.io/kubernetes/pkg/registry/core/rest.LegacyRESTStorageProvider 的 NewLegacyRESTStorage 來初始化基礎對象的 apigroup info,比如初始化 podStorage,serviceStorage 和 nodeStorage 等等。legacy ApiGrouInfo 的 Scheme, ParamaterCodec, NegotiatedSerializer 都是用 "k8s.io/kubernetes/pkg/api" 包下的全局變量初始化的。
Scheme : api. Scheme ,ParameterCodec : api. ParameterCodec ,NegotiatedSerializer : api. Codecs , 復制代碼
然后合并成一個 restStorage 存入 apiGroupInfo 中。
restStorageMap := map[ string ]rest. Storage {"pods" : podStorage. Pod ,"pods/attach" : podStorage. Attach ,"pods/status" : podStorage. Status ,"pods/log" : podStorage. Log ,"pods/exec" : podStorage. Exec ,"pods/portforward" : podStorage. PortForward ,"pods/proxy" : podStorage. Proxy ,"pods/binding" : podStorage. Binding ,"bindings" : podStorage. Binding ,... 復制代碼
舉個例子 podStorage 就是用的 genericregistry.Store,這是一個通用的 etc 輔助結構,把 etcd 抽象成存儲結構。
// REST implements a RESTStorage for pods type REST struct{*genericregistry. StoreproxyTransport http. RoundTripper } 復制代碼
2.3serialization
pkg/api.Codecs 是全局默認的 codec 來自下面這段代碼。
func NewCodecFactory (scheme *runtime. Scheme ) CodecFactory{serializers := newSerializersForScheme(scheme, json. DefaultMetaFactory )returnnewCodecFactory(scheme, serializers) } 復制代碼
默認具體定義了這幾種 serilizer。
func newSerializersForScheme(scheme *runtime. Scheme , mf json. MetaFactory ) []serializerType {jsonSerializer := json. NewSerializer (mf, scheme, scheme, false )jsonPrettySerializer := json. NewSerializer (mf, scheme, scheme, true )yamlSerializer := json. NewYAMLSerializer (mf, scheme, scheme)... 復制代碼
而且標準庫的 json 有很嚴重的性能問題,換用了 json-iter 但是有很多標準庫不兼容的問題,性能提升了大概 20% 但是沒辦法和進主線,我嘗試在上面工作的了一段時間,改了兩個問題還是有錯,由于時間關系,暫時放棄了這個工作,相關的 issue 在這里:https://github.com/kubernetes/kubernetes/pull/54289
func DefaultBuildHandlerChain (apiHandler http. Handler , c * Config ) http. Handler{handler := genericapifilters. WithAuthorization (apiHandler, c. RequestContextMapper , c. Authorizer , c. Serializer )handler = genericfilters. WithMaxInFlightLimit (handler, c. MaxRequestsInFlight , c. MaxMutatingRequestsInFlight , c. RequestContextMapper , c. LongRunningFunc )handler = genericapifilters. WithImpersonation (handler, c. RequestContextMapper , c. Authorizer , c. Serializer )ifutilfeature. DefaultFeatureGate . Enabled (features. AdvancedAuditing ) {handler = genericapifilters. WithAudit (handler, c. RequestContextMapper , c. AuditBackend , c. AuditPolicyChecker , c. LongRunningFunc )} else{handler = genericapifilters. WithLegacyAudit (handler, c. RequestContextMapper , c. LegacyAuditWriter )}failedHandler := genericapifilters. Unauthorized (c. RequestContextMapper , c. Serializer , c. SupportsBasicAuth )ifutilfeature. DefaultFeatureGate . Enabled (features. AdvancedAuditing ) {failedHandler = genericapifilters. WithFailedAuthenticationAudit (failedHandler, c. RequestContextMapper , c. AuditBackend , c. AuditPolicyChecker )}handler = genericapifilters. WithAuthentication (handler, c. RequestContextMapper , c. Authenticator , failedHandler)handler = genericfilters. WithCORS (handler, c. CorsAllowedOriginList , nil , nil , nil , "true" )handler = genericfilters. WithTimeoutForNonLongRunningRequests (handler, c. RequestContextMapper , c. LongRunningFunc , c. RequestTimeout )handler = genericapifilters. WithRequestInfo (handler, c. RequestInfoResolver , c. RequestContextMapper )handler = apirequest. WithRequestContext (handler, c. RequestContextMapper )handler = genericfilters. WithPanicRecovery (handler)returnhandler }復制代碼
2.4filters
首先通過 ./staging/src/k8s.io/apiserver/pkg/server/config.go 下的 DefaultBuildHandlerChain 構建 filters。
2.4.1、panic recover
genericfilters.WithPanicRecovery 在 handler 的最外層對出現的 panic 恢復,并且打印每次請求的 log,所以你想觀察 API 請求的情況可以 grep wrap.go 就能看到。
2.4.2、request context
apirequest.WithRequestContext 給 request 綁定一個 Context
2.4.3、RequestInfo
跟路 url 提取后續請求需要的 group, version, namespace, verb, resource 等信息。
2.4.4、WithTimeoutForNonLongRunningRequests
限制 API 調用時間,超時處理提前終止 write。
2.4.5、WithCORS
允許跨域訪問。
2.4.6、authentication
在 k8s.io/apiserver/pkg/endpoints/filters/authentication.go 下。WithAuthentication 插入鑒權信息,例如證書鑒權,token 鑒權等,并且從鑒權信息當中獲取 user 信息(可能是 service account 也可能是外部用戶)user 身份是由這 里面的幾種方式確認的
2.4.7、authorization
檢查是否有權限進行對應資源的操作。一種是 RBAC 一種是 Node。具體這兩種方式可以看這個介紹:https://kubernetes.io/docs/admin/authorization/,RBAC 主要是針對服務的,而 Node 模式主要是針對 kubelet 的。
2.4.8、impersonation
讓用戶偽裝成其他用戶,比如 admin 可以用普通用戶的身份創建資源。
2.5路由
通過 genericapiserver 的 InstallLegacyAPIGroup 就注冊到路由當中。具體的做法就是根據 version, resource, sub resource, verb 等信息構造路由,然后用 go-restful 注冊處理函數。比如說 GET
route := ws.GET(action. Path ). To (handler).Doc (doc).Param (ws. QueryParameter ( "pretty" , "If 'true', then the output is pretty printed." )).Operation ( "read" +namespaced+kind+strings. Title (subresource)+operationSuffix).Produces (append(storageMeta. ProducesMIMETypes (action. Verb ), mediaTypes...)...).Returns (http. StatusOK , "OK" , producedObject).Writes (producedObject) 復制代碼handler 里面做的內容就是序列化,然后根據具體的要求(GET DELETE 等)到 etcd 中操作,當然本身還有一層緩存,這取決于 API 的 options 是希望更新還是直接讀緩存(緩存會比 etcd 舊一些),比如對于 kubelet 會不斷查詢 node 信息,但是 kubelet 本身并不需要最新的信息,這個時候就會從緩存中讀取。
2.6性能調優
開啟代理 kubectl proxy,就可以通過 localhost 直接訪問 kube-apiserver HTTP 服務。然后執行 go tool pprof http://localhost:8001/debug/pprof/profile 可以獲得 profile 結果,下圖紅色的部分就是調用耗時最多的部分。
除此之外,kube-apiserver 本身也暴露了很多 prometheus 的 metrics 但是往上現在沒有現成的模板,只能根據自己的需求來在 prometheus 當作做 query。可以在 k8s.io/apiserver/pkg/endpoints/metrics/metrics.go 里面看到。 之前也說過,超時間調用時會打 log 的,在代碼中保存了一些 trace 日志,可以通過 grep Trace來過濾。Trace[%d] 這樣開頭, %d 是一個 id 可以看到具體的 trace 信息。
總結
以上是生活随笔為你收集整理的大家都较熟悉之 Kubernetes API 分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: union万能密码By:dangdang
- 下一篇: 洛谷——P1226 取余运算||快速幂