前言
前两篇文章介绍了 kube-apiserver 的认证和鉴权,这里继续往下走,介绍 kube-apiserver 的准入。
准入 admission
不同于前两篇的逆序介绍,这里顺序介绍 admission 流程。从创建准入 options,到根据 options 创建准入 config,接着介绍在 kube-apiserver 的 handler 中是怎么进入准入控制,怎么执行的。
admission options
进入 NewOptions 查看 admission options 是怎么创建的。
1# kubernetes/pkg/controlplane/apiserver/options/options.go
2func NewOptions() *Options {
3 s := Options{
4 ...
5 Admission: kubeoptions.NewAdmissionOptions(),
6 }
7}
8
9# kubernetes/pkg/kubeapiserver/options/admission.go
10func NewAdmissionOptions() *AdmissionOptions {
11 options := genericoptions.NewAdmissionOptions()
12 // register all admission plugins
13 RegisterAllAdmissionPlugins(options.Plugins)
14 // set RecommendedPluginOrder
15 options.RecommendedPluginOrder = AllOrderedPlugins
16 // set DefaultOffPlugins
17 options.DefaultOffPlugins = DefaultOffAdmissionPlugins()
18
19 return &AdmissionOptions{
20 GenericAdmission: options,
21 }
22}
NewAdmissionOptions 返回创建的 AdmissionOptions。其中,options 包括什么内容呢?我们看 NewAdmissionOptions, RegisterAllAdmissionPlugins 和 DefaultOffAdmissionPlugins 函数。
NewAdmissionOptions
1# kubernetes/vendor/k8s.io/apiserver/pkg/server/options/admission.go
2func NewAdmissionOptions() *AdmissionOptions {
3 options := &AdmissionOptions{
4 // 创建 Plugins 对象
5 Plugins: admission.NewPlugins(),
6 Decorators: admission.Decorators{admission.DecoratorFunc(admissionmetrics.WithControllerMetrics)},
7 RecommendedPluginOrder: []string{lifecycle.PluginName, mutatingwebhook.PluginName, validatingadmissionpolicy.PluginName, validatingwebhook.PluginName},
8 DefaultOffPlugins: sets.NewString(),
9 }
10 // 注册 admission plugins 到 Plugins 对象
11 server.RegisterAllAdmissionPlugins(options.Plugins)
12 return options
13}
14
15# kubernetes/vendor/k8s.io/apiserver/pkg/admission/plugins.go
16type Plugins struct {
17 lock sync.Mutex
18 registry map[string]Factory
19}
20
21func NewPlugins() *Plugins {
22 return &Plugins{}
23}
24
25# kubernetes/vendor/k8s.io/apiserver/pkg/server/plugins.go
26func RegisterAllAdmissionPlugins(plugins *admission.Plugins) {
27 lifecycle.Register(plugins)
28 validatingwebhook.Register(plugins)
29 mutatingwebhook.Register(plugins)
30 validatingadmissionpolicy.Register(plugins)
31}
32
33# kubernetes/vendor/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission.go
34func Register(plugins *admission.Plugins) {
35 plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
36 return NewLifecycle(sets.NewString(metav1.NamespaceDefault, metav1.NamespaceSystem, metav1.NamespacePublic))
37 })
38}
39
40# kubernetes/vendor/k8s.io/apiserver/pkg/admission/plugins.go
41func (ps *Plugins) Register(name string, plugin Factory) {
42 ...
43 ps.registry[name] = plugin
44}
在 NewAdmissionOptions 中,创建 Plugins 对象,将 admission plugins 注册到 Plugins 对象。注册的过程实际是写入 admission plugin 到对象 registry 的过程,registry 中存储的是 admission plugin name 和创建 plugin 工厂的映射。
RegisterAllAdmissionPlugins
1# kubernetes/pkg/kubeapiserver/options/plugins.go
2func RegisterAllAdmissionPlugins(plugins *admission.Plugins) {
3 admit.Register(plugins) // DEPRECATED as no real meaning
4 alwayspullimages.Register(plugins)
5 ...
6}
类似于 NewAdmissionOptions 中的注册过程,RegisterAllAdmissionPlugins 将注册所有的 admission plugin 到 Plugins 对象中。注册之后的 Plugins 有 36 种 admission plugin。
DefaultOffAdmissionPlugins
1func DefaultOffAdmissionPlugins() sets.String {
2 defaultOnPlugins := sets.NewString(
3 lifecycle.PluginName, // NamespaceLifecycle
4 limitranger.PluginName, // LimitRanger
5 ...
6 )
7
8 return sets.NewString(AllOrderedPlugins...).Difference(defaultOnPlugins)
9}
经过 DefaultOffAdmissionPlugins 处理后,Plugins 对象中有 20 种默认打开的 admission plugin,16 种默认关闭的 admission plugin。
admission config
创建完 admission options 后开始创建 admission config。
1# kubernetes/cmd/kube-apiserver/app/server.go
2func CreateKubeAPIServerConfig(opts options.CompletedOptions) (
3 *controlplane.Config,
4 aggregatorapiserver.ServiceResolver,
5 []admission.PluginInitializer,
6 error,
7) {
8 err = opts.Admission.ApplyTo(
9 genericConfig,
10 versionedInformers,
11 clientgoExternalClient,
12 dynamicExternalClient,
13 utilfeature.DefaultFeatureGate,
14 pluginInitializers...)
15 if err != nil {
16 return nil, nil, nil, fmt.Errorf("failed to apply admission: %w", err)
17 }
18}
19
20# kubernetes/pkg/kubeapiserver/options/admission.go
21func (a *AdmissionOptions) ApplyTo(
22 c *server.Config,
23 informers informers.SharedInformerFactory,
24 kubeClient kubernetes.Interface,
25 dynamicClient dynamic.Interface,
26 features featuregate.FeatureGate,
27 pluginInitializers ...admission.PluginInitializer,
28) error {
29 if a == nil {
30 return nil
31 }
32
33 if a.PluginNames != nil {
34 // pass PluginNames to generic AdmissionOptions
35 a.GenericAdmission.EnablePlugins, a.GenericAdmission.DisablePlugins = computePluginNames(a.PluginNames, a.GenericAdmission.RecommendedPluginOrder)
36 }
37
38 return a.GenericAdmission.ApplyTo(c, informers, kubeClient, dynamicClient, features, pluginInitializers...)
39}
40
41# kubernetes/vendor/k8s.io/apiserver/pkg/server/options/admission.go
42func (a *AdmissionOptions) ApplyTo(
43 c *server.Config,
44 informers informers.SharedInformerFactory,
45 kubeClient kubernetes.Interface,
46 dynamicClient dynamic.Interface,
47 features featuregate.FeatureGate,
48 pluginInitializers ...admission.PluginInitializer,
49) error {
50 ...
51 admissionChain, err := a.Plugins.NewFromPlugins(pluginNames, pluginsConfigProvider, initializersChain, a.Decorators)
52 if err != nil {
53 return err
54 }
55
56 c.AdmissionControl = admissionmetrics.WithStepMetrics(admissionChain)
57 return nil
58}
经过多层调用到 AdmissionOptions.ApplyTo 方法,重点看其中的 NewFromPlugins。
1# kubernetes/vendor/k8s.io/apiserver/pkg/admission/plugins.go
2func (ps *Plugins) NewFromPlugins(pluginNames []string, configProvider ConfigProvider, pluginInitializer PluginInitializer, decorator Decorator) (Interface, error) {
3 handlers := []Interface{}
4 mutationPlugins := []string{}
5 validationPlugins := []string{}
6 // 循环创建 plugin
7 for _, pluginName := range pluginNames {
8 ...
9 // 调用 Plugins.InitPlugin
10 plugin, err := ps.InitPlugin(pluginName, pluginConfig, pluginInitializer)
11 if err != nil {
12 return nil, err
13 }
14 if plugin != nil {
15 if decorator != nil {
16 handlers = append(handlers, decorator.Decorate(plugin, pluginName))
17 } else {
18 handlers = append(handlers, plugin)
19 }
20 ...
21 }
22 }
23 ...
24 return newReinvocationHandler(chainAdmissionHandler(handlers)), nil
25}
26
27# kubernetes/vendor/k8s.io/apiserver/pkg/admission/plugins.go
28func (ps *Plugins) InitPlugin(name string, config io.Reader, pluginInitializer PluginInitializer) (Interface, error) {
29 // 调用 Plugins.getPlugin 创建 plugin
30 plugin, found, err := ps.getPlugin(name, config)
31 if err != nil {
32 return nil, fmt.Errorf("couldn't init admission plugin %q: %v", name, err)
33 }
34 if !found {
35 return nil, fmt.Errorf("unknown admission plugin: %s", name)
36 }
37
38 return plugin, nil
39}
40
41# kubernetes/vendor/k8s.io/apiserver/pkg/admission/plugins.go
42func (ps *Plugins) getPlugin(name string, config io.Reader) (Interface, bool, error) {
43 ...
44 // 通过 plugin 的 factory 创建 plugin
45 ret, err := f(config2)
46 return ret, true, err
47}
48
49# kubernetes/vendor/k8s.io/apiserver/pkg/admission/plugins.go
50type chainAdmissionHandler []Interface
51
52# kubernetes/vendor/k8s.io/apiserver/pkg/admission/reinvocation.go
53func newReinvocationHandler(admissionChain Interface) Interface {
54 return &reinvoker{admissionChain}
55}
56
57type reinvoker struct {
58 admissionChain Interface
59}
60
61# kubernetes/vendor/k8s.io/apiserver/pkg/admission/interfaces.go
62type Interface interface {
63 // Handles returns true if this admission controller can handle the given operation
64 // where operation can be one of CREATE, UPDATE, DELETE, or CONNECT
65 Handles(operation Operation) bool
66}
NewFromPlugins 主要做了三件事:
- 根据
plugin name,通过plugin factory循环创建plugin。 - 将
plugin添加到handlers,并且转换为chainAdmissionHandler数组,数组中存储的是实现接口Interface的实例。 - 将
chainAdmissionHandler赋给reinvoker。
admission plugin
前面两节介绍了 admission options 和 admission config。在继续往下介绍之前,有必要介绍 admission plugin。
admission plugin 类型分为变更 plugin 和验证 plugin,分别实现了 MutationInterface 和 ValidationInterface 接口。
1# kubernetes/vendor/k8s.io/apiserver/pkg/admission/interfaces.go
2type Interface interface {
3 // Handles returns true if this admission controller can handle the given operation
4 // where operation can be one of CREATE, UPDATE, DELETE, or CONNECT
5 Handles(operation Operation) bool
6}
7
8type MutationInterface interface {
9 Interface
10
11 // Admit makes an admission decision based on the request attributes.
12 // Context is used only for timeout/deadline/cancellation and tracing information.
13 Admit(ctx context.Context, a Attributes, o ObjectInterfaces) (err error)
14}
15
16// ValidationInterface is an abstract, pluggable interface for Admission Control decisions.
17type ValidationInterface interface {
18 Interface
19
20 // Validate makes an admission decision based on the request attributes. It is NOT allowed to mutate
21 // Context is used only for timeout/deadline/cancellation and tracing information.
22 Validate(ctx context.Context, a Attributes, o ObjectInterfaces) (err error)
23}
MutationInterface 和 ValidationInterface 都包括 Interface 接口,实现变更和验证的 plugin 也要实现 Interface 的 Handlers 方法。
以 AlwaysPullImages plugin 为例,查看其实现的方法。
1# kubernetes/plugin/pkg/admission/alwaypullimages/admission.go
2type AlwaysPullImages struct {
3 *admission.Handler
4}
5
6func (a *AlwaysPullImages) Admit(ctx context.Context, attributes admission.Attributes, o admission.ObjectInterfaces) (err error) {
7 ...
8}
9
10func (*AlwaysPullImages) Validate(ctx context.Context, attributes admission.Attributes, o admission.ObjectInterfaces) (err error) {
11 ...
12}
13
14# kubernetes/vendor/k8s.io/apiserver/pkg/admission/handler.go
15// AlwaysPullImages 和 Handler 是组合关系
16// AlwaysPullImages 实现了 Handlers 方法
17type Handler struct {
18 operations sets.String
19 readyFunc ReadyFunc
20}
21
22// Handles returns true for methods that this handler supports
23func (h *Handler) Handles(operation Operation) bool {
24 return h.operations.Has(string(operation))
25}
可以看到,AlwaysPullImages plugin 既是变更 plugin 也是验证 plugin。
那么,plugin 的变更和验证是什么时候调用的呢。继续往下看。
admission handler
admission handler 实际上是一段嵌在 RESTful API handler 的代码,这段代码作用在 CREATE,POST,DELETE action 上,对于 GET action,不需要做变更和验证操作。
查看 admission handler。
1# kubernetes/vendor/k8s.io/apiserver/pkg/endpoints/installer.go
2func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storage, ws *restful.WebService) (*metav1.APIResource, *storageversion.ResourceInfo, error) {
3 admit := a.group.Admit
4 ...
5 for _, action := range actions {
6 switch action.Verb {
7 case "POST": // Create a resource.
8 var handler restful.RouteFunction
9 if isNamedCreater {
10 handler = restfulCreateNamedResource(namedCreater, reqScope, admit)
11 } else {
12 handler = restfulCreateResource(creater, reqScope, admit)
13 }
14 ...
15 route := ws.POST(action.Path).To(handler).
16 Doc(doc).
17 Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
18 Operation("create"+namespaced+kind+strings.Title(subresource)+operationSuffix).
19 Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...).
20 Returns(http.StatusOK, "OK", producedObject).
21 // TODO: in some cases, the API may return a v1.Status instead of the versioned object
22 // but currently go-restful can't handle multiple different objects being returned.
23 Returns(http.StatusCreated, "Created", producedObject).
24 Returns(http.StatusAccepted, "Accepted", producedObject).
25 Reads(defaultVersionedObject).
26 Writes(producedObject)
27 ...
28 }
29 }
30}
这里以 POST action 为例,查看 RESTful API handler 是怎么做准入控制的。
进入 restfulCreateResource(restfulCreateNamedResource 类似)查看 handler 的创建过程。
1# kubernetes/vendor/k8s.io/apiserver/pkg/endpoints/installer.go
2func restfulCreateResource(r rest.Creater, scope handlers.RequestScope, admit admission.Interface) restful.RouteFunction {
3 return func(req *restful.Request, res *restful.Response) {
4 handlers.CreateResource(r, &scope, admit)(res.ResponseWriter, req.Request)
5 }
6}
7
8# kubernetes/vendor/k8s.io/apiserver/pkg/endpoints/handlers/create.go
9// CreateResource returns a function that will handle a resource creation.
10func CreateResource(r rest.Creater, scope *RequestScope, admission admission.Interface) http.HandlerFunc {
11 return createHandler(&namedCreaterAdapter{r}, scope, admission, false)
12}
13
14func createHandler(r rest.NamedCreater, scope *RequestScope, admit admission.Interface, includeName bool) http.HandlerFunc {
15 return func(w http.ResponseWriter, req *http.Request) {
16 admit = admission.WithAudit(admit)
17 // 获得请求的 attributes,该 attributes 会送入准入控制中
18 admissionAttributes := admission.NewAttributesRecord(obj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, options, dryrun.IsDryRun(options.DryRun), userInfo)
19 requestFunc := func() (runtime.Object, error) {
20 return r.Create(
21 ctx,
22 name,
23 obj,
24 // 返回验证准入 attributes 的函数
25 rest.AdmissionToValidateObjectFunc(admit, admissionAttributes, scope),
26 options,
27 )
28 }
29
30 result, err := finisher.FinishRequest(ctx, func() (runtime.Object, error) {
31 ...
32 // 判断 admit 是否实现了变更接口,如果实现了,执行变更方法
33 if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && mutatingAdmission.Handles(admission.Create) {
34 if err := mutatingAdmission.Admit(ctx, admissionAttributes, scope); err != nil {
35 return nil, err
36 }
37 }
38 ...
39 result, err := requestFunc()
40
41 return result, err
42 })
43 }
44}
requestFunc 负责和 etcd 交互以创建资源,它是一个函数,调用点在变更 plugin 之后。对请求的执行顺序是,先执行变更准入,再执行验证准入。
分别看变更和验证准入的调用。
变更准入
1# kubernetes/vendor/k8s.io/apiserver/pkg/endpoints/handlers/create.go
2admit = admission.WithAudit(admit)
3...
4
5result, err := finisher.FinishRequest(ctx, func() (runtime.Object, error) {
6 admit = fieldmanager.NewManagedFieldsValidatingAdmissionController(admit)
7 if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && mutatingAdmission.Handles(admission.Create) {
8 if err := mutatingAdmission.Admit(ctx, admissionAttributes, scope); err != nil {
9 return nil, err
10 }
11 }
12})
13
14# kubernetes/vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/admission.go
15func NewManagedFieldsValidatingAdmissionController(wrap admission.Interface) admission.Interface {
16 if wrap == nil {
17 return nil
18 }
19 return &managedFieldsValidatingAdmissionController{wrap: wrap}
20}
21
22func (admit *managedFieldsValidatingAdmissionController) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) {
23 mutationInterface, isMutationInterface := admit.wrap.(admission.MutationInterface)
24 if !isMutationInterface {
25 return nil
26 }
27 ...
28 objectMeta, err := meta.Accessor(a.GetObject())
29 ...
30
31 managedFieldsBeforeAdmission := objectMeta.GetManagedFields()
32 if err := mutationInterface.Admit(ctx, a, o); err != nil {
33 return err
34 }
35}
36
37# kubernetes/vendor/k8s.io/apiserver/pkg/admission/audit.go
38func WithAudit(i Interface) Interface {
39 if i == nil {
40 return i
41 }
42 return &auditHandler{Interface: i}
43}
44
45func (handler *auditHandler) Admit(ctx context.Context, a Attributes, o ObjectInterfaces) error {
46 if !handler.Interface.Handles(a.GetOperation()) {
47 return nil
48 }
49 ...
50 var err error
51 if mutator, ok := handler.Interface.(MutationInterface); ok {
52 err = mutator.Admit(ctx, a, o)
53 handler.logAnnotations(ctx, a)
54 }
55 return err
56}
可以看到 mutatingAdmission.Admit 的调用链是从 managedFieldsValidatingAdmissionController 到 auditHandler。最终执行到 admission config 中创建的 AdmissionControl。
1# kubernetes/vendor/k8s.io/apiserver/pkg/server/options/admission.go
2func (a *AdmissionOptions) ApplyTo(
3 c *server.Config,
4 informers informers.SharedInformerFactory,
5 kubeClient kubernetes.Interface,
6 dynamicClient dynamic.Interface,
7 features featuregate.FeatureGate,
8 pluginInitializers ...admission.PluginInitializer,
9) error {
10 ...
11 admissionChain, err := a.Plugins.NewFromPlugins(pluginNames, pluginsConfigProvider, initializersChain, a.Decorators)
12 if err != nil {
13 return err
14 }
15
16 c.AdmissionControl = admissionmetrics.WithStepMetrics(admissionChain)
17}
继续查看 AdmissionControl 的 Admit 方法。
1# kubernetes/vendor/k8s.io/apiserver/pkg/admission/metrics/metrics.go
2func WithStepMetrics(i admission.Interface) admission.Interface {
3 return WithMetrics(i, Metrics.ObserveAdmissionStep)
4}
5
6// WithMetrics is a decorator for admission handlers with a generic observer func.
7func WithMetrics(i admission.Interface, observer ObserverFunc, extraLabels ...string) admission.Interface {
8 return &pluginHandlerWithMetrics{
9 Interface: i,
10 observer: observer,
11 extraLabels: extraLabels,
12 }
13}
14
15func (p pluginHandlerWithMetrics) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
16 mutatingHandler, ok := p.Interface.(admission.MutationInterface)
17 if !ok {
18 return nil
19 }
20
21 start := time.Now()
22 err := mutatingHandler.Admit(ctx, a, o)
23 ...
24}
25
26# kubernetes/vendor/k8s.io/apiserver/pkg/admission/plugins.go
27func (ps *Plugins) NewFromPlugins(pluginNames []string, configProvider ConfigProvider, pluginInitializer PluginInitializer, decorator Decorator) (Interface, error) {
28 ...
29 return newReinvocationHandler(chainAdmissionHandler(handlers)), nil
30}
31
32# kubernetes/vendor/k8s.io/apiserver/pkg/admission/reinvocation.go
33func newReinvocationHandler(admissionChain Interface) Interface {
34 return &reinvoker{admissionChain}
35}
36
37func (r *reinvoker) Admit(ctx context.Context, a Attributes, o ObjectInterfaces) error {
38 if mutator, ok := r.admissionChain.(MutationInterface); ok {
39 err := mutator.Admit(ctx, a, o)
40 if err != nil {
41 return err
42 }
43 ...
44 }
45 return nil
46}
47
48# kubernetes/vendor/k8s.io/apiserver/pkg/admission/chain.go
49type chainAdmissionHandler []Interface
50
51func (admissionHandler chainAdmissionHandler) Admit(ctx context.Context, a Attributes, o ObjectInterfaces) error {
52 for _, handler := range admissionHandler {
53 if !handler.Handles(a.GetOperation()) {
54 continue
55 }
56 if mutator, ok := handler.(MutationInterface); ok {
57 err := mutator.Admit(ctx, a, o)
58 if err != nil {
59 return err
60 }
61 }
62 }
63 return nil
64}
通过接口实例的逐层调用,最终执行到 chainAdmissionHandler 的 Admit 方法。在该方法内,遍历 handler。首先执行 handler 的 Handler 方法,查看是否支持 RESTful API action 的变更操作。 如果支持执行 handler 的 Admit 方法。如果不支持,执行下一个 handler。
handler 的 Admit 实际执行的是 plugin.Admit。以 AlwaysPullImages plugin 为例查看其 Admit 变更准入过程。
1# kubernetes/plugin/pkg/admission/alwayspullimages/admission.go
2func (a *AlwaysPullImages) Admit(ctx context.Context, attributes admission.Attributes, o admission.ObjectInterfaces) (err error) {
3 // Ignore all calls to subresources or resources other than pods.
4 if shouldIgnore(attributes) {
5 return nil
6 }
7 pod, ok := attributes.GetObject().(*api.Pod)
8 if !ok {
9 return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
10 }
11
12 pods.VisitContainersWithPath(&pod.Spec, field.NewPath("spec"), func(c *api.Container, _ *field.Path) bool {
13 c.ImagePullPolicy = api.PullAlways
14 return true
15 })
16
17 return nil
18}
可以看到在 VisitContainersWithPath 中,将 container 的 imagePullPolicy 更新为 Always,从而实现变更准入。
验证准入
查看 RESTful API handler 的验证准入过程。
1# kubernetes/vendor/k8s.io/apiserver/endpoints/handlers/create.go
2func createHandler(r rest.NamedCreater, scope *RequestScope, admit admission.Interface, includeName bool) http.HandlerFunc {
3 return func(w http.ResponseWriter, req *http.Request) {
4 requestFunc := func() (runtime.Object, error) {
5 return r.Create(
6 ctx,
7 name,
8 obj,
9 rest.AdmissionToValidateObjectFunc(admit, admissionAttributes, scope),
10 options,
11 )
12 }
13
14 result, err := finisher.FinishRequest(ctx, func() (runtime.Object, error) {
15 if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && mutatingAdmission.Handles(admission.Create) {
16 if err := mutatingAdmission.Admit(ctx, admissionAttributes, scope); err != nil {
17 return nil, err
18 }
19 }
20
21 result, err := requestFunc()
22 return result, err
23 })
24 }
25}
变更准入成功后,开始执行验证准入。验证准入的逻辑定义在 AdmissionToValidateObjectFunc,资源实体 r 和 etcd 交互时,首先进行验证准入:
1# kubernetes/vendor/k8s.io/apiserver/pkg/registry/generic/registry/store.go
2func (e *Store) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
3 if createValidation != nil {
4 // 执行验证准入
5 if err := createValidation(ctx, obj.DeepCopyObject()); err != nil {
6 return nil, err
7 }
8 }
9
10 // 验证准入成功后开始和 etcd 交互
11 name, err := e.ObjectNameFunc(obj)
12 if err != nil {
13 return nil, err
14 }
15 key, err := e.KeyFunc(ctx, name)
16 if err != nil {
17 return nil, err
18 }
19 ...
20}
知道了验证准入的流程。我们看验证准入具体做了什么。
1# kubernetes/vendor/k8s.io/apiserver/pkg/registry/rest/create.go
2func AdmissionToValidateObjectFunc(admit admission.Interface, staticAttributes admission.Attributes, o admission.ObjectInterfaces) ValidateObjectFunc {
3 validatingAdmission, ok := admit.(admission.ValidationInterface)
4 if !ok {
5 return func(ctx context.Context, obj runtime.Object) error { return nil }
6 }
7 return func(ctx context.Context, obj runtime.Object) error {
8 name := staticAttributes.GetName()
9 ...
10
11 finalAttributes := admission.NewAttributesRecord(
12 obj,
13 staticAttributes.GetOldObject(),
14 staticAttributes.GetKind(),
15 staticAttributes.GetNamespace(),
16 name,
17 staticAttributes.GetResource(),
18 staticAttributes.GetSubresource(),
19 staticAttributes.GetOperation(),
20 staticAttributes.GetOperationOptions(),
21 staticAttributes.IsDryRun(),
22 staticAttributes.GetUserInfo(),
23 )
24 if !validatingAdmission.Handles(finalAttributes.GetOperation()) {
25 return nil
26 }
27 return validatingAdmission.Validate(ctx, finalAttributes, o)
28 }
29}
类似于变更准入,首先调用 Handlers 查看 plugin 是否支持 RESTful API 请求的操作。如果支持调用 Validate 进行验证准入。
验证准入的调用过程和变更准入非常类似,这里不过多介绍了。最终,经过层层调用执行 plugin 的验证准入。这里,以 AlwaysPullImages plugin 为例,查看验证准入过程。
1# kubernetes/plugin/pkg/admission/alwayspullimages/admission.go
2func (*AlwaysPullImages) Validate(ctx context.Context, attributes admission.Attributes, o admission.ObjectInterfaces) (err error) {
3 ...
4 pod, ok := attributes.GetObject().(*api.Pod)
5 if !ok {
6 return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
7 }
8
9 var allErrs []error
10 pods.VisitContainersWithPath(&pod.Spec, field.NewPath("spec"), func(c *api.Container, p *field.Path) bool {
11 if c.ImagePullPolicy != api.PullAlways {
12 allErrs = append(allErrs, admission.NewForbidden(attributes,
13 field.NotSupported(p.Child("imagePullPolicy"), c.ImagePullPolicy, []string{string(api.PullAlways)}),
14 ))
15 }
16 return true
17 })
18 if len(allErrs) > 0 {
19 return utilerrors.NewAggregate(allErrs)
20 }
21
22 return nil
23}
可以看到,AlwaysPullImages plugin 验证 container 的 imagePullPolicy 是否是 Always。
小结
通过本篇文章介绍了 kube-apiserver 中的 admission 准入流程。美好的时光总是短暂的,关于 kube-apiserver 的介绍基本结束了。下面开始 kube-scheduler 的介绍,敬请期待。