所有文章 > 日积月累 > 如何基于 k8s.io/apiserver 开发自定义APIServer

如何基于 k8s.io/apiserver 开发自定义APIServer

在 网关篇 和大家分享了基于 k8s.io/apiserver 构建 API 增强能力的开源项目,本篇文章按照约定和大家分享如何基于 k8s.io/apiserver 构建自定义的 APIServer。

基于 k8s.io/apiserver 构建 APIServer 你可以享受到

  1. 可以无缝使用 kubectl 管理抽象资源
  2. 开箱即用的鉴权和授权(authn/authz)能力
  3. 一键式的代理请求到另一个 K8S-API 规范的服务
  4. 丰富可配置的审计日志能力
  5. 完善的 metrics 指标统计

其实这些都是一个基本的 APIServer 所需要的能力

先给大家分享一个故事,22年参与开发 阿里云云产品 SAE-https://www.aliyun.com/product/sae[1] 的时候,经常有用户通过调用阿里云OpenAPI的方式使用我们的产品部署服务,但是因为客户会在自己的CICD平台上接入多种云产品(包括各个厂商的K8S),希望能够用统一的方式管理自己的云产品,当时 一分 哥带着我给 SAE 的 OpenAPI 上套壳封装一个兼容 K8S 的API提供给客户,因为 K8S-API 是一套业界认可的标准API,用户可以使用统一的API定义去管理自己的云产品资源,没错当时我们就是基于 k8s.io/apiserver 去构建了这层代理,虽然后面因为各种原因没有正式上线,但是当 一分 使用 kubectl 直接去操作 SAE 上的应用的时候,着实给了一个作为校招生的我不小震撼,当场对 一分 死心塌地。

其实借由这个故事想告诉大家2件事情

  1. K8S 能够有现在这样在基础架构下的统治地位,他的 API 是起到了关键作用,统一 API 能够让你丝滑的享受整个生态带来的强大能力
  2. 我们开发平台的时候,不一定非得要用传统的后端开发模式开发,如果场景合适,基于 K8S API 这套规范来构建有时候甚至会有更意想不到的效果

本文会大致的讲解一些 k8s.io/apiserver 的基本概念,其实有一部分就是在讲 kube-apiserver的代码,但是我个人感觉技术文章讲代码很容易让大家感到厌烦,并且文章篇幅也会变得很长,大家可以把这篇文章当成一个索引文章,很多具体的实现都会给大家指出代码位置,我希望大家带着概念和实现思路自己去翻阅代码,遇到不懂的概念去搜索文档先有基本的了解,然后再来阅读代码,这样会更加有效。

本文会把相关的代码地址贴出来,你可以在文末引用查看具体的地址,但是微信公众号确实对外链不友好,辛苦读者翻到最下面的文末连接查看详细代码,不过你也可以访问 https://yangsoon.notion.site/Kube-APIServer-K8S-API-1022e15eae2b4701a8395316edfb5226?pvs=4 获得较好的阅读体验

How to Dev

本文一开始提到了鉴权/授权(authn/authz)、准入控制(admission)、请求代理(proxy)、持久化(storage)、审计(aduit)、流量控制(flow control)、这些能力在 Github Repo: k8s.io/apiserver [2] 库中都给留出了Hook点或者现成的函数,我们可以通过实现接口来插入自定义的能力。

开局一张图,让大家知道 k8s.io/apiserver 都由哪些组件构成,在下面讲解每一个自定义部分的时候,我都会把对应的组件在图中标记,大家记住图中组件的位置,在下面配合代码阅读的时候,你可以轻易的根据代码在图中的位置梳理出整个项目的代码结构

自定义资源对象

首先我们先看下如果想基于框架实现一个自定义的资源,应该如何开发,在 网关篇 提到 使用 AA 模式部署的 metrics-server,对外提供了 nodeMetrics 资源对象,用户可以直接使用 kubectl 去管理,下面的几个关键接口(Interface)你需要仔细看一下,之前也说到过 AA 模式提供的资源描述和内建的资源API定义模式一样,那我们索性就直接看内置资源的实现。

