自定義資源對象

首先我們先看下如果想基于框架實(shí)現(xiàn)一個(gè)自定義的資源,應(yīng)該如何開發(fā),在 網(wǎng)關(guān)篇 提到 使用 AA 模式部署的 metrics-server,對外提供了 nodeMetrics 資源對象,用戶可以直接使用 kubectl 去管理,下面的幾個(gè)關(guān)鍵接口(Interface)你需要仔細(xì)看一下,之前也說到過 AA 模式提供的資源描述和內(nèi)建的資源API定義模式一樣,那我們索性就直接看內(nèi)置資源的實(shí)現(xiàn)。

rest.Storage

K8S的內(nèi)置資源對象對外提供 REST 風(fēng)格的交互接口,通過 REST API對外提供服務(wù)都需要實(shí)現(xiàn) rest.Storage 接口。

// Single item interfaces:
// (Method: Current -> Proposed)
//    GET: Getter -> NamedGetter
//    WATCH: (n/a) -> NamedWatcher
//    CREATE: (n/a) -> NamedCreater
//    DELETE: Deleter -> NamedDeleter
//    UPDATE: Update -> NamedUpdater

// Storage is a generic interface for RESTful storage services.
// Resources which are exported to the RESTful API of apiserver need to implement this interface. It is expected
// that objects may implement any of the below interfaces.
type Storage interface {
    // New returns an empty object that can be used with Create and Update after request data has been put into it.
    // This object must be a pointer type for use with Codec.DecodeInto([]byte, runtime.Object)
    New() runtime.Object

    // Destroy cleans up its resources on shutdown.
    // Destroy has to be implemented in thread-safe way and be prepared
    // for being called more than once.
    Destroy()
}

在 Github Dir: kubernetes/pkg/registry[3] 中包括所有內(nèi)置資源的 storage 實(shí)現(xiàn),根據(jù) group 做了區(qū)分,你可以點(diǎn)開自己感興趣的 group 查看具體資源的實(shí)現(xiàn)。

要想要實(shí)現(xiàn)通過 kubectl 去管理自定義資源(get、list、create、patch、update、watch ect),其實(shí)也是對應(yīng)著 REST API中的一些 Verb 行為 還需要按需實(shí)現(xiàn) Github Interface: StandardStorage[4] 接口

// StandardStorage is an interface covering the common verbs. Provided for testing whether a
// resource satisfies the normal storage methods. Use Storage when passing opaque storage objects.
type StandardStorage interface {
    Getter
    Lister
    CreaterUpdater
    GracefulDeleter
    CollectionDeleter
    Watcher
}

每個(gè)接口都對應(yīng)著不同的 REST HTTP Verb,比如:

  1. GET 方法 ->Getter、Lister、Watcher
  2. POST/PUT 方法 -> CreaterUpdater
  3. DELETE 方法 -> GracefulDeleter

你可能想了解,我實(shí)現(xiàn)了這些接口之后,k8s.io/apiserver 是如何幫我實(shí)現(xiàn) HTTP Verb 和 接口方法的映射,那么實(shí)現(xiàn)細(xì)節(jié)請看 Github Method: registerResourceHandlers[5]

大致思路是通過斷言接口類型,來為不同的 HTTP Verb 注冊對應(yīng)的 Handler

// what verbs are supported by the storage, used to know what verbs we support per path
creater, isCreater := storage.(rest.Creater)
namedCreater, isNamedCreater := storage.(rest.NamedCreater)
lister, isLister := storage.(rest.Lister)
getter, isGetter := storage.(rest.Getter)
getterWithOptions, isGetterWithOptions := storage.(rest.GetterWithOptions)
gracefulDeleter, isGracefulDeleter := storage.(rest.GracefulDeleter)
collectionDeleter, isCollectionDeleter := storage.(rest.CollectionDeleter)
updater, isUpdater := storage.(rest.Updater)
patcher, isPatcher := storage.(rest.Patcher)
watcher, isWatcher := storage.(rest.Watcher)
connecter, isConnecter := storage.(rest.Connecter)
storageMeta, isMetadata := storage.(rest.StorageMetadata)
storageVersionProvider, isStorageVersionProvider := storage.(rest.StorageVersionProvider)

