kubernetes:kube-apiserver 系列文章:

0. 前言

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

1. 准入 admission

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

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

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

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

# 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)
}

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

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

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

继续查看 AdmissionControlAdmit 方法。

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

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

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

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

2. 小结

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


Kubernetes:kube-apiserver 之准入的更多相关文章

  1. kubernetes高级之动态准入控制

    系列目录 动态准入控制器文档介绍了如何使用标准的,插件式的准入控制器.但是,但是由于以下原因,插件式的准入控制器在一些场景下并不灵活: 它们需要编译到kube-apiserver里 它们仅在apise ...

  2. kubernetes的apiserver

    1. API Server简介 k8s API Server提供了k8s各类资源对象(pod,RC,Service等)的增删改查及watch等HTTP Rest接口,是整个系统的数据总线和数据中心. ...

  3. 【云原生 · Kubernetes】apiserver高可用

    个人名片: 因为云计算成为了监控工程师‍ 个人博客:念舒_C.ying CSDN主页️:念舒_C.ying 7.1 高可用选型 ipvs+keepalived nginx+keepalived hap ...

  4. 一文读懂 Kubernetes APIServer 原理

    前言 整个Kubernetes技术体系由声明式API以及Controller构成,而kube-apiserver是Kubernetes的声明式api server,并为其它组件交互提供了桥梁.因此加深 ...

  5. 【kubernetes 自带监控】 container级别cadvisor+kubelet,集群级别apiserver

    apiserver https://feisky.gitbooks.io/kubernetes/components/apiserver.html kube-apiserver 支持同时提供 http ...

  6. kubernetes 入门学习

    kubernetes 学习 kubernetes 简介 Kubernetes这个名字源自希腊语,意思是"舵手",也是"管理者","治理者"等 ...

  7. Kubernetes学习笔记_尚硅谷

    https://www.bilibili.com/video/BV1w4411y7Go?p=1 一.K8s介绍 k8s是一个编排容器的工具,其实也是管理应用的全生命周期的一个工具,从创建应用,应用的部 ...

  8. 建设Kubernetes生产环境的16条建议

    点击上方"开源Linux",选择"设为星标" 回复"学习"获取独家整理的学习资料! Kubernetes是用于构建高度可扩展系统的强大工具. ...

  9. 基于Containerd安装部署高可用Kubernetes集群

    转载自:https://blog.weiyigeek.top/2021/7-30-623.html 简述 Kubernetes(后续简称k8s)是 Google(2014年6月) 开源的一个容器编排引 ...

  10. Kubernetes客户端认证——基于CA证书的双向认证方式

    1.Kubernetes 认证方式 Kubernetes集群的访问权限控制由API Server负责,API Server的访问权限控制由身份验证(Authentication).授权(Authori ...

随机推荐

  1. git: 解决每次操作git时都要输入账户、密码

    解决方案 $ git config --global credential.helper store 使用该命令即可解决问题

  2. 基于 ASP.NET 的投票系统

    OnlineVoting 基于 ASP.NET 的投票系统 功能页面 登录 注册 首页 投票广场 查看别人发布的投票. 个人中心 个人资料 换头像.修改密码和其他信息. 发布投票 项目地址:https ...

  3. 解决win10/ubuntu端口占用问题

    win10解决方案 首先打开cmd命令行 命令行里输入 netstat -ano|findstr 被占用端口号 然后可以看到占用该端口号的pid 输入taskkill -f -pid pid号即可 u ...

  4. 文心一言 VS 讯飞星火 VS chatgpt (76)-- 算法导论7.3 1题

    一.如果用go语言,为什么我们分析随机化算法的期望运行时间,而不是其最坏运行时间呢? 文心一言: 在分析算法的性能时,期望运行时间是一个重要的指标,它描述了算法在平均情况下的表现.期望运行时间考虑了所 ...

  5. 给你安利一款带有AI功能的数据库管理工具

    写在前面 说到数据库管理工具,大家应该不陌生了 小伙伴们应该都用过Navicat.DBever.DataGrip.SQLyog.plsqldeveloper等数据库管理工具 这些工具呢都各自有优缺点. ...

  6. Go 并发编程 - Goroutine 基础 (一)

    基础概念 进程与线程 进程是一次程序在操作系统执行的过程,需要消耗一定的CPU.时间.内存.IO等.每个进程都拥有着独立的内存空间和系统资源.进程之间的内存是不共享的.通常需要使用 IPC 机制进行数 ...

  7. 在.NET Framework中使用RocketMQ(阿里云版)实战【第二章】

    章节 第一章:https://www.cnblogs.com/kimiliucn/p/17662052.html 第二章:https://www.cnblogs.com/kimiliucn/p/176 ...

  8. fastapi启动后访问docs不显示页面的问题

    笔者之前正常使用fastapi的docs接口进行各种接口调试,使用很正常,之前安装也都是正常安装流程,没有做任何修改,可以突然有一天不知道为啥,docs接口打开是空白的,接口也没有报错,就是空白,摸索 ...

  9. PC首页资源加载速度由8s降到2s的优化实践

    随着需求的不断开发,前端项目不断膨胀,业务提出:你们的首页加载也太慢啦,我都需要7.8秒才能看到内容,于是乎主管就让我联合后端开启优化专项,目标是3s内展示完全首页的内容. 性能指标 开启优化时,我们 ...

  10. 例子:统计电影类型的个数,以及用bar绘制出来表示

    import pandas as pdimport numpy as npfrom matplotlib import pyplot as plt#获取各种电影类型的数量file='./IMDB-Mo ...