rest.Storage

K8S的内置资源对象对外提供 REST 风格的交互接口,通过 REST API对外提供服务都需要实现 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] 中包括所有内置资源的 storage 实现,根据 group 做了区分,你可以点开自己感兴趣的 group 查看具体资源的实现。

要想要实现通过 kubectl 去管理自定义资源(get、list、create、patch、update、watch ect),其实也是对应着 REST API中的一些 Verb 行为 还需要按需实现 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
}

每个接口都对应着不同的 REST HTTP Verb,比如:

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

你可能想了解,我实现了这些接口之后,k8s.io/apiserver 是如何帮我实现 HTTP Verb 和 接口方法的映射,那么实现细节请看 Github Method: registerResourceHandlers[5]

大致思路是通过断言接口类型,来为不同的 HTTP Verb 注册对应的 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)

不过一般内置资源通过内嵌 Github Object: genericregiser.Store[6] 对象就可以自动实现所有的接口,对象 genericregiser.Store 主要是实现了资源存储到 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 描述中还有一些我们常见的资源的存储实现,以 Github File: Binding[9] 为例,当我们创建 Pod 的Binding子资源的时候,其实是给Pod绑定一个Node,最终效果看起来是给 Pod 的NodeName字段设定一个值。同样的,你可以参考下 Pod 的 Github File Exec[10] 子资源的实现,实现了 Connecter 接口,当请求 Exec 子资源的时候,请求会被代理到 Pod 所在 Node的 kubelet 启动的服务上,这种实现方式和我们上一篇问题提到的 Proxy 子资源实现方式一样,细节还需各位自己看代码研究。

// 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
}

认证/授权(authn/authz)

在讲解认证/授权前,我们先看上面的这张图,这张图的右侧的 Hanlder Chain 里我们可以看到每个 Handler 都实现了我们熟悉的功能,认证授权限流审计等功能。配合着图阅读 Github Func: DefaultBuildHandlerChain[11] 函数的实现,你会微微一笑(原来就是这么个回事)

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 服务端限流
    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)
    // 审计日志
    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)
...
}

这个函数实现了 HTTP Middleware 的能力,用户请求需要经过层层Handler的执行,鉴权、授权的操作就放在这里实现,不仅仅是鉴权授权,审计、准入控制、流量管理都是通过HTTP Middleware嵌套Handler来完成。

如何认证

如果想自定义认证能力,比如接入公司内部的认证系统,ok没关系完全可以

K8S 自己有多种认证器 BasicAuth、ClientCA、TokenAuth、ServiceAccountAuth等, Github Interface: authenticator.Request[12]中 定义了认证器的接口,

// 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] 的时候会把这些认证器注册进去,只有有一个认证器认证通过就通过,所以你可以在这里把你自定义认证器注册进去,只要实现 Request 接口即可。

其实我们最常见的是 x509 的认证器(就是对你的kubeconfig中的客户端证书的认证) Github Type: x509.Authenticator[14]

授权

对于一些自定义资源的 CURD 操作可能需要连接公司内部的授权系统来判断用户是否有权限进行操作,你完全可以自定义授权器来接入公司管控

K8S 有多种授权器 AlwaysAllow、ABAC、Webhook、Node、RBAC等,Github Interface: authorizer.Authorizer[15] 和 Github Interface: authorizer.RuleResolver[16] 两个接口定义了授权器,其中:

Github Interface: authorizer.Authorizer[17] 用于从请求中获取授权信息,如果这一步没成功那么决策为失败

// 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] 用于解析规则,看是否可以对资源进行操作

// 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] 的时候把自定义授权器注册进来,如果对RBAC机制感兴趣你可以查看 Github Type: rbac.RBACAuthorizer[20] 的实现,不需要太多代码就给你的APIServer 支持RBAC的能力

准入控制(admission)

