mirror of https://github.com/k3s-io/k3s
Create a LB for a K8S with the LB-IP provided by user.
parent
bfc60709b1
commit
44ce4aa423
|
@ -13662,6 +13662,10 @@
|
|||
"sessionAffinity": {
|
||||
"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"
|
||||
},
|
||||
"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."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -762,6 +762,7 @@ _kubectl_expose()
|
|||
flags+=("--generator=")
|
||||
flags+=("--labels=")
|
||||
two_word_flags+=("-l")
|
||||
flags+=("--load-balancer-ip=")
|
||||
flags+=("--name=")
|
||||
flags+=("--no-headers")
|
||||
flags+=("--output=")
|
||||
|
|
|
@ -50,6 +50,10 @@ re\-use the labels from the resource it exposes.
|
|||
\fB\-l\fP, \fB\-\-labels\fP=""
|
||||
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
|
||||
\fB\-\-name\fP=""
|
||||
The name for the newly created object.
|
||||
|
|
|
@ -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
|
||||
--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.
|
||||
--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.
|
||||
--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].
|
||||
|
|
|
@ -433,6 +433,7 @@ information about the provisioned balancer will be published in the `Service`'s
|
|||
}
|
||||
],
|
||||
"clusterIP": "10.0.171.239",
|
||||
"loadBalancerIP": "78.11.24.19",
|
||||
"type": "LoadBalancer"
|
||||
},
|
||||
"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`,
|
||||
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
|
||||
|
||||
|
|
|
@ -138,6 +138,7 @@ kube-master
|
|||
label-columns
|
||||
last-release-pr
|
||||
legacy-userspace-proxy
|
||||
load-balancer-ip
|
||||
log-flush-frequency
|
||||
long-running-request-regexp
|
||||
low-diskspace-threshold-mb
|
||||
|
|
|
@ -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 {
|
||||
out.Type = in.Type
|
||||
if in.Ports != nil {
|
||||
out.Ports = make([]ServicePort, len(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.ClusterIP = in.ClusterIP
|
||||
out.Type = in.Type
|
||||
if in.ExternalIPs != nil {
|
||||
out.ExternalIPs = make([]string, len(in.ExternalIPs))
|
||||
for i := range in.ExternalIPs {
|
||||
|
@ -1986,6 +1986,7 @@ func deepCopy_api_ServiceSpec(in ServiceSpec, out *ServiceSpec, c *conversion.Cl
|
|||
} else {
|
||||
out.ExternalIPs = nil
|
||||
}
|
||||
out.LoadBalancerIP = in.LoadBalancerIP
|
||||
out.SessionAffinity = in.SessionAffinity
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1185,6 +1185,9 @@ type LoadBalancerIngress struct {
|
|||
|
||||
// ServiceSpec describes the attributes that a user creates on a service
|
||||
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.
|
||||
Ports []ServicePort `json:"ports"`
|
||||
|
||||
|
@ -1200,13 +1203,17 @@ type ServiceSpec struct {
|
|||
// None can be specified for headless services when proxying is not required
|
||||
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
|
||||
// users to handle external traffic that arrives at a node.
|
||||
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.
|
||||
SessionAffinity ServiceAffinity `json:"sessionAffinity,omitempty"`
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
defaulting.(func(*api.ServiceSpec))(in)
|
||||
}
|
||||
out.Type = ServiceType(in.Type)
|
||||
if in.Ports != nil {
|
||||
out.Ports = make([]ServicePort, len(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.ClusterIP = in.ClusterIP
|
||||
out.Type = ServiceType(in.Type)
|
||||
if in.ExternalIPs != nil {
|
||||
out.ExternalIPs = make([]string, len(in.ExternalIPs))
|
||||
for i := range in.ExternalIPs {
|
||||
|
@ -2200,6 +2200,7 @@ func convert_api_ServiceSpec_To_v1_ServiceSpec(in *api.ServiceSpec, out *Service
|
|||
} else {
|
||||
out.ExternalIPs = nil
|
||||
}
|
||||
out.LoadBalancerIP = in.LoadBalancerIP
|
||||
out.SessionAffinity = ServiceAffinity(in.SessionAffinity)
|
||||
return nil
|
||||
}
|
||||
|
@ -4603,6 +4604,7 @@ func convert_v1_ServiceSpec_To_api_ServiceSpec(in *ServiceSpec, out *api.Service
|
|||
out.ExternalIPs = nil
|
||||
}
|
||||
out.SessionAffinity = api.ServiceAffinity(in.SessionAffinity)
|
||||
out.LoadBalancerIP = in.LoadBalancerIP
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -1992,6 +1992,7 @@ func deepCopy_v1_ServiceSpec(in ServiceSpec, out *ServiceSpec, c *conversion.Clo
|
|||
out.ExternalIPs = nil
|
||||
}
|
||||
out.SessionAffinity = in.SessionAffinity
|
||||
out.LoadBalancerIP = in.LoadBalancerIP
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -1509,6 +1509,13 @@ type ServiceSpec struct {
|
|||
// Defaults to None.
|
||||
// More info: http://releases.k8s.io/HEAD/docs/user-guide/services.md#virtual-ips-and-service-proxies
|
||||
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.
|
||||
|
|
|
@ -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",
|
||||
"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",
|
||||
"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 {
|
||||
|
|
|
@ -81,7 +81,7 @@ type TCPLoadBalancer interface {
|
|||
// if so, what its status is.
|
||||
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(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(name, region string, hosts []string) error
|
||||
// EnsureTCPLoadBalancerDeleted deletes the specified load balancer if it
|
||||
|
|
|
@ -352,7 +352,7 @@ func makeFirewallName(name string) string {
|
|||
// EnsureTCPLoadBalancer is an implementation of TCPLoadBalancer.EnsureTCPLoadBalancer.
|
||||
// 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.
|
||||
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 {
|
||||
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),
|
||||
Target: gce.targetPoolURL(name, region),
|
||||
}
|
||||
if loadBalancerIP != nil {
|
||||
req.IPAddress = loadBalancerIP.String()
|
||||
}
|
||||
|
||||
op, err := gce.service.ForwardingRules.Insert(gce.projectID, region, req).Do()
|
||||
if err != nil && !isHTTPErrorCode(err, http.StatusConflict) {
|
||||
return nil, err
|
||||
|
|
|
@ -525,8 +525,8 @@ func (lb *LoadBalancer) GetTCPLoadBalancer(name, region string) (*api.LoadBalanc
|
|||
// a list of regions (from config) and query/create loadbalancers in
|
||||
// each region.
|
||||
|
||||
func (lb *LoadBalancer) EnsureTCPLoadBalancer(name, region string, externalIP 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)
|
||||
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, loadBalancerIP, ports, hosts, affinity)
|
||||
|
||||
if len(ports) > 1 {
|
||||
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,
|
||||
Persistence: persistence,
|
||||
}
|
||||
if externalIP != nil {
|
||||
createOpts.Address = externalIP.String()
|
||||
if loadBalancerIP != nil {
|
||||
createOpts.Address = loadBalancerIP.String()
|
||||
}
|
||||
|
||||
vip, err := vips.Create(lb.network, createOpts).Extract()
|
||||
|
|
|
@ -378,28 +378,14 @@ func (s *ServiceController) createExternalLoadBalancer(service *api.Service) err
|
|||
return err
|
||||
}
|
||||
name := s.loadBalancerName(service)
|
||||
if len(service.Spec.ExternalIPs) > 0 {
|
||||
for _, publicIP := range service.Spec.ExternalIPs {
|
||||
// TODO: Make this actually work for multiple IPs by using different
|
||||
// names for each. For now, we'll just create the first and break.
|
||||
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
|
||||
}
|
||||
status, err := s.balancer.EnsureTCPLoadBalancer(name, s.zone.Region, net.ParseIP(service.Spec.LoadBalancerIP),
|
||||
ports, hostsFromNodeList(&nodes), service.Spec.SessionAffinity)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
status, err := s.balancer.EnsureTCPLoadBalancer(name, s.zone.Region, nil,
|
||||
ports, hostsFromNodeList(&nodes), service.Spec.SessionAffinity)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
service.Status.LoadBalancer = *status
|
||||
}
|
||||
service.Status.LoadBalancer = *status
|
||||
}
|
||||
|
||||
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 {
|
||||
return true
|
||||
}
|
||||
if !loadBalancerIPsAreEqual(oldService, newService) {
|
||||
return true
|
||||
}
|
||||
if len(oldService.Spec.ExternalIPs) != len(newService.Spec.ExternalIPs) {
|
||||
return true
|
||||
}
|
||||
|
@ -689,3 +678,7 @@ func (s *ServiceController) lockedUpdateLoadBalancerHosts(service *api.Service,
|
|||
func wantsExternalLoadBalancer(service *api.Service) bool {
|
||||
return service.Spec.Type == api.ServiceTypeLoadBalancer
|
||||
}
|
||||
|
||||
func loadBalancerIPsAreEqual(oldService, newService *api.Service) bool {
|
||||
return oldService.Spec.LoadBalancerIP == newService.Spec.LoadBalancerIP
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
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().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().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.")
|
||||
|
|
|
@ -55,6 +55,7 @@ func paramNames() []GeneratorParam {
|
|||
{"labels", false},
|
||||
{"external-ip", false},
|
||||
{"create-external-load-balancer", false},
|
||||
{"load-balancer-ip", false},
|
||||
{"type", false},
|
||||
{"protocol", false},
|
||||
{"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 {
|
||||
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 {
|
||||
switch api.ServiceAffinity(params["session-affinity"]) {
|
||||
case api.ServiceAffinityNone:
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -26,6 +26,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
|
@ -418,6 +419,75 @@ var _ = Describe("Services", func() {
|
|||
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() {
|
||||
serviceName := "nodeportservice-test"
|
||||
ns := namespaces[0]
|
||||
|
|
Loading…
Reference in New Issue