Create a LB for a K8S with the LB-IP provided by user.

pull/6/head
Abhishek Shah 2015-08-20 18:23:24 -07:00
parent bfc60709b1
commit 44ce4aa423
20 changed files with 206 additions and 32 deletions

View File

@ -13662,6 +13662,10 @@
"sessionAffinity": { "sessionAffinity": {
"type": "string", "type": "string",
"description": "Supports \"ClientIP\" and \"None\". Used to maintain session affinity. Enable client IP based session affinity. Must be ClientIP or None. Defaults to None. More info: http://releases.k8s.io/HEAD/docs/user-guide/services.md#virtual-ips-and-service-proxies" "description": "Supports \"ClientIP\" and \"None\". Used to maintain session affinity. Enable client IP based session affinity. Must be ClientIP or None. Defaults to None. More info: http://releases.k8s.io/HEAD/docs/user-guide/services.md#virtual-ips-and-service-proxies"
},
"loadBalancerIP": {
"type": "string",
"description": "Only applies to Service Type: LoadBalancer LoadBalancer will get created with the IP specified in this field. This feature depends on whether the underlying cloud-provider supports specifying the loadBalancerIP when a load balancer is created. This field will be ignored if the cloud-provider does not support the feature."
} }
} }
}, },

View File

@ -762,6 +762,7 @@ _kubectl_expose()
flags+=("--generator=") flags+=("--generator=")
flags+=("--labels=") flags+=("--labels=")
two_word_flags+=("-l") two_word_flags+=("-l")
flags+=("--load-balancer-ip=")
flags+=("--name=") flags+=("--name=")
flags+=("--no-headers") flags+=("--no-headers")
flags+=("--output=") flags+=("--output=")

View File

@ -50,6 +50,10 @@ re\-use the labels from the resource it exposes.
\fB\-l\fP, \fB\-\-labels\fP="" \fB\-l\fP, \fB\-\-labels\fP=""
Labels to apply to the service created by this call. Labels to apply to the service created by this call.
.PP
\fB\-\-load\-balancer\-ip\fP=""
IP to assign to to the Load Balancer. If empty, an ephemeral IP will be created and used(cloud\-provider specific).
.PP .PP
\fB\-\-name\fP="" \fB\-\-name\fP=""
The name for the newly created object. The name for the newly created object.

View File

