Kubernetes:kube-apiserver 之准入
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 的介紹,敬請期待。
總結
以上是生活随笔為你收集整理的Kubernetes:kube-apiserver 之准入的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从DPlayer说起,有哪些开源的H5播
- 下一篇: 原生JS实现视频截图