Skip to content

Commit

Permalink
Merge pull request kubernetes#135 from brendandburns/lb
Browse files Browse the repository at this point in the history
Add load balancing support to services.
  • Loading branch information
lavalamp committed Jun 18, 2014
2 parents 1fd954b + 2759b23 commit 246db91
Show file tree
Hide file tree
Showing 282 changed files with 380,401 additions and 122 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@

# Go test binaries
*.test

# Mercurial files
**/.hg
**/.hg*
8 changes: 8 additions & 0 deletions api/examples/external-service.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"id": "example",
"port": 8000,
"labels": {
"name": "nginx"
},
"createExternalLoadBalancer": true
}
2 changes: 1 addition & 1 deletion api/examples/service.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"id": "example2",
"id": "example",
"port": 8000,
"labels": {
"name": "nginx"
Expand Down
2 changes: 1 addition & 1 deletion cluster/kube-up.sh
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ gcutil addinstance ${MASTER_NAME}\
--image ${IMAGE} \
--tags ${MASTER_TAG} \
--network ${NETWORK} \
--service_account_scopes="storage-ro" \
--service_account_scopes="storage-ro,compute-rw" \
--automatic_restart \
--metadata_from_file startup-script:${KUBE_TEMP}/master-start.sh &

Expand Down
2 changes: 1 addition & 1 deletion cluster/saltbase/salt/apiserver/initd
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="The Kubernetes API server"
NAME=apiserver
DAEMON=/usr/local/bin/apiserver
DAEMON_ARGS=""
DAEMON_ARGS="-cloud_provider gce "
DAEMON_LOG_FILE=/var/log/$NAME.log
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME
Expand Down
23 changes: 20 additions & 3 deletions cmd/apiserver/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"net"
"strconv"

"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
"github.com/GoogleCloudPlatform/kubernetes/pkg/master"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
)
Expand All @@ -32,6 +33,7 @@ var (
port = flag.Uint("port", 8080, "The port to listen on. Default 8080.")
address = flag.String("address", "127.0.0.1", "The address on the local server to listen to. Default 127.0.0.1")
apiPrefix = flag.String("api_prefix", "/api/v1beta1", "The prefix for API requests on the server. Default '/api/v1beta1'")
cloudProvider = flag.String("cloud_provider", "", "The provider for cloud services. Empty string for no provider.")
etcdServerList, machineList util.StringList
)

Expand All @@ -47,12 +49,27 @@ func main() {
log.Fatal("No machines specified!")
}

var m *master.Master
var cloud cloudprovider.Interface
switch *cloudProvider {
case "gce":
var err error
cloud, err = cloudprovider.NewGCECloud()
if err != nil {
log.Fatal("Couldn't connect to GCE cloud: %#v", err)
}
default:
if len(*cloudProvider) > 0 {
log.Printf("Unknown cloud provider: %s", *cloudProvider)
} else {
log.Print("No cloud provider specified.")
}
}

var m *master.Master
if len(etcdServerList) > 0 {
m = master.New(etcdServerList, machineList)
m = master.New(etcdServerList, machineList, cloud)
} else {
m = master.NewMemoryServer(machineList)
m = master.NewMemoryServer(machineList, cloud)
}

