Use a dynamic RESTMapper for admission plugins

This change updates the REST mapper used by all admission plugins to
be backed by cached discovery information.  This cache is updated
every ten seconds via a post start hook and will not attempt to
update on calls to RESTMapping.  It solely relies on the hook to
keep the cache in sync with discovery.

This prevents issues with the OwnerReferencesPermissionEnforcement
admission plugin when it is used with custom resources that set
blockOwnerDeletion.

Signed-off-by: Monis Khan <mkhan@redhat.com>
This commit is contained in:
Monis Khan
2018-04-16 13:15:40 -04:00
parent 33f7d8618b
commit 300751393b
4 changed files with 42 additions and 14 deletions

View File

@@ -59,6 +59,7 @@ go_library(
"//vendor/k8s.io/apiextensions-apiserver/pkg/apiserver:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/apiserver:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/cmd/server:go_default_library", "//vendor/k8s.io/apiextensions-apiserver/pkg/cmd/server:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
@@ -78,6 +79,8 @@ go_library(
"//vendor/k8s.io/apiserver/pkg/server/storage:go_default_library", "//vendor/k8s.io/apiserver/pkg/server/storage:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/etcd3/preflight:go_default_library", "//vendor/k8s.io/apiserver/pkg/storage/etcd3/preflight:go_default_library",
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//vendor/k8s.io/client-go/discovery:go_default_library",
"//vendor/k8s.io/client-go/discovery/cached:go_default_library",
"//vendor/k8s.io/client-go/informers:go_default_library", "//vendor/k8s.io/client-go/informers:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library",

View File

@@ -35,6 +35,7 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
utilerrors "k8s.io/apimachinery/pkg/util/errors" utilerrors "k8s.io/apimachinery/pkg/util/errors"
@@ -54,6 +55,8 @@ import (
serverstorage "k8s.io/apiserver/pkg/server/storage" serverstorage "k8s.io/apiserver/pkg/server/storage"
"k8s.io/apiserver/pkg/storage/etcd3/preflight" "k8s.io/apiserver/pkg/storage/etcd3/preflight"
utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/discovery"
cacheddiscovery "k8s.io/client-go/discovery/cached"
clientgoinformers "k8s.io/client-go/informers" clientgoinformers "k8s.io/client-go/informers"
clientgoclientset "k8s.io/client-go/kubernetes" clientgoclientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
@@ -156,7 +159,7 @@ func CreateServerChain(completedOptions completedServerRunOptions, stopCh <-chan
return nil, err return nil, err
} }
kubeAPIServerConfig, sharedInformers, versionedInformers, insecureServingOptions, serviceResolver, pluginInitializer, err := CreateKubeAPIServerConfig(completedOptions, nodeTunneler, proxyTransport) kubeAPIServerConfig, sharedInformers, versionedInformers, insecureServingOptions, serviceResolver, pluginInitializer, admissionPostStartHook, err := CreateKubeAPIServerConfig(completedOptions, nodeTunneler, proxyTransport)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -171,7 +174,7 @@ func CreateServerChain(completedOptions completedServerRunOptions, stopCh <-chan
return nil, err return nil, err
} }
kubeAPIServer, err := CreateKubeAPIServer(kubeAPIServerConfig, apiExtensionsServer.GenericAPIServer, sharedInformers, versionedInformers) kubeAPIServer, err := CreateKubeAPIServer(kubeAPIServerConfig, apiExtensionsServer.GenericAPIServer, sharedInformers, versionedInformers, admissionPostStartHook)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -218,15 +221,17 @@ func CreateServerChain(completedOptions completedServerRunOptions, stopCh <-chan
} }
// CreateKubeAPIServer creates and wires a workable kube-apiserver // CreateKubeAPIServer creates and wires a workable kube-apiserver
func CreateKubeAPIServer(kubeAPIServerConfig *master.Config, delegateAPIServer genericapiserver.DelegationTarget, sharedInformers informers.SharedInformerFactory, versionedInformers clientgoinformers.SharedInformerFactory) (*master.Master, error) { func CreateKubeAPIServer(kubeAPIServerConfig *master.Config, delegateAPIServer genericapiserver.DelegationTarget, sharedInformers informers.SharedInformerFactory, versionedInformers clientgoinformers.SharedInformerFactory, admissionPostStartHook genericapiserver.PostStartHookFunc) (*master.Master, error) {
kubeAPIServer, err := kubeAPIServerConfig.Complete(versionedInformers).New(delegateAPIServer) kubeAPIServer, err := kubeAPIServerConfig.Complete(versionedInformers).New(delegateAPIServer)
if err != nil { if err != nil {
return nil, err return nil, err
} }
kubeAPIServer.GenericAPIServer.AddPostStartHook("start-kube-apiserver-informers", func(context genericapiserver.PostStartHookContext) error {
kubeAPIServer.GenericAPIServer.AddPostStartHookOrDie("start-kube-apiserver-informers", func(context genericapiserver.PostStartHookContext) error {
sharedInformers.Start(context.StopCh) sharedInformers.Start(context.StopCh)
return nil return nil
}) })
kubeAPIServer.GenericAPIServer.AddPostStartHookOrDie("start-kube-apiserver-admission-initializer", admissionPostStartHook)
return kubeAPIServer, nil return kubeAPIServer, nil
} }
@@ -288,10 +293,11 @@ func CreateKubeAPIServerConfig(
insecureServingInfo *kubeserver.InsecureServingInfo, insecureServingInfo *kubeserver.InsecureServingInfo,
serviceResolver aggregatorapiserver.ServiceResolver, serviceResolver aggregatorapiserver.ServiceResolver,
pluginInitializers []admission.PluginInitializer, pluginInitializers []admission.PluginInitializer,
admissionPostStartHook genericapiserver.PostStartHookFunc,
lastErr error, lastErr error,
) { ) {
var genericConfig *genericapiserver.Config var genericConfig *genericapiserver.Config
genericConfig, sharedInformers, versionedInformers, insecureServingInfo, serviceResolver, pluginInitializers, lastErr = BuildGenericConfig(s.ServerRunOptions, proxyTransport) genericConfig, sharedInformers, versionedInformers, insecureServingInfo, serviceResolver, pluginInitializers, admissionPostStartHook, lastErr = BuildGenericConfig(s.ServerRunOptions, proxyTransport)
if lastErr != nil { if lastErr != nil {
return return
} }
@@ -413,6 +419,7 @@ func BuildGenericConfig(
insecureServingInfo *kubeserver.InsecureServingInfo, insecureServingInfo *kubeserver.InsecureServingInfo,
serviceResolver aggregatorapiserver.ServiceResolver, serviceResolver aggregatorapiserver.ServiceResolver,
pluginInitializers []admission.PluginInitializer, pluginInitializers []admission.PluginInitializer,
admissionPostStartHook genericapiserver.PostStartHookFunc,
lastErr error, lastErr error,
) { ) {
genericConfig = genericapiserver.NewConfig(legacyscheme.Codecs) genericConfig = genericapiserver.NewConfig(legacyscheme.Codecs)
@@ -539,7 +546,7 @@ func BuildGenericConfig(
}, },
} }
} }
pluginInitializers, err = BuildAdmissionPluginInitializers( pluginInitializers, admissionPostStartHook, err = BuildAdmissionPluginInitializers(
s, s,
client, client,
sharedInformers, sharedInformers,
@@ -571,7 +578,7 @@ func BuildAdmissionPluginInitializers(
sharedInformers informers.SharedInformerFactory, sharedInformers informers.SharedInformerFactory,
serviceResolver aggregatorapiserver.ServiceResolver, serviceResolver aggregatorapiserver.ServiceResolver,
webhookAuthWrapper webhookconfig.AuthenticationInfoResolverWrapper, webhookAuthWrapper webhookconfig.AuthenticationInfoResolverWrapper,
) ([]admission.PluginInitializer, error) { ) ([]admission.PluginInitializer, genericapiserver.PostStartHookFunc, error) {
var cloudConfig []byte var cloudConfig []byte
if s.CloudProvider.CloudConfigFile != "" { if s.CloudProvider.CloudConfigFile != "" {
@@ -582,15 +589,33 @@ func BuildAdmissionPluginInitializers(
} }
} }
// TODO: use a dynamic restmapper. See https://github.com/kubernetes/kubernetes/pull/42615. // TODO: drop this REST mapper once client is guaranteed to be non-nil
// See KUBE_API_VERSIONS comment above
restMapper := legacyscheme.Registry.RESTMapper() restMapper := legacyscheme.Registry.RESTMapper()
admissionPostStartHook := func(_ genericapiserver.PostStartHookContext) error {
return nil
}
// We have a functional client so we can use that to build our discovery backed REST mapper
if client != nil && client.Discovery() != nil {
// Use a discovery client capable of being refreshed.
discoveryClient := cacheddiscovery.NewMemCacheClient(client.Discovery())
discoveryRESTMapper := discovery.NewDeferredDiscoveryRESTMapper(discoveryClient, meta.InterfacesForUnstructured)
restMapper = discoveryRESTMapper
admissionPostStartHook = func(context genericapiserver.PostStartHookContext) error {
discoveryRESTMapper.Reset()
go utilwait.Until(discoveryRESTMapper.Reset, 10*time.Second, context.StopCh)
return nil
}
}
quotaConfiguration := quotainstall.NewQuotaConfigurationForAdmission() quotaConfiguration := quotainstall.NewQuotaConfigurationForAdmission()
kubePluginInitializer := kubeapiserveradmission.NewPluginInitializer(client, sharedInformers, cloudConfig, restMapper, quotaConfiguration) kubePluginInitializer := kubeapiserveradmission.NewPluginInitializer(client, sharedInformers, cloudConfig, restMapper, quotaConfiguration)
webhookPluginInitializer := webhookinit.NewPluginInitializer(webhookAuthWrapper, serviceResolver) webhookPluginInitializer := webhookinit.NewPluginInitializer(webhookAuthWrapper, serviceResolver)
return []admission.PluginInitializer{webhookPluginInitializer, kubePluginInitializer}, nil return []admission.PluginInitializer{webhookPluginInitializer, kubePluginInitializer}, admissionPostStartHook, nil
} }
// BuildAuthenticator constructs the authenticator // BuildAuthenticator constructs the authenticator