不過一般內(nèi)置資源通過內(nèi)嵌 Github Object: genericregiser.Store[6] 對象就可以自動(dòng)實(shí)現(xiàn)所有的接口,對象 genericregiser.Store 主要是實(shí)現(xiàn)了資源存儲(chǔ)到 ETCD。

其中Github File: kubernetes/pkg/registry/core/rest/storage_core.go[7] 注冊所有 core 組資源,我們以 core 下的 Github File: Pod[8] 對象為例.

// NewStorage returns a RESTStorage object that will work against pods.
func NewStorage(optsGetter generic.RESTOptionsGetter, k client.ConnectionInfoGetter, proxyTransport http.RoundTripper, podDisruptionBudgetClient policyclient.PodDisruptionBudgetsGetter) (PodStorage, error) {
    store := &genericregistry.Store{
        NewFunc:                  func() runtime.Object { return &api.Pod{} },
        NewListFunc:              func() runtime.Object { return &api.PodList{} },
        PredicateFunc:            registrypod.MatchPod,
        DefaultQualifiedResource: api.Resource("pods"),
        CreateStrategy:      registrypod.Strategy,
        UpdateStrategy:      registrypod.Strategy,
        DeleteStrategy:      registrypod.Strategy,
        ReturnDeletedObject: true,
        TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
    }
    ...
    return PodStorage{
        Pod:                 &REST{store, proxyTransport},
        Binding:             &BindingREST{store: store},
        LegacyBinding:       &LegacyBindingREST{bindingREST},
        Eviction:            newEvictionStorage(store, podDisruptionBudgetClient),
        Status:              &StatusREST{store: &statusStore},
        EphemeralContainers: &EphemeralContainersREST{store: &ephemeralContainersStore},
        Log:                 &podrest.LogREST{Store: store, KubeletConn: k},
        Proxy:               &podrest.ProxyREST{Store: store, ProxyTransport: proxyTransport},
        Exec:                &podrest.ExecREST{Store: store, KubeletConn: k},
        Attach:              &podrest.AttachREST{Store: store, KubeletConn: k},
        PortForward:         &podrest.PortForwardREST{Store: store, KubeletConn: k},
    }, nil
}

在 Pod Storage 描述中還有一些我們常見的資源的存儲(chǔ)實(shí)現(xiàn),以 Github File: Binding[9] 為例,當(dāng)我們創(chuàng)建 Pod 的Binding子資源的時(shí)候,其實(shí)是給Pod綁定一個(gè)Node,最終效果看起來是給 Pod 的NodeName字段設(shè)定一個(gè)值。同樣的,你可以參考下 Pod 的 Github File Exec[10] 子資源的實(shí)現(xiàn),實(shí)現(xiàn)了 Connecter 接口,當(dāng)請求 Exec 子資源的時(shí)候,請求會(huì)被代理到 Pod 所在 Node的 kubelet 啟動(dòng)的服務(wù)上,這種實(shí)現(xiàn)方式和我們上一篇問題提到的 Proxy 子資源實(shí)現(xiàn)方式一樣,細(xì)節(jié)還需各位自己看代碼研究。