其实准入控制可以对应为请求体的参数校验,以及校验一些不合规的参数,或者给请求体中填充一些字段,⚠️ 甚至你可以接入公司的变更系统,在封网期间禁止对资源修改,审批通过后才允许执行操作

实现 Github Snippet: 变更准入控制和验证准入控制接口[21] 接口就可以完成自定义准入校验

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 中所有已启用的准入控制器由 Github Variable: chainAdmissionHandler[22] 数据结构管理,当客户端发送请求给 kube-apiserver 时,Handler 会遍历 Github Variable: chainAdmissionHandler[23] 中启用的准入控制器并执行变更和验证操作,我们想要自定义准入控制就需要把自己的准入控制注册进来。

Github Dir: kubernetes/plugin/pkg/admission[24] 目录下为 K8S 内置的准入控制器, kube-apiserver 在启动时候会调用 Github CodeLine: kubeoptions.NewAdmissionOptions()[25] 把内置的准入控制器列表传到 ServerRunOptions上。

持久化(storage)

数据的持久化也可以自定义,apiserver 默认是使用 Etcd 来作为后端存储,不过没关系!,后端存储也可以自定义,我司就使用 MYSQL 作为后端存储来做必要的数据备份

Github Type: Store[26] 用于etcd的存储,一般k8s资源都嵌入了这个结构体,他实现了下面列出的这些接口,这样k8s资源把 Store 作为内嵌资源,就自然可以实现下面这些接口,也就天然的可以支持一些 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{}

但是我们完全可以自定义一个自己的存储,这里给大家一个参考 Github Repo: mink[27] 来实现一个对接MySQL的存储对象

审计(aduit)

审计是一个 APIServer 不可或缺需要用来扯皮的功能

k8s.io/apiserver 框架已经自带了审计能力 Github Func: WithAudit[28]

你完全可以参考 https://kubernetes.io/zh-cn/docs/tasks/debug/debug-cluster/audit/ 文档里的 Policy 策略完成审计日志的配置

apiVersion: audit.k8s.io/v1 # 这是必填项。
kind: Policy
# 不要在 RequestReceived 阶段为任何请求生成审计事件。
omitStages:
  - "RequestReceived"
rules:
  # 在日志中用 RequestResponse 级别记录 Pod 变化。
  - level: RequestResponse
    resources:
    # 可以替换为你自定义资源的资源组和资源名称
    - group: ""
      resources: ["pods"]

只需要在服务的启动参数里指定下面的参数就可以拥有和K8S一样的审计能力,而你我的朋友,这是你用 k8s.io/apiserver 构建你服务所应得的。  

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

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

流量控制(flow control)

k8s有非常强的服务端限流能力APF,不过目前还不能直接使用,因为需要依赖一个etcd

APF的原理可以参考 https://alexstocks.github.io/html/k8s-apf.html,因为kube-apiserver在启动后的PostHook里会有专门处理FlowSchema和PriorityLevelConfiguration动态控制流量权重,如果想直接使用,需要做一些兼容改造。

不过 上一篇文章中提到的 KubeGateway 提供一另一种服务端限流能力,虽然没有APF那么能的细化限流能力,不过对于需要的小伙伴也足够了,感兴趣的同学可以自行翻阅代码。

More

  1. 如果大家想有手把手的保姆教程,大家可以查看 https://blog.gmem.cc/kubernetes-style-apiserver 这篇博客,写的非常不错详细
  2. 另外本人也提供了一个简单的 demo: https://github.com/yangsoon/apiserver 可以直接执行 make local-run 就可以运行,并且会把数据存储到内存,不依赖etcd,感兴趣的同学可以看相关的实现
  3. 注意本文只是一个索引文章,更多的细节还是需要大家自己去挖掘,下篇 多租k8s 会基于这篇文章的背景知识来看下字节和红帽们是如何基于k8s.io/apiserver 玩转多租k8s

引用链接

[1] 阿里云云产品 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: 变更准入控制和验证准入控制接口: 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

文章转自微信公众号@CNCF

#你可能也喜欢这些API文章!