Commit e9070111 authored by Tim Hockin's avatar Tim Hockin

Core support for ip-per-service

parent 63e19069
...@@ -31,3 +31,4 @@ MINION_IP_RANGES=($(eval echo "10.244.{1..${NUM_MINIONS}}.0/24")) ...@@ -31,3 +31,4 @@ MINION_IP_RANGES=($(eval echo "10.244.{1..${NUM_MINIONS}}.0/24"))
MINION_SCOPES="compute-rw" MINION_SCOPES="compute-rw"
# Increase the sleep interval value if concerned about API rate limits. 3, in seconds, is the default. # Increase the sleep interval value if concerned about API rate limits. 3, in seconds, is the default.
POLL_SLEEP_INTERVAL=3 POLL_SLEEP_INTERVAL=3
PORTAL_NET="10.0.0.0/16"
...@@ -31,3 +31,4 @@ MINION_IP_RANGES=($(eval echo "10.245.{1..${NUM_MINIONS}}.0/24")) ...@@ -31,3 +31,4 @@ MINION_IP_RANGES=($(eval echo "10.245.{1..${NUM_MINIONS}}.0/24"))
MINION_SCOPES="" MINION_SCOPES=""
# Increase the sleep interval value if concerned about API rate limits. 3, in seconds, is the default. # Increase the sleep interval value if concerned about API rate limits. 3, in seconds, is the default.
POLL_SLEEP_INTERVAL=3 POLL_SLEEP_INTERVAL=3
PORTAL_NET="10.0.0.0/16"
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
mkdir -p /srv/salt-overlay/pillar mkdir -p /srv/salt-overlay/pillar
cat <<EOF >/srv/salt-overlay/pillar/cluster-params.sls cat <<EOF >/srv/salt-overlay/pillar/cluster-params.sls
node_instance_prefix: $NODE_INSTANCE_PREFIX node_instance_prefix: $NODE_INSTANCE_PREFIX
portal_net: $PORTAL_NET
EOF EOF
mkdir -p /srv/salt-overlay/salt/nginx mkdir -p /srv/salt-overlay/salt/nginx
......
...@@ -263,6 +263,7 @@ function kube-up { ...@@ -263,6 +263,7 @@ function kube-up {
echo "readonly SERVER_BINARY_TAR_URL='${SERVER_BINARY_TAR_URL}'" echo "readonly SERVER_BINARY_TAR_URL='${SERVER_BINARY_TAR_URL}'"
echo "readonly SALT_TAR_URL='${SALT_TAR_URL}'" echo "readonly SALT_TAR_URL='${SALT_TAR_URL}'"
echo "readonly MASTER_HTPASSWD='${htpasswd}'" echo "readonly MASTER_HTPASSWD='${htpasswd}'"
echo "readonly PORTAL_NET='${PORTAL_NET}'"
grep -v "^#" "${KUBE_ROOT}/cluster/gce/templates/create-dynamic-salt-files.sh" grep -v "^#" "${KUBE_ROOT}/cluster/gce/templates/create-dynamic-salt-files.sh"
grep -v "^#" "${KUBE_ROOT}/cluster/gce/templates/download-release.sh" grep -v "^#" "${KUBE_ROOT}/cluster/gce/templates/download-release.sh"
grep -v "^#" "${KUBE_ROOT}/cluster/gce/templates/salt-master.sh" grep -v "^#" "${KUBE_ROOT}/cluster/gce/templates/salt-master.sh"
......
...@@ -55,5 +55,8 @@ ...@@ -55,5 +55,8 @@
{%- set minion_regexp = "" %} {%- set minion_regexp = "" %}
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if pillar['portal_net'] is defined %}
{% set portal_net = "-portal_net=" + pillar['portal_net'] %}
{% endif %}
DAEMON_ARGS="{{daemon_args}} {{address}} {{machines}} {{etcd_servers}} {{ minion_regexp }} {{ cloud_provider }} --allow_privileged={{pillar['allow_privileged']}}" DAEMON_ARGS="{{daemon_args}} {{address}} {{machines}} {{etcd_servers}} {{ minion_regexp }} {{ cloud_provider }} --allow_privileged={{pillar['allow_privileged']}} {{portal_net}}"
...@@ -22,7 +22,7 @@ DAEMON_ARGS="" ...@@ -22,7 +22,7 @@ DAEMON_ARGS=""
DAEMON_LOG_FILE=/var/log/$NAME.log DAEMON_LOG_FILE=/var/log/$NAME.log
PIDFILE=/var/run/$NAME.pid PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME SCRIPTNAME=/etc/init.d/$NAME
DAEMON_USER=kube-proxy DAEMON_USER=root
# Exit if the package is not installed # Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0 [ -x "$DAEMON" ] || exit 0
......
...@@ -64,6 +64,7 @@ var ( ...@@ -64,6 +64,7 @@ var (
machineList util.StringList machineList util.StringList
corsAllowedOriginList util.StringList corsAllowedOriginList util.StringList
allowPrivileged = flag.Bool("allow_privileged", false, "If true, allow privileged containers.") allowPrivileged = flag.Bool("allow_privileged", false, "If true, allow privileged containers.")
portalNet util.IPNet // TODO: make this a list
// TODO: Discover these by pinging the host machines, and rip out these flags. // TODO: Discover these by pinging the host machines, and rip out these flags.
nodeMilliCPU = flag.Int("node_milli_cpu", 1000, "The amount of MilliCPU provisioned on each node") nodeMilliCPU = flag.Int("node_milli_cpu", 1000, "The amount of MilliCPU provisioned on each node")
nodeMemory = flag.Int("node_memory", 3*1024*1024*1024, "The amount of memory (in bytes) provisioned on each node") nodeMemory = flag.Int("node_memory", 3*1024*1024*1024, "The amount of memory (in bytes) provisioned on each node")
...@@ -75,6 +76,7 @@ func init() { ...@@ -75,6 +76,7 @@ func init() {
flag.Var(&etcdServerList, "etcd_servers", "List of etcd servers to watch (http://ip:port), comma separated. Mutually exclusive with -etcd_config") flag.Var(&etcdServerList, "etcd_servers", "List of etcd servers to watch (http://ip:port), comma separated. Mutually exclusive with -etcd_config")
flag.Var(&machineList, "machines", "List of machines to schedule onto, comma separated.") flag.Var(&machineList, "machines", "List of machines to schedule onto, comma separated.")
flag.Var(&corsAllowedOriginList, "cors_allowed_origins", "List of allowed origins for CORS, comma separated. An allowed origin can be a regular expression to support subdomain matching. If this list is empty CORS will not be enabled.") flag.Var(&corsAllowedOriginList, "cors_allowed_origins", "List of allowed origins for CORS, comma separated. An allowed origin can be a regular expression to support subdomain matching. If this list is empty CORS will not be enabled.")
flag.Var(&portalNet, "portal_net", "A CIDR notation IP range from which to assign portal IPs. This must not overlap with any IP ranges assigned to nodes for pods.")
} }
func verifyMinionFlags() { func verifyMinionFlags() {
...@@ -89,6 +91,13 @@ func verifyMinionFlags() { ...@@ -89,6 +91,13 @@ func verifyMinionFlags() {
} }
} }
// TODO: Longer term we should read this from some config store, rather than a flag.
func verifyPortalFlags() {
if portalNet.IP == nil {
glog.Fatal("No -portal_net specified")
}
}
func initCloudProvider(name string, configFilePath string) cloudprovider.Interface { func initCloudProvider(name string, configFilePath string) cloudprovider.Interface {
var config *os.File var config *os.File
...@@ -141,6 +150,7 @@ func main() { ...@@ -141,6 +150,7 @@ func main() {
verflag.PrintAndExitIfRequested() verflag.PrintAndExitIfRequested()
verifyMinionFlags() verifyMinionFlags()
verifyPortalFlags()
if (*etcdConfigFile != "" && len(etcdServerList) != 0) || (*etcdConfigFile == "" && len(etcdServerList) == 0) { if (*etcdConfigFile != "" && len(etcdServerList) != 0) || (*etcdConfigFile == "" && len(etcdServerList) == 0) {
glog.Fatalf("specify either -etcd_servers or -etcd_config") glog.Fatalf("specify either -etcd_servers or -etcd_config")
...@@ -172,6 +182,7 @@ func main() { ...@@ -172,6 +182,7 @@ func main() {
glog.Fatalf("Invalid storage version or misconfigured etcd: %v", err) glog.Fatalf("Invalid storage version or misconfigured etcd: %v", err)
} }
n := net.IPNet(portalNet)
m := master.New(&master.Config{ m := master.New(&master.Config{
Client: client, Client: client,
Cloud: cloud, Cloud: cloud,
...@@ -188,6 +199,7 @@ func main() { ...@@ -188,6 +199,7 @@ func main() {
resources.Memory: util.NewIntOrStringFromInt(*nodeMemory), resources.Memory: util.NewIntOrStringFromInt(*nodeMemory),
}, },
}, },
PortalNet: &n,
}) })
mux := http.NewServeMux() mux := http.NewServeMux()
......
...@@ -123,11 +123,16 @@ func startComponents(manifestURL string) (apiServerURL string) { ...@@ -123,11 +123,16 @@ func startComponents(manifestURL string) (apiServerURL string) {
} }
// Master // Master
_, portalNet, err := net.ParseCIDR("10.0.0.0/24")
if err != nil {
glog.Fatalf("Unable to parse CIDR: %v", err)
}
m := master.New(&master.Config{ m := master.New(&master.Config{
Client: cl, Client: cl,
EtcdHelper: helper, EtcdHelper: helper,
Minions: machineList, Minions: machineList,
PodInfoGetter: fakePodInfoGetter{}, PodInfoGetter: fakePodInfoGetter{},
PortalNet: portalNet,
}) })
mux := http.NewServeMux() mux := http.NewServeMux()
apiserver.NewAPIGroup(m.API_v1beta1()).InstallREST(mux, "/api/v1beta1") apiserver.NewAPIGroup(m.API_v1beta1()).InstallREST(mux, "/api/v1beta1")
...@@ -349,18 +354,33 @@ func runServiceTest(client *client.Client) { ...@@ -349,18 +354,33 @@ func runServiceTest(client *client.Client) {
if err := wait.Poll(time.Second, time.Second*20, podExists(client, ctx, pod.ID)); err != nil { if err := wait.Poll(time.Second, time.Second*20, podExists(client, ctx, pod.ID)); err != nil {
glog.Fatalf("FAILED: pod never started running %v", err) glog.Fatalf("FAILED: pod never started running %v", err)
} }
svc := api.Service{ svc1 := api.Service{
TypeMeta: api.TypeMeta{ID: "service1"}, TypeMeta: api.TypeMeta{ID: "service1"},
Selector: map[string]string{ Selector: map[string]string{
"name": "thisisalonglabel", "name": "thisisalonglabel",
}, },
Port: 8080, Port: 8080,
} }
_, err = client.CreateService(ctx, &svc) _, err = client.CreateService(ctx, &svc1)
if err != nil {
glog.Fatalf("Failed to create service: %v, %v", svc1, err)
}
if err := wait.Poll(time.Second, time.Second*20, endpointsSet(client, ctx, svc1.ID, 1)); err != nil {
glog.Fatalf("FAILED: unexpected endpoints: %v", err)
}
// A second service with the same port.
svc2 := api.Service{
TypeMeta: api.TypeMeta{ID: "service2"},
Selector: map[string]string{
"name": "thisisalonglabel",
},
Port: 8080,
}
_, err = client.CreateService(ctx, &svc2)
if err != nil { if err != nil {
glog.Fatalf("Failed to create service: %v, %v", svc, err) glog.Fatalf("Failed to create service: %v, %v", svc2, err)
} }
if err := wait.Poll(time.Second, time.Second*10, endpointsSet(client, ctx, svc.ID, 1)); err != nil { if err := wait.Poll(time.Second, time.Second*20, endpointsSet(client, ctx, svc2.ID, 1)); err != nil {
glog.Fatalf("FAILED: unexpected endpoints: %v", err) glog.Fatalf("FAILED: unexpected endpoints: %v", err)
} }
glog.Info("Service test passed.") glog.Info("Service test passed.")
......
...@@ -25,6 +25,8 @@ import ( ...@@ -25,6 +25,8 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/proxy" "github.com/GoogleCloudPlatform/kubernetes/pkg/proxy"
"github.com/GoogleCloudPlatform/kubernetes/pkg/proxy/config" "github.com/GoogleCloudPlatform/kubernetes/pkg/proxy/config"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/exec"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/iptables"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version/verflag" "github.com/GoogleCloudPlatform/kubernetes/pkg/version/verflag"
"github.com/coreos/go-etcd/etcd" "github.com/coreos/go-etcd/etcd"
"github.com/golang/glog" "github.com/golang/glog"
...@@ -40,7 +42,7 @@ var ( ...@@ -40,7 +42,7 @@ var (
func init() { func init() {
client.BindClientConfigFlags(flag.CommandLine, clientConfig) client.BindClientConfigFlags(flag.CommandLine, clientConfig)
flag.Var(&etcdServerList, "etcd_servers", "List of etcd servers to watch (http://ip:port), comma separated (optional). Mutually exclusive with -etcd_config") flag.Var(&etcdServerList, "etcd_servers", "List of etcd servers to watch (http://ip:port), comma separated (optional). Mutually exclusive with -etcd_config")
flag.Var(&bindAddress, "bind_address", "The address for the proxy server to serve on (set to 0.0.0.0 for all interfaces)") flag.Var(&bindAddress, "bind_address", "The IP address for the proxy server to serve on (set to 0.0.0.0 for all interfaces)")
} }
func main() { func main() {
...@@ -97,12 +99,12 @@ func main() { ...@@ -97,12 +99,12 @@ func main() {
} }
loadBalancer := proxy.NewLoadBalancerRR() loadBalancer := proxy.NewLoadBalancerRR()
proxier := proxy.NewProxier(loadBalancer, net.IP(bindAddress)) proxier := proxy.NewProxier(loadBalancer, net.IP(bindAddress), iptables.New(exec.New()))
// Wire proxier to handle changes to services // Wire proxier to handle changes to services
serviceConfig.RegisterHandler(proxier) serviceConfig.RegisterHandler(proxier)
// And wire loadBalancer to handle changes to endpoints to services // And wire loadBalancer to handle changes to endpoints to services
endpointsConfig.RegisterHandler(loadBalancer) endpointsConfig.RegisterHandler(loadBalancer)
// Just loop forever for now... // Just loop forever for now...
select {} proxier.SyncLoop()
} }
...@@ -85,7 +85,7 @@ redis-slave-controller gurpartap/redis name=redis,role=slave 2 ...@@ -85,7 +85,7 @@ redis-slave-controller gurpartap/redis name=redis,role=slave 2
The redis slave configures itself by looking for the Kubernetes service environment variables in the container environment. In particular, the redis slave is started with the following command: The redis slave configures itself by looking for the Kubernetes service environment variables in the container environment. In particular, the redis slave is started with the following command:
```shell ```shell
redis-server --slaveof $SERVICE_HOST $REDIS_MASTER_SERVICE_PORT redis-server --slaveof $REDIS_MASTER_SERVICE_HOST $REDIS_MASTER_SERVICE_PORT
``` ```
Once that's up you can list the pods in the cluster, to verify that the master and slaves are running: Once that's up you can list the pods in the cluster, to verify that the master and slaves are running:
......
...@@ -71,7 +71,7 @@ func HandleError(result interface{}, err error) (r interface{}) { ...@@ -71,7 +71,7 @@ func HandleError(result interface{}, err error) (r interface{}) {
} }
func main() { func main() {
pool = simpleredis.NewConnectionPoolHost(os.Getenv("SERVICE_HOST") + ":" + os.Getenv("REDIS_MASTER_SERVICE_PORT")) pool = simpleredis.NewConnectionPoolHost(os.Getenv("REDIS_MASTER_SERVICE_HOST") + ":" + os.Getenv("REDIS_MASTER_SERVICE_PORT"))
defer pool.Close() defer pool.Close()
r := mux.NewRouter() r := mux.NewRouter()
......
...@@ -71,7 +71,8 @@ ${GO_OUT}/apiserver \ ...@@ -71,7 +71,8 @@ ${GO_OUT}/apiserver \
--port="${API_PORT}" \ --port="${API_PORT}" \
--etcd_servers="http://${ETCD_HOST}:${ETCD_PORT}" \ --etcd_servers="http://${ETCD_HOST}:${ETCD_PORT}" \
--machines="127.0.0.1" \ --machines="127.0.0.1" \
--minion_port=${KUBELET_PORT} 1>&2 & --minion_port=${KUBELET_PORT} \
--portal_net="10.0.0.0/24" 1>&2 &
APISERVER_PID=$! APISERVER_PID=$!
wait_for_url "http://127.0.0.1:${API_PORT}/healthz" "apiserver: " wait_for_url "http://127.0.0.1:${API_PORT}/healthz" "apiserver: "
......
...@@ -423,6 +423,13 @@ type Service struct { ...@@ -423,6 +423,13 @@ type Service struct {
// ContainerPort is the name of the port on the container to direct traffic to. // ContainerPort is the name of the port on the container to direct traffic to.
// Optional, if unspecified use the first port on the container. // Optional, if unspecified use the first port on the container.
ContainerPort util.IntOrString `json:"containerPort,omitempty" yaml:"containerPort,omitempty"` ContainerPort util.IntOrString `json:"containerPort,omitempty" yaml:"containerPort,omitempty"`
// PortalIP is assigned by the master. If specified by the user it will be ignored.
// TODO: This is awkward - if we had a BoundService, it would be better factored.
PortalIP string `json:"portalIP,omitempty" yaml:"portalIP,omitempty"`
// ProxyPort is assigned by the master. If specified by the user it will be ignored.
ProxyPort int `json:"proxyPort,omitempty" yaml:"proxyPort,omitempty"`
} }
// Endpoints is a collection of endpoints that implement the actual service, for example: // Endpoints is a collection of endpoints that implement the actual service, for example:
......
...@@ -451,6 +451,12 @@ type Service struct { ...@@ -451,6 +451,12 @@ type Service struct {
// ContainerPort is the name of the port on the container to direct traffic to. // ContainerPort is the name of the port on the container to direct traffic to.
// Optional, if unspecified use the first port on the container. // Optional, if unspecified use the first port on the container.
ContainerPort util.IntOrString `json:"containerPort,omitempty" yaml:"containerPort,omitempty"` ContainerPort util.IntOrString `json:"containerPort,omitempty" yaml:"containerPort,omitempty"`
// PortalIP is assigned by the master. If specified by the user it will be ignored.
PortalIP string `json:"portalIP,omitempty" yaml:"portalIP,omitempty"`
// ProxyPort is assigned by the master. If specified by the user it will be ignored.
ProxyPort int `json:"proxyPort,omitempty" yaml:"proxyPort,omitempty"`
} }
// Endpoints is a collection of endpoints that implement the actual service, for example: // Endpoints is a collection of endpoints that implement the actual service, for example:
......
...@@ -416,6 +416,12 @@ type Service struct { ...@@ -416,6 +416,12 @@ type Service struct {
// ContainerPort is the name of the port on the container to direct traffic to. // ContainerPort is the name of the port on the container to direct traffic to.
// Optional, if unspecified use the first port on the container. // Optional, if unspecified use the first port on the container.
ContainerPort util.IntOrString `json:"containerPort,omitempty" yaml:"containerPort,omitempty"` ContainerPort util.IntOrString `json:"containerPort,omitempty" yaml:"containerPort,omitempty"`
// PortalIP is assigned by the master. If specified by the user it will be ignored.
PortalIP string `json:"portalIP,omitempty" yaml:"portalIP,omitempty"`
// ProxyPort is assigned by the master. If specified by the user it will be ignored.
ProxyPort int `json:"proxyPort,omitempty" yaml:"proxyPort,omitempty"`
} }
// Endpoints is a collection of endpoints that implement the actual service, for example: // Endpoints is a collection of endpoints that implement the actual service, for example:
......
...@@ -570,6 +570,11 @@ type ReplicationControllerList struct { ...@@ -570,6 +570,11 @@ type ReplicationControllerList struct {
// ServiceStatus represents the current status of a service // ServiceStatus represents the current status of a service
type ServiceStatus struct { type ServiceStatus struct {
// PortalIP is assigned by the master.
PortalIP string `json:"portalIP,omitempty" yaml:"portalIP,omitempty"`
// ProxyPort is assigned by the master. If 0, the proxy will choose an ephemeral port.
ProxyPort int `json:"proxyPort,omitempty" yaml:"proxyPort,omitempty"`
} }
// ServiceSpec describes the attributes that a user creates on a service // ServiceSpec describes the attributes that a user creates on a service
......
...@@ -140,7 +140,7 @@ func (h *HumanReadablePrinter) validatePrintHandlerFunc(printFunc reflect.Value) ...@@ -140,7 +140,7 @@ func (h *HumanReadablePrinter) validatePrintHandlerFunc(printFunc reflect.Value)
var podColumns = []string{"ID", "Image(s)", "Host", "Labels", "Status"} var podColumns = []string{"ID", "Image(s)", "Host", "Labels", "Status"}
var replicationControllerColumns = []string{"ID", "Image(s)", "Selector", "Replicas"} var replicationControllerColumns = []string{"ID", "Image(s)", "Selector", "Replicas"}
var serviceColumns = []string{"ID", "Labels", "Selector", "Port"} var serviceColumns = []string{"ID", "Labels", "Selector", "IP", "Port"}
var minionColumns = []string{"Minion identifier"} var minionColumns = []string{"Minion identifier"}
var statusColumns = []string{"Status"} var statusColumns = []string{"Status"}
var eventColumns = []string{"Name", "Kind", "Status", "Reason", "Message"} var eventColumns = []string{"Name", "Kind", "Status", "Reason", "Message"}
...@@ -226,8 +226,8 @@ func printReplicationControllerList(list *api.ReplicationControllerList, w io.Wr ...@@ -226,8 +226,8 @@ func printReplicationControllerList(list *api.ReplicationControllerList, w io.Wr
} }
func printService(svc *api.Service, w io.Writer) error { func printService(svc *api.Service, w io.Writer) error {
_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%d\n", svc.ID, labels.Set(svc.Labels), _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d\n", svc.ID, labels.Set(svc.Labels),
labels.Set(svc.Selector), svc.Port) labels.Set(svc.Selector), svc.PortalIP, svc.Port)
return err return err
} }
......
...@@ -152,7 +152,7 @@ func (h *HumanReadablePrinter) validatePrintHandlerFunc(printFunc reflect.Value) ...@@ -152,7 +152,7 @@ func (h *HumanReadablePrinter) validatePrintHandlerFunc(printFunc reflect.Value)
var podColumns = []string{"ID", "IMAGE(S)", "HOST", "LABELS", "STATUS"} var podColumns = []string{"ID", "IMAGE(S)", "HOST", "LABELS", "STATUS"}
var replicationControllerColumns = []string{"ID", "IMAGE(S)", "SELECTOR", "REPLICAS"} var replicationControllerColumns = []string{"ID", "IMAGE(S)", "SELECTOR", "REPLICAS"}
var serviceColumns = []string{"ID", "LABELS", "SELECTOR", "PORT"} var serviceColumns = []string{"ID", "LABELS", "SELECTOR", "IP", "PORT"}
var minionColumns = []string{"ID"} var minionColumns = []string{"ID"}
var statusColumns = []string{"STATUS"} var statusColumns = []string{"STATUS"}
...@@ -222,8 +222,8 @@ func printReplicationControllerList(list *api.ReplicationControllerList, w io.Wr ...@@ -222,8 +222,8 @@ func printReplicationControllerList(list *api.ReplicationControllerList, w io.Wr
} }
func printService(svc *api.Service, w io.Writer) error { func printService(svc *api.Service, w io.Writer) error {
_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%d\n", svc.ID, labels.Set(svc.Labels), _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%d\n", svc.ID, labels.Set(svc.Labels),
labels.Set(svc.Selector), svc.Port) labels.Set(svc.Selector), svc.PortalIP, svc.Port)
return err return err
} }
......
...@@ -17,6 +17,7 @@ limitations under the License. ...@@ -17,6 +17,7 @@ limitations under the License.
package master package master
import ( import (
"net"
"net/http" "net/http"
"time" "time"
...@@ -54,6 +55,7 @@ type Config struct { ...@@ -54,6 +55,7 @@ type Config struct {
MinionRegexp string MinionRegexp string
PodInfoGetter client.PodInfoGetter PodInfoGetter client.PodInfoGetter
NodeResources api.NodeResources NodeResources api.NodeResources
PortalNet *net.IPNet
} }
// Master contains state for a Kubernetes cluster master/api server. // Master contains state for a Kubernetes cluster master/api server.
...@@ -67,6 +69,7 @@ type Master struct { ...@@ -67,6 +69,7 @@ type Master struct {
eventRegistry generic.Registry eventRegistry generic.Registry
storage map[string]apiserver.RESTStorage storage map[string]apiserver.RESTStorage
client *client.Client client *client.Client
portalNet *net.IPNet
} }
// NewEtcdHelper returns an EtcdHelper for the provided arguments or an error if the version // NewEtcdHelper returns an EtcdHelper for the provided arguments or an error if the version
...@@ -98,6 +101,7 @@ func New(c *Config) *Master { ...@@ -98,6 +101,7 @@ func New(c *Config) *Master {
eventRegistry: event.NewEtcdRegistry(c.EtcdHelper, uint64(c.EventTTL.Seconds())), eventRegistry: event.NewEtcdRegistry(c.EtcdHelper, uint64(c.EventTTL.Seconds())),
minionRegistry: minionRegistry, minionRegistry: minionRegistry,
client: c.Client, client: c.Client,
portalNet: c.PortalNet,
} }
m.init(c) m.init(c)
return m return m
...@@ -137,7 +141,7 @@ func (m *Master) init(c *Config) { ...@@ -137,7 +141,7 @@ func (m *Master) init(c *Config) {
Minions: m.client, Minions: m.client,
}), }),
"replicationControllers": controller.NewREST(m.controllerRegistry, m.podRegistry), "replicationControllers": controller.NewREST(m.controllerRegistry, m.podRegistry),
"services": service.NewREST(m.serviceRegistry, c.Cloud, m.minionRegistry), "services": service.NewREST(m.serviceRegistry, c.Cloud, m.minionRegistry, m.portalNet),
"endpoints": endpoint.NewREST(m.endpointRegistry), "endpoints": endpoint.NewREST(m.endpointRegistry),
"minions": minion.NewREST(m.minionRegistry), "minions": minion.NewREST(m.minionRegistry),
"events": event.NewREST(m.eventRegistry), "events": event.NewREST(m.eventRegistry),
......
...@@ -48,10 +48,8 @@ func TestMakeManifestNoServices(t *testing.T) { ...@@ -48,10 +48,8 @@ func TestMakeManifestNoServices(t *testing.T) {
} }
container := manifest.Containers[0] container := manifest.Containers[0]
if len(container.Env) != 1 || if len(container.Env) != 0 {
container.Env[0].Name != "SERVICE_HOST" || t.Errorf("Expected zero env vars, got: %#v", manifest)
container.Env[0].Value != "machine" {
t.Errorf("Expected one env vars, got: %#v", manifest)
} }
if manifest.ID != "foobar" { if manifest.ID != "foobar" {
t.Errorf("Failed to assign ID to manifest: %#v", manifest.ID) t.Errorf("Failed to assign ID to manifest: %#v", manifest.ID)
...@@ -69,6 +67,7 @@ func TestMakeManifestServices(t *testing.T) { ...@@ -69,6 +67,7 @@ func TestMakeManifestServices(t *testing.T) {
Kind: util.IntstrInt, Kind: util.IntstrInt,
IntVal: 900, IntVal: 900,
}, },
PortalIP: "1.2.3.4",
}, },
}, },
}, },
...@@ -96,7 +95,7 @@ func TestMakeManifestServices(t *testing.T) { ...@@ -96,7 +95,7 @@ func TestMakeManifestServices(t *testing.T) {
envs := []api.EnvVar{ envs := []api.EnvVar{
{ {
Name: "TEST_SERVICE_HOST", Name: "TEST_SERVICE_HOST",
Value: "machine", Value: "1.2.3.4",
}, },
{ {
Name: "TEST_SERVICE_PORT", Name: "TEST_SERVICE_PORT",
...@@ -104,11 +103,11 @@ func TestMakeManifestServices(t *testing.T) { ...@@ -104,11 +103,11 @@ func TestMakeManifestServices(t *testing.T) {
}, },
{ {
Name: "TEST_PORT", Name: "TEST_PORT",
Value: "tcp://machine:8080", Value: "tcp://1.2.3.4:8080",
}, },
{ {
Name: "TEST_PORT_8080_TCP", Name: "TEST_PORT_8080_TCP",
Value: "tcp://machine:8080", Value: "tcp://1.2.3.4:8080",
}, },
{ {
Name: "TEST_PORT_8080_TCP_PROTO", Name: "TEST_PORT_8080_TCP_PROTO",
...@@ -120,11 +119,7 @@ func TestMakeManifestServices(t *testing.T) { ...@@ -120,11 +119,7 @@ func TestMakeManifestServices(t *testing.T) {
}, },
{ {
Name: "TEST_PORT_8080_TCP_ADDR", Name: "TEST_PORT_8080_TCP_ADDR",
Value: "machine", Value: "1.2.3.4",
},
{
Name: "SERVICE_HOST",
Value: "machine",
}, },
} }
if len(container.Env) != len(envs) { if len(container.Env) != len(envs) {
...@@ -149,6 +144,7 @@ func TestMakeManifestServicesExistingEnvVar(t *testing.T) { ...@@ -149,6 +144,7 @@ func TestMakeManifestServicesExistingEnvVar(t *testing.T) {
Kind: util.IntstrInt, Kind: util.IntstrInt,
IntVal: 900, IntVal: 900,
}, },
PortalIP: "1.2.3.4",
}, },
}, },
}, },
...@@ -186,7 +182,7 @@ func TestMakeManifestServicesExistingEnvVar(t *testing.T) { ...@@ -186,7 +182,7 @@ func TestMakeManifestServicesExistingEnvVar(t *testing.T) {
}, },
{ {
Name: "TEST_SERVICE_HOST", Name: "TEST_SERVICE_HOST",
Value: "machine", Value: "1.2.3.4",
}, },
{ {
Name: "TEST_SERVICE_PORT", Name: "TEST_SERVICE_PORT",
...@@ -194,11 +190,11 @@ func TestMakeManifestServicesExistingEnvVar(t *testing.T) { ...@@ -194,11 +190,11 @@ func TestMakeManifestServicesExistingEnvVar(t *testing.T) {
}, },
{ {
Name: "TEST_PORT", Name: "TEST_PORT",
Value: "tcp://machine:8080", Value: "tcp://1.2.3.4:8080",
}, },
{ {
Name: "TEST_PORT_8080_TCP", Name: "TEST_PORT_8080_TCP",
Value: "tcp://machine:8080", Value: "tcp://1.2.3.4:8080",
}, },
{ {
Name: "TEST_PORT_8080_TCP_PROTO", Name: "TEST_PORT_8080_TCP_PROTO",
...@@ -210,11 +206,7 @@ func TestMakeManifestServicesExistingEnvVar(t *testing.T) { ...@@ -210,11 +206,7 @@ func TestMakeManifestServicesExistingEnvVar(t *testing.T) {
}, },
{ {
Name: "TEST_PORT_8080_TCP_ADDR", Name: "TEST_PORT_8080_TCP_ADDR",
Value: "machine", Value: "1.2.3.4",
},
{
Name: "SERVICE_HOST",
Value: "machine",
}, },
} }
if len(container.Env) != len(envs) { if len(container.Env) != len(envs) {
......
...@@ -17,6 +17,8 @@ limitations under the License. ...@@ -17,6 +17,8 @@ limitations under the License.
package registrytest package registrytest
import ( import (
"sync"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch" "github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
...@@ -27,6 +29,7 @@ func NewServiceRegistry() *ServiceRegistry { ...@@ -27,6 +29,7 @@ func NewServiceRegistry() *ServiceRegistry {
} }
type ServiceRegistry struct { type ServiceRegistry struct {
mu sync.Mutex
List api.ServiceList List api.ServiceList
Service *api.Service Service *api.Service
Err error Err error
...@@ -39,48 +42,84 @@ type ServiceRegistry struct { ...@@ -39,48 +42,84 @@ type ServiceRegistry struct {
} }
func (r *ServiceRegistry) ListServices(ctx api.Context) (*api.ServiceList, error) { func (r *ServiceRegistry) ListServices(ctx api.Context) (*api.ServiceList, error) {
return &r.List, r.Err r.mu.Lock()
defer r.mu.Unlock()
// Return by copy to avoid data races
res := new(api.ServiceList)
*res = r.List
res.Items = append([]api.Service{}, r.List.Items...)
return res, r.Err
} }
func (r *ServiceRegistry) CreateService(ctx api.Context, svc *api.Service) error { func (r *ServiceRegistry) CreateService(ctx api.Context, svc *api.Service) error {
r.mu.Lock()
defer r.mu.Unlock()
r.Service = svc r.Service = svc
r.List.Items = append(r.List.Items, *svc) r.List.Items = append(r.List.Items, *svc)
return r.Err return r.Err
} }
func (r *ServiceRegistry) GetService(ctx api.Context, id string) (*api.Service, error) { func (r *ServiceRegistry) GetService(ctx api.Context, id string) (*api.Service, error) {
r.mu.Lock()
defer r.mu.Unlock()
r.GottenID = id r.GottenID = id
return r.Service, r.Err return r.Service, r.Err
} }
func (r *ServiceRegistry) DeleteService(ctx api.Context, id string) error { func (r *ServiceRegistry) DeleteService(ctx api.Context, id string) error {
r.mu.Lock()
defer r.mu.Unlock()
r.DeletedID = id r.DeletedID = id
r.Service = nil
return r.Err return r.Err
} }
func (r *ServiceRegistry) UpdateService(ctx api.Context, svc *api.Service) error { func (r *ServiceRegistry) UpdateService(ctx api.Context, svc *api.Service) error {
r.mu.Lock()
defer r.mu.Unlock()
r.UpdatedID = svc.ID r.UpdatedID = svc.ID
r.Service = svc
return r.Err return r.Err
} }
func (r *ServiceRegistry) WatchServices(ctx api.Context, label labels.Selector, field labels.Selector, resourceVersion string) (watch.Interface, error) { func (r *ServiceRegistry) WatchServices(ctx api.Context, label labels.Selector, field labels.Selector, resourceVersion string) (watch.Interface, error) {
r.mu.Lock()
defer r.mu.Unlock()
return nil, r.Err return nil, r.Err
} }
func (r *ServiceRegistry) ListEndpoints(ctx api.Context) (*api.EndpointsList, error) { func (r *ServiceRegistry) ListEndpoints(ctx api.Context) (*api.EndpointsList, error) {
r.mu.Lock()
defer r.mu.Unlock()
return &r.EndpointsList, r.Err return &r.EndpointsList, r.Err
} }
func (r *ServiceRegistry) GetEndpoints(ctx api.Context, id string) (*api.Endpoints, error) { func (r *ServiceRegistry) GetEndpoints(ctx api.Context, id string) (*api.Endpoints, error) {
r.mu.Lock()
defer r.mu.Unlock()
r.GottenID = id r.GottenID = id
return &r.Endpoints, r.Err return &r.Endpoints, r.Err
} }
func (r *ServiceRegistry) UpdateEndpoints(ctx api.Context, e *api.Endpoints) error { func (r *ServiceRegistry) UpdateEndpoints(ctx api.Context, e *api.Endpoints) error {
r.mu.Lock()
defer r.mu.Unlock()
r.Endpoints = *e r.Endpoints = *e
return r.Err return r.Err
} }
func (r *ServiceRegistry) WatchEndpoints(ctx api.Context, label, field labels.Selector, resourceVersion string) (watch.Interface, error) { func (r *ServiceRegistry) WatchEndpoints(ctx api.Context, label, field labels.Selector, resourceVersion string) (watch.Interface, error) {
r.mu.Lock()
defer r.mu.Unlock()
return nil, r.Err return nil, r.Err
} }
...@@ -32,7 +32,6 @@ type ipAllocator struct { ...@@ -32,7 +32,6 @@ type ipAllocator struct {
} }
// newIPAllocator creates and intializes a new ipAllocator object. // newIPAllocator creates and intializes a new ipAllocator object.
// FIXME: resync from storage at startup.
func newIPAllocator(subnet *net.IPNet) *ipAllocator { func newIPAllocator(subnet *net.IPNet) *ipAllocator {
if subnet == nil || subnet.IP == nil || subnet.Mask == nil { if subnet == nil || subnet.IP == nil || subnet.Mask == nil {
return nil return nil
......
...@@ -19,6 +19,7 @@ package service ...@@ -19,6 +19,7 @@ package service
import ( import (
"fmt" "fmt"
"math/rand" "math/rand"
"net"
"strconv" "strconv"
"strings" "strings"
...@@ -32,21 +33,49 @@ import ( ...@@ -32,21 +33,49 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch" "github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
"github.com/golang/glog"
) )
// REST adapts a service registry into apiserver's RESTStorage model. // REST adapts a service registry into apiserver's RESTStorage model.
type REST struct { type REST struct {
registry Registry registry Registry
cloud cloudprovider.Interface cloud cloudprovider.Interface
machines minion.Registry machines minion.Registry
portalMgr *ipAllocator
} }
// NewREST returns a new REST. // 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{ return &REST{
registry: registry, registry: registry,
cloud: cloud, cloud: cloud,
machines: machines, 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 ...@@ -61,9 +90,16 @@ func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Obje
srv.CreationTimestamp = util.Now() 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) { return apiserver.MakeAsync(func() (runtime.Object, error) {
// TODO: Consider moving this to a rectification loop, so that we make/remove external load balancers // TODO: Consider moving this to a rectification loop, so that we make/remove external load balancers
// correctly no matter what http operations happen. // correctly no matter what http operations happen.
srv.ProxyPort = 0
if srv.CreateExternalLoadBalancer { if srv.CreateExternalLoadBalancer {
if rs.cloud == nil { if rs.cloud == nil {
return nil, fmt.Errorf("requested an external service, but no cloud provider supplied.") 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 ...@@ -88,6 +124,9 @@ func (rs *REST) Create(ctx api.Context, obj runtime.Object) (<-chan runtime.Obje
if err != nil { if err != nil {
return nil, err 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) err := rs.registry.CreateService(ctx, srv)
if err != nil { if err != nil {
...@@ -110,6 +149,7 @@ func (rs *REST) Delete(ctx api.Context, id string) (<-chan runtime.Object, error ...@@ -110,6 +149,7 @@ func (rs *REST) Delete(ctx api.Context, id string) (<-chan runtime.Object, error
if err != nil { if err != nil {
return nil, err return nil, err
} }
rs.portalMgr.Release(net.ParseIP(service.PortalIP))
return apiserver.MakeAsync(func() (runtime.Object, error) { return apiserver.MakeAsync(func() (runtime.Object, error) {
rs.deleteExternalLoadBalancer(service) rs.deleteExternalLoadBalancer(service)
return &api.Status{Status: api.StatusSuccess}, rs.registry.DeleteService(ctx, id) return &api.Status{Status: api.StatusSuccess}, rs.registry.DeleteService(ctx, id)
...@@ -161,16 +201,13 @@ func GetServiceEnvironmentVariables(ctx api.Context, registry Registry, machine ...@@ -161,16 +201,13 @@ func GetServiceEnvironmentVariables(ctx api.Context, registry Registry, machine
for _, service := range services.Items { for _, service := range services.Items {
// Host // Host
name := makeEnvVariableName(service.ID) + "_SERVICE_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 // Port
name = makeEnvVariableName(service.ID) + "_SERVICE_PORT" name = makeEnvVariableName(service.ID) + "_SERVICE_PORT"
result = append(result, api.EnvVar{Name: name, Value: strconv.Itoa(service.Port)}) result = append(result, api.EnvVar{Name: name, Value: strconv.Itoa(service.Port)})
// Docker-compatible vars. // 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 return result, nil
} }
...@@ -183,8 +220,15 @@ func (rs *REST) Update(ctx api.Context, obj runtime.Object) (<-chan runtime.Obje ...@@ -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 nil, errors.NewInvalid("service", srv.ID, errs)
} }
return apiserver.MakeAsync(func() (runtime.Object, error) { 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 // 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 { if err != nil {
return nil, err return nil, err
} }
...@@ -234,7 +278,7 @@ func makeEnvVariableName(str string) string { ...@@ -234,7 +278,7 @@ func makeEnvVariableName(str string) string {
return strings.ToUpper(strings.Replace(str, "-", "_", -1)) 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) prefix := makeEnvVariableName(service.ID)
protocol := string(api.ProtocolTCP) protocol := string(api.ProtocolTCP)
if service.Protocol != "" { if service.Protocol != "" {
...@@ -244,11 +288,11 @@ func makeLinkVariables(service api.Service, machine string) []api.EnvVar { ...@@ -244,11 +288,11 @@ func makeLinkVariables(service api.Service, machine string) []api.EnvVar {
return []api.EnvVar{ return []api.EnvVar{
{ {
Name: prefix + "_PORT", 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, 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", Name: portPrefix + "_PROTO",
...@@ -260,7 +304,7 @@ func makeLinkVariables(service api.Service, machine string) []api.EnvVar { ...@@ -260,7 +304,7 @@ func makeLinkVariables(service api.Service, machine string) []api.EnvVar {
}, },
{ {
Name: portPrefix + "_ADDR", Name: portPrefix + "_ADDR",
Value: machine, Value: service.PortalIP,
}, },
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment