52baf68d50
Signed-off-by: Michał Żyłowski <michal.zylowski@intel.com>
418 lines
16 KiB
Go
418 lines
16 KiB
Go
/*
|
|
Copyright 2014 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 master
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"reflect"
|
|
"strconv"
|
|
"time"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
utilnet "k8s.io/apimachinery/pkg/util/net"
|
|
"k8s.io/apiserver/pkg/server/healthz"
|
|
"k8s.io/kubernetes/cmd/kube-apiserver/app/options"
|
|
"k8s.io/kubernetes/pkg/api"
|
|
apiv1 "k8s.io/kubernetes/pkg/api/v1"
|
|
appsapi "k8s.io/kubernetes/pkg/apis/apps/v1beta1"
|
|
authenticationv1beta1 "k8s.io/kubernetes/pkg/apis/authentication/v1beta1"
|
|
authorizationapiv1beta1 "k8s.io/kubernetes/pkg/apis/authorization/v1beta1"
|
|
autoscalingapiv1 "k8s.io/kubernetes/pkg/apis/autoscaling/v1"
|
|
batchapiv1 "k8s.io/kubernetes/pkg/apis/batch/v1"
|
|
certificatesapiv1beta1 "k8s.io/kubernetes/pkg/apis/certificates/v1beta1"
|
|
extensionsapiv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
|
|
policyapiv1beta1 "k8s.io/kubernetes/pkg/apis/policy/v1beta1"
|
|
rbacapi "k8s.io/kubernetes/pkg/apis/rbac/v1alpha1"
|
|
rbacv1beta1 "k8s.io/kubernetes/pkg/apis/rbac/v1beta1"
|
|
storageapiv1beta1 "k8s.io/kubernetes/pkg/apis/storage/v1beta1"
|
|
corev1client "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/core/v1"
|
|
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
|
|
"k8s.io/kubernetes/pkg/genericapiserver/registry/generic"
|
|
genericregistry "k8s.io/kubernetes/pkg/genericapiserver/registry/generic/registry"
|
|
genericapiserver "k8s.io/kubernetes/pkg/genericapiserver/server"
|
|
kubeletclient "k8s.io/kubernetes/pkg/kubelet/client"
|
|
"k8s.io/kubernetes/pkg/master/thirdparty"
|
|
"k8s.io/kubernetes/pkg/master/tunneler"
|
|
"k8s.io/kubernetes/pkg/routes"
|
|
nodeutil "k8s.io/kubernetes/pkg/util/node"
|
|
|
|
"github.com/golang/glog"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
|
// RESTStorage installers
|
|
appsrest "k8s.io/kubernetes/pkg/registry/apps/rest"
|
|
authenticationrest "k8s.io/kubernetes/pkg/registry/authentication/rest"
|
|
authorizationrest "k8s.io/kubernetes/pkg/registry/authorization/rest"
|
|
autoscalingrest "k8s.io/kubernetes/pkg/registry/autoscaling/rest"
|
|
batchrest "k8s.io/kubernetes/pkg/registry/batch/rest"
|
|
certificatesrest "k8s.io/kubernetes/pkg/registry/certificates/rest"
|
|
corerest "k8s.io/kubernetes/pkg/registry/core/rest"
|
|
extensionsrest "k8s.io/kubernetes/pkg/registry/extensions/rest"
|
|
policyrest "k8s.io/kubernetes/pkg/registry/policy/rest"
|
|
rbacrest "k8s.io/kubernetes/pkg/registry/rbac/rest"
|
|
storagerest "k8s.io/kubernetes/pkg/registry/storage/rest"
|
|
)
|
|
|
|
const (
|
|
// DefaultEndpointReconcilerInterval is the default amount of time for how often the endpoints for
|
|
// the kubernetes Service are reconciled.
|
|
DefaultEndpointReconcilerInterval = 10 * time.Second
|
|
)
|
|
|
|
type Config struct {
|
|
GenericConfig *genericapiserver.Config
|
|
|
|
APIResourceConfigSource genericapiserver.APIResourceConfigSource
|
|
StorageFactory genericapiserver.StorageFactory
|
|
EnableWatchCache bool
|
|
EnableCoreControllers bool
|
|
EndpointReconcilerConfig EndpointReconcilerConfig
|
|
DeleteCollectionWorkers int
|
|
EventTTL time.Duration
|
|
KubeletClientConfig kubeletclient.KubeletClientConfig
|
|
|
|
// Used to start and monitor tunneling
|
|
Tunneler tunneler.Tunneler
|
|
EnableUISupport bool
|
|
EnableLogsSupport bool
|
|
ProxyTransport http.RoundTripper
|
|
|
|
// Values to build the IP addresses used by discovery
|
|
// The range of IPs to be assigned to services with type=ClusterIP or greater
|
|
ServiceIPRange net.IPNet
|
|
// The IP address for the GenericAPIServer service (must be inside ServiceIPRange)
|
|
APIServerServiceIP net.IP
|
|
// Port for the apiserver service.
|
|
APIServerServicePort int
|
|
|
|
// TODO, we can probably group service related items into a substruct to make it easier to configure
|
|
// the API server items and `Extra*` fields likely fit nicely together.
|
|
|
|
// The range of ports to be assigned to services with type=NodePort or greater
|
|
ServiceNodePortRange utilnet.PortRange
|
|
// Additional ports to be exposed on the GenericAPIServer service
|
|
// extraServicePorts is injectable in the event that more ports
|
|
// (other than the default 443/tcp) are exposed on the GenericAPIServer
|
|
// and those ports need to be load balanced by the GenericAPIServer
|
|
// service because this pkg is linked by out-of-tree projects
|
|
// like openshift which want to use the GenericAPIServer but also do
|
|
// more stuff.
|
|
ExtraServicePorts []api.ServicePort
|
|
// Additional ports to be exposed on the GenericAPIServer endpoints
|
|
// Port names should align with ports defined in ExtraServicePorts
|
|
ExtraEndpointPorts []api.EndpointPort
|
|
// If non-zero, the "kubernetes" services uses this port as NodePort.
|
|
KubernetesServiceNodePort int
|
|
|
|
// Number of masters running; all masters must be started with the
|
|
// same value for this field. (Numbers > 1 currently untested.)
|
|
MasterCount int
|
|
}
|
|
|
|
// EndpointReconcilerConfig holds the endpoint reconciler and endpoint reconciliation interval to be
|
|
// used by the master.
|
|
type EndpointReconcilerConfig struct {
|
|
Reconciler EndpointReconciler
|
|
Interval time.Duration
|
|
}
|
|
|
|
// Master contains state for a Kubernetes cluster master/api server.
|
|
type Master struct {
|
|
GenericAPIServer *genericapiserver.GenericAPIServer
|
|
}
|
|
|
|
type completedConfig struct {
|
|
*Config
|
|
}
|
|
|
|
// Complete fills in any fields not set that are required to have valid data. It's mutating the receiver.
|
|
func (c *Config) Complete() completedConfig {
|
|
c.GenericConfig.Complete()
|
|
|
|
serviceIPRange, apiServerServiceIP, err := DefaultServiceIPRange(c.ServiceIPRange)
|
|
if err != nil {
|
|
glog.Fatalf("Error determining service IP ranges: %v", err)
|
|
}
|
|
if c.ServiceIPRange.IP == nil {
|
|
c.ServiceIPRange = serviceIPRange
|
|
}
|
|
if c.APIServerServiceIP == nil {
|
|
c.APIServerServiceIP = apiServerServiceIP
|
|
}
|
|
|
|
discoveryAddresses := genericapiserver.DefaultDiscoveryAddresses{DefaultAddress: c.GenericConfig.ExternalAddress}
|
|
discoveryAddresses.DiscoveryCIDRRules = append(discoveryAddresses.DiscoveryCIDRRules,
|
|
genericapiserver.DiscoveryCIDRRule{IPRange: c.ServiceIPRange, Address: net.JoinHostPort(c.APIServerServiceIP.String(), strconv.Itoa(c.APIServerServicePort))})
|
|
c.GenericConfig.DiscoveryAddresses = discoveryAddresses
|
|
|
|
if c.ServiceNodePortRange.Size == 0 {
|
|
// TODO: Currently no way to specify an empty range (do we need to allow this?)
|
|
// We should probably allow this for clouds that don't require NodePort to do load-balancing (GCE)
|
|
// but then that breaks the strict nestedness of ServiceType.
|
|
// Review post-v1
|
|
c.ServiceNodePortRange = options.DefaultServiceNodePortRange
|
|
glog.Infof("Node port range unspecified. Defaulting to %v.", c.ServiceNodePortRange)
|
|
}
|
|
|
|
// enable swagger UI only if general UI support is on
|
|
c.GenericConfig.EnableSwaggerUI = c.GenericConfig.EnableSwaggerUI && c.EnableUISupport
|
|
|
|
if c.EndpointReconcilerConfig.Interval == 0 {
|
|
c.EndpointReconcilerConfig.Interval = DefaultEndpointReconcilerInterval
|
|
}
|
|
|
|
if c.EndpointReconcilerConfig.Reconciler == nil {
|
|
// use a default endpoint reconciler if nothing is set
|
|
endpointClient := coreclient.NewForConfigOrDie(c.GenericConfig.LoopbackClientConfig)
|
|
c.EndpointReconcilerConfig.Reconciler = NewMasterCountEndpointReconciler(c.MasterCount, endpointClient)
|
|
}
|
|
|
|
// this has always been hardcoded true in the past
|
|
c.GenericConfig.EnableMetrics = true
|
|
|
|
return completedConfig{c}
|
|
}
|
|
|
|
// SkipComplete provides a way to construct a server instance without config completion.
|
|
func (c *Config) SkipComplete() completedConfig {
|
|
return completedConfig{c}
|
|
}
|
|
|
|
// New returns a new instance of Master from the given config.
|
|
// Certain config fields will be set to a default value if unset.
|
|
// Certain config fields must be specified, including:
|
|
// KubeletClientConfig
|
|
func (c completedConfig) New() (*Master, error) {
|
|
if reflect.DeepEqual(c.KubeletClientConfig, kubeletclient.KubeletClientConfig{}) {
|
|
return nil, fmt.Errorf("Master.New() called with empty config.KubeletClientConfig")
|
|
}
|
|
|
|
s, err := c.Config.GenericConfig.SkipComplete().New() // completion is done in Complete, no need for a second time
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if c.EnableUISupport {
|
|
routes.UIRedirect{}.Install(s.HandlerContainer)
|
|
}
|
|
if c.EnableLogsSupport {
|
|
routes.Logs{}.Install(s.HandlerContainer)
|
|
}
|
|
|
|
m := &Master{
|
|
GenericAPIServer: s,
|
|
}
|
|
|
|
restOptionsFactory := &restOptionsFactory{
|
|
deleteCollectionWorkers: c.DeleteCollectionWorkers,
|
|
enableGarbageCollection: c.GenericConfig.EnableGarbageCollection,
|
|
storageFactory: c.StorageFactory,
|
|
}
|
|
|
|
if c.EnableWatchCache {
|
|
restOptionsFactory.storageDecorator = genericregistry.StorageWithCacher
|
|
} else {
|
|
restOptionsFactory.storageDecorator = generic.UndecoratedStorage
|
|
}
|
|
|
|
// install legacy rest storage
|
|
if c.APIResourceConfigSource.AnyResourcesForVersionEnabled(apiv1.SchemeGroupVersion) {
|
|
legacyRESTStorageProvider := corerest.LegacyRESTStorageProvider{
|
|
StorageFactory: c.StorageFactory,
|
|
ProxyTransport: c.ProxyTransport,
|
|
KubeletClientConfig: c.KubeletClientConfig,
|
|
EventTTL: c.EventTTL,
|
|
ServiceIPRange: c.ServiceIPRange,
|
|
ServiceNodePortRange: c.ServiceNodePortRange,
|
|
LoopbackClientConfig: c.GenericConfig.LoopbackClientConfig,
|
|
}
|
|
m.InstallLegacyAPI(c.Config, restOptionsFactory, legacyRESTStorageProvider)
|
|
}
|
|
|
|
restStorageProviders := []RESTStorageProvider{
|
|
appsrest.RESTStorageProvider{},
|
|
authenticationrest.RESTStorageProvider{Authenticator: c.GenericConfig.Authenticator},
|
|
authorizationrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorizer},
|
|
autoscalingrest.RESTStorageProvider{},
|
|
batchrest.RESTStorageProvider{},
|
|
certificatesrest.RESTStorageProvider{},
|
|
extensionsrest.RESTStorageProvider{ResourceInterface: thirdparty.NewThirdPartyResourceServer(s, c.StorageFactory)},
|
|
policyrest.RESTStorageProvider{},
|
|
rbacrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorizer},
|
|
storagerest.RESTStorageProvider{},
|
|
}
|
|
m.InstallAPIs(c.Config.APIResourceConfigSource, restOptionsFactory, restStorageProviders...)
|
|
|
|
if c.Tunneler != nil {
|
|
m.installTunneler(c.Tunneler, corev1client.NewForConfigOrDie(c.GenericConfig.LoopbackClientConfig).Nodes())
|
|
}
|
|
|
|
return m, nil
|
|
}
|
|
|
|
func (m *Master) InstallLegacyAPI(c *Config, restOptionsGetter generic.RESTOptionsGetter, legacyRESTStorageProvider corerest.LegacyRESTStorageProvider) {
|
|
legacyRESTStorage, apiGroupInfo, err := legacyRESTStorageProvider.NewLegacyRESTStorage(restOptionsGetter)
|
|
if err != nil {
|
|
glog.Fatalf("Error building core storage: %v", err)
|
|
}
|
|
|
|
if c.EnableCoreControllers {
|
|
coreClient := coreclient.NewForConfigOrDie(c.GenericConfig.LoopbackClientConfig)
|
|
bootstrapController := c.NewBootstrapController(legacyRESTStorage, coreClient, coreClient)
|
|
if err := m.GenericAPIServer.AddPostStartHook("bootstrap-controller", bootstrapController.PostStartHook); err != nil {
|
|
glog.Fatalf("Error registering PostStartHook %q: %v", "bootstrap-controller", err)
|
|
}
|
|
}
|
|
|
|
if err := m.GenericAPIServer.InstallLegacyAPIGroup(genericapiserver.DefaultLegacyAPIPrefix, &apiGroupInfo); err != nil {
|
|
glog.Fatalf("Error in registering group versions: %v", err)
|
|
}
|
|
}
|
|
|
|
func (m *Master) installTunneler(nodeTunneler tunneler.Tunneler, nodeClient corev1client.NodeInterface) {
|
|
nodeTunneler.Run(nodeAddressProvider{nodeClient}.externalAddresses)
|
|
m.GenericAPIServer.AddHealthzChecks(healthz.NamedCheck("SSH Tunnel Check", tunneler.TunnelSyncHealthChecker(nodeTunneler)))
|
|
prometheus.NewGaugeFunc(prometheus.GaugeOpts{
|
|
Name: "apiserver_proxy_tunnel_sync_latency_secs",
|
|
Help: "The time since the last successful synchronization of the SSH tunnels for proxy requests.",
|
|
}, func() float64 { return float64(nodeTunneler.SecondsSinceSync()) })
|
|
}
|
|
|
|
// RESTStorageProvider is a factory type for REST storage.
|
|
type RESTStorageProvider interface {
|
|
GroupName() string
|
|
NewRESTStorage(apiResourceConfigSource genericapiserver.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool)
|
|
}
|
|
|
|
// InstallAPIs will install the APIs for the restStorageProviders if they are enabled.
|
|
func (m *Master) InstallAPIs(apiResourceConfigSource genericapiserver.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter, restStorageProviders ...RESTStorageProvider) {
|
|
apiGroupsInfo := []genericapiserver.APIGroupInfo{}
|
|
|
|
for _, restStorageBuilder := range restStorageProviders {
|
|
groupName := restStorageBuilder.GroupName()
|
|
if !apiResourceConfigSource.AnyResourcesForGroupEnabled(groupName) {
|
|
glog.V(1).Infof("Skipping disabled API group %q.", groupName)
|
|
continue
|
|
}
|
|
apiGroupInfo, enabled := restStorageBuilder.NewRESTStorage(apiResourceConfigSource, restOptionsGetter)
|
|
if !enabled {
|
|
glog.Warningf("Problem initializing API group %q, skipping.", groupName)
|
|
continue
|
|
}
|
|
glog.V(1).Infof("Enabling API group %q.", groupName)
|
|
|
|
if postHookProvider, ok := restStorageBuilder.(genericapiserver.PostStartHookProvider); ok {
|
|
name, hook, err := postHookProvider.PostStartHook()
|
|
if err != nil {
|
|
glog.Fatalf("Error building PostStartHook: %v", err)
|
|
}
|
|
if err := m.GenericAPIServer.AddPostStartHook(name, hook); err != nil {
|
|
glog.Fatalf("Error registering PostStartHook %q: %v", name, err)
|
|
}
|
|
}
|
|
|
|
apiGroupsInfo = append(apiGroupsInfo, apiGroupInfo)
|
|
}
|
|
|
|
for i := range apiGroupsInfo {
|
|
if err := m.GenericAPIServer.InstallAPIGroup(&apiGroupsInfo[i]); err != nil {
|
|
glog.Fatalf("Error in registering group versions: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
type restOptionsFactory struct {
|
|
deleteCollectionWorkers int
|
|
enableGarbageCollection bool
|
|
storageFactory genericapiserver.StorageFactory
|
|
storageDecorator generic.StorageDecorator
|
|
}
|
|
|
|
func (f *restOptionsFactory) GetRESTOptions(resource schema.GroupResource) (generic.RESTOptions, error) {
|
|
storageConfig, err := f.storageFactory.NewConfig(resource)
|
|
if err != nil {
|
|
return generic.RESTOptions{}, fmt.Errorf("Unable to find storage destination for %v, due to %v", resource, err.Error())
|
|
}
|
|
|
|
return generic.RESTOptions{
|
|
StorageConfig: storageConfig,
|
|
Decorator: f.storageDecorator,
|
|
DeleteCollectionWorkers: f.deleteCollectionWorkers,
|
|
EnableGarbageCollection: f.enableGarbageCollection,
|
|
ResourcePrefix: f.storageFactory.ResourcePrefix(resource),
|
|
}, nil
|
|
}
|
|
|
|
type nodeAddressProvider struct {
|
|
nodeClient corev1client.NodeInterface
|
|
}
|
|
|
|
func (n nodeAddressProvider) externalAddresses() ([]string, error) {
|
|
preferredAddressTypes := []apiv1.NodeAddressType{
|
|
apiv1.NodeExternalIP,
|
|
apiv1.NodeLegacyHostIP,
|
|
}
|
|
nodes, err := n.nodeClient.List(metav1.ListOptions{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
addrs := []string{}
|
|
for ix := range nodes.Items {
|
|
node := &nodes.Items[ix]
|
|
addr, err := nodeutil.GetPreferredNodeAddress(node, preferredAddressTypes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
addrs = append(addrs, addr)
|
|
}
|
|
return addrs, nil
|
|
}
|
|
|
|
func DefaultAPIResourceConfigSource() *genericapiserver.ResourceConfig {
|
|
ret := genericapiserver.NewResourceConfig()
|
|
ret.EnableVersions(
|
|
apiv1.SchemeGroupVersion,
|
|
extensionsapiv1beta1.SchemeGroupVersion,
|
|
batchapiv1.SchemeGroupVersion,
|
|
authenticationv1beta1.SchemeGroupVersion,
|
|
autoscalingapiv1.SchemeGroupVersion,
|
|
appsapi.SchemeGroupVersion,
|
|
policyapiv1beta1.SchemeGroupVersion,
|
|
rbacv1beta1.SchemeGroupVersion,
|
|
rbacapi.SchemeGroupVersion,
|
|
storageapiv1beta1.SchemeGroupVersion,
|
|
certificatesapiv1beta1.SchemeGroupVersion,
|
|
authorizationapiv1beta1.SchemeGroupVersion,
|
|
)
|
|
|
|
// all extensions resources except these are disabled by default
|
|
ret.EnableResources(
|
|
extensionsapiv1beta1.SchemeGroupVersion.WithResource("daemonsets"),
|
|
extensionsapiv1beta1.SchemeGroupVersion.WithResource("deployments"),
|
|
extensionsapiv1beta1.SchemeGroupVersion.WithResource("horizontalpodautoscalers"),
|
|
extensionsapiv1beta1.SchemeGroupVersion.WithResource("ingresses"),
|
|
extensionsapiv1beta1.SchemeGroupVersion.WithResource("networkpolicies"),
|
|
extensionsapiv1beta1.SchemeGroupVersion.WithResource("replicasets"),
|
|
extensionsapiv1beta1.SchemeGroupVersion.WithResource("thirdpartyresources"),
|
|
extensionsapiv1beta1.SchemeGroupVersion.WithResource("podsecuritypolicies"),
|
|
)
|
|
|
|
return ret
|
|
}
|