Core support for ip-per-service

This commit is contained in:
Tim Hockin
2014-09-18 16:03:34 -07:00
parent 63e1906902
commit e907011111
26 changed files with 735 additions and 227 deletions

View File

@@ -19,6 +19,7 @@ package service
import (
"fmt"
"math/rand"
"net"
"strconv"
"strings"
@@ -32,21 +33,49 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
"github.com/golang/glog"
)
// REST adapts a service registry into apiserver's RESTStorage model.
type REST struct {
registry Registry
cloud cloudprovider.Interface
machines minion.Registry
registry Registry
cloud cloudprovider.Interface
machines minion.Registry
portalMgr *ipAllocator
}
// NewREST returns a new REST.
func NewREST(registry Registry, cloud cloudprovider.Interface, machines minion.Registry) *REST {
func NewREST(registry Registry, cloud cloudprovider.Interface, machines minion.Registry, portalNet *net.IPNet) *REST {
// TODO: Before we can replicate masters, this has to be synced (e.g. lives in etcd)
ipa := newIPAllocator(portalNet)
reloadIPsFromStorage(ipa, registry)
return &REST{
registry: registry,
cloud: cloud,
machines: machines,
registry: registry,
cloud: cloud,
machines: machines,
portalMgr: ipa,
}
}
// Helper: mark all previously allocated IPs in the allocator.
func reloadIPsFromStorage(ipa *ipAllocator, registry Registry) {
services, err := registry.ListServices(api.NewContext())
if err != nil {
// This is really bad.
glog.Errorf("can't list services to init service REST: %s", err)
return
}
for i := range services.Items {
s := &services.Items[i]
if s.PortalIP == "" {
glog.Warningf("service %q has no PortalIP", s.ID)
continue
}
if err := ipa.Allocate(net.ParseIP(s.PortalIP)); err != nil {
// This is really bad.
glog.Errorf("service %q PortalIP %s could not be allocated: %s", s.ID, s.PortalIP, err)
}
}
}
@@ -61,9 +90,16 @@ func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Obje
srv.CreationTimestamp = util.Now()
if ip, err := rs.portalMgr.AllocateNext(); err != nil {
return nil, err
} else {
srv.PortalIP = ip.String()
}
return apiserver.MakeAsync(func() (runtime.Object, error) {
// TODO: Consider moving this to a rectification loop, so that we make/remove external load balancers
// correctly no matter what http operations happen.
srv.ProxyPort = 0
if srv.CreateExternalLoadBalancer {
if rs.cloud == nil {
return nil, fmt.Errorf("requested an external service, but no cloud provider supplied.")
@@ -88,6 +124,9 @@ func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Obje
if err != nil {
return nil, err
}
// External load-balancers require a known port for the service proxy.
// TODO: If we end up brokering HostPorts between Pods and Services, this can be any port.
srv.ProxyPort = srv.Port
}
err := rs.registry.CreateService(ctx, srv)
if err != nil {
@@ -110,6 +149,7 @@ func (rs *REST) Delete(ctx api.Context, id string) (<-chan runtime.Object, error
if err != nil {
return nil, err
}
rs.portalMgr.Release(net.ParseIP(service.PortalIP))
return apiserver.MakeAsync(func() (runtime.Object, error) {
rs.deleteExternalLoadBalancer(service)
return &api.Status{Status: api.StatusSuccess}, rs.registry.DeleteService(ctx, id)
@@ -161,16 +201,13 @@ func GetServiceEnvironmentVariables(ctx api.Context, registry Registry, machine
for _, service := range services.Items {
// Host
name := makeEnvVariableName(service.ID) + "_SERVICE_HOST"
result = append(result, api.EnvVar{Name: name, Value: machine})
result = append(result, api.EnvVar{Name: name, Value: service.PortalIP})
// Port
name = makeEnvVariableName(service.ID) + "_SERVICE_PORT"
result = append(result, api.EnvVar{Name: name, Value: strconv.Itoa(service.Port)})
// Docker-compatible vars.
result = append(result, makeLinkVariables(service, machine)...)
result = append(result, makeLinkVariables(service)...)
}
// The 'SERVICE_HOST' variable is deprecated.
// TODO(thockin): get rid of it once ip-per-service is in and "deployed".
result = append(result, api.EnvVar{Name: "SERVICE_HOST", Value: machine})
return result, nil
}
@@ -183,8 +220,15 @@ func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan runtime.Obje
return nil, errors.NewInvalid("service", srv.ID, errs)
}
return apiserver.MakeAsync(func() (runtime.Object, error) {
cur, err := rs.registry.GetService(ctx, srv.ID)
if err != nil {
return nil, err
}
// Copy over non-user fields.
srv.PortalIP = cur.PortalIP
srv.ProxyPort = cur.ProxyPort
// TODO: check to see if external load balancer status changed
err := rs.registry.UpdateService(ctx, srv)
err = rs.registry.UpdateService(ctx, srv)
if err != nil {
return nil, err
}
@@ -234,7 +278,7 @@ func makeEnvVariableName(str string) string {
return strings.ToUpper(strings.Replace(str, "-", "_", -1))
}
func makeLinkVariables(service api.Service, machine string) []api.EnvVar {
func makeLinkVariables(service api.Service) []api.EnvVar {
prefix := makeEnvVariableName(service.ID)
protocol := string(api.ProtocolTCP)
if service.Protocol != "" {
@@ -244,11 +288,11 @@ func makeLinkVariables(service api.Service, machine string) []api.EnvVar {
return []api.EnvVar{
{
Name: prefix + "_PORT",
Value: fmt.Sprintf("%s://%s:%d", strings.ToLower(protocol), machine, service.Port),
Value: fmt.Sprintf("%s://%s:%d", strings.ToLower(protocol), service.PortalIP, service.Port),
},
{
Name: portPrefix,
Value: fmt.Sprintf("%s://%s:%d", strings.ToLower(protocol), machine, service.Port),
Value: fmt.Sprintf("%s://%s:%d", strings.ToLower(protocol), service.PortalIP, service.Port),
},
{
Name: portPrefix + "_PROTO",
@@ -260,7 +304,7 @@ func makeLinkVariables(service api.Service, machine string) []api.EnvVar {
},
{
Name: portPrefix + "_ADDR",
Value: machine,
Value: service.PortalIP,
},
}
}