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": {
|
"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."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -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=")
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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].
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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.")
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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"
|
"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]
|
||||||
|
|
Loading…
Reference in New Issue