前言

前两篇文章介绍了 kube-apiserver 的认证和鉴权,这里继续往下走,介绍 kube-apiserver 的准入。

准入 admission

不同于前两篇的逆序介绍,这里顺序介绍 admission 流程。从创建准入 options,到根据 options 创建准入 config,接着介绍在 kube-apiserverhandler 中是怎么进入准入控制,怎么执行的。

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 包括什么内容呢?我们看 NewAdmissionOptionsRegisterAllAdmissionPluginsDefaultOffAdmissionPlugins 函数。

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 pluginPlugins 对象中。注册之后的 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 optionsadmission config。在继续往下介绍之前,有必要介绍 admission plugin

admission plugin 类型分为变更 plugin 和验证 plugin,分别实现了 MutationInterfaceValidationInterface 接口。

 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}

MutationInterfaceValidationInterface 都包括 Interface 接口,实现变更和验证的 plugin 也要实现 InterfaceHandlers 方法。

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 的代码,这段代码作用在 CREATEPOSTDELETE 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 是怎么做准入控制的。

进入 restfulCreateResourcerestfulCreateNamedResource 类似)查看 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 的调用链是从 managedFieldsValidatingAdmissionControllerauditHandler。最终执行到 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}

继续查看 AdmissionControlAdmit 方法。

 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}

通过接口实例的逐层调用,最终执行到 chainAdmissionHandlerAdmit 方法。在该方法内,遍历 handler。首先执行 handlerHandler 方法,查看是否支持 RESTful API action 的变更操作。 如果支持执行 handlerAdmit 方法。如果不支持,执行下一个 handler

handlerAdmit 实际执行的是 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 中,将 containerimagePullPolicy 更新为 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,资源实体 retcd 交互时,首先进行验证准入:

 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 验证 containerimagePullPolicy 是否是 Always

小结

通过本篇文章介绍了 kube-apiserver 中的 admission 准入流程。美好的时光总是短暂的,关于 kube-apiserver 的介绍基本结束了。下面开始 kube-scheduler 的介绍,敬请期待。