2019-01-12 04:58:27 +00:00
/ *
Copyright 2017 The Kubernetes Authors .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
// Package app does all of the work necessary to create a Kubernetes
// APIServer by binding together the API, master and APIServer infrastructure.
// It can be configured and called directly or via the hyperkube framework.
package app
import (
"fmt"
"io/ioutil"
"net/http"
"strings"
"sync"
"k8s.io/klog"
apiextensionsinformers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2019-04-07 17:07:55 +00:00
"k8s.io/apimachinery/pkg/runtime"
2019-01-12 04:58:27 +00:00
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/features"
genericapiserver "k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/server/healthz"
genericoptions "k8s.io/apiserver/pkg/server/options"
utilfeature "k8s.io/apiserver/pkg/util/feature"
kubeexternalinformers "k8s.io/client-go/informers"
"k8s.io/client-go/tools/cache"
"k8s.io/kube-aggregator/pkg/apis/apiregistration"
2019-04-07 17:07:55 +00:00
v1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
2019-01-12 04:58:27 +00:00
"k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1"
aggregatorapiserver "k8s.io/kube-aggregator/pkg/apiserver"
aggregatorscheme "k8s.io/kube-aggregator/pkg/apiserver/scheme"
apiregistrationclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/internalclientset/typed/apiregistration/internalversion"
informers "k8s.io/kube-aggregator/pkg/client/informers/internalversion/apiregistration/internalversion"
"k8s.io/kube-aggregator/pkg/controllers/autoregister"
"k8s.io/kubernetes/cmd/kube-apiserver/app/options"
"k8s.io/kubernetes/pkg/master/controller/crdregistration"
)
func createAggregatorConfig (
kubeAPIServerConfig genericapiserver . Config ,
commandOptions * options . ServerRunOptions ,
externalInformers kubeexternalinformers . SharedInformerFactory ,
serviceResolver aggregatorapiserver . ServiceResolver ,
proxyTransport * http . Transport ,
pluginInitializers [ ] admission . PluginInitializer ,
) ( * aggregatorapiserver . Config , error ) {
// make a shallow copy to let us twiddle a few things
// most of the config actually remains the same. We only need to mess with a couple items related to the particulars of the aggregator
genericConfig := kubeAPIServerConfig
// override genericConfig.AdmissionControl with kube-aggregator's scheme,
// because aggregator apiserver should use its own scheme to convert its own resources.
2019-04-07 17:07:55 +00:00
err := commandOptions . Admission . ApplyTo (
2019-01-12 04:58:27 +00:00
& genericConfig ,
externalInformers ,
genericConfig . LoopbackClientConfig ,
pluginInitializers ... )
2019-04-07 17:07:55 +00:00
if err != nil {
return nil , err
}
2019-01-12 04:58:27 +00:00
// copy the etcd options so we don't mutate originals.
etcdOptions := * commandOptions . Etcd
etcdOptions . StorageConfig . Paging = utilfeature . DefaultFeatureGate . Enabled ( features . APIListChunking )
etcdOptions . StorageConfig . Codec = aggregatorscheme . Codecs . LegacyCodec ( v1beta1 . SchemeGroupVersion , v1 . SchemeGroupVersion )
2019-04-07 17:07:55 +00:00
etcdOptions . StorageConfig . EncodeVersioner = runtime . NewMultiGroupVersioner ( v1beta1 . SchemeGroupVersion , schema . GroupKind { Group : v1beta1 . GroupName } )
2019-01-12 04:58:27 +00:00
genericConfig . RESTOptionsGetter = & genericoptions . SimpleRestOptionsFactory { Options : etcdOptions }
// override MergedResourceConfig with aggregator defaults and registry
if err := commandOptions . APIEnablement . ApplyTo (
& genericConfig ,
aggregatorapiserver . DefaultAPIResourceConfigSource ( ) ,
aggregatorscheme . Scheme ) ; err != nil {
return nil , err
}
var certBytes , keyBytes [ ] byte
if len ( commandOptions . ProxyClientCertFile ) > 0 && len ( commandOptions . ProxyClientKeyFile ) > 0 {
certBytes , err = ioutil . ReadFile ( commandOptions . ProxyClientCertFile )
if err != nil {
return nil , err
}
keyBytes , err = ioutil . ReadFile ( commandOptions . ProxyClientKeyFile )
if err != nil {
return nil , err
}
}
aggregatorConfig := & aggregatorapiserver . Config {
GenericConfig : & genericapiserver . RecommendedConfig {
Config : genericConfig ,
SharedInformerFactory : externalInformers ,
} ,
ExtraConfig : aggregatorapiserver . ExtraConfig {
ProxyClientCert : certBytes ,
ProxyClientKey : keyBytes ,
ServiceResolver : serviceResolver ,
ProxyTransport : proxyTransport ,
} ,
}
return aggregatorConfig , nil
}
func createAggregatorServer ( aggregatorConfig * aggregatorapiserver . Config , delegateAPIServer genericapiserver . DelegationTarget , apiExtensionInformers apiextensionsinformers . SharedInformerFactory ) ( * aggregatorapiserver . APIAggregator , error ) {
aggregatorServer , err := aggregatorConfig . Complete ( ) . NewWithDelegate ( delegateAPIServer )
if err != nil {
return nil , err
}
// create controllers for auto-registration
apiRegistrationClient , err := apiregistrationclient . NewForConfig ( aggregatorConfig . GenericConfig . LoopbackClientConfig )
if err != nil {
return nil , err
}
autoRegistrationController := autoregister . NewAutoRegisterController ( aggregatorServer . APIRegistrationInformers . Apiregistration ( ) . InternalVersion ( ) . APIServices ( ) , apiRegistrationClient )
apiServices := apiServicesToRegister ( delegateAPIServer , autoRegistrationController )
2019-04-07 17:07:55 +00:00
crdRegistrationController := crdregistration . NewCRDRegistrationController (
2019-01-12 04:58:27 +00:00
apiExtensionInformers . Apiextensions ( ) . InternalVersion ( ) . CustomResourceDefinitions ( ) ,
autoRegistrationController )
2019-04-07 17:07:55 +00:00
err = aggregatorServer . GenericAPIServer . AddPostStartHook ( "kube-apiserver-autoregistration" , func ( context genericapiserver . PostStartHookContext ) error {
2019-01-12 04:58:27 +00:00
go crdRegistrationController . Run ( 5 , context . StopCh )
go func ( ) {
// let the CRD controller process the initial set of CRDs before starting the autoregistration controller.
// this prevents the autoregistration controller's initial sync from deleting APIServices for CRDs that still exist.
// we only need to do this if CRDs are enabled on this server. We can't use discovery because we are the source for discovery.
if aggregatorConfig . GenericConfig . MergedResourceConfig . AnyVersionForGroupEnabled ( "apiextensions.k8s.io" ) {
crdRegistrationController . WaitForInitialSync ( )
}
autoRegistrationController . Run ( 5 , context . StopCh )
} ( )
return nil
} )
2019-04-07 17:07:55 +00:00
if err != nil {
return nil , err
}
2019-01-12 04:58:27 +00:00
2019-04-07 17:07:55 +00:00
err = aggregatorServer . GenericAPIServer . AddHealthzChecks (
2019-01-12 04:58:27 +00:00
makeAPIServiceAvailableHealthzCheck (
"autoregister-completion" ,
apiServices ,
aggregatorServer . APIRegistrationInformers . Apiregistration ( ) . InternalVersion ( ) . APIServices ( ) ,
) ,
)
2019-04-07 17:07:55 +00:00
if err != nil {
return nil , err
}
2019-01-12 04:58:27 +00:00
return aggregatorServer , nil
}
func makeAPIService ( gv schema . GroupVersion ) * apiregistration . APIService {
apiServicePriority , ok := apiVersionPriorities [ gv ]
if ! ok {
// if we aren't found, then we shouldn't register ourselves because it could result in a CRD group version
// being permanently stuck in the APIServices list.
klog . Infof ( "Skipping APIService creation for %v" , gv )
return nil
}
return & apiregistration . APIService {
ObjectMeta : metav1 . ObjectMeta { Name : gv . Version + "." + gv . Group } ,
Spec : apiregistration . APIServiceSpec {
Group : gv . Group ,
Version : gv . Version ,
GroupPriorityMinimum : apiServicePriority . group ,
VersionPriority : apiServicePriority . version ,
} ,
}
}
// makeAPIServiceAvailableHealthzCheck returns a healthz check that returns healthy
// once all of the specified services have been observed to be available at least once.
func makeAPIServiceAvailableHealthzCheck ( name string , apiServices [ ] * apiregistration . APIService , apiServiceInformer informers . APIServiceInformer ) healthz . HealthzChecker {
// Track the auto-registered API services that have not been observed to be available yet
pendingServiceNamesLock := & sync . RWMutex { }
pendingServiceNames := sets . NewString ( )
for _ , service := range apiServices {
pendingServiceNames . Insert ( service . Name )
}
// When an APIService in the list is seen as available, remove it from the pending list
handleAPIServiceChange := func ( service * apiregistration . APIService ) {
pendingServiceNamesLock . Lock ( )
defer pendingServiceNamesLock . Unlock ( )
if ! pendingServiceNames . Has ( service . Name ) {
return
}
if apiregistration . IsAPIServiceConditionTrue ( service , apiregistration . Available ) {
pendingServiceNames . Delete ( service . Name )
}
}
// Watch add/update events for APIServices
apiServiceInformer . Informer ( ) . AddEventHandler ( cache . ResourceEventHandlerFuncs {
AddFunc : func ( obj interface { } ) { handleAPIServiceChange ( obj . ( * apiregistration . APIService ) ) } ,
UpdateFunc : func ( old , new interface { } ) { handleAPIServiceChange ( new . ( * apiregistration . APIService ) ) } ,
} )
// Don't return healthy until the pending list is empty
return healthz . NamedCheck ( name , func ( r * http . Request ) error {
pendingServiceNamesLock . RLock ( )
defer pendingServiceNamesLock . RUnlock ( )
if pendingServiceNames . Len ( ) > 0 {
return fmt . Errorf ( "missing APIService: %v" , pendingServiceNames . List ( ) )
}
return nil
} )
}
// priority defines group priority that is used in discovery. This controls
// group position in the kubectl output.
type priority struct {
// group indicates the order of the group relative to other groups.
group int32
// version indicates the relative order of the version inside of its group.
version int32
}
// The proper way to resolve this letting the aggregator know the desired group and version-within-group order of the underlying servers
// is to refactor the genericapiserver.DelegationTarget to include a list of priorities based on which APIs were installed.
// This requires the APIGroupInfo struct to evolve and include the concept of priorities and to avoid mistakes, the core storage map there needs to be updated.
// That ripples out every bit as far as you'd expect, so for 1.7 we'll include the list here instead of being built up during storage.
var apiVersionPriorities = map [ schema . GroupVersion ] priority {
{ Group : "" , Version : "v1" } : { group : 18000 , version : 1 } ,
// extensions is above the rest for CLI compatibility, though the level of unqualified resource compatibility we
// can reasonably expect seems questionable.
{ Group : "extensions" , Version : "v1beta1" } : { group : 17900 , version : 1 } ,
// to my knowledge, nothing below here collides
2019-04-07 17:07:55 +00:00
{ Group : "apps" , Version : "v1beta1" } : { group : 17800 , version : 1 } ,
{ Group : "apps" , Version : "v1beta2" } : { group : 17800 , version : 9 } ,
{ Group : "apps" , Version : "v1" } : { group : 17800 , version : 15 } ,
{ Group : "events.k8s.io" , Version : "v1beta1" } : { group : 17750 , version : 5 } ,
{ Group : "authentication.k8s.io" , Version : "v1" } : { group : 17700 , version : 15 } ,
{ Group : "authentication.k8s.io" , Version : "v1beta1" } : { group : 17700 , version : 9 } ,
{ Group : "authorization.k8s.io" , Version : "v1" } : { group : 17600 , version : 15 } ,
{ Group : "authorization.k8s.io" , Version : "v1beta1" } : { group : 17600 , version : 9 } ,
{ Group : "autoscaling" , Version : "v1" } : { group : 17500 , version : 15 } ,
{ Group : "autoscaling" , Version : "v2beta1" } : { group : 17500 , version : 9 } ,
{ Group : "autoscaling" , Version : "v2beta2" } : { group : 17500 , version : 1 } ,
{ Group : "batch" , Version : "v1" } : { group : 17400 , version : 15 } ,
{ Group : "batch" , Version : "v1beta1" } : { group : 17400 , version : 9 } ,
{ Group : "batch" , Version : "v2alpha1" } : { group : 17400 , version : 9 } ,
{ Group : "certificates.k8s.io" , Version : "v1beta1" } : { group : 17300 , version : 9 } ,
{ Group : "networking.k8s.io" , Version : "v1" } : { group : 17200 , version : 15 } ,
{ Group : "networking.k8s.io" , Version : "v1beta1" } : { group : 17200 , version : 9 } ,
{ Group : "policy" , Version : "v1beta1" } : { group : 17100 , version : 9 } ,
{ Group : "rbac.authorization.k8s.io" , Version : "v1" } : { group : 17000 , version : 15 } ,
{ Group : "rbac.authorization.k8s.io" , Version : "v1beta1" } : { group : 17000 , version : 12 } ,
{ Group : "rbac.authorization.k8s.io" , Version : "v1alpha1" } : { group : 17000 , version : 9 } ,
{ Group : "settings.k8s.io" , Version : "v1alpha1" } : { group : 16900 , version : 9 } ,
{ Group : "storage.k8s.io" , Version : "v1" } : { group : 16800 , version : 15 } ,
{ Group : "storage.k8s.io" , Version : "v1beta1" } : { group : 16800 , version : 9 } ,
{ Group : "storage.k8s.io" , Version : "v1alpha1" } : { group : 16800 , version : 1 } ,
{ Group : "apiextensions.k8s.io" , Version : "v1beta1" } : { group : 16700 , version : 9 } ,
{ Group : "admissionregistration.k8s.io" , Version : "v1" } : { group : 16700 , version : 15 } ,
{ Group : "admissionregistration.k8s.io" , Version : "v1beta1" } : { group : 16700 , version : 12 } ,
{ Group : "scheduling.k8s.io" , Version : "v1" } : { group : 16600 , version : 15 } ,
{ Group : "scheduling.k8s.io" , Version : "v1beta1" } : { group : 16600 , version : 12 } ,
{ Group : "scheduling.k8s.io" , Version : "v1alpha1" } : { group : 16600 , version : 9 } ,
{ Group : "coordination.k8s.io" , Version : "v1" } : { group : 16500 , version : 15 } ,
{ Group : "coordination.k8s.io" , Version : "v1beta1" } : { group : 16500 , version : 9 } ,
{ Group : "auditregistration.k8s.io" , Version : "v1alpha1" } : { group : 16400 , version : 1 } ,
{ Group : "node.k8s.io" , Version : "v1alpha1" } : { group : 16300 , version : 1 } ,
{ Group : "node.k8s.io" , Version : "v1beta1" } : { group : 16300 , version : 9 } ,
2019-01-12 04:58:27 +00:00
// Append a new group to the end of the list if unsure.
// You can use min(existing group)-100 as the initial value for a group.
// Version can be set to 9 (to have space around) for a new group.
}
func apiServicesToRegister ( delegateAPIServer genericapiserver . DelegationTarget , registration autoregister . AutoAPIServiceRegistration ) [ ] * apiregistration . APIService {
apiServices := [ ] * apiregistration . APIService { }
for _ , curr := range delegateAPIServer . ListedPaths ( ) {
if curr == "/api/v1" {
apiService := makeAPIService ( schema . GroupVersion { Group : "" , Version : "v1" } )
registration . AddAPIServiceToSyncOnStart ( apiService )
apiServices = append ( apiServices , apiService )
continue
}
if ! strings . HasPrefix ( curr , "/apis/" ) {
continue
}
// this comes back in a list that looks like /apis/rbac.authorization.k8s.io/v1alpha1
tokens := strings . Split ( curr , "/" )
if len ( tokens ) != 4 {
continue
}
apiService := makeAPIService ( schema . GroupVersion { Group : tokens [ 2 ] , Version : tokens [ 3 ] } )
if apiService == nil {
continue
}
registration . AddAPIServiceToSyncOnStart ( apiService )
apiServices = append ( apiServices , apiService )
}
return apiServices
}