Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding hostname to groups discovery information #20626

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/swagger-spec/api.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"swaggerVersion": "1.2",
"apiVersion": "",
"basePath": "https://10.10.10.10:6443",
"basePath": "https://10.10.10.10:443",
"resourcePath": "/api",
"apis": [
{
Expand Down
2 changes: 1 addition & 1 deletion api/swagger-spec/apis.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"swaggerVersion": "1.2",
"apiVersion": "",
"basePath": "https://10.10.10.10:6443",
"basePath": "https://10.10.10.10:443",
"resourcePath": "/apis",
"apis": [
{
Expand Down
2 changes: 1 addition & 1 deletion api/swagger-spec/extensions.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"swaggerVersion": "1.2",
"apiVersion": "",
"basePath": "https://10.10.10.10:6443",
"basePath": "https://10.10.10.10:443",
"resourcePath": "/apis/extensions",
"apis": [
{
Expand Down
2 changes: 1 addition & 1 deletion api/swagger-spec/extensions_v1beta1.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"swaggerVersion": "1.2",
"apiVersion": "extensions/v1beta1",
"basePath": "https://10.10.10.10:6443",
"basePath": "https://10.10.10.10:443",
"resourcePath": "/apis/extensions/v1beta1",
"apis": [
{
Expand Down
2 changes: 1 addition & 1 deletion api/swagger-spec/v1.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"swaggerVersion": "1.2",
"apiVersion": "v1",
"basePath": "https://10.10.10.10:6443",
"basePath": "https://10.10.10.10:443",
"resourcePath": "/api/v1",
"apis": [
{
Expand Down
2 changes: 1 addition & 1 deletion api/swagger-spec/version.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"swaggerVersion": "1.2",
"apiVersion": "",
"basePath": "https://10.10.10.10:6443",
"basePath": "https://10.10.10.10:443",
"resourcePath": "/version",
"apis": [
{
Expand Down
25 changes: 25 additions & 0 deletions pkg/api/unversioned/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,14 @@ type APIVersions struct {
TypeMeta `json:",inline"`
// versions are the api versions that are available.
Versions []string `json:"versions"`
// a map of client CIDR to server address that is serving this group.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To help clients reach servers in the most network-efficient way possible, a map of client CIDR to a server address believed reachable and ideal from that CIDR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added

// This is to help clients reach servers in the most network-efficient way possible.
// Clients can use the appropriate server address as per the CIDR that they match.
// In case of multiple matches, clients should use the longest matching CIDR.
// The server returns only those CIDRs that it thinks that the client can match.
// For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP.
// Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.
ServerAddressByClientCIDRs []ServerAddressByClientCIDR `json:"serverAddressByClientCIDRs"`
}

// APIGroupList is a list of APIGroup, to allow clients to discover the API at
Expand All @@ -329,6 +337,23 @@ type APIGroup struct {
// preferredVersion is the version preferred by the API server, which
// probably is the storage version.
PreferredVersion GroupVersionForDiscovery `json:"preferredVersion,omitempty"`
// a map of client CIDR to server address that is serving this group.
// This is to help clients reach servers in the most network-efficient way possible.
// Clients can use the appropriate server address as per the CIDR that they match.
// In case of multiple matches, clients should use the longest matching CIDR.
// The server returns only those CIDRs that it thinks that the client can match.
// For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP.
// Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.
ServerAddressByClientCIDRs []ServerAddressByClientCIDR `json:"serverAddressByClientCIDRs"`
}

// ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match.
type ServerAddressByClientCIDR struct {
// The CIDR with which clients can match their IP to figure out the server address that they should use.
ClientCIDR string `json:"clientCIDR"`
// Address of this server, suitable for a client that matches the above CIDR.
// This can be a hostname, hostname:port, IP or IP:port.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will we ever omit the port? I think we have to include it all the time?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we can omit it if its 80?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't it always be https? so 443 :) But yeah, I guess it's not a big deal.

ServerAddress string `json:"serverAddress"`
}

// GroupVersion contains the "group/version" and "version" string of a version.
Expand Down
24 changes: 18 additions & 6 deletions pkg/api/unversioned/types_swagger_doc_generated.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ package unversioned

// AUTO-GENERATED FUNCTIONS START HERE
var map_APIGroup = map[string]string{
"": "APIGroup contains the name, the supported versions, and the preferred version of a group.",
"name": "name is the name of the group.",
"versions": "versions are the versions supported in this group.",
"preferredVersion": "preferredVersion is the version preferred by the API server, which probably is the storage version.",
"": "APIGroup contains the name, the supported versions, and the preferred version of a group.",
"name": "name is the name of the group.",
"versions": "versions are the versions supported in this group.",
"preferredVersion": "preferredVersion is the version preferred by the API server, which probably is the storage version.",
"serverAddressByClientCIDRs": "a map of client CIDR to server address that is serving this group. This is to help clients reach servers in the most network-efficient way possible. Clients can use the appropriate server address as per the CIDR that they match. In case of multiple matches, clients should use the longest matching CIDR. The server returns only those CIDRs that it thinks that the client can match. For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.",
}

func (APIGroup) SwaggerDoc() map[string]string {
Expand Down Expand Up @@ -69,8 +70,9 @@ func (APIResourceList) SwaggerDoc() map[string]string {
}

var map_APIVersions = map[string]string{
"": "APIVersions lists the versions that are available, to allow clients to discover the API at /api, which is the root path of the legacy v1 API.",
"versions": "versions are the api versions that are available.",
"": "APIVersions lists the versions that are available, to allow clients to discover the API at /api, which is the root path of the legacy v1 API.",
"versions": "versions are the api versions that are available.",
"serverAddressByClientCIDRs": "a map of client CIDR to server address that is serving this group. This is to help clients reach servers in the most network-efficient way possible. Clients can use the appropriate server address as per the CIDR that they match. In case of multiple matches, clients should use the longest matching CIDR. The server returns only those CIDRs that it thinks that the client can match. For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.",
}

func (APIVersions) SwaggerDoc() map[string]string {
Expand Down Expand Up @@ -145,6 +147,16 @@ func (RootPaths) SwaggerDoc() map[string]string {
return map_RootPaths
}

var map_ServerAddressByClientCIDR = map[string]string{
"": "ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match.",
"clientCIDR": "The CIDR with which clients can match their IP to figure out the server address that they should use.",
"serverAddress": "Address of this server, suitable for a client that matches the above CIDR. This can be a hostname, hostname:port, IP or IP:port.",
}

func (ServerAddressByClientCIDR) SwaggerDoc() map[string]string {
return map_ServerAddressByClientCIDR
}

var map_Status = map[string]string{
"": "Status is a return value for calls that don't return other objects.",
"metadata": "Standard list metadata. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds",
Expand Down
14 changes: 7 additions & 7 deletions pkg/apiserver/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,10 +218,10 @@ func serviceErrorHandler(s runtime.NegotiatedSerializer, requestResolver *Reques
}

// Adds a service to return the supported api versions at the legacy /api.
func AddApiWebService(s runtime.NegotiatedSerializer, container *restful.Container, apiPrefix string, versions []string) {
func AddApiWebService(s runtime.NegotiatedSerializer, container *restful.Container, apiPrefix string, getAPIVersionsFunc func(req *restful.Request) *unversioned.APIVersions) {
// TODO: InstallREST should register each version automatically

versionHandler := APIVersionHandler(s, versions[:]...)
versionHandler := APIVersionHandler(s, getAPIVersionsFunc)
ws := new(restful.WebService)
ws.Path(apiPrefix)
ws.Doc("get available API versions")
Expand All @@ -234,7 +234,7 @@ func AddApiWebService(s runtime.NegotiatedSerializer, container *restful.Contain
}

// Adds a service to return the supported api versions at /apis.
func AddApisWebService(s runtime.NegotiatedSerializer, container *restful.Container, apiPrefix string, f func() []unversioned.APIGroup) {
func AddApisWebService(s runtime.NegotiatedSerializer, container *restful.Container, apiPrefix string, f func(req *restful.Request) []unversioned.APIGroup) {
rootAPIHandler := RootAPIHandler(s, f)
ws := new(restful.WebService)
ws.Path(apiPrefix)
Expand Down Expand Up @@ -279,16 +279,16 @@ func handleVersion(req *restful.Request, resp *restful.Response) {
}

// APIVersionHandler returns a handler which will list the provided versions as available.
func APIVersionHandler(s runtime.NegotiatedSerializer, versions ...string) restful.RouteFunction {
func APIVersionHandler(s runtime.NegotiatedSerializer, getAPIVersionsFunc func(req *restful.Request) *unversioned.APIVersions) restful.RouteFunction {
return func(req *restful.Request, resp *restful.Response) {
writeNegotiated(s, unversioned.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, &unversioned.APIVersions{Versions: versions})
writeNegotiated(s, unversioned.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, getAPIVersionsFunc(req))
}
}

// RootAPIHandler returns a handler which will list the provided groups and versions as available.
func RootAPIHandler(s runtime.NegotiatedSerializer, f func() []unversioned.APIGroup) restful.RouteFunction {
func RootAPIHandler(s runtime.NegotiatedSerializer, f func(req *restful.Request) []unversioned.APIGroup) restful.RouteFunction {
return func(req *restful.Request, resp *restful.Response) {
writeNegotiated(s, unversioned.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, &unversioned.APIGroupList{Groups: f()})
writeNegotiated(s, unversioned.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, &unversioned.APIGroupList{Groups: f(req)})
}
}

Expand Down
80 changes: 57 additions & 23 deletions pkg/genericapiserver/genericapiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,9 +256,12 @@ type Config struct {
// The range of IPs to be assigned to services with type=ClusterIP or greater
ServiceClusterIPRange *net.IPNet

// The IP address for the GenericAPIServer service (must be inside ServiceClusterIPRange
// The IP address for the GenericAPIServer service (must be inside ServiceClusterIPRange)
ServiceReadWriteIP net.IP

// Port for the apiserver service.
ServiceReadWritePort int

// The range of ports to be assigned to services with type=NodePort or greater
ServiceNodePortRange utilnet.PortRange

Expand Down Expand Up @@ -308,8 +311,9 @@ type GenericAPIServer struct {
ApiGroupVersionOverrides map[string]APIGroupVersionOverride
RequestContextMapper api.RequestContextMapper

// External host is the name that should be used in external (public internet) URLs for this GenericAPIServer
externalHost string
// ExternalAddress is the address (hostname or IP and port) that should be used in
// external (public internet) URLs for this GenericAPIServer.
ExternalAddress string
// ClusterIP is the IP address of the GenericAPIServer within the cluster.
ClusterIP net.IP
PublicReadWritePort int
Expand Down Expand Up @@ -370,6 +374,9 @@ func setDefaults(c *Config) {
glog.V(4).Infof("Setting GenericAPIServer service IP to %q (read-write).", serviceReadWriteIP)
c.ServiceReadWriteIP = serviceReadWriteIP
}
if c.ServiceReadWritePort == 0 {
c.ServiceReadWritePort = 443
}
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)
Expand All @@ -392,6 +399,13 @@ func setDefaults(c *Config) {
if c.RequestContextMapper == nil {
c.RequestContextMapper = api.NewRequestContextMapper()
}
if len(c.ExternalHost) == 0 && c.PublicAddress != nil {
hostAndPort := c.PublicAddress.String()
if c.ReadWritePort != 0 {
hostAndPort = net.JoinHostPort(hostAndPort, strconv.Itoa(c.ServiceReadWritePort))
}
c.ExternalHost = hostAndPort
}
}

// New returns a new instance of GenericAPIServer from the given config.
Expand Down Expand Up @@ -444,13 +458,12 @@ func New(c *Config) (*GenericAPIServer, error) {
cacheTimeout: c.CacheTimeout,
MinRequestTimeout: time.Duration(c.MinRequestTimeout) * time.Second,

MasterCount: c.MasterCount,
externalHost: c.ExternalHost,
ClusterIP: c.PublicAddress,
PublicReadWritePort: c.ReadWritePort,
ServiceReadWriteIP: c.ServiceReadWriteIP,
// TODO: ServiceReadWritePort should be passed in as an argument, it may not always be 443
ServiceReadWritePort: 443,
MasterCount: c.MasterCount,
ExternalAddress: c.ExternalHost,
ClusterIP: c.PublicAddress,
PublicReadWritePort: c.ReadWritePort,
ServiceReadWriteIP: c.ServiceReadWriteIP,
ServiceReadWritePort: c.ServiceReadWritePort,
ExtraServicePorts: c.ExtraServicePorts,
ExtraEndpointPorts: c.ExtraEndpointPorts,

Expand Down Expand Up @@ -603,7 +616,7 @@ func (s *GenericAPIServer) InstallAPIGroups(groupsInfo []APIGroupInfo) error {

// Installs handler at /apis to list all group versions for discovery
func (s *GenericAPIServer) installGroupsDiscoveryHandler() {
apiserver.AddApisWebService(s.Serializer, s.HandlerContainer, s.APIGroupPrefix, func() []unversioned.APIGroup {
apiserver.AddApisWebService(s.Serializer, s.HandlerContainer, s.APIGroupPrefix, func(req *restful.Request) []unversioned.APIGroup {
// Return the list of supported groups in sorted order (to have a deterministic order).
groups := []unversioned.APIGroup{}
groupNames := make([]string, len(s.apiGroupsForDiscovery))
Expand All @@ -614,7 +627,10 @@ func (s *GenericAPIServer) installGroupsDiscoveryHandler() {
}
sort.Strings(groupNames)
for _, groupName := range groupNames {
groups = append(groups, s.apiGroupsForDiscovery[groupName])
apiGroup := s.apiGroupsForDiscovery[groupName]
// Add ServerAddressByClientCIDRs.
apiGroup.ServerAddressByClientCIDRs = s.getServerAddressByClientCIDRs(req.Request)
groups = append(groups, apiGroup)
}
return groups
})
Expand Down Expand Up @@ -738,7 +754,13 @@ func (s *GenericAPIServer) installAPIGroup(apiGroupInfo *APIGroupInfo) error {
// Install the version handler.
if apiGroupInfo.IsLegacyGroup {
// Add a handler at /api to enumerate the supported api versions.
apiserver.AddApiWebService(s.Serializer, s.HandlerContainer, apiPrefix, apiVersions)
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
})
} else {
// 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
Expand Down Expand Up @@ -781,6 +803,27 @@ func (s *GenericAPIServer) RemoveAPIGroupForDiscovery(groupName string) {
delete(s.apiGroupsForDiscovery, groupName)
}

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
}

func (s *GenericAPIServer) getAPIGroupVersion(apiGroupInfo *APIGroupInfo, groupVersion unversioned.GroupVersion, apiPrefix string) (*apiserver.APIGroupVersion, error) {
storage := make(map[string]rest.Storage)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you can just delete this code?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nvm, I see you actually moved it.

for k, v := range apiGroupInfo.VersionedResourcesStorageMap[groupVersion.Version] {
Expand Down Expand Up @@ -817,17 +860,8 @@ func (s *GenericAPIServer) newAPIGroupVersion(groupMeta apimachinery.GroupMeta,
// 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() {
hostAndPort := s.externalHost
hostAndPort := s.ExternalAddress
protocol := "https://"

// TODO: this is kind of messed up, we should just pipe in the full URL from the outside, rather
// than guessing at it.
if len(s.externalHost) == 0 && s.ClusterIP != nil {
host := s.ClusterIP.String()
if s.PublicReadWritePort != 0 {
hostAndPort = net.JoinHostPort(host, strconv.Itoa(s.PublicReadWritePort))
}
}
webServicesUrl := protocol + hostAndPort

// Enable swagger UI and discovery API
Expand Down