// Create ensures a pod is bound to a specific host.
func (r *BindingREST) Create(ctx context.Context, name string, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (out runtime.Object, err error) {
    binding, ok := obj.(*api.Binding)
    if !ok {
        return nil, errors.NewBadRequest(fmt.Sprintf("not a Binding object: %#v", obj))
    }
    if name != binding.Name {
        return nil, errors.NewBadRequest("name in URL does not match name in Binding object")
    }
    // TODO: move me to a binding strategy
    if errs := validation.ValidatePodBinding(binding); len(errs) != 0 {
        return nil, errs.ToAggregate()
    }
    if createValidation != nil {
        if err := createValidation(ctx, binding.DeepCopyObject()); err != nil {
            return nil, err
        }
    }
    err = r.assignPod(ctx, binding.UID, binding.ResourceVersion, binding.Name, binding.Target.Name, binding.Annotations, dryrun.IsDryRun(options.DryRun))
    out = &metav1.Status{Status: metav1.StatusSuccess}
    return
}

認(rèn)證/授權(quán)(authn/authz)

在講解認(rèn)證/授權(quán)前,我們先看上面的這張圖,這張圖的右側(cè)的 Hanlder Chain 里我們可以看到每個(gè) Handler 都實(shí)現(xiàn)了我們熟悉的功能,認(rèn)證授權(quán)限流審計(jì)等功能。配合著圖閱讀 Github Func: DefaultBuildHandlerChain[11] 函數(shù)的實(shí)現(xiàn),你會(huì)微微一笑(原來就是這么個(gè)回事)

func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) http.Handler {
    handler := apiHandler

    handler = filterlatency.TrackCompleted(handler)
    handler = genericapifilters.WithAuthorization(handler, c.Authorization.Authorizer, c.Serializer)
    handler = filterlatency.TrackStarted(handler, c.TracerProvider, "authorization")
    // APF 服務(wù)端限流
    if c.FlowControl != nil {
        workEstimatorCfg := flowcontrolrequest.DefaultWorkEstimatorConfig()
        requestWorkEstimator := flowcontrolrequest.NewWorkEstimator(
            c.StorageObjectCountTracker.Get, c.FlowControl.GetInterestedWatchCount, workEstimatorCfg, c.FlowControl.GetMaxSeats)
        handler = filterlatency.TrackCompleted(handler)
        handler = genericfilters.WithPriorityAndFairness(handler, c.LongRunningFunc, c.FlowControl, requestWorkEstimator, c.RequestTimeout/4)
        handler = filterlatency.TrackStarted(handler, c.TracerProvider, "priorityandfairness")
    } else {
        handler = genericfilters.WithMaxInFlightLimit(handler, c.MaxRequestsInFlight, c.MaxMutatingRequestsInFlight, c.LongRunningFunc)
    }

    handler = filterlatency.TrackCompleted(handler)
    // 角色扮演
    handler = genericapifilters.WithImpersonation(handler, c.Authorization.Authorizer, c.Serializer)
    handler = filterlatency.TrackStarted(handler, c.TracerProvider, "impersonation")

    handler = filterlatency.TrackCompleted(handler)
    // 審計(jì)日志
    handler = genericapifilters.WithAudit(handler, c.AuditBackend, c.AuditPolicyRuleEvaluator, c.LongRunningFunc)
    handler = filterlatency.TrackStarted(handler, c.TracerProvider, "audit")

   // authn/authz
    failedHandler := genericapifilters.Unauthorized(c.Serializer)
    failedHandler = genericapifilters.WithFailedAuthenticationAudit(failedHandler, c.AuditBackend, c.AuditPolicyRuleEvaluator)
...
}

這個(gè)函數(shù)實(shí)現(xiàn)了 HTTP Middleware 的能力,用戶請求需要經(jīng)過層層Handler的執(zhí)行,鑒權(quán)、授權(quán)的操作就放在這里實(shí)現(xiàn),不僅僅是鑒權(quán)授權(quán),審計(jì)、準(zhǔn)入控制、流量管理都是通過HTTP Middleware嵌套Handler來完成。

如何認(rèn)證

如果想自定義認(rèn)證能力,比如接入公司內(nèi)部的認(rèn)證系統(tǒng),ok沒關(guān)系完全可以

K8S 自己有多種認(rèn)證器 BasicAuth、ClientCA、TokenAuth、ServiceAccountAuth等, Github Interface: authenticator.Request[12]中 定義了認(rèn)證器的接口,