@ -73,6 +73,7 @@ $ kubectl expose rc streamer --port=4100 --protocol=udp --name=video-stream
-f, --filename=[]: Filename, directory, or URL to a file identifying the resource to expose a service -f, --filename=[]: Filename, directory, or URL to a file identifying the resource to expose a service
--generator="service/v2": The name of the API generator to use. There are 2 generators: 'service/v1' and 'service/v2'. The only difference between them is that service port in v1 is named 'default', while it is left unnamed in v2. Default is 'service/v2'. --generator="service/v2": The name of the API generator to use. There are 2 generators: 'service/v1' and 'service/v2'. The only difference between them is that service port in v1 is named 'default', while it is left unnamed in v2. Default is 'service/v2'.
-l, --labels="": Labels to apply to the service created by this call. -l, --labels="": Labels to apply to the service created by this call.
--load-balancer-ip="": IP to assign to to the Load Balancer. If empty, an ephemeral IP will be created and used(cloud-provider specific).
--name="": The name for the newly created object. --name="": The name for the newly created object.
--no-headers[=false]: When using the default output, don't print headers. --no-headers[=false]: When using the default output, don't print headers.
-o, --output="": Output format. One of: json|yaml|wide|name|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=... See golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [http://releases.k8s.io/HEAD/docs/user-guide/jsonpath.md]. -o, --output="": Output format. One of: json|yaml|wide|name|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=... See golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [http://releases.k8s.io/HEAD/docs/user-guide/jsonpath.md].

View File

@ -433,6 +433,7 @@ information about the provisioned balancer will be published in the `Service`'s
} }
], ],
"clusterIP": "10.0.171.239", "clusterIP": "10.0.171.239",
"loadBalancerIP": "78.11.24.19",
"type": "LoadBalancer" "type": "LoadBalancer"
}, },
"status": { "status": {
@ -448,7 +449,11 @@ information about the provisioned balancer will be published in the `Service`'s
``` ```
Traffic from the external load balancer will be directed at the backend `Pods`, Traffic from the external load balancer will be directed at the backend `Pods`,
though exactly how that works depends on the cloud provider. though exactly how that works depends on the cloud provider. Some cloud providers allow
the `loadBalancerIP` to be specified. In those cases, the load-balancer will be created
with the user-specified `loadBalancerIP`. If the `loadBalancerIP` field is not specified,
an ephemeral IP will be assigned to the loadBalancer. If the `loadBalancerIP` is specified, but the
cloud provider does not support the feature, the field will be ignored.
## Shortcomings ## Shortcomings

View File

@ -138,6 +138,7 @@ kube-master
label-columns label-columns
last-release-pr last-release-pr
legacy-userspace-proxy legacy-userspace-proxy
load-balancer-ip
log-flush-frequency log-flush-frequency
long-running-request-regexp long-running-request-regexp
low-diskspace-threshold-mb low-diskspace-threshold-mb

View File

@ -1958,6 +1958,7 @@ func deepCopy_api_ServicePort(in ServicePort, out *ServicePort, c *conversion.Cl
} }
func deepCopy_api_ServiceSpec(in ServiceSpec, out *ServiceSpec, c *conversion.Cloner) error { func deepCopy_api_ServiceSpec(in ServiceSpec, out *ServiceSpec, c *conversion.Cloner) error {
out.Type = in.Type
if in.Ports != nil { if in.Ports != nil {
out.Ports = make([]ServicePort, len(in.Ports)) out.Ports = make([]ServicePort, len(in.Ports))
for i := range in.Ports { for i := range in.Ports {
@ -1977,7 +1978,6 @@ func deepCopy_api_ServiceSpec(in ServiceSpec, out *ServiceSpec, c *conversion.Cl
out.Selector = nil out.Selector = nil
} }
out.ClusterIP = in.ClusterIP out.ClusterIP = in.ClusterIP
out.Type = in.Type
if in.ExternalIPs != nil { if in.ExternalIPs != nil {
out.ExternalIPs = make([]string, len(in.ExternalIPs)) out.ExternalIPs = make([]string, len(in.ExternalIPs))
for i := range in.ExternalIPs { for i := range in.ExternalIPs {
@ -1986,6 +1986,7 @@ func deepCopy_api_ServiceSpec(in ServiceSpec, out *ServiceSpec, c *conversion.Cl
} else { } else {
out.ExternalIPs = nil out.ExternalIPs = nil
} }
out.LoadBalancerIP = in.LoadBalancerIP
out.SessionAffinity = in.SessionAffinity out.SessionAffinity = in.SessionAffinity
return nil return nil
} }

View File

@ -1185,6 +1185,9 @@ type LoadBalancerIngress struct {
// ServiceSpec describes the attributes that a user creates on a service // ServiceSpec describes the attributes that a user creates on a service
type ServiceSpec struct { type ServiceSpec struct {
// Type determines how the service will be exposed. Valid options: ClusterIP, NodePort, LoadBalancer
Type ServiceType `json:"type,omitempty"`
// Required: The list of ports that are exposed by this service. // Required: The list of ports that are exposed by this service.
Ports []ServicePort `json:"ports"` Ports []ServicePort `json:"ports"`
@ -1200,13 +1203,17 @@ type ServiceSpec struct {
// None can be specified for headless services when proxying is not required // None can be specified for headless services when proxying is not required
ClusterIP string `json:"clusterIP,omitempty"` ClusterIP string `json:"clusterIP,omitempty"`
// Type determines how the service will be exposed. Valid options: ClusterIP, NodePort, LoadBalancer
Type ServiceType `json:"type,omitempty"`
// ExternalIPs are used by external load balancers, or can be set by // ExternalIPs are used by external load balancers, or can be set by
// users to handle external traffic that arrives at a node. // users to handle external traffic that arrives at a node.
ExternalIPs []string `json:"externalIPs,omitempty"` ExternalIPs []string `json:"externalIPs,omitempty"`
// Only applies to Service Type: LoadBalancer
// LoadBalancer will get created with the IP specified in this field.
// This feature depends on whether the underlying cloud-provider supports specifying
// the loadBalancerIP when a load balancer is created.
// This field will be ignored if the cloud-provider does not support the feature.
LoadBalancerIP string `json:"loadBalancerIP,omitempty"`
// Required: Supports "ClientIP" and "None". Used to maintain session affinity. // Required: Supports "ClientIP" and "None". Used to maintain session affinity.
SessionAffinity ServiceAffinity `json:"sessionAffinity,omitempty"` SessionAffinity ServiceAffinity `json:"sessionAffinity,omitempty"`
} }

View File

@ -2172,6 +2172,7 @@ func convert_api_ServiceSpec_To_v1_ServiceSpec(in *api.ServiceSpec, out *Service
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*api.ServiceSpec))(in) defaulting.(func(*api.ServiceSpec))(in)
} }
out.Type = ServiceType(in.Type)
if in.Ports != nil { if in.Ports != nil {
out.Ports = make([]ServicePort, len(in.Ports)) out.Ports = make([]ServicePort, len(in.Ports))
for i := range in.Ports { for i := range in.Ports {
@ -2191,7 +2192,6 @@ func convert_api_ServiceSpec_To_v1_ServiceSpec(in *api.ServiceSpec, out *Service
out.Selector = nil out.Selector = nil
} }
out.ClusterIP = in.ClusterIP out.ClusterIP = in.ClusterIP
out.Type = ServiceType(in.Type)
if in.ExternalIPs != nil { if in.ExternalIPs != nil {
out.ExternalIPs = make([]string, len(in.ExternalIPs)) out.ExternalIPs = make([]string, len(in.ExternalIPs))
for i := range in.ExternalIPs { for i := range in.ExternalIPs {
@ -2200,6 +2200,7 @@ func convert_api_ServiceSpec_To_v1_ServiceSpec(in *api.ServiceSpec, out *Service
} else { } else {
out.ExternalIPs = nil out.ExternalIPs = nil
} }
out.LoadBalancerIP = in.LoadBalancerIP
out.SessionAffinity = ServiceAffinity(in.SessionAffinity) out.SessionAffinity = ServiceAffinity(in.SessionAffinity)
return nil return nil
} }
@ -4603,6 +4604,7 @@ func convert_v1_ServiceSpec_To_api_ServiceSpec(in *ServiceSpec, out *api.Service
out.ExternalIPs = nil out.ExternalIPs = nil
} }
out.SessionAffinity = api.ServiceAffinity(in.SessionAffinity) out.SessionAffinity = api.ServiceAffinity(in.SessionAffinity)
out.LoadBalancerIP = in.LoadBalancerIP
return nil return nil
} }

View File

@ -1992,6 +1992,7 @@ func deepCopy_v1_ServiceSpec(in ServiceSpec, out *ServiceSpec, c *conversion.Clo
out.ExternalIPs = nil out.ExternalIPs = nil
} }
out.SessionAffinity = in.SessionAffinity out.SessionAffinity = in.SessionAffinity
out.LoadBalancerIP = in.LoadBalancerIP
return nil return nil
} }

View File

@ -1509,6 +1509,13 @@ type ServiceSpec struct {
// Defaults to None. // Defaults to None.
// More info: http://releases.k8s.io/HEAD/docs/user-guide/services.md#virtual-ips-and-service-proxies // More info: http://releases.k8s.io/HEAD/docs/user-guide/services.md#virtual-ips-and-service-proxies
SessionAffinity ServiceAffinity `json:"sessionAffinity,omitempty"` SessionAffinity ServiceAffinity `json:"sessionAffinity,omitempty"`
// Only applies to Service Type: LoadBalancer
// LoadBalancer will get created with the IP specified in this field.
// This feature depends on whether the underlying cloud-provider supports specifying
// the loadBalancerIP when a load balancer is created.
// This field will be ignored if the cloud-provider does not support the feature.
LoadBalancerIP string `json:"loadBalancerIP,omitempty"`
} }
// ServicePort conatins information on service's port. // ServicePort conatins information on service's port.

View File

@ -1272,6 +1272,7 @@ var map_ServiceSpec = map[string]string{
"type": "Type of exposed service. Must be ClusterIP, NodePort, or LoadBalancer. Defaults to ClusterIP. More info: http://releases.k8s.io/HEAD/docs/user-guide/services.md#external-services", "type": "Type of exposed service. Must be ClusterIP, NodePort, or LoadBalancer. Defaults to ClusterIP. More info: http://releases.k8s.io/HEAD/docs/user-guide/services.md#external-services",
"externalIPs": "ExternalIPs are used by external load balancers, or can be set by users to handle external traffic that arrives at a node. Externally visible IPs (e.g. load balancers) that should be proxied to this service.", "externalIPs": "ExternalIPs are used by external load balancers, or can be set by users to handle external traffic that arrives at a node. Externally visible IPs (e.g. load balancers) that should be proxied to this service.",
"sessionAffinity": "Supports \"ClientIP\" and \"None\". Used to maintain session affinity. Enable client IP based session affinity. Must be ClientIP or None. Defaults to None. More info: http://releases.k8s.io/HEAD/docs/user-guide/services.md#virtual-ips-and-service-proxies", "sessionAffinity": "Supports \"ClientIP\" and \"None\". Used to maintain session affinity. Enable client IP based session affinity. Must be ClientIP or None. Defaults to None. More info: http://releases.k8s.io/HEAD/docs/user-guide/services.md#virtual-ips-and-service-proxies",
"loadBalancerIP": "Only applies to Service Type: LoadBalancer LoadBalancer will get created with the IP specified in this field. This feature depends on whether the underlying cloud-provider supports specifying the loadBalancerIP when a load balancer is created. This field will be ignored if the cloud-provider does not support the feature.",
} }
func (ServiceSpec) SwaggerDoc() map[string]string { func (ServiceSpec) SwaggerDoc() map[string]string {

View File

@ -81,7 +81,7 @@ type TCPLoadBalancer interface {
// if so, what its status is. // if so, what its status is.
GetTCPLoadBalancer(name, region string) (status *api.LoadBalancerStatus, exists bool, err error) GetTCPLoadBalancer(name, region string) (status *api.LoadBalancerStatus, exists bool, err error)
// EnsureTCPLoadBalancer creates a new tcp load balancer, or updates an existing one. Returns the status of the balancer // EnsureTCPLoadBalancer creates a new tcp load balancer, or updates an existing one. Returns the status of the balancer
EnsureTCPLoadBalancer(name, region string, externalIP net.IP, ports []*api.ServicePort, hosts []string, affinityType api.ServiceAffinity) (*api.LoadBalancerStatus, error) EnsureTCPLoadBalancer(name, region string, loadBalancerIP net.IP, ports []*api.ServicePort, hosts []string, affinityType api.ServiceAffinity) (*api.LoadBalancerStatus, error)
// UpdateTCPLoadBalancer updates hosts under the specified load balancer. // UpdateTCPLoadBalancer updates hosts under the specified load balancer.
UpdateTCPLoadBalancer(name, region string, hosts []string) error UpdateTCPLoadBalancer(name, region string, hosts []string) error
// EnsureTCPLoadBalancerDeleted deletes the specified load balancer if it // EnsureTCPLoadBalancerDeleted deletes the specified load balancer if it

View File

@ -352,7 +352,7 @@ func makeFirewallName(name string) string {
// EnsureTCPLoadBalancer is an implementation of TCPLoadBalancer.EnsureTCPLoadBalancer. // EnsureTCPLoadBalancer is an implementation of TCPLoadBalancer.EnsureTCPLoadBalancer.
// TODO(a-robinson): Don't just ignore specified IP addresses. Check if they're // TODO(a-robinson): Don't just ignore specified IP addresses. Check if they're
// owned by the project and available to be used, and use them if they are. // owned by the project and available to be used, and use them if they are.
func (gce *GCECloud) EnsureTCPLoadBalancer(name, region string, externalIP net.IP, ports []*api.ServicePort, hosts []string, affinityType api.ServiceAffinity) (*api.LoadBalancerStatus, error) { func (gce *GCECloud) EnsureTCPLoadBalancer(name, region string, loadBalancerIP net.IP, ports []*api.ServicePort, hosts []string, affinityType api.ServiceAffinity) (*api.LoadBalancerStatus, error) {
if len(hosts) == 0 { if len(hosts) == 0 {
return nil, fmt.Errorf("Cannot EnsureTCPLoadBalancer() with no hosts") return nil, fmt.Errorf("Cannot EnsureTCPLoadBalancer() with no hosts")
} }
@ -399,6 +399,10 @@ func (gce *GCECloud) EnsureTCPLoadBalancer(name, region string, externalIP net.I
PortRange: fmt.Sprintf("%d-%d", minPort, maxPort), PortRange: fmt.Sprintf("%d-%d", minPort, maxPort),
Target: gce.targetPoolURL(name, region), Target: gce.targetPoolURL(name, region),
} }
if loadBalancerIP != nil {
req.IPAddress = loadBalancerIP.String()
}
op, err := gce.service.ForwardingRules.Insert(gce.projectID, region, req).Do() op, err := gce.service.ForwardingRules.Insert(gce.projectID, region, req).Do()
if err != nil && !isHTTPErrorCode(err, http.StatusConflict) { if err != nil && !isHTTPErrorCode(err, http.StatusConflict) {
return nil, err return nil, err

View File

@ -525,8 +525,8 @@ func (lb *LoadBalancer) GetTCPLoadBalancer(name, region string) (*api.LoadBalanc
// a list of regions (from config) and query/create loadbalancers in // a list of regions (from config) and query/create loadbalancers in
// each region. // each region.
func (lb *LoadBalancer) EnsureTCPLoadBalancer(name, region string, externalIP net.IP, ports []*api.ServicePort, hosts []string, affinity api.ServiceAffinity) (*api.LoadBalancerStatus, error) { func (lb *LoadBalancer) EnsureTCPLoadBalancer(name, region string, loadBalancerIP net.IP, ports []*api.ServicePort, hosts []string, affinity api.ServiceAffinity) (*api.LoadBalancerStatus, error) {
glog.V(4).Infof("EnsureTCPLoadBalancer(%v, %v, %v, %v, %v, %v)", name, region, externalIP, ports, hosts, affinity) glog.V(4).Infof("EnsureTCPLoadBalancer(%v, %v, %v, %v, %v, %v)", name, region, loadBalancerIP, ports, hosts, affinity)
if len(ports) > 1 { if len(ports) > 1 {
return nil, fmt.Errorf("multiple ports are not yet supported in openstack load balancers") return nil, fmt.Errorf("multiple ports are not yet supported in openstack load balancers")
@ -618,8 +618,8 @@ func (lb *LoadBalancer) EnsureTCPLoadBalancer(name, region string, externalIP ne
SubnetID: lb.opts.SubnetId, SubnetID: lb.opts.SubnetId,
Persistence: persistence, Persistence: persistence,
} }
if externalIP != nil { if loadBalancerIP != nil {
createOpts.Address = externalIP.String() createOpts.Address = loadBalancerIP.String()
} }
vip, err := vips.Create(lb.network, createOpts).Extract() vip, err := vips.Create(lb.network, createOpts).Extract()

View File

@ -378,28 +378,14 @@ func (s *ServiceController) createExternalLoadBalancer(service *api.Service) err
return err return err
} }
name := s.loadBalancerName(service) name := s.loadBalancerName(service)
if len(service.Spec.ExternalIPs) > 0 { status, err := s.balancer.EnsureTCPLoadBalancer(name, s.zone.Region, net.ParseIP(service.Spec.LoadBalancerIP),
for _, publicIP := range service.Spec.ExternalIPs { ports, hostsFromNodeList(&nodes), service.Spec.SessionAffinity)
// TODO: Make this actually work for multiple IPs by using different if err != nil {
// names for each. For now, we'll just create the first and break. return err
status, err := s.balancer.EnsureTCPLoadBalancer(name, s.zone.Region, net.ParseIP(publicIP),
ports, hostsFromNodeList(&nodes), service.Spec.SessionAffinity)
if err != nil {
return err
} else {
service.Status.LoadBalancer = *status
}
break
}
} else { } else {
status, err := s.balancer.EnsureTCPLoadBalancer(name, s.zone.Region, nil, service.Status.LoadBalancer = *status
ports, hostsFromNodeList(&nodes), service.Spec.SessionAffinity)
if err != nil {
return err
} else {
service.Status.LoadBalancer = *status
}
} }
return nil return nil
} }
@ -477,6 +463,9 @@ func needsUpdate(oldService *api.Service, newService *api.Service) bool {
if !portsEqualForLB(oldService, newService) || oldService.Spec.SessionAffinity != newService.Spec.SessionAffinity { if !portsEqualForLB(oldService, newService) || oldService.Spec.SessionAffinity != newService.Spec.SessionAffinity {
return true return true
} }
if !loadBalancerIPsAreEqual(oldService, newService) {
return true
}
if len(oldService.Spec.ExternalIPs) != len(newService.Spec.ExternalIPs) { if len(oldService.Spec.ExternalIPs) != len(newService.Spec.ExternalIPs) {
return true return true
} }
@ -689,3 +678,7 @@ func (s *ServiceController) lockedUpdateLoadBalancerHosts(service *api.Service,
func wantsExternalLoadBalancer(service *api.Service) bool { func wantsExternalLoadBalancer(service *api.Service) bool {
return service.Spec.Type == api.ServiceTypeLoadBalancer return service.Spec.Type == api.ServiceTypeLoadBalancer
} }
func loadBalancerIPsAreEqual(oldService, newService *api.Service) bool {
return oldService.Spec.LoadBalancerIP == newService.Spec.LoadBalancerIP
}

View File

@ -75,6 +75,7 @@ func NewCmdExposeService(f *cmdutil.Factory, out io.Writer) *cobra.Command {
// TODO: remove create-external-load-balancer in code on or after Aug 25, 2016. // TODO: remove create-external-load-balancer in code on or after Aug 25, 2016.
cmd.Flags().Bool("create-external-load-balancer", false, "If true, create an external load balancer for this service (trumped by --type). Implementation is cloud provider dependent. Default is 'false'.") cmd.Flags().Bool("create-external-load-balancer", false, "If true, create an external load balancer for this service (trumped by --type). Implementation is cloud provider dependent. Default is 'false'.")
cmd.Flags().MarkDeprecated("create-external-load-balancer", "use --type=\"LoadBalancer\" instead") cmd.Flags().MarkDeprecated("create-external-load-balancer", "use --type=\"LoadBalancer\" instead")
cmd.Flags().String("load-balancer-ip", "", "IP to assign to to the Load Balancer. If empty, an ephemeral IP will be created and used(cloud-provider specific).")
cmd.Flags().String("selector", "", "A label selector to use for this service. If empty (the default) infer the selector from the replication controller.") cmd.Flags().String("selector", "", "A label selector to use for this service. If empty (the default) infer the selector from the replication controller.")
cmd.Flags().StringP("labels", "l", "", "Labels to apply to the service created by this call.") cmd.Flags().StringP("labels", "l", "", "Labels to apply to the service created by this call.")
cmd.Flags().Bool("dry-run", false, "If true, only print the object that would be sent, without creating it.") cmd.Flags().Bool("dry-run", false, "If true, only print the object that would be sent, without creating it.")

View File

@ -55,6 +55,7 @@ func paramNames() []GeneratorParam {
{"labels", false}, {"labels", false},
{"external-ip", false}, {"external-ip", false},
{"create-external-load-balancer", false}, {"create-external-load-balancer", false},
{"load-balancer-ip", false},
{"type", false}, {"type", false},
{"protocol", false}, {"protocol", false},
{"container-port", false}, // alias of target-port {"container-port", false}, // alias of target-port
@ -149,6 +150,9 @@ func generate(genericParams map[string]interface{}) (runtime.Object, error) {
if len(params["type"]) != 0 { if len(params["type"]) != 0 {
service.Spec.Type = api.ServiceType(params["type"]) service.Spec.Type = api.ServiceType(params["type"])
} }
if service.Spec.Type == api.ServiceTypeLoadBalancer {
service.Spec.LoadBalancerIP = params["load-balancer-ip"]
}
if len(params["session-affinity"]) != 0 { if len(params["session-affinity"]) != 0 {
switch api.ServiceAffinity(params["session-affinity"]) { switch api.ServiceAffinity(params["session-affinity"]) {
case api.ServiceAffinityNone: case api.ServiceAffinityNone:

View File

@ -0,0 +1,67 @@
/*
Copyright 2014 The Kubernetes Authors 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 e2e
import (
"fmt"
"github.com/golang/glog"
"os/exec"
"regexp"
"strings"
)
func createGCEStaticIP(name string) (string, error) {
// gcloud compute --project "abshah-kubernetes-001" addresses create "test-static-ip" --region "us-central1"
// abshah@abhidesk:~/go/src/code.google.com/p/google-api-go-client/compute/v1$ gcloud compute --project "abshah-kubernetes-001" addresses create "test-static-ip" --region "us-central1"
// Created [https://www.googleapis.com/compute/v1/projects/abshah-kubernetes-001/regions/us-central1/addresses/test-static-ip].
// NAME REGION ADDRESS STATUS
// test-static-ip us-central1 104.197.143.7 RESERVED
output, err := exec.Command("gcloud", "compute", "addresses", "create",
name, "--project", testContext.CloudConfig.ProjectID,
"--region", "us-central1", "-q").CombinedOutput()
if err != nil {
return "", err
}
glog.Errorf("Creating static IP with name:%s in project: %s", name, testContext.CloudConfig.ProjectID)
text := string(output)
if strings.Contains(text, "RESERVED") {
r, _ := regexp.Compile("[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+")
staticIP := r.FindString(text)
if staticIP == "" {
glog.Errorf("Static IP creation output is \n %s", text)
return "", fmt.Errorf("Static IP not found in gcloud compute command output")
} else {
return staticIP, nil
}
} else {
return "", fmt.Errorf("Static IP Could not be reserved.")
}
}
func deleteGCEStaticIP(name string) error {
// gcloud compute --project "abshah-kubernetes-001" addresses create "test-static-ip" --region "us-central1"
// abshah@abhidesk:~/go/src/code.google.com/p/google-api-go-client/compute/v1$ gcloud compute --project "abshah-kubernetes-001" addresses create "test-static-ip" --region "us-central1"
// Created [https://www.googleapis.com/compute/v1/projects/abshah-kubernetes-001/regions/us-central1/addresses/test-static-ip].
// NAME REGION ADDRESS STATUS
// test-static-ip us-central1 104.197.143.7 RESERVED
_, err := exec.Command("gcloud", "compute", "addresses", "delete",
name, "--project", testContext.CloudConfig.ProjectID,
"--region", "us-central1", "-q").CombinedOutput()
return err
}

View File

@ -26,6 +26,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/golang/glog"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
@ -418,6 +419,75 @@ var _ = Describe("Services", func() {
testLoadBalancerReachable(ingress, inboundPort) testLoadBalancerReachable(ingress, inboundPort)
}) })
It("should be able to create a functioning external load balancer with user-provided load balancer ip", func() {
// requires ExternalLoadBalancer
SkipUnlessProviderIs("gce", "gke")
serviceName := "lb-test-with-user-ip"
ns := namespaces[0]
t := NewWebserverTest(c, ns, serviceName)
defer func() {
defer GinkgoRecover()
errs := t.Cleanup()
if len(errs) != 0 {
Failf("errors in cleanup: %v", errs)
}
}()
inboundPort := 3000
service := t.BuildServiceSpec()
service.Spec.Type = api.ServiceTypeLoadBalancer
service.Spec.Ports[0].Port = inboundPort
service.Spec.Ports[0].TargetPort = util.NewIntOrStringFromInt(80)
By("creating an external static ip")
rand.Seed(time.Now().UTC().UnixNano())
staticIPName := fmt.Sprintf("e2e-external-lb-test-%d", rand.Intn(65535))
glog.Errorf("static ip name is %s", staticIPName)
loadBalancerIP, err := createGCEStaticIP(staticIPName)
Expect(err).NotTo(HaveOccurred())
defer func() {
deleteGCEStaticIP(staticIPName)
}()
service.Spec.LoadBalancerIP = loadBalancerIP
By("creating service " + serviceName + " with external load balancer in namespace " + ns)
result, err := t.CreateService(service)
Expect(err).NotTo(HaveOccurred())
// Wait for the load balancer to be created asynchronously, which is
// currently indicated by ingress point(s) being added to the status.
result, err = waitForLoadBalancerIngress(c, serviceName, ns)
Expect(err).NotTo(HaveOccurred())
if len(result.Status.LoadBalancer.Ingress) != 1 {
Failf("got unexpected number (%v) of ingress points for externally load balanced service: %v", result.Status.LoadBalancer.Ingress, result)
}
ingress := result.Status.LoadBalancer.Ingress[0]
Expect(ingress.IP).To(Equal(loadBalancerIP))
if len(result.Spec.Ports) != 1 {
Failf("got unexpected len(Spec.Ports) for LoadBalancer service: %v", result)
}
port := result.Spec.Ports[0]
if port.NodePort == 0 {
Failf("got unexpected Spec.Ports[0].nodePort for LoadBalancer service: %v", result)
}
if !ServiceNodePortRange.Contains(port.NodePort) {
Failf("got unexpected (out-of-range) port for LoadBalancer service: %v", result)
}
By("creating pod to be part of service " + serviceName)
t.CreateWebserverRC(1)
By("hitting the pod through the service's NodePort")
testReachable(pickMinionIP(c), port.NodePort)
By("hitting the pod through the service's external load balancer")
testLoadBalancerReachable(ingress, inboundPort)
})
It("should be able to create a functioning NodePort service", func() { It("should be able to create a functioning NodePort service", func() {
serviceName := "nodeportservice-test" serviceName := "nodeportservice-test"
ns := namespaces[0] ns := namespaces[0]