View File

@@ -744,14 +744,14 @@ func startRealMasterOrDie(t *testing.T, certDir string) (*allClient, clientv3.KV
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
kubeAPIServerConfig, sharedInformers, versionedInformers, _, _, _, err := app.CreateKubeAPIServerConfig(completedOptions, tunneler, proxyTransport) kubeAPIServerConfig, sharedInformers, versionedInformers, _, _, _, admissionPostStartHook, err := app.CreateKubeAPIServerConfig(completedOptions, tunneler, proxyTransport)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
kubeAPIServerConfig.ExtraConfig.APIResourceConfigSource = &allResourceSource{} // force enable all resources kubeAPIServerConfig.ExtraConfig.APIResourceConfigSource = &allResourceSource{} // force enable all resources
kubeAPIServer, err := app.CreateKubeAPIServer(kubeAPIServerConfig, genericapiserver.NewEmptyDelegate(), sharedInformers, versionedInformers) kubeAPIServer, err := app.CreateKubeAPIServer(kubeAPIServerConfig, genericapiserver.NewEmptyDelegate(), sharedInformers, versionedInformers, admissionPostStartHook)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@@ -113,7 +113,7 @@ func TestAggregatedAPIServer(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
kubeAPIServerConfig, sharedInformers, versionedInformers, _, _, _, err := app.CreateKubeAPIServerConfig(completedOptions, tunneler, proxyTransport) kubeAPIServerConfig, sharedInformers, versionedInformers, _, _, _, admissionPostStartHook, err := app.CreateKubeAPIServerConfig(completedOptions, tunneler, proxyTransport)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -124,7 +124,7 @@ func TestAggregatedAPIServer(t *testing.T) {
kubeAPIServerClientConfig.ServerName = "" kubeAPIServerClientConfig.ServerName = ""
kubeClientConfigValue.Store(kubeAPIServerClientConfig) kubeClientConfigValue.Store(kubeAPIServerClientConfig)
kubeAPIServer, err := app.CreateKubeAPIServer(kubeAPIServerConfig, genericapiserver.NewEmptyDelegate(), sharedInformers, versionedInformers) kubeAPIServer, err := app.CreateKubeAPIServer(kubeAPIServerConfig, genericapiserver.NewEmptyDelegate(), sharedInformers, versionedInformers, admissionPostStartHook)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -135,7 +135,7 @@ func TestAggregatedAPIServer(t *testing.T) {
}() }()
// just use json because everyone speaks it // just use json because everyone speaks it
err = wait.PollImmediate(100*time.Millisecond, 10*time.Second, func() (done bool, err error) { err = wait.PollImmediate(time.Second, time.Minute, func() (done bool, err error) {
obj := kubeClientConfigValue.Load() obj := kubeClientConfigValue.Load()
if obj == nil { if obj == nil {
return false, nil return false, nil