log.Fatal(m.Run(net.JoinHostPort(*address, strconv.Itoa(int(*port))), *apiPrefix))
Expand Down
2 changes: 1 addition & 1 deletion cmd/localkube/localkube.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func fake_kubelet() {

// Starts api services (the master). Never returns.
func api_server() {
m := master.New([]string{*etcd_server}, []string{*kubelet_address})
m := master.New([]string{*etcd_server}, []string{*kubelet_address}, nil)
log.Fatal(m.Run(net.JoinHostPort(*master_address, strconv.Itoa(int(*master_port))), *apiPrefix))
}

Expand Down
2 changes: 1 addition & 1 deletion examples/guestbook/frontend-controller.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "frontendController",
"desiredState": {
"replicas": 3,
"replicas": 1,
"replicasInSet": {"name": "frontend"},
"podTemplate": {
"desiredState": {
Expand Down
7 changes: 4 additions & 3 deletions pkg/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,10 @@ type ServiceList struct {
// Defines a service abstraction by a name (for example, mysql) consisting of local port
// (for example 3306) that the proxy listens on, and the labels that define the service.
type Service struct {
JSONBase `json:",inline" yaml:",inline"`
Port int `json:"port,omitempty" yaml:"port,omitempty"`
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
JSONBase `json:",inline" yaml:",inline"`
Port int `json:"port,omitempty" yaml:"port,omitempty"`
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
CreateExternalLoadBalancer bool `json:"createExternalLoadBalancer,omitempty" yaml:"createExternalLoadBalancer,omitempty"`
}

// Defines the endpoints that implement the actual service, for example:
Expand Down
8 changes: 6 additions & 2 deletions pkg/apiserver/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,12 @@ func (server *ApiServer) handleREST(parts []string, requestUrl *url.URL, req *ht
server.error(err, w)
return
}
storage.Create(obj)
server.write(200, obj, w)
err = storage.Create(obj)
if err != nil {
server.error(err, w)
} else {
server.write(200, obj, w)
}
return
case "DELETE":
if len(parts) != 2 {
Expand Down
31 changes: 31 additions & 0 deletions pkg/cloudprovider/cloud.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
Copyright 2014 Google Inc. All rights reserved.
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 cloudprovider

// CloudInterface is an abstract, pluggable interface for cloud providers
type Interface interface {
// TCPLoadBalancer returns a balancer interface, or nil if none is supported. Returns an error if one occurs.
TCPLoadBalancer() (TCPLoadBalancer, error)
}

type TCPLoadBalancer interface {
// TODO: Break this up into different interfaces (LB, etc) when we have more than one type of service
TCPLoadBalancerExists(name, region string) (bool, error)
CreateTCPLoadBalancer(name, region string, port int, hosts []string) error
UpdateTCPLoadBalancer(name, region string, hosts []string) error
DeleteTCPLoadBalancer(name, region string) error
}
20 changes: 20 additions & 0 deletions pkg/cloudprovider/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
Copyright 2014 Google Inc. All rights reserved.
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 cloudprovider supplies interfaces and implementations for cloud service providers
package cloudprovider

import ()
164 changes: 164 additions & 0 deletions pkg/cloudprovider/gce.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*
Copyright 2014 Google Inc. All rights reserved.
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 cloudprovider

import (
"fmt"
"io/ioutil"
"net/http"
"strconv"
"strings"
"time"

"code.google.com/p/goauth2/compute/serviceaccount"
compute "code.google.com/p/google-api-go-client/compute/v1"
)

type GCECloud struct {
service *compute.Service
projectID string
zone string
}

func getProjectAndZone() (string, string, error) {
client := http.Client{}
url := "http://metadata/computeMetadata/v1/instance/zone"
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return "", "", err
}
req.Header.Add("X-Google-Metadata-Request", "True")
res, err := client.Do(req)
if err != nil {
return "", "", err
}
defer res.Body.Close()
data, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", "", err
}
parts := strings.Split(string(data), "/")
if len(parts) != 4 {
return "", "", fmt.Errorf("Unexpected response: %s", string(data))
}
return parts[1], parts[3], nil
}

func NewGCECloud() (*GCECloud, error) {
projectID, zone, err := getProjectAndZone()
if err != nil {
return nil, err
}
client, err := serviceaccount.NewClient(&serviceaccount.Options{})
if err != nil {
return nil, err
}
svc, err := compute.New(client)
if err != nil {
return nil, err
}
return &GCECloud{
service: svc,
projectID: projectID,
zone: zone,
}, nil
}

func (gce *GCECloud) TCPLoadBalancer() (TCPLoadBalancer, error) {
return gce, nil
}

func makeHostLink(projectID, zone, host string) string {
ix := strings.Index(host, ".")
if ix != -1 {
host = host[:ix]
}
return fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s/zones/%s/instances/%s",
projectID, zone, host)
}

func (gce *GCECloud) makeTargetPool(name, region string, hosts []string) (string, error) {
var instances []string
for _, host := range hosts {
instances = append(instances, makeHostLink(gce.projectID, gce.zone, host))
}
pool := &compute.TargetPool{
Name: name,
Instances: instances,
}
_, err := gce.service.TargetPools.Insert(gce.projectID, region, pool).Do()
if err != nil {
return "", err
}
link := fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s/regions/%s/targetPools/%s", gce.projectID, region, name)
return link, nil
}

func (gce *GCECloud) waitForRegionOp(op *compute.Operation, region string) error {
pollOp := op
for pollOp.Status != "DONE" {
var err error
time.Sleep(time.Second * 10)
pollOp, err = gce.service.RegionOperations.Get(gce.projectID, region, op.Name).Do()
if err != nil {
return err
}
}
return nil
}

func (gce *GCECloud) TCPLoadBalancerExists(name, region string) (bool, error) {
_, err := gce.service.ForwardingRules.Get(gce.projectID, region, name).Do()
return false, err
}

func (gce *GCECloud) CreateTCPLoadBalancer(name, region string, port int, hosts []string) error {
pool, err := gce.makeTargetPool(name, region, hosts)
if err != nil {
return err
}
req := &compute.ForwardingRule{
Name: name,
IPProtocol: "TCP",
PortRange: strconv.Itoa(port),
Target: pool,
}
_, err = gce.service.ForwardingRules.Insert(gce.projectID, region, req).Do()
return err
}

func (gce *GCECloud) UpdateTCPLoadBalancer(name, region string, hosts []string) error {
var refs []*compute.InstanceReference
for _, host := range hosts {
refs = append(refs, &compute.InstanceReference{host})
}
req := &compute.TargetPoolsAddInstanceRequest{
Instances: refs,
}

_, err := gce.service.TargetPools.AddInstance(gce.projectID, region, name, req).Do()
return err
}

func (gce *GCECloud) DeleteTCPLoadBalancer(name, region string) error {
_, err := gce.service.ForwardingRules.Delete(gce.projectID, region, name).Do()
if err != nil {
return err
}
_, err = gce.service.TargetPools.Delete(gce.projectID, region, name).Do()
return err
}
Loading

0 comments on commit 246db91

Please sign in to comment.