2015-11-16 21:46:00 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2014 The Kubernetes Authors .
2015-11-16 21:46:00 +00:00
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 genericapiserver
import (
"crypto/tls"
2015-12-22 21:22:28 +00:00
"fmt"
2015-11-16 21:46:00 +00:00
"net"
"net/http"
"net/http/pprof"
2016-02-03 03:05:21 +00:00
"os"
2016-01-09 02:30:33 +00:00
"path"
"regexp"
2016-02-03 01:29:17 +00:00
"sort"
2015-11-16 21:46:00 +00:00
"strconv"
"strings"
"time"
"k8s.io/kubernetes/pkg/admission"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/rest"
2015-12-22 21:22:28 +00:00
"k8s.io/kubernetes/pkg/api/unversioned"
2016-01-13 22:40:56 +00:00
"k8s.io/kubernetes/pkg/apimachinery"
2015-11-16 21:46:00 +00:00
"k8s.io/kubernetes/pkg/apiserver"
"k8s.io/kubernetes/pkg/auth/authenticator"
"k8s.io/kubernetes/pkg/auth/authorizer"
"k8s.io/kubernetes/pkg/auth/handlers"
2016-04-27 21:25:59 +00:00
"k8s.io/kubernetes/pkg/cloudprovider"
2016-05-06 15:15:36 +00:00
"k8s.io/kubernetes/pkg/genericapiserver/options"
2015-11-16 21:46:00 +00:00
"k8s.io/kubernetes/pkg/registry/generic"
2016-04-08 19:57:28 +00:00
"k8s.io/kubernetes/pkg/registry/generic/registry"
2015-11-16 21:46:00 +00:00
ipallocator "k8s.io/kubernetes/pkg/registry/service/ipallocator"
2015-12-21 05:27:49 +00:00
"k8s.io/kubernetes/pkg/runtime"
2015-11-16 21:46:00 +00:00
"k8s.io/kubernetes/pkg/ui"
"k8s.io/kubernetes/pkg/util"
2016-03-09 14:03:24 +00:00
"k8s.io/kubernetes/pkg/util/crypto"
2016-01-06 15:56:41 +00:00
utilnet "k8s.io/kubernetes/pkg/util/net"
2016-01-15 07:32:10 +00:00
utilruntime "k8s.io/kubernetes/pkg/util/runtime"
2015-11-16 21:46:00 +00:00
"k8s.io/kubernetes/pkg/util/sets"
2016-01-09 02:30:33 +00:00
systemd "github.com/coreos/go-systemd/daemon"
2015-11-16 21:46:00 +00:00
"github.com/emicklei/go-restful"
"github.com/emicklei/go-restful/swagger"
"github.com/golang/glog"
)
2016-05-06 15:15:36 +00:00
const globalTimeout = time . Minute
2015-11-16 21:46:00 +00:00
2015-12-22 21:22:28 +00:00
// Info about an API group.
type APIGroupInfo struct {
2016-01-13 22:40:56 +00:00
GroupMeta apimachinery . GroupMeta
2015-12-22 21:22:28 +00:00
// Info about the resources in this group. Its a map from version to resource to the storage.
VersionedResourcesStorageMap map [ string ] map [ string ] rest . Storage
// True, if this is the legacy group ("/v1").
IsLegacyGroup bool
// OptionsExternalVersion controls the APIVersion used for common objects in the
// schema like api.Status, api.DeleteOptions, and api.ListOptions. Other implementors may
// define a version "v1beta1" but want to use the Kubernetes "v1" internal objects.
// If nil, defaults to groupMeta.GroupVersion.
// TODO: Remove this when https://github.com/kubernetes/kubernetes/issues/19018 is fixed.
OptionsExternalVersion * unversioned . GroupVersion
2015-12-21 05:15:35 +00:00
// Scheme includes all of the types used by this group and how to convert between them (or
// to convert objects from outside of this group that are accepted in this API).
// TODO: replace with interfaces
Scheme * runtime . Scheme
// NegotiatedSerializer controls how this group encodes and decodes data
NegotiatedSerializer runtime . NegotiatedSerializer
// ParameterCodec performs conversions for query parameters passed to API calls
ParameterCodec runtime . ParameterCodec
2016-02-24 19:10:37 +00:00
// SubresourceGroupVersionKind contains the GroupVersionKind overrides for each subresource that is
// accessible from this API group version. The GroupVersionKind is that of the external version of
// the subresource. The key of this map should be the path of the subresource. The keys here should
// match the keys in the Storage map above for subresources.
SubresourceGroupVersionKind map [ string ] unversioned . GroupVersionKind
2015-12-22 21:22:28 +00:00
}
2015-11-16 21:46:00 +00:00
// Config is a structure used to configure a GenericAPIServer.
type Config struct {
2016-05-06 15:15:36 +00:00
// The storage factory for other objects
2016-03-16 14:17:04 +00:00
StorageFactory StorageFactory
2015-11-16 21:46:00 +00:00
// allow downstream consumers to disable the core controller loops
EnableLogsSupport bool
EnableUISupport bool
2016-03-15 23:41:07 +00:00
// Allow downstream consumers to disable swagger.
// This includes returning the generated swagger spec at /swaggerapi and swagger ui at /swagger-ui.
2015-11-16 21:46:00 +00:00
EnableSwaggerSupport bool
2016-03-15 23:41:07 +00:00
// Allow downstream consumers to disable swagger ui.
// Note that this is ignored if either EnableSwaggerSupport or EnableUISupport is false.
EnableSwaggerUI bool
2015-11-16 21:46:00 +00:00
// Allows api group versions or specific resources to be conditionally enabled/disabled.
2016-03-22 16:45:23 +00:00
APIResourceConfigSource APIResourceConfigSource
2015-11-16 21:46:00 +00:00
// allow downstream consumers to disable the index route
EnableIndex bool
EnableProfiling bool
EnableWatchCache bool
APIPrefix string
APIGroupPrefix string
CorsAllowedOriginList [ ] string
Authenticator authenticator . Request
// TODO(roberthbailey): Remove once the server no longer supports http basic auth.
SupportsBasicAuth bool
Authorizer authorizer . Authorizer
AdmissionControl admission . Interface
MasterServiceNamespace string
2016-05-25 21:20:41 +00:00
// TODO(ericchiang): Determine if policy escalation checks should be an admission controller.
AuthorizerRBACSuperUser string
2015-11-16 21:46:00 +00:00
// Map requests to contexts. Exported so downstream consumers can provider their own mappers
RequestContextMapper api . RequestContextMapper
2015-12-21 05:27:49 +00:00
// Required, the interface for serializing and converting objects to and from the wire
Serializer runtime . NegotiatedSerializer
2015-11-16 21:46:00 +00:00
// If specified, all web services will be registered into this container
RestfulContainer * restful . Container
// If specified, requests will be allocated a random timeout between this value, and twice this value.
// Note that it is up to the request handlers to ignore or honor this timeout. In seconds.
MinRequestTimeout int
// Number of masters running; all masters must be started with the
// same value for this field. (Numbers > 1 currently untested.)
MasterCount int
// The port on PublicAddress where a read-write server will be installed.
// Defaults to 6443 if not set.
ReadWritePort int
// ExternalHost is the host name to use for external (public internet) facing URLs (e.g. Swagger)
ExternalHost string
// PublicAddress is the IP address where members of the cluster (kubelet,
// kube-proxy, services, etc.) can reach the GenericAPIServer.
// If nil or 0.0.0.0, the host's default interface will be used.
PublicAddress net . IP
// Control the interval that pod, node IP, and node heath status caches
// expire.
CacheTimeout time . Duration
// The range of IPs to be assigned to services with type=ClusterIP or greater
ServiceClusterIPRange * net . IPNet
2016-02-04 05:55:04 +00:00
// The IP address for the GenericAPIServer service (must be inside ServiceClusterIPRange)
2015-11-16 21:46:00 +00:00
ServiceReadWriteIP net . IP
2016-02-04 05:55:04 +00:00
// Port for the apiserver service.
ServiceReadWritePort int
2015-11-16 21:46:00 +00:00
// The range of ports to be assigned to services with type=NodePort or greater
2016-01-06 15:56:41 +00:00
ServiceNodePortRange utilnet . PortRange
2015-11-16 21:46:00 +00:00
// Used to customize default proxy dial/tls options
ProxyDialer apiserver . ProxyDialerFunc
ProxyTLSClientConfig * tls . Config
// 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
KubernetesServiceNodePort int
}
// GenericAPIServer contains state for a Kubernetes cluster api server.
type GenericAPIServer struct {
// "Inputs", Copied from Config
ServiceClusterIPRange * net . IPNet
2016-01-06 15:56:41 +00:00
ServiceNodePortRange utilnet . PortRange
2015-11-16 21:46:00 +00:00
cacheTimeout time . Duration
MinRequestTimeout time . Duration
2016-03-22 16:45:23 +00:00
mux apiserver . Mux
MuxHelper * apiserver . MuxHelper
HandlerContainer * restful . Container
RootWebService * restful . WebService
enableLogsSupport bool
enableUISupport bool
enableSwaggerSupport bool
enableSwaggerUI bool
enableProfiling bool
enableWatchCache bool
APIPrefix string
APIGroupPrefix string
corsAllowedOriginList [ ] string
authenticator authenticator . Request
authorizer authorizer . Authorizer
AdmissionControl admission . Interface
MasterCount int
RequestContextMapper api . RequestContextMapper
2015-11-16 21:46:00 +00:00
2016-02-04 05:55:04 +00:00
// ExternalAddress is the address (hostname or IP and port) that should be used in
// external (public internet) URLs for this GenericAPIServer.
ExternalAddress string
2015-11-16 21:46:00 +00:00
// ClusterIP is the IP address of the GenericAPIServer within the cluster.
ClusterIP net . IP
PublicReadWritePort int
ServiceReadWriteIP net . IP
ServiceReadWritePort int
masterServices * util . Runner
ExtraServicePorts [ ] api . ServicePort
ExtraEndpointPorts [ ] api . EndpointPort
// storage contains the RESTful endpoints exposed by this GenericAPIServer
storage map [ string ] rest . Storage
2015-12-21 05:15:35 +00:00
// Serializer controls how common API objects not in a group/version prefix are serialized for this server.
// Individual APIGroups may define their own serializers.
Serializer runtime . NegotiatedSerializer
2015-11-16 21:46:00 +00:00
// "Outputs"
Handler http . Handler
InsecureHandler http . Handler
// Used for custom proxy dialing, and proxy TLS options
ProxyTransport http . RoundTripper
KubernetesServiceNodePort int
2016-02-03 01:29:17 +00:00
// Map storing information about all groups to be exposed in discovery response.
// The map is from name to the group.
apiGroupsForDiscovery map [ string ] unversioned . APIGroup
2015-11-16 21:46:00 +00:00
}
func ( s * GenericAPIServer ) StorageDecorator ( ) generic . StorageDecorator {
if s . enableWatchCache {
2016-04-08 19:57:28 +00:00
return registry . StorageWithCacher
2015-11-16 21:46:00 +00:00
}
return generic . UndecoratedStorage
}
// setDefaults fills in any fields not set that are required to have valid data.
func setDefaults ( c * Config ) {
if c . ServiceClusterIPRange == nil {
defaultNet := "10.0.0.0/24"
glog . Warningf ( "Network range for service cluster IPs is unspecified. Defaulting to %v." , defaultNet )
_ , serviceClusterIPRange , err := net . ParseCIDR ( defaultNet )
if err != nil {
glog . Fatalf ( "Unable to parse CIDR: %v" , err )
}
if size := ipallocator . RangeSize ( serviceClusterIPRange ) ; size < 8 {
glog . Fatalf ( "The service cluster IP range must be at least %d IP addresses" , 8 )
}
c . ServiceClusterIPRange = serviceClusterIPRange
}
if c . ServiceReadWriteIP == nil {
// Select the first valid IP from ServiceClusterIPRange to use as the GenericAPIServer service IP.
serviceReadWriteIP , err := ipallocator . GetIndexedIP ( c . ServiceClusterIPRange , 1 )
if err != nil {
glog . Fatalf ( "Failed to generate service read-write IP for GenericAPIServer service: %v" , err )
}
glog . V ( 4 ) . Infof ( "Setting GenericAPIServer service IP to %q (read-write)." , serviceReadWriteIP )
c . ServiceReadWriteIP = serviceReadWriteIP
}
2016-02-04 05:55:04 +00:00
if c . ServiceReadWritePort == 0 {
c . ServiceReadWritePort = 443
}
2015-11-16 21:46:00 +00:00
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
2016-01-06 15:56:41 +00:00
defaultServiceNodePortRange := utilnet . PortRange { Base : 30000 , Size : 2768 }
2015-11-16 21:46:00 +00:00
c . ServiceNodePortRange = defaultServiceNodePortRange
glog . Infof ( "Node port range unspecified. Defaulting to %v." , c . ServiceNodePortRange )
}
if c . MasterCount == 0 {
// Clearly, there will be at least one GenericAPIServer.
c . MasterCount = 1
}
if c . ReadWritePort == 0 {
c . ReadWritePort = 6443
}
if c . CacheTimeout == 0 {
c . CacheTimeout = 5 * time . Second
}
if c . RequestContextMapper == nil {
c . RequestContextMapper = api . NewRequestContextMapper ( )
}
2016-02-04 05:55:04 +00:00
if len ( c . ExternalHost ) == 0 && c . PublicAddress != nil {
hostAndPort := c . PublicAddress . String ( )
if c . ReadWritePort != 0 {
2016-06-03 18:15:54 +00:00
hostAndPort = net . JoinHostPort ( hostAndPort , strconv . Itoa ( c . ReadWritePort ) )
2016-02-04 05:55:04 +00:00
}
c . ExternalHost = hostAndPort
}
2015-11-16 21:46:00 +00:00
}
// New returns a new instance of GenericAPIServer from the given config.
// Certain config fields will be set to a default value if unset,
// including:
// ServiceClusterIPRange
// ServiceNodePortRange
// MasterCount
// ReadWritePort
// PublicAddress
// Public fields:
// Handler -- The returned GenericAPIServer has a field TopHandler which is an
// http.Handler which handles all the endpoints provided by the GenericAPIServer,
// including the API, the UI, and miscellaneous debugging endpoints. All
// these are subject to authorization and authentication.
// InsecureHandler -- an http.Handler which handles all the same
// endpoints as Handler, but no authorization and authentication is done.
// Public methods:
// HandleWithAuth -- Allows caller to add an http.Handler for an endpoint
// that uses the same authentication and authorization (if any is configured)
// as the GenericAPIServer's built-in endpoints.
// If the caller wants to add additional endpoints not using the GenericAPIServer's
// auth, then the caller should create a handler for those endpoints, which delegates the
// any unhandled paths to "Handler".
2016-02-03 22:26:11 +00:00
func New ( c * Config ) ( * GenericAPIServer , error ) {
2016-02-02 21:03:49 +00:00
if c . Serializer == nil {
2016-02-03 22:26:11 +00:00
return nil , fmt . Errorf ( "Genericapiserver.New() called with config.Serializer == nil" )
2016-02-02 21:03:49 +00:00
}
2015-11-16 21:46:00 +00:00
setDefaults ( c )
s := & GenericAPIServer {
2016-03-22 16:45:23 +00:00
ServiceClusterIPRange : c . ServiceClusterIPRange ,
ServiceNodePortRange : c . ServiceNodePortRange ,
RootWebService : new ( restful . WebService ) ,
enableLogsSupport : c . EnableLogsSupport ,
enableUISupport : c . EnableUISupport ,
enableSwaggerSupport : c . EnableSwaggerSupport ,
enableSwaggerUI : c . EnableSwaggerUI ,
enableProfiling : c . EnableProfiling ,
enableWatchCache : c . EnableWatchCache ,
APIPrefix : c . APIPrefix ,
APIGroupPrefix : c . APIGroupPrefix ,
corsAllowedOriginList : c . CorsAllowedOriginList ,
authenticator : c . Authenticator ,
authorizer : c . Authorizer ,
AdmissionControl : c . AdmissionControl ,
RequestContextMapper : c . RequestContextMapper ,
Serializer : c . Serializer ,
2015-11-16 21:46:00 +00:00
cacheTimeout : c . CacheTimeout ,
MinRequestTimeout : time . Duration ( c . MinRequestTimeout ) * time . Second ,
2016-02-04 05:55:04 +00:00
MasterCount : c . MasterCount ,
ExternalAddress : c . ExternalHost ,
ClusterIP : c . PublicAddress ,
PublicReadWritePort : c . ReadWritePort ,
ServiceReadWriteIP : c . ServiceReadWriteIP ,
ServiceReadWritePort : c . ServiceReadWritePort ,
2015-11-16 21:46:00 +00:00
ExtraServicePorts : c . ExtraServicePorts ,
ExtraEndpointPorts : c . ExtraEndpointPorts ,
KubernetesServiceNodePort : c . KubernetesServiceNodePort ,
2016-02-03 01:29:17 +00:00
apiGroupsForDiscovery : map [ string ] unversioned . APIGroup { } ,
2015-11-16 21:46:00 +00:00
}
var handlerContainer * restful . Container
if c . RestfulContainer != nil {
s . mux = c . RestfulContainer . ServeMux
handlerContainer = c . RestfulContainer
} else {
mux := http . NewServeMux ( )
s . mux = mux
2015-12-21 05:27:49 +00:00
handlerContainer = NewHandlerContainer ( mux , c . Serializer )
2015-11-16 21:46:00 +00:00
}
s . HandlerContainer = handlerContainer
// Use CurlyRouter to be able to use regular expressions in paths. Regular expressions are required in paths for example for proxy (where the path is proxy/{kind}/{name}/{*})
s . HandlerContainer . Router ( restful . CurlyRouter { } )
s . MuxHelper = & apiserver . MuxHelper { Mux : s . mux , RegisteredPaths : [ ] string { } }
s . init ( c )
2016-02-03 22:26:11 +00:00
return s , nil
2015-11-16 21:46:00 +00:00
}
func ( s * GenericAPIServer ) NewRequestInfoResolver ( ) * apiserver . RequestInfoResolver {
return & apiserver . RequestInfoResolver {
2016-03-23 23:45:24 +00:00
APIPrefixes : sets . NewString ( strings . Trim ( s . APIPrefix , "/" ) , strings . Trim ( s . APIGroupPrefix , "/" ) ) , // all possible API prefixes
GrouplessAPIPrefixes : sets . NewString ( strings . Trim ( s . APIPrefix , "/" ) ) , // APIPrefixes that won't have groups (legacy)
2015-11-16 21:46:00 +00:00
}
}
// HandleWithAuth adds an http.Handler for pattern to an http.ServeMux
// Applies the same authentication and authorization (if any is configured)
// to the request is used for the GenericAPIServer's built-in endpoints.
func ( s * GenericAPIServer ) HandleWithAuth ( pattern string , handler http . Handler ) {
// TODO: Add a way for plugged-in endpoints to translate their
// URLs into attributes that an Authorizer can understand, and have
// sensible policy defaults for plugged-in endpoints. This will be different
// for generic endpoints versus REST object endpoints.
// TODO: convert to go-restful
s . MuxHelper . Handle ( pattern , handler )
}
// HandleFuncWithAuth adds an http.Handler for pattern to an http.ServeMux
// Applies the same authentication and authorization (if any is configured)
// to the request is used for the GenericAPIServer's built-in endpoints.
func ( s * GenericAPIServer ) HandleFuncWithAuth ( pattern string , handler func ( http . ResponseWriter , * http . Request ) ) {
// TODO: convert to go-restful
s . MuxHelper . HandleFunc ( pattern , handler )
}
2015-12-21 05:27:49 +00:00
func NewHandlerContainer ( mux * http . ServeMux , s runtime . NegotiatedSerializer ) * restful . Container {
2015-11-16 21:46:00 +00:00
container := restful . NewContainer ( )
container . ServeMux = mux
2015-12-21 05:27:49 +00:00
apiserver . InstallRecoverHandler ( s , container )
2015-11-16 21:46:00 +00:00
return container
}
// init initializes GenericAPIServer.
func ( s * GenericAPIServer ) init ( c * Config ) {
if c . ProxyDialer != nil || c . ProxyTLSClientConfig != nil {
2016-01-06 15:56:41 +00:00
s . ProxyTransport = utilnet . SetTransportDefaults ( & http . Transport {
2015-11-16 21:46:00 +00:00
Dial : c . ProxyDialer ,
TLSClientConfig : c . ProxyTLSClientConfig ,
} )
}
// Register root handler.
// We do not register this using restful Webservice since we do not want to surface this in api docs.
// Allow GenericAPIServer to be embedded in contexts which already have something registered at the root
if c . EnableIndex {
s . mux . HandleFunc ( "/" , apiserver . IndexHandler ( s . HandlerContainer , s . MuxHelper ) )
}
if c . EnableLogsSupport {
apiserver . InstallLogsSupport ( s . MuxHelper )
}
if c . EnableUISupport {
2016-03-15 23:41:07 +00:00
ui . InstallSupport ( s . MuxHelper , s . enableSwaggerSupport && s . enableSwaggerUI )
2015-11-16 21:46:00 +00:00
}
if c . EnableProfiling {
s . mux . HandleFunc ( "/debug/pprof/" , pprof . Index )
s . mux . HandleFunc ( "/debug/pprof/profile" , pprof . Profile )
s . mux . HandleFunc ( "/debug/pprof/symbol" , pprof . Symbol )
}
2016-04-27 01:51:37 +00:00
apiserver . InstallVersionHandler ( s . MuxHelper , s . HandlerContainer )
2015-11-16 21:46:00 +00:00
handler := http . Handler ( s . mux . ( * http . ServeMux ) )
// TODO: handle CORS and auth using go-restful
// See github.com/emicklei/go-restful/blob/GenericAPIServer/examples/restful-CORS-filter.go, and
// github.com/emicklei/go-restful/blob/GenericAPIServer/examples/restful-basic-authentication.go
if len ( c . CorsAllowedOriginList ) > 0 {
allowedOriginRegexps , err := util . CompileRegexps ( c . CorsAllowedOriginList )
if err != nil {
glog . Fatalf ( "Invalid CORS allowed origin, --cors-allowed-origins flag was set to %v - %v" , strings . Join ( c . CorsAllowedOriginList , "," ) , err )
}
handler = apiserver . CORS ( handler , allowedOriginRegexps , nil , nil , "true" )
}
s . InsecureHandler = handler
attributeGetter := apiserver . NewRequestAttributeGetter ( s . RequestContextMapper , s . NewRequestInfoResolver ( ) )
handler = apiserver . WithAuthorizationCheck ( handler , attributeGetter , s . authorizer )
2016-04-26 13:31:43 +00:00
handler = apiserver . WithImpersonation ( handler , s . RequestContextMapper , s . authorizer )
2015-11-16 21:46:00 +00:00
// Install Authenticator
if c . Authenticator != nil {
authenticatedHandler , err := handlers . NewRequestAuthenticator ( s . RequestContextMapper , c . Authenticator , handlers . Unauthorized ( c . SupportsBasicAuth ) , handler )
if err != nil {
glog . Fatalf ( "Could not initialize authenticator: %v" , err )
}
handler = authenticatedHandler
}
// TODO: Make this optional? Consumers of GenericAPIServer depend on this currently.
s . Handler = handler
// After all wrapping is done, put a context filter around both handlers
if handler , err := api . NewRequestContextFilter ( s . RequestContextMapper , s . Handler ) ; err != nil {
glog . Fatalf ( "Could not initialize request context filter: %v" , err )
} else {
s . Handler = handler
}
if handler , err := api . NewRequestContextFilter ( s . RequestContextMapper , s . InsecureHandler ) ; err != nil {
glog . Fatalf ( "Could not initialize request context filter: %v" , err )
} else {
s . InsecureHandler = handler
}
2016-02-03 01:29:17 +00:00
s . installGroupsDiscoveryHandler ( )
2015-11-16 21:46:00 +00:00
}
2016-03-28 21:03:42 +00:00
// Exposes the given group versions in API. Helper method to install multiple group versions at once.
2015-12-22 21:22:28 +00:00
func ( s * GenericAPIServer ) InstallAPIGroups ( groupsInfo [ ] APIGroupInfo ) error {
for _ , apiGroupInfo := range groupsInfo {
2016-03-28 21:03:42 +00:00
if err := s . InstallAPIGroup ( & apiGroupInfo ) ; err != nil {
2015-12-22 21:22:28 +00:00
return err
}
}
return nil
}
2016-02-03 01:29:17 +00:00
// Installs handler at /apis to list all group versions for discovery
func ( s * GenericAPIServer ) installGroupsDiscoveryHandler ( ) {
2016-02-04 05:55:04 +00:00
apiserver . AddApisWebService ( s . Serializer , s . HandlerContainer , s . APIGroupPrefix , func ( req * restful . Request ) [ ] unversioned . APIGroup {
2016-02-03 01:29:17 +00:00
// Return the list of supported groups in sorted order (to have a deterministic order).
groups := [ ] unversioned . APIGroup { }
groupNames := make ( [ ] string , len ( s . apiGroupsForDiscovery ) )
var i int = 0
for groupName := range s . apiGroupsForDiscovery {
groupNames [ i ] = groupName
i ++
}
sort . Strings ( groupNames )
for _ , groupName := range groupNames {
2016-02-04 05:55:04 +00:00
apiGroup := s . apiGroupsForDiscovery [ groupName ]
// Add ServerAddressByClientCIDRs.
apiGroup . ServerAddressByClientCIDRs = s . getServerAddressByClientCIDRs ( req . Request )
groups = append ( groups , apiGroup )
2016-02-03 01:29:17 +00:00
}
return groups
} )
}
2016-04-15 01:01:31 +00:00
// TODO: Longer term we should read this from some config store, rather than a flag.
2016-05-06 15:15:36 +00:00
func verifyClusterIPFlags ( options * options . ServerRunOptions ) {
2016-04-15 01:01:31 +00:00
if options . ServiceClusterIPRange . IP == nil {
glog . Fatal ( "No --service-cluster-ip-range specified" )
}
var ones , bits = options . ServiceClusterIPRange . Mask . Size ( )
if bits - ones > 20 {
glog . Fatal ( "Specified --service-cluster-ip-range is too large" )
}
}
2016-05-06 15:15:36 +00:00
func NewConfig ( options * options . ServerRunOptions ) * Config {
2016-04-15 01:01:31 +00:00
return & Config {
APIGroupPrefix : options . APIGroupPrefix ,
APIPrefix : options . APIPrefix ,
CorsAllowedOriginList : options . CorsAllowedOriginList ,
EnableIndex : true ,
EnableLogsSupport : options . EnableLogsSupport ,
EnableProfiling : options . EnableProfiling ,
EnableSwaggerSupport : true ,
EnableSwaggerUI : options . EnableSwaggerUI ,
EnableUISupport : true ,
EnableWatchCache : options . EnableWatchCache ,
ExternalHost : options . ExternalHost ,
KubernetesServiceNodePort : options . KubernetesServiceNodePort ,
MasterCount : options . MasterCount ,
MinRequestTimeout : options . MinRequestTimeout ,
PublicAddress : options . AdvertiseAddress ,
ReadWritePort : options . SecurePort ,
ServiceClusterIPRange : & options . ServiceClusterIPRange ,
ServiceNodePortRange : options . ServiceNodePortRange ,
}
}
2016-05-06 15:15:36 +00:00
func verifyServiceNodePort ( options * options . ServerRunOptions ) {
2016-06-23 09:39:53 +00:00
if options . KubernetesServiceNodePort < 0 || options . KubernetesServiceNodePort > 65535 {
glog . Fatalf ( "--kubernetes-service-node-port %v must be between 0 and 65535, inclusive. If 0, the Kubernetes master service will be of type ClusterIP." , options . KubernetesServiceNodePort )
}
2016-04-15 01:01:31 +00:00
if options . KubernetesServiceNodePort > 0 && ! options . ServiceNodePortRange . Contains ( options . KubernetesServiceNodePort ) {
glog . Fatalf ( "Kubernetes service port range %v doesn't contain %v" , options . ServiceNodePortRange , ( options . KubernetesServiceNodePort ) )
}
}
2016-05-06 15:15:36 +00:00
func verifyEtcdServersList ( options * options . ServerRunOptions ) {
2016-04-27 21:25:59 +00:00
if len ( options . StorageConfig . ServerList ) == 0 {
glog . Fatalf ( "--etcd-servers must be specified" )
}
}
2016-06-23 09:39:53 +00:00
func verifySecurePort ( options * options . ServerRunOptions ) {
if options . SecurePort < 0 || options . SecurePort > 65535 {
glog . Fatalf ( "--secure-port %v must be between 0 and 65535, inclusive. 0 for turning off secure port." , options . SecurePort )
}
}
// TODO: Allow 0 to turn off insecure port.
func verifyInsecurePort ( options * options . ServerRunOptions ) {
if options . InsecurePort < 1 || options . InsecurePort > 65535 {
glog . Fatalf ( "--insecure-port %v must be between 1 and 65535, inclusive." , options . InsecurePort )
}
}
2016-05-06 15:15:36 +00:00
func ValidateRunOptions ( options * options . ServerRunOptions ) {
2016-04-15 01:01:31 +00:00
verifyClusterIPFlags ( options )
verifyServiceNodePort ( options )
2016-04-27 21:25:59 +00:00
verifyEtcdServersList ( options )
2016-06-23 09:39:53 +00:00
verifySecurePort ( options )
verifyInsecurePort ( options )
2016-04-15 01:01:31 +00:00
}
2016-05-06 15:15:36 +00:00
func DefaultAndValidateRunOptions ( options * options . ServerRunOptions ) {
2016-04-15 01:01:31 +00:00
ValidateRunOptions ( options )
2016-04-27 21:25:59 +00:00
2016-04-15 01:01:31 +00:00
// If advertise-address is not specified, use bind-address. If bind-address
// is not usable (unset, 0.0.0.0, or loopback), we will use the host's default
// interface as valid public addr for master (see: util/net#ValidPublicAddrForMaster)
if options . AdvertiseAddress == nil || options . AdvertiseAddress . IsUnspecified ( ) {
hostIP , err := utilnet . ChooseBindAddress ( options . BindAddress )
if err != nil {
glog . Fatalf ( "Unable to find suitable network address.error='%v' . " +
"Try to set the AdvertiseAddress directly or provide a valid BindAddress to fix this." , err )
}
options . AdvertiseAddress = hostIP
}
glog . Infof ( "Will report %v as public IP address." , options . AdvertiseAddress )
2016-04-27 21:25:59 +00:00
// Set default value for ExternalHost if not specified.
if len ( options . ExternalHost ) == 0 {
// TODO: extend for other providers
if options . CloudProvider == "gce" {
cloud , err := cloudprovider . InitCloudProvider ( options . CloudProvider , options . CloudConfigFile )
if err != nil {
glog . Fatalf ( "Cloud provider could not be initialized: %v" , err )
}
instances , supported := cloud . Instances ( )
if ! supported {
glog . Fatalf ( "GCE cloud provider has no instances. this shouldn't happen. exiting." )
}
name , err := os . Hostname ( )
if err != nil {
glog . Fatalf ( "Failed to get hostname: %v" , err )
}
addrs , err := instances . NodeAddresses ( name )
if err != nil {
glog . Warningf ( "Unable to obtain external host address from cloud provider: %v" , err )
} else {
for _ , addr := range addrs {
if addr . Type == api . NodeExternalIP {
options . ExternalHost = addr . Address
}
}
}
}
}
2016-04-15 01:01:31 +00:00
}
2016-05-06 15:15:36 +00:00
func ( s * GenericAPIServer ) Run ( options * options . ServerRunOptions ) {
2016-03-28 21:03:42 +00:00
if s . enableSwaggerSupport {
s . InstallSwaggerAPI ( )
}
// We serve on 2 ports. See docs/accessing_the_api.md
2016-01-09 02:30:33 +00:00
secureLocation := ""
if options . SecurePort != 0 {
secureLocation = net . JoinHostPort ( options . BindAddress . String ( ) , strconv . Itoa ( options . SecurePort ) )
}
insecureLocation := net . JoinHostPort ( options . InsecureBindAddress . String ( ) , strconv . Itoa ( options . InsecurePort ) )
var sem chan bool
if options . MaxRequestsInFlight > 0 {
sem = make ( chan bool , options . MaxRequestsInFlight )
}
longRunningRE := regexp . MustCompile ( options . LongRunningRequestRE )
2016-02-18 19:56:05 +00:00
longRunningRequestCheck := apiserver . BasicLongRunningRequestCheck ( longRunningRE , map [ string ] string { "watch" : "true" } )
2016-01-09 02:30:33 +00:00
longRunningTimeout := func ( req * http . Request ) ( <- chan time . Time , string ) {
// TODO unify this with apiserver.MaxInFlightLimit
2016-02-18 19:56:05 +00:00
if longRunningRequestCheck ( req ) {
2016-01-09 02:30:33 +00:00
return nil , ""
}
2016-03-15 02:18:55 +00:00
return time . After ( globalTimeout ) , ""
2016-01-09 02:30:33 +00:00
}
if secureLocation != "" {
handler := apiserver . TimeoutHandler ( s . Handler , longRunningTimeout )
secureServer := & http . Server {
Addr : secureLocation ,
2016-02-18 19:56:05 +00:00
Handler : apiserver . MaxInFlightLimit ( sem , longRunningRequestCheck , apiserver . RecoverPanics ( handler ) ) ,
2016-01-09 02:30:33 +00:00
MaxHeaderBytes : 1 << 20 ,
TLSConfig : & tls . Config {
2016-05-24 14:18:28 +00:00
// Can't use SSLv3 because of POODLE and BEAST
// Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher
// Can't use TLSv1.1 because of RC4 cipher usage
MinVersion : tls . VersionTLS12 ,
2016-01-09 02:30:33 +00:00
} ,
}
if len ( options . ClientCAFile ) > 0 {
2016-03-09 14:03:24 +00:00
clientCAs , err := crypto . CertPoolFromFile ( options . ClientCAFile )
2016-01-09 02:30:33 +00:00
if err != nil {
glog . Fatalf ( "Unable to load client CA file: %v" , err )
}
// Populate PeerCertificates in requests, but don't reject connections without certificates
// This allows certificates to be validated by authenticators, while still allowing other auth types
secureServer . TLSConfig . ClientAuth = tls . RequestClientCert
// Specify allowed CAs for client certificates
secureServer . TLSConfig . ClientCAs = clientCAs
}
glog . Infof ( "Serving securely on %s" , secureLocation )
if options . TLSCertFile == "" && options . TLSPrivateKeyFile == "" {
options . TLSCertFile = path . Join ( options . CertDirectory , "apiserver.crt" )
options . TLSPrivateKeyFile = path . Join ( options . CertDirectory , "apiserver.key" )
// TODO (cjcullen): Is ClusterIP the right address to sign a cert with?
alternateIPs := [ ] net . IP { s . ServiceReadWriteIP }
alternateDNS := [ ] string { "kubernetes.default.svc" , "kubernetes.default" , "kubernetes" }
// It would be nice to set a fqdn subject alt name, but only the kubelets know, the apiserver is clueless
// alternateDNS = append(alternateDNS, "kubernetes.default.svc.CLUSTER.DNS.NAME")
2016-05-24 03:25:12 +00:00
if crypto . ShouldGenSelfSignedCerts ( options . TLSCertFile , options . TLSPrivateKeyFile ) {
2016-02-03 03:05:21 +00:00
if err := crypto . GenerateSelfSignedCert ( s . ClusterIP . String ( ) , options . TLSCertFile , options . TLSPrivateKeyFile , alternateIPs , alternateDNS ) ; err != nil {
glog . Errorf ( "Unable to generate self signed cert: %v" , err )
} else {
2016-05-05 21:56:47 +00:00
glog . Infof ( "Using self-signed cert (%s, %s)" , options . TLSCertFile , options . TLSPrivateKeyFile )
2016-02-03 03:05:21 +00:00
}
2016-01-09 02:30:33 +00:00
}
}
go func ( ) {
2016-01-15 07:32:10 +00:00
defer utilruntime . HandleCrash ( )
2016-01-09 02:30:33 +00:00
for {
// err == systemd.SdNotifyNoSocket when not running on a systemd system
if err := systemd . SdNotify ( "READY=1\n" ) ; err != nil && err != systemd . SdNotifyNoSocket {
glog . Errorf ( "Unable to send systemd daemon successful start message: %v\n" , err )
}
if err := secureServer . ListenAndServeTLS ( options . TLSCertFile , options . TLSPrivateKeyFile ) ; err != nil {
glog . Errorf ( "Unable to listen for secure (%v); will try again." , err )
}
time . Sleep ( 15 * time . Second )
}
} ( )
} else {
// err == systemd.SdNotifyNoSocket when not running on a systemd system
if err := systemd . SdNotify ( "READY=1\n" ) ; err != nil && err != systemd . SdNotifyNoSocket {
glog . Errorf ( "Unable to send systemd daemon successful start message: %v\n" , err )
}
}
handler := apiserver . TimeoutHandler ( s . InsecureHandler , longRunningTimeout )
http := & http . Server {
Addr : insecureLocation ,
Handler : apiserver . RecoverPanics ( handler ) ,
MaxHeaderBytes : 1 << 20 ,
}
glog . Infof ( "Serving insecurely on %s" , insecureLocation )
glog . Fatal ( http . ListenAndServe ( ) )
}
2016-03-28 21:03:42 +00:00
// Exposes the given group version in API.
func ( s * GenericAPIServer ) InstallAPIGroup ( apiGroupInfo * APIGroupInfo ) error {
2015-12-22 21:22:28 +00:00
apiPrefix := s . APIGroupPrefix
if apiGroupInfo . IsLegacyGroup {
apiPrefix = s . APIPrefix
}
// Install REST handlers for all the versions in this group.
apiVersions := [ ] string { }
for _ , groupVersion := range apiGroupInfo . GroupMeta . GroupVersions {
apiVersions = append ( apiVersions , groupVersion . Version )
apiGroupVersion , err := s . getAPIGroupVersion ( apiGroupInfo , groupVersion , apiPrefix )
if err != nil {
return err
}
if apiGroupInfo . OptionsExternalVersion != nil {
apiGroupVersion . OptionsExternalVersion = apiGroupInfo . OptionsExternalVersion
}
if err := apiGroupVersion . InstallREST ( s . HandlerContainer ) ; err != nil {
return fmt . Errorf ( "Unable to setup API %v: %v" , apiGroupInfo , err )
}
}
// Install the version handler.
if apiGroupInfo . IsLegacyGroup {
// Add a handler at /api to enumerate the supported api versions.
2016-02-04 05:55:04 +00:00
apiserver . AddApiWebService ( s . Serializer , s . HandlerContainer , apiPrefix , func ( req * restful . Request ) * unversioned . APIVersions {
apiVersionsForDiscovery := unversioned . APIVersions {
ServerAddressByClientCIDRs : s . getServerAddressByClientCIDRs ( req . Request ) ,
Versions : apiVersions ,
}
return & apiVersionsForDiscovery
} )
2015-12-22 21:22:28 +00:00
} else {
2016-02-01 20:25:14 +00:00
// Do not register empty group or empty version. Doing so claims /apis/ for the wrong entity to be returned.
// Catching these here places the error much closer to its origin
if len ( apiGroupInfo . GroupMeta . GroupVersion . Group ) == 0 {
return fmt . Errorf ( "cannot register handler with an empty group for %#v" , * apiGroupInfo )
}
if len ( apiGroupInfo . GroupMeta . GroupVersion . Version ) == 0 {
return fmt . Errorf ( "cannot register handler with an empty version for %#v" , * apiGroupInfo )
}
2015-12-22 21:22:28 +00:00
// Add a handler at /apis/<groupName> to enumerate all versions supported by this group.
apiVersionsForDiscovery := [ ] unversioned . GroupVersionForDiscovery { }
for _ , groupVersion := range apiGroupInfo . GroupMeta . GroupVersions {
apiVersionsForDiscovery = append ( apiVersionsForDiscovery , unversioned . GroupVersionForDiscovery {
GroupVersion : groupVersion . String ( ) ,
Version : groupVersion . Version ,
} )
}
preferedVersionForDiscovery := unversioned . GroupVersionForDiscovery {
GroupVersion : apiGroupInfo . GroupMeta . GroupVersion . String ( ) ,
Version : apiGroupInfo . GroupMeta . GroupVersion . Version ,
}
apiGroup := unversioned . APIGroup {
Name : apiGroupInfo . GroupMeta . GroupVersion . Group ,
Versions : apiVersionsForDiscovery ,
PreferredVersion : preferedVersionForDiscovery ,
}
2016-02-03 01:29:17 +00:00
s . AddAPIGroupForDiscovery ( apiGroup )
2015-12-21 05:27:49 +00:00
apiserver . AddGroupWebService ( s . Serializer , s . HandlerContainer , apiPrefix + "/" + apiGroup . Name , apiGroup )
2015-12-22 21:22:28 +00:00
}
2015-12-21 05:27:49 +00:00
apiserver . InstallServiceErrorHandler ( s . Serializer , s . HandlerContainer , s . NewRequestInfoResolver ( ) , apiVersions )
2015-12-22 21:22:28 +00:00
return nil
}
2016-02-03 01:29:17 +00:00
func ( s * GenericAPIServer ) AddAPIGroupForDiscovery ( apiGroup unversioned . APIGroup ) {
s . apiGroupsForDiscovery [ apiGroup . Name ] = apiGroup
}
func ( s * GenericAPIServer ) RemoveAPIGroupForDiscovery ( groupName string ) {
delete ( s . apiGroupsForDiscovery , groupName )
}
2016-02-04 05:55:04 +00:00
func ( s * GenericAPIServer ) getServerAddressByClientCIDRs ( req * http . Request ) [ ] unversioned . ServerAddressByClientCIDR {
addressCIDRMap := [ ] unversioned . ServerAddressByClientCIDR {
{
ClientCIDR : "0.0.0.0/0" ,
ServerAddress : s . ExternalAddress ,
} ,
}
// Add internal CIDR if the request came from internal IP.
clientIP := utilnet . GetClientIP ( req )
clusterCIDR := s . ServiceClusterIPRange
if clusterCIDR . Contains ( clientIP ) {
addressCIDRMap = append ( addressCIDRMap , unversioned . ServerAddressByClientCIDR {
ClientCIDR : clusterCIDR . String ( ) ,
ServerAddress : net . JoinHostPort ( s . ServiceReadWriteIP . String ( ) , strconv . Itoa ( s . ServiceReadWritePort ) ) ,
} )
}
return addressCIDRMap
}
2015-12-22 21:22:28 +00:00
func ( s * GenericAPIServer ) getAPIGroupVersion ( apiGroupInfo * APIGroupInfo , groupVersion unversioned . GroupVersion , apiPrefix string ) ( * apiserver . APIGroupVersion , error ) {
storage := make ( map [ string ] rest . Storage )
for k , v := range apiGroupInfo . VersionedResourcesStorageMap [ groupVersion . Version ] {
storage [ strings . ToLower ( k ) ] = v
}
version , err := s . newAPIGroupVersion ( apiGroupInfo . GroupMeta , groupVersion )
version . Root = apiPrefix
version . Storage = storage
2015-12-21 05:27:49 +00:00
version . ParameterCodec = apiGroupInfo . ParameterCodec
version . Serializer = apiGroupInfo . NegotiatedSerializer
version . Creater = apiGroupInfo . Scheme
version . Convertor = apiGroupInfo . Scheme
2016-04-29 01:21:35 +00:00
version . Copier = apiGroupInfo . Scheme
2015-12-21 05:27:49 +00:00
version . Typer = apiGroupInfo . Scheme
2016-02-24 19:10:37 +00:00
version . SubresourceGroupVersionKind = apiGroupInfo . SubresourceGroupVersionKind
2015-12-22 21:22:28 +00:00
return version , err
}
2016-01-13 22:40:56 +00:00
func ( s * GenericAPIServer ) newAPIGroupVersion ( groupMeta apimachinery . GroupMeta , groupVersion unversioned . GroupVersion ) ( * apiserver . APIGroupVersion , error ) {
2015-12-22 21:22:28 +00:00
return & apiserver . APIGroupVersion {
RequestInfoResolver : s . NewRequestInfoResolver ( ) ,
GroupVersion : groupVersion ,
Linker : groupMeta . SelfLinker ,
Mapper : groupMeta . RESTMapper ,
Admit : s . AdmissionControl ,
Context : s . RequestContextMapper ,
MinRequestTimeout : s . MinRequestTimeout ,
} , nil
}
2015-11-16 21:46:00 +00:00
// InstallSwaggerAPI installs the /swaggerapi/ endpoint to allow schema discovery
2016-02-04 06:39:18 +00:00
// and traversal. It is optional to allow consumers of the Kubernetes GenericAPIServer to
2015-11-16 21:46:00 +00:00
// register their own web services into the Kubernetes mux prior to initialization
// of swagger, so that other resource types show up in the documentation.
func ( s * GenericAPIServer ) InstallSwaggerAPI ( ) {
2016-02-04 05:55:04 +00:00
hostAndPort := s . ExternalAddress
2015-11-16 21:46:00 +00:00
protocol := "https://"
webServicesUrl := protocol + hostAndPort
// Enable swagger UI and discovery API
swaggerConfig := swagger . Config {
WebServicesUrl : webServicesUrl ,
WebServices : s . HandlerContainer . RegisteredWebServices ( ) ,
ApiPath : "/swaggerapi/" ,
SwaggerPath : "/swaggerui/" ,
SwaggerFilePath : "/swagger-ui/" ,
}
swagger . RegisterSwaggerService ( swaggerConfig , s . HandlerContainer )
}