// Request attempts to extract authentication information from a request and
// returns a Response or an error if the request could not be checked.
type Request interface {
    AuthenticateRequest(req *http.Request) (*Response, bool, error)
}

在初始化 RecommendedOptions.Authentication[13] 的時(shí)候會(huì)把這些認(rèn)證器注冊進(jìn)去,只有有一個(gè)認(rèn)證器認(rèn)證通過就通過,所以你可以在這里把你自定義認(rèn)證器注冊進(jìn)去,只要實(shí)現(xiàn) Request 接口即可。

其實(shí)我們最常見的是 x509 的認(rèn)證器(就是對你的kubeconfig中的客戶端證書的認(rèn)證) Github Type: x509.Authenticator[14]。

授權(quán)

對于一些自定義資源的 CURD 操作可能需要連接公司內(nèi)部的授權(quán)系統(tǒng)來判斷用戶是否有權(quán)限進(jìn)行操作,你完全可以自定義授權(quán)器來接入公司管控

K8S 有多種授權(quán)器 AlwaysAllow、ABAC、Webhook、Node、RBAC等,Github Interface: authorizer.Authorizer[15] 和 Github Interface: authorizer.RuleResolver[16] 兩個(gè)接口定義了授權(quán)器,其中:

Github Interface: authorizer.Authorizer[17] 用于從請求中獲取授權(quán)信息,如果這一步?jīng)]成功那么決策為失敗

// Authorizer makes an authorization decision based on information gained by making
// zero or more calls to methods of the Attributes interface. It returns nil when an action is
// authorized, otherwise it returns an error.
type Authorizer interface {
Authorize(ctx context.Context, a Attributes) (authorized Decision, reason string, err error)
}

Github Interface: authorizer.RuleResolver[18] 用于解析規(guī)則,看是否可以對資源進(jìn)行操作

// RuleResolver provides a mechanism for resolving the list of rules that apply to a given user within a namespace.
type RuleResolver interface {
// RulesFor get the list of cluster wide rules, the list of rules in the specific namespace, incomplete status and errors.
RulesFor(user user.Info, namespace string) ([]ResourceRuleInfo, []NonResourceRuleInfo, bool, error)
}

在初始化 RecommendedOptions.Authorization[19] 的時(shí)候把自定義授權(quán)器注冊進(jìn)來,如果對RBAC機(jī)制感興趣你可以查看 Github Type: rbac.RBACAuthorizer[20] 的實(shí)現(xiàn),不需要太多代碼就給你的APIServer 支持RBAC的能力

準(zhǔn)入控制(admission)

其實(shí)準(zhǔn)入控制可以對應(yīng)為請求體的參數(shù)校驗(yàn),以及校驗(yàn)一些不合規(guī)的參數(shù),或者給請求體中填充一些字段,?? 甚至你可以接入公司的變更系統(tǒng),在封網(wǎng)期間禁止對資源修改,審批通過后才允許執(zhí)行操作

實(shí)現(xiàn) Github Snippet: 變更準(zhǔn)入控制和驗(yàn)證準(zhǔn)入控制接口[21] 接口就可以完成自定義準(zhǔn)入校驗(yàn)

type Interface interface {
    // Handles returns true if this admission controller can handle the given operation
    // where operation can be one of CREATE, UPDATE, DELETE, or CONNECT
    Handles(operation Operation) bool
}

type MutationInterface interface {
    Interface

    // Admit makes an admission decision based on the request attributes.
    // Context is used only for timeout/deadline/cancellation and tracing information.
    Admit(ctx context.Context, a Attributes, o ObjectInterfaces) (err error)
}

// ValidationInterface is an abstract, pluggable interface for Admission Control decisions.
type ValidationInterface interface {
    Interface

    // Validate makes an admission decision based on the request attributes.  It is NOT allowed to mutate
    // Context is used only for timeout/deadline/cancellation and tracing information.
    Validate(ctx context.Context, a Attributes, o ObjectInterfaces) (err error)
}

