你們都較熟悉之 Kubernetes API 分析


做者:高朋, 科賽網(kesci.com)工程師。
致力於讓深度學習和數據分析走進我的的開放平臺,已完成全部數據分析平臺部署於K8S。

 Kubernetes概覽

如下是 k8s 的總體架構,在 master 節點上主要是 kube-apiserver(整合了 kube-aggregator),還有 kube-scheduler,以及 kube-controller-manager,包括後端存儲 etcd。 node


其中 kube-apiserver 是一個比較關鍵的部分,並且前期寫得坑不少,致使這一部分雖然看起來是一個 API server 其實代碼很複雜,特別冗餘,並且目前對 kube-apiserver 還要作拆分,可以支持插入第三方的 apiserver,也就是又一個 aggregated apiserver 的 feature,也是和 kube-apiserver 和裏面包的一層 genericserver 揉合在一塊兒了,感受一個大的系統 API server 越寫越挫是一個通病,還好如今 k8s 迷途知返正在調整。linux


kube-apiserver

Kube-apiserver 能夠是認爲在 generic server 上封裝的一層官方默認的 apiserver,有第三方須要的狀況下,本身也能夠在 generic server 上封裝一層加入到集成模式中,這裏主要介紹 kube-apiserver 的結構。git

2.1restful API

kube-apiserver 是一個 restful 服務,請求直接經過 HTTP 請求發送,例如建立一個 ubuntu 的 pod,用如下的 pod.yaml 文件。github

apiVersion: v1
kind: 
Pod
metadata:
  name: ubuntu1
  labels:
    name: ubuntu1
spec:
  containers:
  - name: ubuntu1
    image: ubuntu
    command: [
"sleep"
, 
"1d"
]
複製代碼

執行命令 kubectl create -f ./pod.yaml -v=8,能夠看到對應的 POST 請求以下。json

Request

Body
: {
"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
Response

Headers
:

Content
-
Type
: application/json

Content
-
Length
: 
1208

Date
: 
Wed
, 
18

Oct

2017

15
:
04
:
17
 GMT
Response

Body
: {
"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 的分類大概有下面這幾種。 ubuntu


路徑上總體分紅 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。api

2.2重要結構體

2.2.一、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)

if
 err != 
nil
 {

return

generic
.
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),
    }

if
 f.
Options
.
EnableWatchCache
 {
        sizes, err := 
ParseWatchCacheSizes
(f.
Options
.
WatchCacheSizes
)

if
 err != 
nil
 {

return

generic
.
RESTOptions
{}, err
        }
        cacheSize, ok := sizes[resource]

if
 !ok {
            cacheSize = f.
Options
.
DefaultWatchCacheSize
        }
        ret.
Decorator
 = genericregistry.
StorageWithCacher
(cacheSize)
    }

return
 ret, 
nil
}

2.2.二、APIGroupInfo
複製代碼


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 單獨處理。

if
 c.
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.
Store
    proxyTransport http.
RoundTripper
}
複製代碼

2.3serialization

pkg/api.Codecs 是全局默認的 codec 來自下面這段代碼。

func 
NewCodecFactory
(scheme *runtime.
Scheme
) 
CodecFactory
 {
    serializers := newSerializersForScheme(scheme, json.
DefaultMetaFactory
)

return
 newCodecFactory(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
)

if
 utilfeature.
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
)

if
 utilfeature.
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)

return
 handler
}

複製代碼

2.4filters

首先經過 ./staging/src/k8s.io/apiserver/pkg/server/config.go 下的 DefaultBuildHandlerChain 構建 filters。

2.4.一、panic recover

genericfilters.WithPanicRecovery 在 handler 的最外層對出現的 panic 恢復,而且打印每次請求的 log,因此你想觀察 API 請求的狀況能夠 grep wrap.go 就能看到。

2.4.二、request context

apirequest.WithRequestContext 給 request 綁定一個 Context

2.4.三、RequestInfo

跟路 url 提取後續請求須要的 group, version, namespace, verb, resource 等信息。

2.4.四、WithTimeoutForNonLongRunningRequests

限制 API 調用時間,超時處理提早終止 write。

2.4.五、WithCORS

容許跨域訪問。

2.4.六、authentication

在 k8s.io/apiserver/pkg/endpoints/filters/authentication.go 下。WithAuthentication 插入鑑權信息,例如證書鑑權,token 鑑權等,而且從鑑權信息當中獲取 user 信息(多是 service account 也多是外部用戶)user 身份是由這 裏面的幾種方式確認的

2.4.七、authorization

檢查是否有權限進行對應資源的操做。一種是 RBAC 一種是 Node。具體這兩種方式能夠看這個介紹:https://kubernetes.io/docs/admin/authorization/,RBAC 主要是針對服務的,而 Node 模式主要是針對 kubelet 的。

2.4.八、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 信息。

相關文章
相關標籤/搜索