예제 #1
0
// Validate allocation of a nodePort when the externalTraffic=OnlyLocal annotation is set
// and type is LoadBalancer
func TestServiceRegistryExternalTrafficAnnotationHealthCheckNodePortAllocation(t *testing.T) {
	ctx := genericapirequest.NewDefaultContext()
	storage, _ := NewTestREST(t, nil)
	svc := &api.Service{
		ObjectMeta: metav1.ObjectMeta{Name: "external-lb-esipp",
			Annotations: map[string]string{
				service.BetaAnnotationExternalTraffic: service.AnnotationValueExternalTrafficLocal,
			},
		},
		Spec: api.ServiceSpec{
			Selector:        map[string]string{"bar": "baz"},
			SessionAffinity: api.ServiceAffinityNone,
			Type:            api.ServiceTypeLoadBalancer,
			Ports: []api.ServicePort{{
				Port:       6502,
				Protocol:   api.ProtocolTCP,
				TargetPort: intstr.FromInt(6502),
			}},
		},
	}
	created_svc, err := storage.Create(ctx, svc)
	if created_svc == nil || err != nil {
		t.Errorf("Unexpected failure creating service %v", err)
	}
	created_service := created_svc.(*api.Service)
	if !service.NeedsHealthCheck(created_service) {
		t.Errorf("Unexpected missing annotation %s", service.BetaAnnotationExternalTraffic)
	}
	port := service.GetServiceHealthCheckNodePort(created_service)
	if port == 0 {
		t.Errorf("Failed to allocate and create the health check node port annotation %s", service.BetaAnnotationHealthCheckNodePort)
	}

}
예제 #2
0
// Validate that the health check nodePort is not allocated when service type is ClusterIP
func TestServiceRegistryExternalTrafficAnnotationClusterIP(t *testing.T) {
	ctx := api.NewDefaultContext()
	storage, _ := NewTestREST(t, nil)
	svc := &api.Service{
		ObjectMeta: api.ObjectMeta{Name: "external-lb-esipp",
			Annotations: map[string]string{
				service.AnnotationExternalTraffic: service.AnnotationValueExternalTrafficGlobal,
			},
		},
		Spec: api.ServiceSpec{
			Selector:        map[string]string{"bar": "baz"},
			SessionAffinity: api.ServiceAffinityNone,
			Type:            api.ServiceTypeClusterIP,
			Ports: []api.ServicePort{{
				Port:       6502,
				Protocol:   api.ProtocolTCP,
				TargetPort: intstr.FromInt(6502),
			}},
		},
	}
	created_svc, err := storage.Create(ctx, svc)
	if created_svc == nil || err != nil {
		t.Errorf("Unexpected failure creating service %v", err)
	}
	created_service := created_svc.(*api.Service)
	// Make sure that ClusterIP services do not have the health check node port allocated
	port := service.GetServiceHealthCheckNodePort(created_service)
	if port != 0 {
		t.Errorf("Unexpected allocation of health check node port annotation %s", service.AnnotationHealthCheckNodePort)
	}
}
예제 #3
0
func (rs *REST) Delete(ctx api.Context, id string) (runtime.Object, error) {
	service, err := rs.registry.GetService(ctx, id)
	if err != nil {
		return nil, err
	}

	err = rs.registry.DeleteService(ctx, id)
	if err != nil {
		return nil, err
	}

	// TODO: can leave dangling endpoints, and potentially return incorrect
	// endpoints if a new service is created with the same name
	err = rs.endpoints.DeleteEndpoints(ctx, id)
	if err != nil && !errors.IsNotFound(err) {
		return nil, err
	}

	if api.IsServiceIPSet(service) {
		rs.serviceIPs.Release(net.ParseIP(service.Spec.ClusterIP))
	}

	for _, nodePort := range CollectServiceNodePorts(service) {
		err := rs.serviceNodePorts.Release(nodePort)
		if err != nil {
			// these should be caught by an eventual reconciliation / restart
			glog.Errorf("Error releasing service %s node port %d: %v", service.Name, nodePort, err)
		}
	}

	if shouldCheckOrAssignHealthCheckNodePort(service) {
		nodePort := apiservice.GetServiceHealthCheckNodePort(service)
		if nodePort > 0 {
			err := rs.serviceNodePorts.Release(int(nodePort))
			if err != nil {
				// these should be caught by an eventual reconciliation / restart
				utilruntime.HandleError(fmt.Errorf("Error releasing service health check %s node port %d: %v", service.Name, nodePort, err))
			}
		}
	}
	return &unversioned.Status{Status: unversioned.StatusSuccess}, nil
}
예제 #4
0
func (rs *REST) healthCheckNodePortUpdate(oldService, service *api.Service) (bool, error) {
	// Health Check Node Port handling during updates
	//
	// Case 1. Transition from globalTraffic to OnlyLocal for the ESIPP annotation
	//
	//   Allocate a health check node port or attempt to reserve the user-specified one, if provided.
	//   Insert health check node port as an annotation into the service's annotations
	//
	// Case 2. Transition from OnlyLocal to Global for the ESIPP annotation
	//
	//   Free the existing healthCheckNodePort and clear the health check nodePort annotation
	//
	// Case 3. No change (Global ---stays--> Global) but prevent invalid annotation manipulations
	//
	//   Reject insertion of the "service.alpha.kubernetes.io/healthcheck-nodeport" annotation
	//
	// Case 4. No change (OnlyLocal ---stays--> OnlyLocal) but prevent invalid annotation manipulations
	//
	//   Reject deletion of the "service.alpha.kubernetes.io/healthcheck-nodeport" annotation
	//   Reject changing the value of the healthCheckNodePort annotation
	//
	oldServiceHasHealthCheckNodePort := shouldCheckOrAssignHealthCheckNodePort(oldService)
	oldHealthCheckNodePort := apiservice.GetServiceHealthCheckNodePort(oldService)

	assignHealthCheckNodePort := shouldCheckOrAssignHealthCheckNodePort(service)
	requestedHealthCheckNodePort := apiservice.GetServiceHealthCheckNodePort(service)

	switch {
	case !oldServiceHasHealthCheckNodePort && assignHealthCheckNodePort:
		glog.Infof("Transition from Global LB service to OnlyLocal service")
		if requestedHealthCheckNodePort > 0 {
			// If the request has a health check nodePort in mind, attempt to reserve it
			err := rs.serviceNodePorts.Allocate(int(requestedHealthCheckNodePort))
			if err != nil {
				errmsg := fmt.Sprintf("Failed to allocate requested HealthCheck nodePort %v:%v",
					requestedHealthCheckNodePort, err)
				el := field.ErrorList{field.Invalid(field.NewPath("metadata", "annotations"),
					apiservice.AnnotationHealthCheckNodePort, errmsg)}
				return false, errors.NewInvalid(api.Kind("Service"), service.Name, el)
			}
			glog.Infof("Reserved user requested nodePort: %d", requestedHealthCheckNodePort)
		} else {
			// If the request has no health check nodePort specified, allocate any
			healthCheckNodePort, err := rs.serviceNodePorts.AllocateNext()
			if err != nil {
				// TODO: what error should be returned here?  It's not a
				// field-level validation failure (the field is valid), and it's
				// not really an internal error.
				return false, errors.NewInternalError(fmt.Errorf("failed to allocate a nodePort: %v", err))
			}
			// Insert the newly allocated health check port as an annotation (plan of record for Alpha)
			service.Annotations[apiservice.AnnotationHealthCheckNodePort] = fmt.Sprintf("%d", healthCheckNodePort)
			glog.Infof("Reserved health check nodePort: %d", healthCheckNodePort)
		}

	case oldServiceHasHealthCheckNodePort && !assignHealthCheckNodePort:
		glog.Infof("Transition from OnlyLocal LB service to Global service")
		err := rs.serviceNodePorts.Release(int(oldHealthCheckNodePort))
		if err != nil {
			glog.Warningf("Error releasing service health check %s node port %d: %v", service.Name, oldHealthCheckNodePort, err)
			return false, errors.NewInternalError(fmt.Errorf("failed to free health check nodePort: %v", err))
		} else {
			delete(service.Annotations, apiservice.AnnotationHealthCheckNodePort)
			glog.Infof("Freed health check nodePort: %d", oldHealthCheckNodePort)
		}

	case !oldServiceHasHealthCheckNodePort && !assignHealthCheckNodePort:
		if _, ok := service.Annotations[apiservice.AnnotationHealthCheckNodePort]; ok {
			glog.Warningf("Attempt to insert health check node port annotation DENIED")
			el := field.ErrorList{field.Invalid(field.NewPath("metadata", "annotations"),
				apiservice.AnnotationHealthCheckNodePort, "Cannot insert healthcheck nodePort annotation")}
			return false, errors.NewInvalid(api.Kind("Service"), service.Name, el)
		}

	case oldServiceHasHealthCheckNodePort && assignHealthCheckNodePort:
		if _, ok := service.Annotations[apiservice.AnnotationHealthCheckNodePort]; !ok {
			glog.Warningf("Attempt to delete health check node port annotation DENIED")
			el := field.ErrorList{field.Invalid(field.NewPath("metadata", "annotations"),
				apiservice.AnnotationHealthCheckNodePort, "Cannot delete healthcheck nodePort annotation")}
			return false, errors.NewInvalid(api.Kind("Service"), service.Name, el)
		}
		if oldHealthCheckNodePort != requestedHealthCheckNodePort {
			glog.Warningf("Attempt to change value of health check node port annotation DENIED")
			el := field.ErrorList{field.Invalid(field.NewPath("metadata", "annotations"),
				apiservice.AnnotationHealthCheckNodePort, "Cannot change healthcheck nodePort during update")}
			return false, errors.NewInvalid(api.Kind("Service"), service.Name, el)
		}
	}
	return true, nil
}
예제 #5
0
// OnServiceUpdate tracks the active set of service proxies.
// They will be synchronized using syncProxyRules()
func (proxier *Proxier) OnServiceUpdate(allServices []api.Service) {
	start := time.Now()
	defer func() {
		glog.V(4).Infof("OnServiceUpdate took %v for %d services", time.Since(start), len(allServices))
	}()
	proxier.mu.Lock()
	defer proxier.mu.Unlock()
	proxier.haveReceivedServiceUpdate = true

	activeServices := make(map[proxy.ServicePortName]bool) // use a map as a set

	for i := range allServices {
		service := &allServices[i]
		svcName := types.NamespacedName{
			Namespace: service.Namespace,
			Name:      service.Name,
		}

		// if ClusterIP is "None" or empty, skip proxying
		if !api.IsServiceIPSet(service) {
			glog.V(3).Infof("Skipping service %s due to clusterIP = %q", svcName, service.Spec.ClusterIP)
			continue
		}

		for i := range service.Spec.Ports {
			servicePort := &service.Spec.Ports[i]

			serviceName := proxy.ServicePortName{
				NamespacedName: svcName,
				Port:           servicePort.Name,
			}
			activeServices[serviceName] = true
			info, exists := proxier.serviceMap[serviceName]
			if exists && proxier.sameConfig(info, service, servicePort) {
				// Nothing changed.
				continue
			}
			if exists {
				// Something changed.
				glog.V(3).Infof("Something changed for service %q: removing it", serviceName)
				delete(proxier.serviceMap, serviceName)
			}
			serviceIP := net.ParseIP(service.Spec.ClusterIP)
			glog.V(1).Infof("Adding new service %q at %s:%d/%s", serviceName, serviceIP, servicePort.Port, servicePort.Protocol)
			info = newServiceInfo(serviceName)
			info.clusterIP = serviceIP
			info.port = int(servicePort.Port)
			info.protocol = servicePort.Protocol
			info.nodePort = int(servicePort.NodePort)
			info.externalIPs = service.Spec.ExternalIPs
			// Deep-copy in case the service instance changes
			info.loadBalancerStatus = *api.LoadBalancerStatusDeepCopy(&service.Status.LoadBalancer)
			info.sessionAffinityType = service.Spec.SessionAffinity
			info.loadBalancerSourceRanges = service.Spec.LoadBalancerSourceRanges
			info.onlyNodeLocalEndpoints = apiservice.NeedsHealthCheck(service) && featuregate.DefaultFeatureGate.ExternalTrafficLocalOnly()
			if info.onlyNodeLocalEndpoints {
				p := apiservice.GetServiceHealthCheckNodePort(service)
				if p == 0 {
					glog.Errorf("Service does not contain necessary annotation %v",
						apiservice.AnnotationHealthCheckNodePort)
				} else {
					info.healthCheckNodePort = int(p)
					// Turn on healthcheck responder to listen on the health check nodePort
					healthcheck.AddServiceListener(serviceName.NamespacedName, info.healthCheckNodePort)
				}
			}
			proxier.serviceMap[serviceName] = info

			glog.V(4).Infof("added serviceInfo(%s): %s", serviceName, spew.Sdump(info))
		}
	}

	staleUDPServices := sets.NewString()
	// Remove serviceports missing from the update.
	for name, info := range proxier.serviceMap {
		if !activeServices[name] {
			glog.V(1).Infof("Removing service %q", name)
			if info.protocol == api.ProtocolUDP {
				staleUDPServices.Insert(info.clusterIP.String())
			}
			delete(proxier.serviceMap, name)
			if info.onlyNodeLocalEndpoints && info.healthCheckNodePort > 0 {
				// Remove ServiceListener health check nodePorts from the health checker
				// TODO - Stats
				healthcheck.DeleteServiceListener(name.NamespacedName, info.healthCheckNodePort)
			}
		}
	}
	proxier.syncProxyRules()
	proxier.deleteServiceConnections(staleUDPServices.List())

}