kube-apiserver 中所有已啟用的準(zhǔn)入控制器由 Github Variable: chainAdmissionHandler[22] 數(shù)據(jù)結(jié)構(gòu)管理,當(dāng)客戶端發(fā)送請求給 kube-apiserver 時(shí),Handler 會(huì)遍歷 Github Variable: chainAdmissionHandler[23] 中啟用的準(zhǔn)入控制器并執(zhí)行變更和驗(yàn)證操作,我們想要自定義準(zhǔn)入控制就需要把自己的準(zhǔn)入控制注冊進(jìn)來。

Github Dir: kubernetes/plugin/pkg/admission[24] 目錄下為 K8S 內(nèi)置的準(zhǔn)入控制器, kube-apiserver 在啟動(dòng)時(shí)候會(huì)調(diào)用 Github CodeLine: kubeoptions.NewAdmissionOptions()[25] 把內(nèi)置的準(zhǔn)入控制器列表傳到 ServerRunOptions上。

持久化(storage)

數(shù)據(jù)的持久化也可以自定義,apiserver 默認(rèn)是使用 Etcd 來作為后端存儲(chǔ),不過沒關(guān)系!,后端存儲(chǔ)也可以自定義,我司就使用 MYSQL 作為后端存儲(chǔ)來做必要的數(shù)據(jù)備份

Github Type: Store[26] 用于etcd的存儲(chǔ),一般k8s資源都嵌入了這個(gè)結(jié)構(gòu)體,他實(shí)現(xiàn)了下面列出的這些接口,這樣k8s資源把 Store 作為內(nèi)嵌資源,就自然可以實(shí)現(xiàn)下面這些接口,也就天然的可以支持一些 REST 的操作。


// Note: the rest.StandardStorage interface aggregates the common REST verbs
var _ rest.StandardStorage = &Store{}
var _ rest.Exporter = &Store{}
var _ rest.TableConvertor = &Store{}
var _ GenericStore = &Store{}

但是我們完全可以自定義一個(gè)自己的存儲(chǔ),這里給大家一個(gè)參考 Github Repo: mink[27] 來實(shí)現(xiàn)一個(gè)對接MySQL的存儲(chǔ)對象

審計(jì)(aduit)

審計(jì)是一個(gè) APIServer 不可或缺需要用來扯皮的功能

k8s.io/apiserver 框架已經(jīng)自帶了審計(jì)能力 Github Func: WithAudit[28]

你完全可以參考 https://kubernetes.io/zh-cn/docs/tasks/debug/debug-cluster/audit/ 文檔里的 Policy 策略完成審計(jì)日志的配置

apiVersion: audit.k8s.io/v1 # 這是必填項(xiàng)。
kind: Policy
# 不要在 RequestReceived 階段為任何請求生成審計(jì)事件。
omitStages:
  - "RequestReceived"
rules:
  # 在日志中用 RequestResponse 級別記錄 Pod 變化。
  - level: RequestResponse
    resources:
    # 可以替換為你自定義資源的資源組和資源名稱
    - group: ""
      resources: ["pods"]

只需要在服務(wù)的啟動(dòng)參數(shù)里指定下面的參數(shù)就可以擁有和K8S一樣的審計(jì)能力,而你我的朋友,這是你用 k8s.io/apiserver 構(gòu)建你服務(wù)所應(yīng)得的。  

- --audit-policy-file=/etc/kubernetes/audit-policy.yaml

- --audit-log-path=/var/log/kubernetes/audit/audit.log

流量控制(flow control)

k8s有非常強(qiáng)的服務(wù)端限流能力APF,不過目前還不能直接使用,因?yàn)樾枰蕾囈粋€(gè)etcd

APF的原理可以參考 https://alexstocks.github.io/html/k8s-apf.html,因?yàn)閗ube-apiserver在啟動(dòng)后的PostHook里會(huì)有專門處理FlowSchema和PriorityLevelConfiguration動(dòng)態(tài)控制流量權(quán)重,如果想直接使用,需要做一些兼容改造。

