Commit 35eba22c authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #41162 from MrHohn/esipp-ga

Automatic merge from submit-queue (batch tested with PRs 45623, 45241, 45460, 41162) Promotes Source IP preservation for Virtual IPs from Beta to GA Fixes #33625. Feature issue: kubernetes/features#27. Bullet points: - Declare 2 fields (ExternalTraffic and HealthCheckNodePort) that mirror the ESIPP annotations. - ESIPP alpha annotations will be ignored. - Existing ESIPP beta annotations will still be fully supported. - Allow promoting beta annotations to first class fields or reversely. - Disallow setting invalid ExternalTraffic and HealthCheckNodePort on services. Default ExternalTraffic field for nodePort or loadBalancer type service to "Global" if not set. **Release note**: ```release-note Promotes Source IP preservation for Virtual IPs to GA. Two api fields are defined correspondingly: - Service.Spec.ExternalTrafficPolicy <- 'service.beta.kubernetes.io/external-traffic' annotation. - Service.Spec.HealthCheckNodePort <- 'service.beta.kubernetes.io/healthcheck-nodeport' annotation. ```
parents dbcfa96c 12b6c2b8
......@@ -45084,6 +45084,15 @@
"description": "externalName is the external reference that kubedns or equivalent will return as a CNAME record for this service. No proxying will be involved. Must be a valid DNS name and requires Type to be ExternalName.",
"type": "string"
},
"externalTrafficPolicy": {
"description": "externalTrafficPolicy denotes if this Service desires to route external traffic to local endpoints only. This preserves Source IP and avoids a second hop for LoadBalancer and Nodeport type services.",
"type": "string"
},
"healthCheckNodePort": {
"description": "healthCheckNodePort specifies the healthcheck nodePort for the service. If not specified, HealthCheckNodePort is created by the service api backend with the allocated nodePort. Will use user-specified nodePort value if specified by the client. Only effects when Type is set to LoadBalancer and ExternalTrafficPolicy is set to Local.",
"type": "integer",
"format": "int32"
},
"loadBalancerIP": {
"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.",
"type": "string"
......@@ -21041,6 +21041,15 @@
"externalName": {
"type": "string",
"description": "externalName is the external reference that kubedns or equivalent will return as a CNAME record for this service. No proxying will be involved. Must be a valid DNS name and requires Type to be ExternalName."
},
"externalTrafficPolicy": {
"type": "string",
"description": "externalTrafficPolicy denotes if this Service desires to route external traffic to local endpoints only. This preserves Source IP and avoids a second hop for LoadBalancer and Nodeport type services."
},
"healthCheckNodePort": {
"type": "integer",
"format": "int32",
"description": "healthCheckNodePort specifies the healthcheck nodePort for the service. If not specified, HealthCheckNodePort is created by the service api backend with the allocated nodePort. Will use user-specified nodePort value if specified by the client. Only effects when Type is set to LoadBalancer and ExternalTrafficPolicy is set to Local."
}
}
},
......
......@@ -9695,6 +9695,20 @@ Examples:<br>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">externalTrafficPolicy</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">externalTrafficPolicy denotes if this Service desires to route external traffic to local endpoints only. This preserves Source IP and avoids a second hop for LoadBalancer and Nodeport type services.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">healthCheckNodePort</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">healthCheckNodePort specifies the healthcheck nodePort for the service. If not specified, HealthCheckNodePort is created by the service api backend with the allocated nodePort. Will use user-specified nodePort value if specified by the client. Only effects when Type is set to LoadBalancer and ExternalTrafficPolicy is set to Local.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">integer (int32)</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
......@@ -9927,7 +9941,7 @@ Examples:<br>
</div>
<div id="footer">
<div id="footer-text">
Last updated 2017-04-26 21:11:18 UTC
Last updated 2017-05-07 16:52:23 UTC
</div>
</div>
</body>
......
......@@ -12228,6 +12228,15 @@
"description": "externalName is the external reference that kubedns or equivalent will return as a CNAME record for this service. No proxying will be involved. Must be a valid DNS name and requires Type to be ExternalName.",
"type": "string"
},
"externalTrafficPolicy": {
"description": "externalTrafficPolicy denotes if this Service desires to route external traffic to local endpoints only. This preserves Source IP and avoids a second hop for LoadBalancer and Nodeport type services.",
"type": "string"
},
"healthCheckNodePort": {
"description": "healthCheckNodePort specifies the healthcheck nodePort for the service. If not specified, HealthCheckNodePort is created by the service api backend with the allocated nodePort. Will use user-specified nodePort value if specified by the client. Only effects when Type is set to LoadBalancer and ExternalTrafficPolicy is set to Local.",
"type": "integer",
"format": "int32"
},
"loadBalancerIP": {
"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.",
"type": "string"
......
......@@ -30,6 +30,8 @@ go_test(
deps = [
"//pkg/api:go_default_library",
"//pkg/util/net/sets:go_default_library",
"//vendor/github.com/davecgh/go-spew/spew:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
],
)
......
......@@ -26,25 +26,19 @@ const (
// Not all cloud providers support this annotation, though AWS & GCE do.
AnnotationLoadBalancerSourceRangesKey = "service.beta.kubernetes.io/load-balancer-source-ranges"
// AnnotationValueExternalTrafficLocal Value of annotation to specify local endpoints behaviour
// AnnotationValueExternalTrafficLocal Value of annotation to specify local endpoints behavior.
AnnotationValueExternalTrafficLocal = "OnlyLocal"
// AnnotationValueExternalTrafficGlobal Value of annotation to specify global (legacy) behaviour
// AnnotationValueExternalTrafficGlobal Value of annotation to specify global (legacy) behavior.
AnnotationValueExternalTrafficGlobal = "Global"
// TODO: The alpha annotations have been deprecated, remove them when we move this feature to GA.
// TODO: The beta annotations have been deprecated, remove them when we release k8s 1.8.
// AlphaAnnotationHealthCheckNodePort Annotation specifying the healthcheck nodePort for the service
// If not specified, annotation is created by the service api backend with the allocated nodePort
// Will use user-specified nodePort value if specified by the client
AlphaAnnotationHealthCheckNodePort = "service.alpha.kubernetes.io/healthcheck-nodeport"
// AlphaAnnotationExternalTraffic An annotation that denotes if this Service desires to route external traffic to local
// endpoints only. This preserves Source IP and avoids a second hop.
AlphaAnnotationExternalTraffic = "service.alpha.kubernetes.io/external-traffic"
// BetaAnnotationHealthCheckNodePort is the beta version of AlphaAnnotationHealthCheckNodePort.
// BetaAnnotationHealthCheckNodePort Annotation specifying the healthcheck nodePort for the service.
// If not specified, annotation is created by the service api backend with the allocated nodePort.
// Will use user-specified nodePort value if specified by the client.
BetaAnnotationHealthCheckNodePort = "service.beta.kubernetes.io/healthcheck-nodeport"
// BetaAnnotationExternalTraffic is the beta version of AlphaAnnotationExternalTraffic.
// BetaAnnotationExternalTraffic An annotation that denotes if this Service desires to route
// external traffic to local endpoints only. This preserves Source IP and avoids a second hop.
BetaAnnotationExternalTraffic = "service.beta.kubernetes.io/external-traffic"
)
......@@ -76,23 +76,22 @@ func RequestsOnlyLocalTraffic(service *api.Service) bool {
service.Spec.Type != api.ServiceTypeNodePort {
return false
}
// First check the alpha annotation and then the beta. This is so existing
// Services continue to work till the user decides to transition to beta.
// If they transition to beta, there's no way to go back to alpha without
// rolling back the cluster.
for _, annotation := range []string{AlphaAnnotationExternalTraffic, BetaAnnotationExternalTraffic} {
if l, ok := service.Annotations[annotation]; ok {
switch l {
case AnnotationValueExternalTrafficLocal:
return true
case AnnotationValueExternalTrafficGlobal:
return false
default:
glog.Errorf("Invalid value for annotation %v: %v", annotation, l)
}
// First check the beta annotation and then the first class field. This is so that
// existing Services continue to work till the user decides to transition to the
// first class field.
if l, ok := service.Annotations[BetaAnnotationExternalTraffic]; ok {
switch l {
case AnnotationValueExternalTrafficLocal:
return true
case AnnotationValueExternalTrafficGlobal:
return false
default:
glog.Errorf("Invalid value for annotation %v: %v", BetaAnnotationExternalTraffic, l)
return false
}
}
return false
return service.Spec.ExternalTrafficPolicy == api.ServiceExternalTrafficPolicyTypeLocal
}
// NeedsHealthCheck Check if service needs health check.
......@@ -103,21 +102,61 @@ func NeedsHealthCheck(service *api.Service) bool {
return RequestsOnlyLocalTraffic(service)
}
// GetServiceHealthCheckNodePort Return health check node port annotation for service, if one exists
// GetServiceHealthCheckNodePort Return health check node port for service, if one exists
func GetServiceHealthCheckNodePort(service *api.Service) int32 {
// First check the alpha annotation and then the beta. This is so existing
// Services continue to work till the user decides to transition to beta.
// If they transition to beta, there's no way to go back to alpha without
// rolling back the cluster.
for _, annotation := range []string{AlphaAnnotationHealthCheckNodePort, BetaAnnotationHealthCheckNodePort} {
if l, ok := service.Annotations[annotation]; ok {
p, err := strconv.Atoi(l)
if err != nil {
glog.Errorf("Failed to parse annotation %v: %v", annotation, err)
continue
}
return int32(p)
// First check the beta annotation and then the first class field. This is so that
// existing Services continue to work till the user decides to transition to the
// first class field.
if l, ok := service.Annotations[BetaAnnotationHealthCheckNodePort]; ok {
p, err := strconv.Atoi(l)
if err != nil {
glog.Errorf("Failed to parse annotation %v: %v", BetaAnnotationHealthCheckNodePort, err)
return 0
}
return int32(p)
}
return service.Spec.HealthCheckNodePort
}
// SetDefaultExternalTrafficPolicyIfNeeded defaults the ExternalTrafficPolicy field
// for NodePort / LoadBalancer service to Global for consistency.
// TODO: Move this default logic to default.go once beta annotation is deprecated.
func SetDefaultExternalTrafficPolicyIfNeeded(service *api.Service) {
if _, ok := service.Annotations[BetaAnnotationExternalTraffic]; ok {
// Don't default this field if beta annotation exists.
return
} else if (service.Spec.Type == api.ServiceTypeNodePort ||
service.Spec.Type == api.ServiceTypeLoadBalancer) &&
service.Spec.ExternalTrafficPolicy == "" {
service.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyTypeGlobal
}
}
// ClearExternalTrafficPolicy resets the ExternalTrafficPolicy field.
func ClearExternalTrafficPolicy(service *api.Service) {
// First check the beta annotation and then the first class field. This is so that
// existing Services continue to work till the user decides to transition to the
// first class field.
if _, ok := service.Annotations[BetaAnnotationExternalTraffic]; ok {
delete(service.Annotations, BetaAnnotationExternalTraffic)
return
}
service.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyType("")
}
// SetServiceHealthCheckNodePort sets the given health check node port on service.
// It does not check whether this service needs healthCheckNodePort.
func SetServiceHealthCheckNodePort(service *api.Service, hcNodePort int32) {
// First check the beta annotation and then the first class field. This is so that
// existing Services continue to work till the user decides to transition to the
// first class field.
if _, ok := service.Annotations[BetaAnnotationExternalTraffic]; ok {
if hcNodePort == 0 {
delete(service.Annotations, BetaAnnotationHealthCheckNodePort)
} else {
service.Annotations[BetaAnnotationHealthCheckNodePort] = fmt.Sprintf("%d", hcNodePort)
}
return
}
return 0
service.Spec.HealthCheckNodePort = hcNodePort
}
......@@ -2453,6 +2453,16 @@ const (
ServiceTypeExternalName ServiceType = "ExternalName"
)
// Service External Traffic Policy Type string
type ServiceExternalTrafficPolicyType string
const (
// ServiceExternalTrafficPolicyTypeLocal specifies local endpoints behavior.
ServiceExternalTrafficPolicyTypeLocal ServiceExternalTrafficPolicyType = "Local"
// ServiceExternalTrafficPolicyTypeGlobal specifies global (legacy) behavior.
ServiceExternalTrafficPolicyTypeGlobal ServiceExternalTrafficPolicyType = "Global"
)
// ServiceStatus represents the current status of a service
type ServiceStatus struct {
// LoadBalancer contains the current status of the load-balancer,
......@@ -2552,6 +2562,20 @@ type ServiceSpec struct {
// cloud-provider does not support the feature."
// +optional
LoadBalancerSourceRanges []string
// externalTrafficPolicy denotes if this Service desires to route external traffic to
// local endpoints only. This preserves Source IP and avoids a second hop for
// LoadBalancer and Nodeport type services.
// +optional
ExternalTrafficPolicy ServiceExternalTrafficPolicyType
// healthCheckNodePort specifies the healthcheck nodePort for the service.
// If not specified, HealthCheckNodePort is created by the service api
// backend with the allocated nodePort. Will use user-specified nodePort value
// if specified by the client. Only effects when Type is set to LoadBalancer
// and ExternalTrafficPolicy is set to Local.
// +optional
HealthCheckNodePort int32
}
type ServicePort struct {
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -3697,6 +3697,20 @@ message ServiceSpec {
// Must be a valid DNS name and requires Type to be ExternalName.
// +optional
optional string externalName = 10;
// externalTrafficPolicy denotes if this Service desires to route external traffic to
// local endpoints only. This preserves Source IP and avoids a second hop for
// LoadBalancer and Nodeport type services.
// +optional
optional string externalTrafficPolicy = 11;
// healthCheckNodePort specifies the healthcheck nodePort for the service.
// If not specified, HealthCheckNodePort is created by the service api
// backend with the allocated nodePort. Will use user-specified nodePort value
// if specified by the client. Only effects when Type is set to LoadBalancer
// and ExternalTrafficPolicy is set to Local.
// +optional
optional int32 healthCheckNodePort = 12;
}
// ServiceStatus represents the current status of a service.
......
......@@ -30,6 +30,8 @@ go_test(
deps = [
"//pkg/api/v1:go_default_library",
"//pkg/util/net/sets:go_default_library",
"//vendor/github.com/davecgh/go-spew/spew:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
],
)
......
......@@ -26,25 +26,19 @@ const (
// Not all cloud providers support this annotation, though AWS & GCE do.
AnnotationLoadBalancerSourceRangesKey = "service.beta.kubernetes.io/load-balancer-source-ranges"
// AnnotationValueExternalTrafficLocal Value of annotation to specify local endpoints behaviour
// AnnotationValueExternalTrafficLocal Value of annotation to specify local endpoints behavior.
AnnotationValueExternalTrafficLocal = "OnlyLocal"
// AnnotationValueExternalTrafficGlobal Value of annotation to specify global (legacy) behaviour
// AnnotationValueExternalTrafficGlobal Value of annotation to specify global (legacy) behavior.
AnnotationValueExternalTrafficGlobal = "Global"
// TODO: The alpha annotations have been deprecated, remove them when we move this feature to GA.
// TODO: The beta annotations have been deprecated, remove them when we release k8s 1.8.
// AlphaAnnotationHealthCheckNodePort Annotation specifying the healthcheck nodePort for the service
// If not specified, annotation is created by the service api backend with the allocated nodePort
// Will use user-specified nodePort value if specified by the client
AlphaAnnotationHealthCheckNodePort = "service.alpha.kubernetes.io/healthcheck-nodeport"
// AlphaAnnotationExternalTraffic An annotation that denotes if this Service desires to route external traffic to local
// endpoints only. This preserves Source IP and avoids a second hop.
AlphaAnnotationExternalTraffic = "service.alpha.kubernetes.io/external-traffic"
// BetaAnnotationHealthCheckNodePort is the beta version of AlphaAnnotationHealthCheckNodePort.
// BetaAnnotationHealthCheckNodePort Annotation specifying the healthcheck nodePort for the service.
// If not specified, annotation is created by the service api backend with the allocated nodePort.
// Will use user-specified nodePort value if specified by the client.
BetaAnnotationHealthCheckNodePort = "service.beta.kubernetes.io/healthcheck-nodeport"
// BetaAnnotationExternalTraffic is the beta version of AlphaAnnotationExternalTraffic.
// BetaAnnotationExternalTraffic An annotation that denotes if this Service desires to route
// external traffic to local endpoints only. This preserves Source IP and avoids a second hop.
BetaAnnotationExternalTraffic = "service.beta.kubernetes.io/external-traffic"
)
......@@ -76,23 +76,22 @@ func RequestsOnlyLocalTraffic(service *v1.Service) bool {
service.Spec.Type != v1.ServiceTypeNodePort {
return false
}
// First check the alpha annotation and then the beta. This is so existing
// Services continue to work till the user decides to transition to beta.
// If they transition to beta, there's no way to go back to alpha without
// rolling back the cluster.
for _, annotation := range []string{AlphaAnnotationExternalTraffic, BetaAnnotationExternalTraffic} {
if l, ok := service.Annotations[annotation]; ok {
switch l {
case AnnotationValueExternalTrafficLocal:
return true
case AnnotationValueExternalTrafficGlobal:
return false
default:
glog.Errorf("Invalid value for annotation %v: %v", annotation, l)
}
// First check the beta annotation and then the first class field. This is so that
// existing Services continue to work till the user decides to transition to the
// first class field.
if l, ok := service.Annotations[BetaAnnotationExternalTraffic]; ok {
switch l {
case AnnotationValueExternalTrafficLocal:
return true
case AnnotationValueExternalTrafficGlobal:
return false
default:
glog.Errorf("Invalid value for annotation %v: %v", BetaAnnotationExternalTraffic, l)
return false
}
}
return false
return service.Spec.ExternalTrafficPolicy == v1.ServiceExternalTrafficPolicyTypeLocal
}
// NeedsHealthCheck Check if service needs health check.
......@@ -103,23 +102,62 @@ func NeedsHealthCheck(service *v1.Service) bool {
return RequestsOnlyLocalTraffic(service)
}
// GetServiceHealthCheckNodePort Return health check node port annotation for service, if one exists
// GetServiceHealthCheckNodePort Return health check node port for service, if one exists
func GetServiceHealthCheckNodePort(service *v1.Service) int32 {
// First check the alpha annotation and then the beta. This is so existing
// Services continue to work till the user decides to transition to beta.
// If they transition to beta, there's no way to go back to alpha without
// rolling back the cluster.
for _, annotation := range []string{AlphaAnnotationHealthCheckNodePort, BetaAnnotationHealthCheckNodePort} {
if l, ok := service.Annotations[annotation]; ok {
p, err := strconv.Atoi(l)
if err != nil {
glog.Errorf("Failed to parse annotation %v: %v", annotation, err)
continue
}
return int32(p)
// First check the beta annotation and then the first class field. This is so that
// existing Services continue to work till the user decides to transition to the
// first class field.
if l, ok := service.Annotations[BetaAnnotationHealthCheckNodePort]; ok {
p, err := strconv.Atoi(l)
if err != nil {
glog.Errorf("Failed to parse annotation %v: %v", BetaAnnotationHealthCheckNodePort, err)
return 0
}
return int32(p)
}
return service.Spec.HealthCheckNodePort
}
// SetDefaultExternalTrafficPolicyIfNeeded defaults the ExternalTrafficPolicy field
// for NodePort / LoadBalancer service to Global for consistency.
// TODO: Move this default logic to default.go once beta annotation is deprecated.
func SetDefaultExternalTrafficPolicyIfNeeded(service *v1.Service) {
if _, ok := service.Annotations[BetaAnnotationExternalTraffic]; ok {
// Don't default this field if beta annotation exists.
return
} else if (service.Spec.Type == v1.ServiceTypeNodePort ||
service.Spec.Type == v1.ServiceTypeLoadBalancer) &&
service.Spec.ExternalTrafficPolicy == "" {
service.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyTypeGlobal
}
}
// ClearExternalTrafficPolicy resets the ExternalTrafficPolicy field.
func ClearExternalTrafficPolicy(service *v1.Service) {
// First check the beta annotation and then the first class field. This is so existing
// Services continue to work till the user decides to transition to the first class field.
if _, ok := service.Annotations[BetaAnnotationExternalTraffic]; ok {
delete(service.Annotations, BetaAnnotationExternalTraffic)
return
}
service.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyType("")
}
// SetServiceHealthCheckNodePort sets the given health check node port on service.
// It does not check whether this service needs healthCheckNodePort.
func SetServiceHealthCheckNodePort(service *v1.Service, hcNodePort int32) {
// First check the beta annotation and then the first class field. This is so that
// existing Services continue to work till the user decides to transition to the
// first class field.
if _, ok := service.Annotations[BetaAnnotationExternalTraffic]; ok {
if hcNodePort == 0 {
delete(service.Annotations, BetaAnnotationHealthCheckNodePort)
} else {
service.Annotations[BetaAnnotationHealthCheckNodePort] = fmt.Sprintf("%d", hcNodePort)
}
return
}
return 0
service.Spec.HealthCheckNodePort = hcNodePort
}
// GetServiceHealthCheckPathPort Return the path and nodePort programmed into the Cloud LB Health Check
......
......@@ -2792,6 +2792,16 @@ const (
ServiceTypeExternalName ServiceType = "ExternalName"
)
// Service External Traffic Policy Type string
type ServiceExternalTrafficPolicyType string
const (
// ServiceExternalTrafficPolicyTypeLocal specifies local endpoints behavior.
ServiceExternalTrafficPolicyTypeLocal ServiceExternalTrafficPolicyType = "Local"
// ServiceExternalTrafficPolicyTypeGlobal specifies global (legacy) behavior.
ServiceExternalTrafficPolicyTypeGlobal ServiceExternalTrafficPolicyType = "Global"
)
// ServiceStatus represents the current status of a service.
type ServiceStatus struct {
// LoadBalancer contains the current status of the load-balancer,
......@@ -2904,6 +2914,20 @@ type ServiceSpec struct {
// Must be a valid DNS name and requires Type to be ExternalName.
// +optional
ExternalName string `json:"externalName,omitempty" protobuf:"bytes,10,opt,name=externalName"`
// externalTrafficPolicy denotes if this Service desires to route external traffic to
// local endpoints only. This preserves Source IP and avoids a second hop for
// LoadBalancer and Nodeport type services.
// +optional
ExternalTrafficPolicy ServiceExternalTrafficPolicyType `json:"externalTrafficPolicy,omitempty" protobuf:"bytes,11,opt,name=externalTrafficPolicy"`
// healthCheckNodePort specifies the healthcheck nodePort for the service.
// If not specified, HealthCheckNodePort is created by the service api
// backend with the allocated nodePort. Will use user-specified nodePort value
// if specified by the client. Only effects when Type is set to LoadBalancer
// and ExternalTrafficPolicy is set to Local.
// +optional
HealthCheckNodePort int32 `json:"healthCheckNodePort,omitempty" protobuf:"bytes,12,opt,name=healthCheckNodePort"`
}
// ServicePort contains information on service's port.
......
......@@ -1844,6 +1844,8 @@ var map_ServiceSpec = map[string]string{
"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.",
"loadBalancerSourceRanges": "If specified and supported by the platform, this will restrict traffic through the cloud-provider load-balancer will be restricted to the specified client IPs. This field will be ignored if the cloud-provider does not support the feature.\" More info: http://kubernetes.io/docs/user-guide/services-firewalls",
"externalName": "externalName is the external reference that kubedns or equivalent will return as a CNAME record for this service. No proxying will be involved. Must be a valid DNS name and requires Type to be ExternalName.",
"externalTrafficPolicy": "externalTrafficPolicy denotes if this Service desires to route external traffic to local endpoints only. This preserves Source IP and avoids a second hop for LoadBalancer and Nodeport type services.",
"healthCheckNodePort": "healthCheckNodePort specifies the healthcheck nodePort for the service. If not specified, HealthCheckNodePort is created by the service api backend with the allocated nodePort. Will use user-specified nodePort value if specified by the client. Only effects when Type is set to LoadBalancer and ExternalTrafficPolicy is set to Local.",
}
func (ServiceSpec) SwaggerDoc() map[string]string {
......
......@@ -4721,6 +4721,8 @@ func autoConvert_v1_ServiceSpec_To_api_ServiceSpec(in *ServiceSpec, out *api.Ser
out.LoadBalancerIP = in.LoadBalancerIP
out.LoadBalancerSourceRanges = *(*[]string)(unsafe.Pointer(&in.LoadBalancerSourceRanges))
out.ExternalName = in.ExternalName
out.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyType(in.ExternalTrafficPolicy)
out.HealthCheckNodePort = in.HealthCheckNodePort
return nil
}
......@@ -4739,6 +4741,8 @@ func autoConvert_api_ServiceSpec_To_v1_ServiceSpec(in *api.ServiceSpec, out *Ser
out.LoadBalancerIP = in.LoadBalancerIP
out.SessionAffinity = ServiceAffinity(in.SessionAffinity)
out.LoadBalancerSourceRanges = *(*[]string)(unsafe.Pointer(&in.LoadBalancerSourceRanges))
out.ExternalTrafficPolicy = ServiceExternalTrafficPolicyType(in.ExternalTrafficPolicy)
out.HealthCheckNodePort = in.HealthCheckNodePort
return nil
}
......
......@@ -2742,7 +2742,8 @@ func ValidateService(service *api.Service) field.ErrorList {
}
}
allErrs = append(allErrs, validateServiceExternalTrafficFields(service)...)
allErrs = append(allErrs, validateServiceExternalTrafficFieldsValue(service)...)
allErrs = append(allErrs, validateServiceExternalTrafficAPIVersion(service)...)
return allErrs
}
......@@ -2785,61 +2786,68 @@ func validateServicePort(sp *api.ServicePort, requireName, isHeadlessService boo
return allErrs
}
// validateServiceExternalTrafficFields validates ExternalTraffic related annotations
// validateServiceExternalTrafficFieldsValue validates ExternalTraffic related annotations
// have legal value.
func validateServiceExternalTrafficFields(service *api.Service) field.ErrorList {
func validateServiceExternalTrafficFieldsValue(service *api.Service) field.ErrorList {
allErrs := field.ErrorList{}
for _, annotation := range []string{apiservice.AlphaAnnotationExternalTraffic, apiservice.BetaAnnotationExternalTraffic} {
if l, ok := service.Annotations[annotation]; ok {
if l != apiservice.AnnotationValueExternalTrafficLocal &&
l != apiservice.AnnotationValueExternalTrafficGlobal {
allErrs = append(allErrs, field.Invalid(field.NewPath("metadata", "annotations").Key(annotation), l,
fmt.Sprintf("ExternalTraffic must be %v or %v", apiservice.AnnotationValueExternalTrafficLocal, apiservice.AnnotationValueExternalTrafficGlobal)))
}
// Check beta annotations.
if l, ok := service.Annotations[apiservice.BetaAnnotationExternalTraffic]; ok {
if l != apiservice.AnnotationValueExternalTrafficLocal &&
l != apiservice.AnnotationValueExternalTrafficGlobal {
allErrs = append(allErrs, field.Invalid(field.NewPath("metadata", "annotations").Key(apiservice.BetaAnnotationExternalTraffic), l,
fmt.Sprintf("ExternalTraffic must be %v or %v", apiservice.AnnotationValueExternalTrafficLocal, apiservice.AnnotationValueExternalTrafficGlobal)))
}
}
for _, annotation := range []string{apiservice.AlphaAnnotationHealthCheckNodePort, apiservice.BetaAnnotationHealthCheckNodePort} {
if l, ok := service.Annotations[annotation]; ok {
p, err := strconv.Atoi(l)
if err != nil {
allErrs = append(allErrs, field.Invalid(field.NewPath("metadata", "annotations").Key(annotation), l,
"HealthCheckNodePort must be a valid port number"))
} else if p <= 0 {
allErrs = append(allErrs, field.Invalid(field.NewPath("metadata", "annotations").Key(annotation), l,
"HealthCheckNodePort must be greater than 0"))
}
if l, ok := service.Annotations[apiservice.BetaAnnotationHealthCheckNodePort]; ok {
p, err := strconv.Atoi(l)
if err != nil {
allErrs = append(allErrs, field.Invalid(field.NewPath("metadata", "annotations").Key(apiservice.BetaAnnotationHealthCheckNodePort), l,
"HealthCheckNodePort must be a valid port number"))
} else if p <= 0 {
allErrs = append(allErrs, field.Invalid(field.NewPath("metadata", "annotations").Key(apiservice.BetaAnnotationHealthCheckNodePort), l,
"HealthCheckNodePort must be greater than 0"))
}
}
allErrs = append(allErrs, validateServiceExternalTrafficAPIVersion(service)...)
// Check first class fields.
if service.Spec.ExternalTrafficPolicy != "" &&
service.Spec.ExternalTrafficPolicy != api.ServiceExternalTrafficPolicyTypeGlobal &&
service.Spec.ExternalTrafficPolicy != api.ServiceExternalTrafficPolicyTypeLocal {
allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("externalTrafficPolicy"), service.Spec.ExternalTrafficPolicy,
fmt.Sprintf("ExternalTrafficPolicy must be empty, %v or %v", api.ServiceExternalTrafficPolicyTypeGlobal, api.ServiceExternalTrafficPolicyTypeLocal)))
}
if service.Spec.HealthCheckNodePort < 0 {
allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("healthCheckNodePort"), service.Spec.HealthCheckNodePort,
"HealthCheckNodePort must be not less than 0"))
}
return allErrs
}
// serviceExternalTrafficStatus stores flags indicating whether ExternalTraffic
// related beta annotations and alpha annotations are set on service.
// related beta annotations and GA fields are set on service.
type serviceExternalTrafficStatus struct {
alphaExternalTrafficIsSet bool
alphaHealthCheckIsSet bool
betaExternalTrafficIsSet bool
betaHealthCheckIsSet bool
betaExternalTrafficIsSet bool
betaHealthCheckIsSet bool
gaExternalTrafficIsSet bool
gaHealthCheckIsSet bool
}
func (s *serviceExternalTrafficStatus) useAlphaExternalTrafficWithBeta() bool {
return s.alphaExternalTrafficIsSet && (s.betaExternalTrafficIsSet || s.betaHealthCheckIsSet)
func (s *serviceExternalTrafficStatus) useBetaExternalTrafficWithGA() bool {
return s.betaExternalTrafficIsSet && (s.gaExternalTrafficIsSet || s.gaHealthCheckIsSet)
}
func (s *serviceExternalTrafficStatus) useAlphaHealthCheckWithBeta() bool {
return s.alphaHealthCheckIsSet && (s.betaExternalTrafficIsSet || s.betaHealthCheckIsSet)
func (s *serviceExternalTrafficStatus) useBetaHealthCheckWithGA() bool {
return s.betaHealthCheckIsSet && (s.gaExternalTrafficIsSet || s.gaHealthCheckIsSet)
}
func getServiceExternalTrafficStatus(service *api.Service) *serviceExternalTrafficStatus {
s := serviceExternalTrafficStatus{}
_, s.alphaExternalTrafficIsSet = service.Annotations[apiservice.AlphaAnnotationExternalTraffic]
_, s.alphaHealthCheckIsSet = service.Annotations[apiservice.AlphaAnnotationHealthCheckNodePort]
_, s.betaExternalTrafficIsSet = service.Annotations[apiservice.BetaAnnotationExternalTraffic]
_, s.betaHealthCheckIsSet = service.Annotations[apiservice.BetaAnnotationHealthCheckNodePort]
s.gaExternalTrafficIsSet = service.Spec.ExternalTrafficPolicy != ""
s.gaHealthCheckIsSet = service.Spec.HealthCheckNodePort != 0
return &s
}
......@@ -2850,16 +2858,39 @@ func validateServiceExternalTrafficAPIVersion(service *api.Service) field.ErrorL
status := getServiceExternalTrafficStatus(service)
if status.useAlphaExternalTrafficWithBeta() {
fieldPath := field.NewPath("metadata", "annotations").Key(apiservice.AlphaAnnotationExternalTraffic)
msg := fmt.Sprintf("please replace the alpha annotation with beta annotation")
allErrs = append(allErrs, field.Invalid(fieldPath, apiservice.AlphaAnnotationExternalTraffic, msg))
if status.useBetaExternalTrafficWithGA() {
fieldPath := field.NewPath("metadata", "annotations").Key(apiservice.BetaAnnotationExternalTraffic)
msg := fmt.Sprintf("please replace the beta annotation with 'ExternalTrafficPolicy' field")
allErrs = append(allErrs, field.Invalid(fieldPath, apiservice.BetaAnnotationExternalTraffic, msg))
}
if status.useBetaHealthCheckWithGA() {
fieldPath := field.NewPath("metadata", "annotations").Key(apiservice.BetaAnnotationHealthCheckNodePort)
msg := fmt.Sprintf("please replace the beta annotation with 'HealthCheckNodePort' field")
allErrs = append(allErrs, field.Invalid(fieldPath, apiservice.BetaAnnotationHealthCheckNodePort, msg))
}
return allErrs
}
// ValidateServiceExternalTrafficFieldsCombination validates if ExternalTrafficPolicy,
// HealthCheckNodePort and Type combination are legal. For update, it should be called
// after clearing externalTraffic related fields for the ease of transitioning between
// different service types.
func ValidateServiceExternalTrafficFieldsCombination(service *api.Service) field.ErrorList {
allErrs := field.ErrorList{}
if service.Spec.Type != api.ServiceTypeLoadBalancer &&
service.Spec.Type != api.ServiceTypeNodePort &&
service.Spec.ExternalTrafficPolicy != "" {
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "externalTrafficPolicy"), service.Spec.ExternalTrafficPolicy,
"ExternalTrafficPolicy can only be set on NodePort and LoadBalancer service"))
}
if status.useAlphaHealthCheckWithBeta() {
fieldPath := field.NewPath("metadata", "annotations").Key(apiservice.AlphaAnnotationHealthCheckNodePort)
msg := fmt.Sprintf("please replace the alpha annotation with beta annotation")
allErrs = append(allErrs, field.Invalid(fieldPath, apiservice.AlphaAnnotationHealthCheckNodePort, msg))
if !apiservice.NeedsHealthCheck(service) &&
service.Spec.HealthCheckNodePort != 0 {
allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "healthCheckNodePort"), service.Spec.HealthCheckNodePort,
"HealthCheckNodePort can only be set on LoadBalancer service with ExternalTrafficPolicy=Local"))
}
return allErrs
......
......@@ -5656,9 +5656,19 @@ func TestValidateService(t *testing.T) {
numErrs: 1,
},
{
name: "LoadBalancer allows onlyLocal alpha annotations",
name: "invalid node port with clusterIP None",
tweakSvc: func(s *api.Service) {
s.Annotations[service.AlphaAnnotationExternalTraffic] = service.AnnotationValueExternalTrafficLocal
s.Spec.Type = api.ServiceTypeNodePort
s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt(1)})
s.Spec.ClusterIP = "None"
},
numErrs: 1,
},
// ESIPP section begins.
{
name: "LoadBalancer allows onlyLocal beta annotations",
tweakSvc: func(s *api.Service) {
s.Annotations[service.BetaAnnotationExternalTraffic] = service.AnnotationValueExternalTrafficLocal
},
numErrs: 0,
},
......@@ -5689,14 +5699,68 @@ func TestValidateService(t *testing.T) {
numErrs: 1,
},
{
name: "invalid node port with clusterIP None",
name: "valid healthCheckNodePort beta annotation",
tweakSvc: func(s *api.Service) {
s.Spec.Type = api.ServiceTypeNodePort
s.Spec.Ports = append(s.Spec.Ports, api.ServicePort{Name: "q", Port: 1, Protocol: "TCP", NodePort: 1, TargetPort: intstr.FromInt(1)})
s.Spec.ClusterIP = "None"
s.Spec.Type = api.ServiceTypeLoadBalancer
s.Annotations[service.BetaAnnotationExternalTraffic] = service.AnnotationValueExternalTrafficLocal
s.Annotations[service.BetaAnnotationHealthCheckNodePort] = "31100"
},
numErrs: 0,
},
{
name: "invalid externalTraffic field",
tweakSvc: func(s *api.Service) {
s.Spec.Type = api.ServiceTypeLoadBalancer
s.Spec.ExternalTrafficPolicy = "invalid"
},
numErrs: 1,
},
{
name: "nagative healthCheckNodePort field",
tweakSvc: func(s *api.Service) {
s.Spec.Type = api.ServiceTypeLoadBalancer
s.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyTypeLocal
s.Spec.HealthCheckNodePort = -1
},
numErrs: 1,
},
{
name: "nagative healthCheckNodePort field",
tweakSvc: func(s *api.Service) {
s.Spec.Type = api.ServiceTypeLoadBalancer
s.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyTypeLocal
s.Spec.HealthCheckNodePort = 31100
},
numErrs: 0,
},
{
name: "disallows use ExternalTraffic beta annotation with first class field",
tweakSvc: func(s *api.Service) {
s.Spec.Type = api.ServiceTypeLoadBalancer
s.Annotations[service.BetaAnnotationExternalTraffic] = service.AnnotationValueExternalTrafficLocal
s.Spec.HealthCheckNodePort = 3001
},
numErrs: 1,
},
{
name: "disallows duplicated ExternalTraffic beta annotation with first class field",
tweakSvc: func(s *api.Service) {
s.Spec.Type = api.ServiceTypeLoadBalancer
s.Annotations[service.BetaAnnotationExternalTraffic] = service.AnnotationValueExternalTrafficLocal
s.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyTypeLocal
},
numErrs: 1,
},
{
name: "disallows use HealthCheckNodePort beta annotation with first class field",
tweakSvc: func(s *api.Service) {
s.Spec.Type = api.ServiceTypeLoadBalancer
s.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyTypeLocal
s.Annotations[service.BetaAnnotationHealthCheckNodePort] = "3001"
},
numErrs: 1,
},
// ESIPP section ends.
}
for _, tc := range testCases {
......@@ -5709,6 +5773,75 @@ func TestValidateService(t *testing.T) {
}
}
func TestValidateServiceExternalTrafficFieldsCombination(t *testing.T) {
testCases := []struct {
name string
tweakSvc func(svc *api.Service) // Given a basic valid service, each test case can customize it.
numErrs int
}{
{
name: "valid loadBalancer service with externalTrafficPolicy and healthCheckNodePort set",
tweakSvc: func(s *api.Service) {
s.Spec.Type = api.ServiceTypeLoadBalancer
s.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyTypeLocal
s.Spec.HealthCheckNodePort = 34567
},
numErrs: 0,
},
{
name: "valid nodePort service with externalTrafficPolicy set",
tweakSvc: func(s *api.Service) {
s.Spec.Type = api.ServiceTypeNodePort
s.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyTypeLocal
},
numErrs: 0,
},
{
name: "valid clusterIP service with none of externalTrafficPolicy and healthCheckNodePort set",
tweakSvc: func(s *api.Service) {
s.Spec.Type = api.ServiceTypeClusterIP
},
numErrs: 0,
},
{
name: "cannot set healthCheckNodePort field on loadBalancer service with externalTrafficPolicy!=Local",
tweakSvc: func(s *api.Service) {
s.Spec.Type = api.ServiceTypeLoadBalancer
s.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyTypeGlobal
s.Spec.HealthCheckNodePort = 34567
},
numErrs: 1,
},
{
name: "cannot set healthCheckNodePort field on nodePort service",
tweakSvc: func(s *api.Service) {
s.Spec.Type = api.ServiceTypeNodePort
s.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyTypeLocal
s.Spec.HealthCheckNodePort = 34567
},
numErrs: 1,
},
{
name: "cannot set externalTrafficPolicy or healthCheckNodePort fields on clusterIP service",
tweakSvc: func(s *api.Service) {
s.Spec.Type = api.ServiceTypeClusterIP
s.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyTypeLocal
s.Spec.HealthCheckNodePort = 34567
},
numErrs: 2,
},
}
for _, tc := range testCases {
svc := makeValidService()
tc.tweakSvc(&svc)
errs := ValidateServiceExternalTrafficFieldsCombination(&svc)
if len(errs) != tc.numErrs {
t.Errorf("Unexpected error list for case %q: %v", tc.name, errs.ToAggregate())
}
}
}
func TestValidateReplicationControllerStatus(t *testing.T) {
tests := []struct {
name string
......@@ -7088,40 +7221,46 @@ func TestValidateServiceUpdate(t *testing.T) {
numErrs: 1,
},
{
name: "Service allows removing onlyLocal alpha annotations",
name: "Service allows removing onlyLocal beta annotations",
tweakSvc: func(oldSvc, newSvc *api.Service) {
oldSvc.Annotations[service.AlphaAnnotationExternalTraffic] = service.AnnotationValueExternalTrafficLocal
oldSvc.Annotations[service.AlphaAnnotationHealthCheckNodePort] = "3001"
oldSvc.Annotations[service.BetaAnnotationExternalTraffic] = service.AnnotationValueExternalTrafficLocal
oldSvc.Annotations[service.BetaAnnotationHealthCheckNodePort] = "3001"
},
numErrs: 0,
},
{
name: "Service allows modifying onlyLocal alpha annotations",
name: "Service allows modifying onlyLocal beta annotations",
tweakSvc: func(oldSvc, newSvc *api.Service) {
oldSvc.Annotations[service.AlphaAnnotationExternalTraffic] = service.AnnotationValueExternalTrafficLocal
oldSvc.Annotations[service.AlphaAnnotationHealthCheckNodePort] = "3001"
newSvc.Annotations[service.AlphaAnnotationExternalTraffic] = service.AnnotationValueExternalTrafficGlobal
newSvc.Annotations[service.AlphaAnnotationHealthCheckNodePort] = oldSvc.Annotations[service.AlphaAnnotationHealthCheckNodePort]
oldSvc.Spec.Type = api.ServiceTypeLoadBalancer
oldSvc.Annotations[service.BetaAnnotationExternalTraffic] = service.AnnotationValueExternalTrafficLocal
oldSvc.Annotations[service.BetaAnnotationHealthCheckNodePort] = "3001"
newSvc.Spec.Type = api.ServiceTypeLoadBalancer
newSvc.Annotations[service.BetaAnnotationExternalTraffic] = service.AnnotationValueExternalTrafficGlobal
newSvc.Annotations[service.BetaAnnotationHealthCheckNodePort] = oldSvc.Annotations[service.BetaAnnotationHealthCheckNodePort]
},
numErrs: 0,
},
{
name: "Service disallows promoting one of the onlyLocal pair to beta",
name: "Service disallows promoting one of the onlyLocal pair to GA",
tweakSvc: func(oldSvc, newSvc *api.Service) {
oldSvc.Annotations[service.AlphaAnnotationExternalTraffic] = service.AnnotationValueExternalTrafficLocal
oldSvc.Annotations[service.AlphaAnnotationHealthCheckNodePort] = "3001"
newSvc.Annotations[service.BetaAnnotationExternalTraffic] = service.AnnotationValueExternalTrafficGlobal
newSvc.Annotations[service.AlphaAnnotationHealthCheckNodePort] = oldSvc.Annotations[service.AlphaAnnotationHealthCheckNodePort]
oldSvc.Spec.Type = api.ServiceTypeLoadBalancer
oldSvc.Annotations[service.BetaAnnotationExternalTraffic] = service.AnnotationValueExternalTrafficLocal
oldSvc.Annotations[service.BetaAnnotationHealthCheckNodePort] = "3001"
newSvc.Spec.Type = api.ServiceTypeLoadBalancer
newSvc.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyTypeLocal
newSvc.Annotations[service.BetaAnnotationHealthCheckNodePort] = oldSvc.Annotations[service.BetaAnnotationHealthCheckNodePort]
},
numErrs: 1,
},
{
name: "Service allows changing both onlyLocal annotations from alpha to beta",
name: "Service allows changing both onlyLocal annotations from beta to GA",
tweakSvc: func(oldSvc, newSvc *api.Service) {
oldSvc.Annotations[service.AlphaAnnotationExternalTraffic] = service.AnnotationValueExternalTrafficLocal
oldSvc.Annotations[service.AlphaAnnotationHealthCheckNodePort] = "3001"
newSvc.Annotations[service.BetaAnnotationExternalTraffic] = service.AnnotationValueExternalTrafficLocal
newSvc.Annotations[service.BetaAnnotationHealthCheckNodePort] = oldSvc.Annotations[service.AlphaAnnotationHealthCheckNodePort]
oldSvc.Spec.Type = api.ServiceTypeLoadBalancer
oldSvc.Annotations[service.BetaAnnotationExternalTraffic] = service.AnnotationValueExternalTrafficLocal
oldSvc.Annotations[service.BetaAnnotationHealthCheckNodePort] = "3001"
newSvc.Spec.Type = api.ServiceTypeLoadBalancer
newSvc.Spec.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyTypeLocal
newSvc.Spec.HealthCheckNodePort = 3001
},
numErrs: 0,
},
......
......@@ -66,10 +66,8 @@ func TestReconcileLoadBalancerAddPort(t *testing.T) {
func TestReconcileLoadBalancerNodeHealth(t *testing.T) {
az := getTestCloud()
svc := getTestService("servicea", v1.ProtocolTCP, 80)
svc.Annotations = map[string]string{
serviceapi.BetaAnnotationExternalTraffic: serviceapi.AnnotationValueExternalTrafficLocal,
serviceapi.BetaAnnotationHealthCheckNodePort: "32456",
}
svc.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyTypeLocal
svc.Spec.HealthCheckNodePort = int32(32456)
configProperties := getTestPublicFipConfigurationProperties()
lb := getTestLoadBalancer()
......
......@@ -311,9 +311,9 @@ func (gce *GCECloud) EnsureLoadBalancer(clusterName string, apiService *v1.Servi
}
// Ensure health checks are created for this target pool to pass to createTargetPool for health check links
// Alternately, if the annotation on the service was removed, we need to recreate the target pool without
// health checks. This needs to be prior to the forwarding rule deletion below otherwise it is not possible
// to delete just the target pool or http health checks later.
// Alternately, if the service has ExternalTrafficPolicy field set from Local to Global, we need to recreate
// the target pool without health checks. This needs to be prior to the forwarding rule deletion below otherwise
// it is not possible to delete just the target pool or http health checks later.
var hcToCreate *compute.HttpHealthCheck
hcExisting, err := gce.GetHttpHealthCheck(loadBalancerName)
if err != nil && !isHTTPErrorCode(err, http.StatusNotFound) {
......@@ -325,7 +325,7 @@ func (gce *GCECloud) EnsureLoadBalancer(clusterName string, apiService *v1.Servi
// This logic exists to detect a transition for a pre-existing service and turn on
// the tpNeedsUpdate flag to delete/recreate fwdrule/tpool adding the health check
// to the target pool.
glog.V(2).Infof("Annotation external-traffic=OnlyLocal added to new or pre-existing service")
glog.V(2).Infof("ExternalTrafficPolicy field set to Local on new or pre-existing service")
tpNeedsUpdate = true
}
hcToCreate, err = gce.ensureHttpHealthCheck(loadBalancerName, path, healthCheckNodePort)
......
......@@ -98,7 +98,7 @@ func init() {
// To add a new feature, define a key for it above and add it here. The features will be
// available throughout Kubernetes binaries.
var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureSpec{
ExternalTrafficLocalOnly: {Default: true, PreRelease: utilfeature.Beta},
ExternalTrafficLocalOnly: {Default: true, PreRelease: utilfeature.GA},
AppArmor: {Default: true, PreRelease: utilfeature.Beta},
DynamicKubeletConfig: {Default: false, PreRelease: utilfeature.Alpha},
DynamicVolumeProvisioning: {Default: true, PreRelease: utilfeature.Alpha},
......
......@@ -1055,7 +1055,7 @@ func (proxier *Proxier) syncProxyRules() {
svcXlbChain := serviceLBChainName(svcNameString, protocol)
if svcInfo.onlyNodeLocalEndpoints {
// Only for services with the externalTraffic annotation set to OnlyLocal
// Only for services request OnlyLocal traffic
// create the per-service LB chain, retaining counters if possible.
if lbChain, ok := existingNATChains[svcXlbChain]; ok {
writeLine(natChains, lbChain)
......@@ -1371,7 +1371,7 @@ func (proxier *Proxier) syncProxyRules() {
continue
}
// Now write ingress loadbalancing & DNAT rules only for services that have a localOnly annotation
// Now write ingress loadbalancing & DNAT rules only for services that request OnlyLocal traffic.
// TODO - This logic may be combinable with the block above that creates the svc balancer chain
localEndpoints := make([]*endpointsInfo, 0)
localEndpointChains := make([]utiliptables.Chain, 0)
......
......@@ -2453,6 +2453,16 @@ const (
ServiceTypeExternalName ServiceType = "ExternalName"
)
// Service External Traffic Policy Type string
type ServiceExternalTrafficPolicyType string
const (
// ServiceExternalTrafficPolicyTypeLocal specifies local endpoints behavior.
ServiceExternalTrafficPolicyTypeLocal ServiceExternalTrafficPolicyType = "Local"
// ServiceExternalTrafficPolicyTypeGlobal specifies global (legacy) behavior.
ServiceExternalTrafficPolicyTypeGlobal ServiceExternalTrafficPolicyType = "Global"
)
// ServiceStatus represents the current status of a service
type ServiceStatus struct {
// LoadBalancer contains the current status of the load-balancer,
......@@ -2552,6 +2562,20 @@ type ServiceSpec struct {
// cloud-provider does not support the feature."
// +optional
LoadBalancerSourceRanges []string
// externalTrafficPolicy denotes if this Service desires to route external traffic to
// local endpoints only. This preserves Source IP and avoids a second hop for
// LoadBalancer and Nodeport type services.
// +optional
ExternalTrafficPolicy ServiceExternalTrafficPolicyType
// healthCheckNodePort specifies the healthcheck nodePort for the service.
// If not specified, HealthCheckNodePort is created by the service api
// backend with the allocated nodePort. Will use user-specified nodePort value
// if specified by the client. Only effects when Type is set to LoadBalancer
// and ExternalTrafficPolicy is set to Local.
// +optional
HealthCheckNodePort int32
}
type ServicePort struct {
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -3697,6 +3697,20 @@ message ServiceSpec {
// Must be a valid DNS name and requires Type to be ExternalName.
// +optional
optional string externalName = 10;
// externalTrafficPolicy denotes if this Service desires to route external traffic to
// local endpoints only. This preserves Source IP and avoids a second hop for
// LoadBalancer and Nodeport type services.
// +optional
optional string externalTrafficPolicy = 11;
// healthCheckNodePort specifies the healthcheck nodePort for the service.
// If not specified, HealthCheckNodePort is created by the service api
// backend with the allocated nodePort. Will use user-specified nodePort value
// if specified by the client. Only effects when Type is set to LoadBalancer
// and ExternalTrafficPolicy is set to Local.
// +optional
optional int32 healthCheckNodePort = 12;
}
// ServiceStatus represents the current status of a service.
......
......@@ -2792,6 +2792,16 @@ const (
ServiceTypeExternalName ServiceType = "ExternalName"
)
// Service External Traffic Policy Type string
type ServiceExternalTrafficPolicyType string
const (
// ServiceExternalTrafficPolicyTypeLocal specifies local endpoints behavior.
ServiceExternalTrafficPolicyTypeLocal ServiceExternalTrafficPolicyType = "Local"
// ServiceExternalTrafficPolicyTypeGlobal specifies global (legacy) behavior.
ServiceExternalTrafficPolicyTypeGlobal ServiceExternalTrafficPolicyType = "Global"
)
// ServiceStatus represents the current status of a service.
type ServiceStatus struct {
// LoadBalancer contains the current status of the load-balancer,
......@@ -2904,6 +2914,20 @@ type ServiceSpec struct {
// Must be a valid DNS name and requires Type to be ExternalName.
// +optional
ExternalName string `json:"externalName,omitempty" protobuf:"bytes,10,opt,name=externalName"`
// externalTrafficPolicy denotes if this Service desires to route external traffic to
// local endpoints only. This preserves Source IP and avoids a second hop for
// LoadBalancer and Nodeport type services.
// +optional
ExternalTrafficPolicy ServiceExternalTrafficPolicyType `json:"externalTrafficPolicy,omitempty" protobuf:"bytes,11,opt,name=externalTrafficPolicy"`
// healthCheckNodePort specifies the healthcheck nodePort for the service.
// If not specified, HealthCheckNodePort is created by the service api
// backend with the allocated nodePort. Will use user-specified nodePort value
// if specified by the client. Only effects when Type is set to LoadBalancer
// and ExternalTrafficPolicy is set to Local.
// +optional
HealthCheckNodePort int32 `json:"healthCheckNodePort,omitempty" protobuf:"bytes,12,opt,name=healthCheckNodePort"`
}
// ServicePort contains information on service's port.
......
......@@ -1844,6 +1844,8 @@ var map_ServiceSpec = map[string]string{
"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.",
"loadBalancerSourceRanges": "If specified and supported by the platform, this will restrict traffic through the cloud-provider load-balancer will be restricted to the specified client IPs. This field will be ignored if the cloud-provider does not support the feature.\" More info: http://kubernetes.io/docs/user-guide/services-firewalls",
"externalName": "externalName is the external reference that kubedns or equivalent will return as a CNAME record for this service. No proxying will be involved. Must be a valid DNS name and requires Type to be ExternalName.",
"externalTrafficPolicy": "externalTrafficPolicy denotes if this Service desires to route external traffic to local endpoints only. This preserves Source IP and avoids a second hop for LoadBalancer and Nodeport type services.",
"healthCheckNodePort": "healthCheckNodePort specifies the healthcheck nodePort for the service. If not specified, HealthCheckNodePort is created by the service api backend with the allocated nodePort. Will use user-specified nodePort value if specified by the client. Only effects when Type is set to LoadBalancer and ExternalTrafficPolicy is set to Local.",
}
func (ServiceSpec) SwaggerDoc() map[string]string {
......
......@@ -4721,6 +4721,8 @@ func autoConvert_v1_ServiceSpec_To_api_ServiceSpec(in *ServiceSpec, out *api.Ser
out.LoadBalancerIP = in.LoadBalancerIP
out.LoadBalancerSourceRanges = *(*[]string)(unsafe.Pointer(&in.LoadBalancerSourceRanges))
out.ExternalName = in.ExternalName
out.ExternalTrafficPolicy = api.ServiceExternalTrafficPolicyType(in.ExternalTrafficPolicy)
out.HealthCheckNodePort = in.HealthCheckNodePort
return nil
}
......@@ -4739,6 +4741,8 @@ func autoConvert_api_ServiceSpec_To_v1_ServiceSpec(in *api.ServiceSpec, out *Ser
out.LoadBalancerIP = in.LoadBalancerIP
out.SessionAffinity = ServiceAffinity(in.SessionAffinity)
out.LoadBalancerSourceRanges = *(*[]string)(unsafe.Pointer(&in.LoadBalancerSourceRanges))
out.ExternalTrafficPolicy = ServiceExternalTrafficPolicyType(in.ExternalTrafficPolicy)
out.HealthCheckNodePort = in.HealthCheckNodePort
return nil
}
......
......@@ -60,7 +60,7 @@ var _ = framework.KubeDescribe("Firewall rule", func() {
nodesSet := sets.NewString(nodesNames...)
// OnlyLocal service is needed to examine which exact nodes the requests are being forwarded to by the Load Balancer on GCE
By("Creating a LoadBalancer type service with onlyLocal annotation")
By("Creating a LoadBalancer type service with ExternalTrafficPolicy=Local")
svc := jig.CreateOnlyLocalLoadBalancerService(ns, serviceName,
framework.LoadBalancerCreateTimeoutDefault, false, func(svc *v1.Service) {
svc.Spec.Ports = []v1.ServicePort{{Protocol: "TCP", Port: framework.FirewallTestHttpPort}}
......
......@@ -45,7 +45,6 @@ go_library(
"//pkg/api/v1/helper:go_default_library",
"//pkg/api/v1/node:go_default_library",
"//pkg/api/v1/pod:go_default_library",
"//pkg/api/v1/service:go_default_library",
"//pkg/apis/apps/v1beta1:go_default_library",
"//pkg/apis/authorization/v1beta1:go_default_library",
"//pkg/apis/batch:go_default_library",
......
......@@ -34,7 +34,6 @@ import (
"k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/api/v1/service"
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/client/retry"
......@@ -189,15 +188,15 @@ func (j *ServiceTestJig) ChangeServiceType(namespace, name string, newType v1.Se
}
}
// CreateOnlyLocalNodePortService creates a loadbalancer service and sanity checks its
// nodePort. If createPod is true, it also creates an RC with 1 replica of
// CreateOnlyLocalNodePortService creates a NodePort service with
// ExternalTrafficPolicy set to Local and sanity checks its nodePort.
// If createPod is true, it also creates an RC with 1 replica of
// the standard netexec container used everywhere in this test.
func (j *ServiceTestJig) CreateOnlyLocalNodePortService(namespace, serviceName string, createPod bool) *v1.Service {
By("creating a service " + namespace + "/" + serviceName + " with type=NodePort and annotation for local-traffic-only")
By("creating a service " + namespace + "/" + serviceName + " with type=NodePort and ExternalTrafficPolicy=Local")
svc := j.CreateTCPServiceOrFail(namespace, func(svc *v1.Service) {
svc.Spec.Type = v1.ServiceTypeNodePort
svc.ObjectMeta.Annotations = map[string]string{
service.BetaAnnotationExternalTraffic: service.AnnotationValueExternalTrafficLocal}
svc.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyTypeLocal
svc.Spec.Ports = []v1.ServicePort{{Protocol: "TCP", Port: 80}}
})
......@@ -209,18 +208,18 @@ func (j *ServiceTestJig) CreateOnlyLocalNodePortService(namespace, serviceName s
return svc
}
// CreateOnlyLocalLoadBalancerService creates a loadbalancer service and waits for it to
// acquire an ingress IP. If createPod is true, it also creates an RC with 1
// replica of the standard netexec container used everywhere in this test.
// CreateOnlyLocalLoadBalancerService creates a loadbalancer service with
// ExternalTrafficPolicy set to Local and waits for it to acquire an ingress IP.
// If createPod is true, it also creates an RC with 1 replica of
// the standard netexec container used everywhere in this test.
func (j *ServiceTestJig) CreateOnlyLocalLoadBalancerService(namespace, serviceName string, timeout time.Duration, createPod bool,
tweak func(svc *v1.Service)) *v1.Service {
By("creating a service " + namespace + "/" + serviceName + " with type=LoadBalancer and annotation for local-traffic-only")
By("creating a service " + namespace + "/" + serviceName + " with type=LoadBalancer and ExternalTrafficPolicy=Local")
svc := j.CreateTCPServiceOrFail(namespace, func(svc *v1.Service) {
svc.Spec.Type = v1.ServiceTypeLoadBalancer
// We need to turn affinity off for our LB distribution tests
svc.Spec.SessionAffinity = v1.ServiceAffinityNone
svc.ObjectMeta.Annotations = map[string]string{
service.BetaAnnotationExternalTraffic: service.AnnotationValueExternalTrafficLocal}
svc.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyTypeLocal
if tweak != nil {
tweak(svc)
}
......
......@@ -1441,7 +1441,7 @@ var _ = framework.KubeDescribe("ESIPP [Slow]", func() {
}
})
It("should handle updates to source ip annotation", func() {
It("should handle updates to ExternalTrafficPolicy field", func() {
namespace := f.Namespace.Name
serviceName := "external-local"
jig := framework.NewServiceTestJig(cs, serviceName)
......@@ -1458,16 +1458,15 @@ var _ = framework.KubeDescribe("ESIPP [Slow]", func() {
Expect(cs.Core().Services(svc.Namespace).Delete(svc.Name, nil)).NotTo(HaveOccurred())
}()
// save the health check node port because it disappears when lift the annotation.
// save the health check node port because it disappears when ESIPP is turned off.
healthCheckNodePort := int(service.GetServiceHealthCheckNodePort(svc))
By("turning ESIPP off")
svc = jig.UpdateServiceOrFail(svc.Namespace, svc.Name, func(svc *v1.Service) {
svc.ObjectMeta.Annotations[service.BetaAnnotationExternalTraffic] =
service.AnnotationValueExternalTrafficGlobal
svc.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyTypeGlobal
})
if service.GetServiceHealthCheckNodePort(svc) > 0 {
framework.Failf("Service HealthCheck NodePort annotation still present")
framework.Failf("Service HealthCheck NodePort still present")
}
endpointNodeMap := jig.GetEndpointNodes(svc)
......@@ -1525,13 +1524,11 @@ var _ = framework.KubeDescribe("ESIPP [Slow]", func() {
// If the health check nodePort has NOT been freed, the new service
// creation will fail.
By("turning ESIPP annotation back on")
By("setting ExternalTraffic field back to OnlyLocal")
svc = jig.UpdateServiceOrFail(svc.Namespace, svc.Name, func(svc *v1.Service) {
svc.ObjectMeta.Annotations[service.BetaAnnotationExternalTraffic] =
service.AnnotationValueExternalTrafficLocal
svc.Spec.ExternalTrafficPolicy = v1.ServiceExternalTrafficPolicyTypeLocal
// Request the same healthCheckNodePort as before, to test the user-requested allocation path
svc.ObjectMeta.Annotations[service.BetaAnnotationHealthCheckNodePort] =
fmt.Sprintf("%d", healthCheckNodePort)
svc.Spec.HealthCheckNodePort = int32(healthCheckNodePort)
})
pollErr = wait.PollImmediate(framework.Poll, framework.KubeProxyLagTimeout, func() (bool, error) {
content := jig.GetHTTPContent(ingressIP, svcTCPPort, framework.KubeProxyLagTimeout, path)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment