kubernetes:kube-apiserver
系列文章:
- Kubernetes:kube-apiserver 之 scheme(一)
- Kubernetes:kube-apiserver 之 scheme(二)
- Kubernetes:kube-apiserver 之啟動流程(一)
- Kubernetes:kube-apiserver 之啟動流程(二)
- Kubernetes:kube-apiserver 和 etcd 的互動
- Kubernetes:kube-apiserver 之認證
- Kubernetes:kube-apiserver 之鑑權
0. 前言
前兩篇文章介紹了 kube-apiserver
的認證和鑑權,這裡繼續往下走,介紹 kube-apiserver
的准入。
1. 准入 admission
不同於前兩篇的逆序介紹,這裡順序介紹 admission
流程。從建立准入 options
,到根據 options
建立准入 config
,接著介紹在 kube-apiserver
的 handler
中是怎麼進入准入控制,怎麼執行的。
1.1 admission options
進入 NewOptions
檢視 admission options
是怎麼建立的。
# kubernetes/pkg/controlplane/apiserver/options/options.go
func NewOptions() *Options {
s := Options{
...
Admission: kubeoptions.NewAdmissionOptions(),
}
}
# kubernetes/pkg/kubeapiserver/options/admission.go
func NewAdmissionOptions() *AdmissionOptions {
options := genericoptions.NewAdmissionOptions()
// register all admission plugins
RegisterAllAdmissionPlugins(options.Plugins)
// set RecommendedPluginOrder
options.RecommendedPluginOrder = AllOrderedPlugins
// set DefaultOffPlugins
options.DefaultOffPlugins = DefaultOffAdmissionPlugins()
return &AdmissionOptions{
GenericAdmission: options,
}
}
NewAdmissionOptions
返回建立的 AdmissionOptions
。其中,options
包括什麼內容呢?我們看 NewAdmissionOptions
, RegisterAllAdmissionPlugins
和 DefaultOffAdmissionPlugins
函式。
NewAdmissionOptions
# kubernetes/vendor/k8s.io/apiserver/pkg/server/options/admission.go
func NewAdmissionOptions() *AdmissionOptions {
options := &AdmissionOptions{
// 建立 Plugins 物件
Plugins: admission.NewPlugins(),
Decorators: admission.Decorators{admission.DecoratorFunc(admissionmetrics.WithControllerMetrics)},
RecommendedPluginOrder: []string{lifecycle.PluginName, mutatingwebhook.PluginName, validatingadmissionpolicy.PluginName, validatingwebhook.PluginName},
DefaultOffPlugins: sets.NewString(),
}
// 註冊 admission plugins 到 Plugins 物件
server.RegisterAllAdmissionPlugins(options.Plugins)
return options
}
# kubernetes/vendor/k8s.io/apiserver/pkg/admission/plugins.go
type Plugins struct {
lock sync.Mutex
registry map[string]Factory
}
func NewPlugins() *Plugins {
return &Plugins{}
}
# kubernetes/vendor/k8s.io/apiserver/pkg/server/plugins.go
func RegisterAllAdmissionPlugins(plugins *admission.Plugins) {
lifecycle.Register(plugins)
validatingwebhook.Register(plugins)
mutatingwebhook.Register(plugins)
validatingadmissionpolicy.Register(plugins)
}
# kubernetes/vendor/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission.go
func Register(plugins *admission.Plugins) {
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
return NewLifecycle(sets.NewString(metav1.NamespaceDefault, metav1.NamespaceSystem, metav1.NamespacePublic))
})
}
# kubernetes/vendor/k8s.io/apiserver/pkg/admission/plugins.go
func (ps *Plugins) Register(name string, plugin Factory) {
...
ps.registry[name] = plugin
}
在 NewAdmissionOptions
中,建立 Plugins
物件,將 admission plugins
註冊到 Plugins
物件。註冊的過程實際是寫入 admission plugin
到物件 registry
的過程,registry
中儲存的是 admission plugin name
和建立 plugin
工廠的對映。
RegisterAllAdmissionPlugins
# kubernetes/pkg/kubeapiserver/options/plugins.go
func RegisterAllAdmissionPlugins(plugins *admission.Plugins) {
admit.Register(plugins) // DEPRECATED as no real meaning
alwayspullimages.Register(plugins)
...
}
類似於 NewAdmissionOptions
中的註冊過程,RegisterAllAdmissionPlugins
將註冊所有的 admission plugin
到 Plugins
物件中。註冊之後的 Plugins
有 36 種 admission plugin
。
DefaultOffAdmissionPlugins
func DefaultOffAdmissionPlugins() sets.String {
defaultOnPlugins := sets.NewString(
lifecycle.PluginName, // NamespaceLifecycle
limitranger.PluginName, // LimitRanger
...
)
return sets.NewString(AllOrderedPlugins...).Difference(defaultOnPlugins)
}
經過 DefaultOffAdmissionPlugins
處理後,Plugins
物件中有 20 種預設開啟的 admission plugin
,16 種預設關閉的 admission plugin
。
1.2 admission config
建立完 admission options
後開始建立 admission config
。
# kubernetes/cmd/kube-apiserver/app/server.go
func CreateKubeAPIServerConfig(opts options.CompletedOptions) (
*controlplane.Config,
aggregatorapiserver.ServiceResolver,
[]admission.PluginInitializer,
error,
) {
err = opts.Admission.ApplyTo(
genericConfig,
versionedInformers,
clientgoExternalClient,
dynamicExternalClient,
utilfeature.DefaultFeatureGate,
pluginInitializers...)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to apply admission: %w", err)
}
}
# kubernetes/pkg/kubeapiserver/options/admission.go
func (a *AdmissionOptions) ApplyTo(
c *server.Config,
informers informers.SharedInformerFactory,
kubeClient kubernetes.Interface,
dynamicClient dynamic.Interface,
features featuregate.FeatureGate,
pluginInitializers ...admission.PluginInitializer,
) error {
if a == nil {
return nil
}
if a.PluginNames != nil {
// pass PluginNames to generic AdmissionOptions
a.GenericAdmission.EnablePlugins, a.GenericAdmission.DisablePlugins = computePluginNames(a.PluginNames, a.GenericAdmission.RecommendedPluginOrder)
}
return a.GenericAdmission.ApplyTo(c, informers, kubeClient, dynamicClient, features, pluginInitializers...)
}
# kubernetes/vendor/k8s.io/apiserver/pkg/server/options/admission.go
func (a *AdmissionOptions) ApplyTo(
c *server.Config,
informers informers.SharedInformerFactory,
kubeClient kubernetes.Interface,
dynamicClient dynamic.Interface,
features featuregate.FeatureGate,
pluginInitializers ...admission.PluginInitializer,
) error {
...
admissionChain, err := a.Plugins.NewFromPlugins(pluginNames, pluginsConfigProvider, initializersChain, a.Decorators)
if err != nil {
return err
}
c.AdmissionControl = admissionmetrics.WithStepMetrics(admissionChain)
return nil
}
經過多層呼叫到 AdmissionOptions.ApplyTo
方法,重點看其中的 NewFromPlugins
。
# kubernetes/vendor/k8s.io/apiserver/pkg/admission/plugins.go
func (ps *Plugins) NewFromPlugins(pluginNames []string, configProvider ConfigProvider, pluginInitializer PluginInitializer, decorator Decorator) (Interface, error) {
handlers := []Interface{}
mutationPlugins := []string{}
validationPlugins := []string{}
// 迴圈建立 plugin
for _, pluginName := range pluginNames {
...
// 呼叫 Plugins.InitPlugin
plugin, err := ps.InitPlugin(pluginName, pluginConfig, pluginInitializer)
if err != nil {
return nil, err
}
if plugin != nil {
if decorator != nil {
handlers = append(handlers, decorator.Decorate(plugin, pluginName))
} else {
handlers = append(handlers, plugin)
}
...
}
}
...
return newReinvocationHandler(chainAdmissionHandler(handlers)), nil
}
# kubernetes/vendor/k8s.io/apiserver/pkg/admission/plugins.go
func (ps *Plugins) InitPlugin(name string, config io.Reader, pluginInitializer PluginInitializer) (Interface, error) {
// 呼叫 Plugins.getPlugin 建立 plugin
plugin, found, err := ps.getPlugin(name, config)
if err != nil {
return nil, fmt.Errorf("couldn't init admission plugin %q: %v", name, err)
}
if !found {
return nil, fmt.Errorf("unknown admission plugin: %s", name)
}
return plugin, nil
}
# kubernetes/vendor/k8s.io/apiserver/pkg/admission/plugins.go
func (ps *Plugins) getPlugin(name string, config io.Reader) (Interface, bool, error) {
...
// 透過 plugin 的 factory 建立 plugin
ret, err := f(config2)
return ret, true, err
}
# kubernetes/vendor/k8s.io/apiserver/pkg/admission/plugins.go
type chainAdmissionHandler []Interface
# kubernetes/vendor/k8s.io/apiserver/pkg/admission/reinvocation.go
func newReinvocationHandler(admissionChain Interface) Interface {
return &reinvoker{admissionChain}
}
type reinvoker struct {
admissionChain Interface
}
# kubernetes/vendor/k8s.io/apiserver/pkg/admission/interfaces.go
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
}
NewFromPlugins
主要做了三件事:
- 根據
plugin name
,透過plugin factory
迴圈建立plugin
。 - 將
plugin
新增到handlers
,並且轉換為chainAdmissionHandler
陣列,陣列中儲存的是實現介面Interface
的例項。 - 將
chainAdmissionHandler
賦給reinvoker
。
1.3 admission plugin
前面兩節介紹了 admission options
和 admission config
。在繼續往下介紹之前,有必要介紹 admission plugin
。
admission plugin
型別分為變更 plugin
和驗證 plugin
,分別實現了 MutationInterface
和 ValidationInterface
介面。
# kubernetes/vendor/k8s.io/apiserver/pkg/admission/interfaces.go
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)
}
MutationInterface
和 ValidationInterface
都包括 Interface
介面,實現變更和驗證的 plugin
也要實現 Interface
的 Handlers
方法。
以 AlwaysPullImages plugin
為例,檢視其實現的方法。
# kubernetes/plugin/pkg/admission/alwaypullimages/admission.go
type AlwaysPullImages struct {
*admission.Handler
}
func (a *AlwaysPullImages) Admit(ctx context.Context, attributes admission.Attributes, o admission.ObjectInterfaces) (err error) {
...
}
func (*AlwaysPullImages) Validate(ctx context.Context, attributes admission.Attributes, o admission.ObjectInterfaces) (err error) {
...
}
# kubernetes/vendor/k8s.io/apiserver/pkg/admission/handler.go
// AlwaysPullImages 和 Handler 是組合關係
// AlwaysPullImages 實現了 Handlers 方法
type Handler struct {
operations sets.String
readyFunc ReadyFunc
}
// Handles returns true for methods that this handler supports
func (h *Handler) Handles(operation Operation) bool {
return h.operations.Has(string(operation))
}
可以看到,AlwaysPullImages plugin
既是變更 plugin
也是驗證 plugin
。
那麼,plugin
的變更和驗證是什麼時候呼叫的呢。繼續往下看。
1.4 admission handler
admission handler
實際上是一段嵌在 RESTful API handler
的程式碼,這段程式碼作用在 CREATE
,POST
,DELETE
action 上,對於 GET
action,不需要做變更和驗證操作。
檢視 admission handler
。
# kubernetes/vendor/k8s.io/apiserver/pkg/endpoints/installer.go
func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storage, ws *restful.WebService) (*metav1.APIResource, *storageversion.ResourceInfo, error) {
admit := a.group.Admit
...
for _, action := range actions {
switch action.Verb {
case "POST": // Create a resource.
var handler restful.RouteFunction
if isNamedCreater {
handler = restfulCreateNamedResource(namedCreater, reqScope, admit)
} else {
handler = restfulCreateResource(creater, reqScope, admit)
}
...
route := ws.POST(action.Path).To(handler).
Doc(doc).
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
Operation("create"+namespaced+kind+strings.Title(subresource)+operationSuffix).
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...).
Returns(http.StatusOK, "OK", producedObject).
// TODO: in some cases, the API may return a v1.Status instead of the versioned object
// but currently go-restful can't handle multiple different objects being returned.
Returns(http.StatusCreated, "Created", producedObject).
Returns(http.StatusAccepted, "Accepted", producedObject).
Reads(defaultVersionedObject).
Writes(producedObject)
...
}
}
}
這裡以 POST
action 為例,檢視 RESTful API handler
是怎麼做准入控制的。
進入 restfulCreateResource
(restfulCreateNamedResource
類似)檢視 handler
的建立過程。
# kubernetes/vendor/k8s.io/apiserver/pkg/endpoints/installer.go
func restfulCreateResource(r rest.Creater, scope handlers.RequestScope, admit admission.Interface) restful.RouteFunction {
return func(req *restful.Request, res *restful.Response) {
handlers.CreateResource(r, &scope, admit)(res.ResponseWriter, req.Request)
}
}
# kubernetes/vendor/k8s.io/apiserver/pkg/endpoints/handlers/create.go
// CreateResource returns a function that will handle a resource creation.
func CreateResource(r rest.Creater, scope *RequestScope, admission admission.Interface) http.HandlerFunc {
return createHandler(&namedCreaterAdapter{r}, scope, admission, false)
}
func createHandler(r rest.NamedCreater, scope *RequestScope, admit admission.Interface, includeName bool) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
admit = admission.WithAudit(admit)
// 獲得請求的 attributes,該 attributes 會送入准入控制中
admissionAttributes := admission.NewAttributesRecord(obj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, options, dryrun.IsDryRun(options.DryRun), userInfo)
requestFunc := func() (runtime.Object, error) {
return r.Create(
ctx,
name,
obj,
// 返回驗證准入 attributes 的函式
rest.AdmissionToValidateObjectFunc(admit, admissionAttributes, scope),
options,
)
}
result, err := finisher.FinishRequest(ctx, func() (runtime.Object, error) {
...
// 判斷 admit 是否實現了變更介面,如果實現了,執行變更方法
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && mutatingAdmission.Handles(admission.Create) {
if err := mutatingAdmission.Admit(ctx, admissionAttributes, scope); err != nil {
return nil, err
}
}
...
result, err := requestFunc()
return result, err
})
}
}
requestFunc
負責和 etcd
互動以建立資源,它是一個函式,呼叫點在變更 plugin
之後。對請求的執行順序是,先執行變更准入,再執行驗證准入。
分別看變更和驗證准入的呼叫。
1.4.1 變更准入
# kubernetes/vendor/k8s.io/apiserver/pkg/endpoints/handlers/create.go
admit = admission.WithAudit(admit)
...
result, err := finisher.FinishRequest(ctx, func() (runtime.Object, error) {
admit = fieldmanager.NewManagedFieldsValidatingAdmissionController(admit)
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && mutatingAdmission.Handles(admission.Create) {
if err := mutatingAdmission.Admit(ctx, admissionAttributes, scope); err != nil {
return nil, err
}
}
})
# kubernetes/vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/admission.go
func NewManagedFieldsValidatingAdmissionController(wrap admission.Interface) admission.Interface {
if wrap == nil {
return nil
}
return &managedFieldsValidatingAdmissionController{wrap: wrap}
}
func (admit *managedFieldsValidatingAdmissionController) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) {
mutationInterface, isMutationInterface := admit.wrap.(admission.MutationInterface)
if !isMutationInterface {
return nil
}
...
objectMeta, err := meta.Accessor(a.GetObject())
...
managedFieldsBeforeAdmission := objectMeta.GetManagedFields()
if err := mutationInterface.Admit(ctx, a, o); err != nil {
return err
}
}
# kubernetes/vendor/k8s.io/apiserver/pkg/admission/audit.go
func WithAudit(i Interface) Interface {
if i == nil {
return i
}
return &auditHandler{Interface: i}
}
func (handler *auditHandler) Admit(ctx context.Context, a Attributes, o ObjectInterfaces) error {
if !handler.Interface.Handles(a.GetOperation()) {
return nil
}
...
var err error
if mutator, ok := handler.Interface.(MutationInterface); ok {
err = mutator.Admit(ctx, a, o)
handler.logAnnotations(ctx, a)
}
return err
}
可以看到 mutatingAdmission.Admit
的呼叫鏈是從 managedFieldsValidatingAdmissionController
到 auditHandler
。最終執行到 admission config
中建立的 AdmissionControl
。
# kubernetes/vendor/k8s.io/apiserver/pkg/server/options/admission.go
func (a *AdmissionOptions) ApplyTo(
c *server.Config,
informers informers.SharedInformerFactory,
kubeClient kubernetes.Interface,
dynamicClient dynamic.Interface,
features featuregate.FeatureGate,
pluginInitializers ...admission.PluginInitializer,
) error {
...
admissionChain, err := a.Plugins.NewFromPlugins(pluginNames, pluginsConfigProvider, initializersChain, a.Decorators)
if err != nil {
return err
}
c.AdmissionControl = admissionmetrics.WithStepMetrics(admissionChain)
}
繼續檢視 AdmissionControl
的 Admit
方法。
# kubernetes/vendor/k8s.io/apiserver/pkg/admission/metrics/metrics.go
func WithStepMetrics(i admission.Interface) admission.Interface {
return WithMetrics(i, Metrics.ObserveAdmissionStep)
}
// WithMetrics is a decorator for admission handlers with a generic observer func.
func WithMetrics(i admission.Interface, observer ObserverFunc, extraLabels ...string) admission.Interface {
return &pluginHandlerWithMetrics{
Interface: i,
observer: observer,
extraLabels: extraLabels,
}
}
func (p pluginHandlerWithMetrics) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
mutatingHandler, ok := p.Interface.(admission.MutationInterface)
if !ok {
return nil
}
start := time.Now()
err := mutatingHandler.Admit(ctx, a, o)
...
}
# kubernetes/vendor/k8s.io/apiserver/pkg/admission/plugins.go
func (ps *Plugins) NewFromPlugins(pluginNames []string, configProvider ConfigProvider, pluginInitializer PluginInitializer, decorator Decorator) (Interface, error) {
...
return newReinvocationHandler(chainAdmissionHandler(handlers)), nil
}
# kubernetes/vendor/k8s.io/apiserver/pkg/admission/reinvocation.go
func newReinvocationHandler(admissionChain Interface) Interface {
return &reinvoker{admissionChain}
}
func (r *reinvoker) Admit(ctx context.Context, a Attributes, o ObjectInterfaces) error {
if mutator, ok := r.admissionChain.(MutationInterface); ok {
err := mutator.Admit(ctx, a, o)
if err != nil {
return err
}
...
}
return nil
}
# kubernetes/vendor/k8s.io/apiserver/pkg/admission/chain.go
type chainAdmissionHandler []Interface
func (admissionHandler chainAdmissionHandler) Admit(ctx context.Context, a Attributes, o ObjectInterfaces) error {
for _, handler := range admissionHandler {
if !handler.Handles(a.GetOperation()) {
continue
}
if mutator, ok := handler.(MutationInterface); ok {
err := mutator.Admit(ctx, a, o)
if err != nil {
return err
}
}
}
return nil
}
透過介面例項的逐層呼叫,最終執行到 chainAdmissionHandler
的 Admit
方法。在該方法內,遍歷 handler
。首先執行 handler
的 Handler
方法,檢視是否支援 RESTful API action
的變更操作。 如果支援執行 handler
的 Admit
方法。如果不支援,執行下一個 handler
。
handler
的 Admit
實際執行的是 plugin.Admit
。以 AlwaysPullImages plugin
為例檢視其 Admit
變更准入過程。
# kubernetes/plugin/pkg/admission/alwayspullimages/admission.go
func (a *AlwaysPullImages) Admit(ctx context.Context, attributes admission.Attributes, o admission.ObjectInterfaces) (err error) {
// Ignore all calls to subresources or resources other than pods.
if shouldIgnore(attributes) {
return nil
}
pod, ok := attributes.GetObject().(*api.Pod)
if !ok {
return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
}
pods.VisitContainersWithPath(&pod.Spec, field.NewPath("spec"), func(c *api.Container, _ *field.Path) bool {
c.ImagePullPolicy = api.PullAlways
return true
})
return nil
}
可以看到在 VisitContainersWithPath
中,將 container
的 imagePullPolicy
更新為 Always
,從而實現變更准入。
1.4.2 驗證准入
檢視 RESTful API handler
的驗證准入過程。
# kubernetes/vendor/k8s.io/apiserver/endpoints/handlers/create.go
func createHandler(r rest.NamedCreater, scope *RequestScope, admit admission.Interface, includeName bool) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
requestFunc := func() (runtime.Object, error) {
return r.Create(
ctx,
name,
obj,
rest.AdmissionToValidateObjectFunc(admit, admissionAttributes, scope),
options,
)
}
result, err := finisher.FinishRequest(ctx, func() (runtime.Object, error) {
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && mutatingAdmission.Handles(admission.Create) {
if err := mutatingAdmission.Admit(ctx, admissionAttributes, scope); err != nil {
return nil, err
}
}
result, err := requestFunc()
return result, err
})
}
}
變更准入成功後,開始執行驗證准入。驗證准入的邏輯定義在 AdmissionToValidateObjectFunc
,資源實體 r
和 etcd
互動時,首先進行驗證准入:
# kubernetes/vendor/k8s.io/apiserver/pkg/registry/generic/registry/store.go
func (e *Store) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
if createValidation != nil {
// 執行驗證准入
if err := createValidation(ctx, obj.DeepCopyObject()); err != nil {
return nil, err
}
}
// 驗證准入成功後開始和 etcd 互動
name, err := e.ObjectNameFunc(obj)
if err != nil {
return nil, err
}
key, err := e.KeyFunc(ctx, name)
if err != nil {
return nil, err
}
...
}
知道了驗證准入的流程。我們看驗證准入具體做了什麼。
# kubernetes/vendor/k8s.io/apiserver/pkg/registry/rest/create.go
func AdmissionToValidateObjectFunc(admit admission.Interface, staticAttributes admission.Attributes, o admission.ObjectInterfaces) ValidateObjectFunc {
validatingAdmission, ok := admit.(admission.ValidationInterface)
if !ok {
return func(ctx context.Context, obj runtime.Object) error { return nil }
}
return func(ctx context.Context, obj runtime.Object) error {
name := staticAttributes.GetName()
...
finalAttributes := admission.NewAttributesRecord(
obj,
staticAttributes.GetOldObject(),
staticAttributes.GetKind(),
staticAttributes.GetNamespace(),
name,
staticAttributes.GetResource(),
staticAttributes.GetSubresource(),
staticAttributes.GetOperation(),
staticAttributes.GetOperationOptions(),
staticAttributes.IsDryRun(),
staticAttributes.GetUserInfo(),
)
if !validatingAdmission.Handles(finalAttributes.GetOperation()) {
return nil
}
return validatingAdmission.Validate(ctx, finalAttributes, o)
}
}
類似於變更准入,首先呼叫 Handlers
檢視 plugin
是否支援 RESTful API
請求的操作。如果支援呼叫 Validate
進行驗證准入。
驗證准入的呼叫過程和變更准入非常類似,這裡不過多介紹了。最終,經過層層呼叫執行 plugin
的驗證准入。這裡,以 AlwaysPullImages plugin
為例,檢視驗證准入過程。
# kubernetes/plugin/pkg/admission/alwayspullimages/admission.go
func (*AlwaysPullImages) Validate(ctx context.Context, attributes admission.Attributes, o admission.ObjectInterfaces) (err error) {
...
pod, ok := attributes.GetObject().(*api.Pod)
if !ok {
return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
}
var allErrs []error
pods.VisitContainersWithPath(&pod.Spec, field.NewPath("spec"), func(c *api.Container, p *field.Path) bool {
if c.ImagePullPolicy != api.PullAlways {
allErrs = append(allErrs, admission.NewForbidden(attributes,
field.NotSupported(p.Child("imagePullPolicy"), c.ImagePullPolicy, []string{string(api.PullAlways)}),
))
}
return true
})
if len(allErrs) > 0 {
return utilerrors.NewAggregate(allErrs)
}
return nil
}
可以看到,AlwaysPullImages plugin
驗證 container
的 imagePullPolicy
是否是 Always
。
2. 小結
透過本篇文章介紹了 kube-apiserver
中的 admission
准入流程。美好的時光總是短暫的,關於 kube-apiserver
的介紹基本結束了。下面開始 kube-scheduler
的介紹,敬請期待。