不過 上一篇文章中提到的 KubeGateway 提供一另一種服務(wù)端限流能力,雖然沒有APF那么能的細(xì)化限流能力,不過對于需要的小伙伴也足夠了,感興趣的同學(xué)可以自行翻閱代碼。

More

  1. 如果大家想有手把手的保姆教程,大家可以查看 https://blog.gmem.cc/kubernetes-style-apiserver 這篇博客,寫的非常不錯(cuò)詳細(xì)
  2. 另外本人也提供了一個(gè)簡單的 demo: https://github.com/yangsoon/apiserver 可以直接執(zhí)行 make local-run 就可以運(yùn)行,并且會(huì)把數(shù)據(jù)存儲(chǔ)到內(nèi)存,不依賴etcd,感興趣的同學(xué)可以看相關(guān)的實(shí)現(xiàn)
  3. 注意本文只是一個(gè)索引文章,更多的細(xì)節(jié)還是需要大家自己去挖掘,下篇 多租k8s 會(huì)基于這篇文章的背景知識來看下字節(jié)和紅帽們是如何基于k8s.io/apiserver 玩轉(zhuǎn)多租k8s

引用鏈接

[1] 阿里云云產(chǎn)品 SAE-https://www.aliyun.com/product/sae: https://www.aliyun.com/product/sae
[2] Github Repo: k8s.io/apiserver : https://github.com/kubernetes/apiserver
[3] Github Dir: kubernetes/pkg/registry: https://github.com/kubernetes/kubernetes/tree/8b98305858b107369f2c9b9fd8ef1c5b0da078c0/pkg/registry
[4] Github Interface: StandardStorage: https://github.com/kubernetes/kubernetes/blob/4d33d837c8be778044d50755de83f8738e957c13/staging/src/k8s.io/apiserver/pkg/registry/rest/rest.go#L276
[5] Github Method: registerResourceHandlers: https://github.com/kubernetes/kubernetes/blob/1c2d446648662529282a3bb1528a6dbb50700fdb/staging/src/k8s.io/apiserver/pkg/endpoints/installer.go#L190
[6] Github Object: genericregiser.Store: https://github.com/kubernetes/kubernetes/blob/1c2d446648662529282a3bb1528a6dbb50700fdb/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go#L81
[7] Github File: kubernetes/pkg/registry/core/rest/storage_core.go: https://github.com/kubernetes/kubernetes/blob/d1c296431e0ff2363131707054c4c75ad59cd2c0/pkg/registry/core/rest/storage_core.go#L104
[8] Github File: Pod: https://github.com/kubernetes/kubernetes/blob/d1c296431e0ff2363131707054c4c75ad59cd2c0/pkg/registry/core/rest/storage_core.go#L173
[9] Github File: Binding: https://github.com/kubernetes/kubernetes/blob/d1c296431e0ff2363131707054c4c75ad59cd2c0/pkg/registry/core/pod/storage/storage.go#L159
[10] Github File Exec: https://github.com/kubernetes/kubernetes/blob/2acdbae664bbc5ff9cd5d1ec07f93a14f444cef5/pkg/registry/core/pod/rest/subresources.go#L168
[11] Github Func: DefaultBuildHandlerChain: https://github.com/kubernetes/apiserver/blob/9dc08c72a8d36aad9e4508497417d5c6231610fa/pkg/server/config.go#L978
[12] Github Interface: authenticator.Request: https://github.com/kubernetes/apiserver/blob/9dc08c72a8d36aad9e4508497417d5c6231610fa/pkg/authentication/authenticator/interfaces.go#L34
[13] RecommendedOptions.Authentication: https://github.com/kubernetes/apiserver/blob/9dc08c72a8d36aad9e4508497417d5c6231610fa/pkg/server/options/recommended.go#L67
[14] Github Type: x509.Authenticator: https://github.com/kubernetes/apiserver/blob/9dc08c72a8d36aad9e4508497417d5c6231610fa/pkg/authentication/request/x509/x509.go#L133
[15] Github Interface: authorizer.Authorizer: https://github.com/kubernetes/apiserver/blob/9dc08c72a8d36aad9e4508497417d5c6231610fa/pkg/authorization/authorizer/interfaces.go#L70
[16] Github Interface: authorizer.RuleResolver: https://github.com/kubernetes/apiserver/blob/9dc08c72a8d36aad9e4508497417d5c6231610fa/pkg/authorization/authorizer/interfaces.go#L81
[17] Github Interface: authorizer.Authorizer: https://github.com/kubernetes/apiserver/blob/9dc08c72a8d36aad9e4508497417d5c6231610fa/pkg/authorization/authorizer/interfaces.go#L70
[18] Github Interface: authorizer.RuleResolver: https://github.com/kubernetes/apiserver/blob/9dc08c72a8d36aad9e4508497417d5c6231610fa/pkg/authorization/authorizer/interfaces.go#L81
[19] RecommendedOptions.Authorization: https://github.com/kubernetes/apiserver/blob/9dc08c72a8d36aad9e4508497417d5c6231610fa/pkg/server/options/recommended.go#L68
[20] Github Type: rbac.RBACAuthorizer: https://github.com/kubernetes/kubernetes/blob/4a89df5617b8e1e26abb16150502d04e6c180533/plugin/pkg/auth/authorizer/rbac/rbac.go#L50
[21] Github Snippet: 變更準(zhǔn)入控制和驗(yàn)證準(zhǔn)入控制接口: https://github.com/kubernetes/apiserver/blob/9dc08c72a8d36aad9e4508497417d5c6231610fa/pkg/admission/interfaces.go#L123-L144
[22] Github Variable: chainAdmissionHandler: https://github.com/kubernetes/apiserver/blob/master/pkg/admission/chain.go#L23
[23] Github Variable: chainAdmissionHandler: https://github.com/kubernetes/apiserver/blob/master/pkg/admission/chain.go#L23
[24] Github Dir: kubernetes/plugin/pkg/admission: https://github.com/kubernetes/kubernetes/tree/release-1.20/plugin/pkg/admission
[25] Github CodeLine: kubeoptions.NewAdmissionOptions(): https://github.com/kubernetes/kubernetes/blob/4a89df5617b8e1e26abb16150502d04e6c180533/cmd/kube-apiserver/app/options/options.go#L105
[26] Github Type: Store: https://github.com/kubernetes/apiserver/blob/9dc08c72a8d36aad9e4508497417d5c6231610fa/pkg/registry/generic/registry/store.go#L97
[27] Github Repo: mink: https://github.com/acorn-io/mink
[28] Github Func: WithAudit: https://github.com/kubernetes/apiserver/blob/9dc08c72a8d36aad9e4508497417d5c6231610fa/pkg/endpoints/filters/audit.go#L42

文章轉(zhuǎn)自微信公眾號@CNCF

上一篇:

有哪些新聞媒體提供Open API?

下一篇:

python機(jī)器人Agent編程——實(shí)現(xiàn)一個(gè)本地大模型和爬蟲結(jié)合的手機(jī)號歸屬地天氣查詢Agent
#你可能也喜歡這些API文章!

我們有何不同?

API服務(wù)商零注冊

多API并行試用

數(shù)據(jù)驅(qū)動(dòng)選型,提升決策效率

查看全部API→
??

熱門場景實(shí)測,選對API

#AI文本生成大模型API

對比大模型API的內(nèi)容創(chuàng)意新穎性、情感共鳴力、商業(yè)轉(zhuǎn)化潛力

25個(gè)渠道
一鍵對比試用API 限時(shí)免費(fèi)

#AI深度推理大模型API

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

10個(gè)渠道
一鍵對比試用API 限時(shí)免費(fèi)