github.com/docker/compose-on-kubernetes@v0.5.0/internal/convert/service.go (about)

     1  package convert
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/docker/compose-on-kubernetes/api/compose/latest"
     8  	"github.com/docker/compose-on-kubernetes/internal/stackresources"
     9  	apiv1 "k8s.io/api/core/v1"
    10  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    11  	"k8s.io/apimachinery/pkg/util/intstr"
    12  )
    13  
    14  const (
    15  	// publishedServiceSuffix is the suffix given to services that are exposed
    16  	// externally.
    17  	publishedServiceSuffix      = "-published"
    18  	publishedOnRandomPortSuffix = "-random-ports"
    19  	// headlessPortName is the name given to the port for headless services.
    20  	headlessPortName = "headless"
    21  	// headlessPort is the port allocated for headless services.
    22  	headlessPort = 55555
    23  )
    24  
    25  // ServiceStrategy defines a strategy to use for converting stacks ports to kubernetes services
    26  type ServiceStrategy interface {
    27  	randomServiceType() apiv1.ServiceType
    28  	publishedServiceType() apiv1.ServiceType
    29  	convertServicePort(config latest.ServicePortConfig, originalPublished, originalRandom []apiv1.ServicePort) (port apiv1.ServicePort, published bool)
    30  }
    31  
    32  // ServiceStrategyFor returns the correct strategy for a desired publishServiceType
    33  func ServiceStrategyFor(publishServiceType apiv1.ServiceType) (ServiceStrategy, error) {
    34  	switch publishServiceType {
    35  	case apiv1.ServiceTypeLoadBalancer:
    36  		return loadBalancerServiceStrategy{}, nil
    37  	case apiv1.ServiceTypeNodePort:
    38  		return nodePortServiceStrategy{}, nil
    39  	}
    40  	return nil, fmt.Errorf("No strategy for service type %s", publishServiceType)
    41  }
    42  
    43  func findPortOrDefault(ports []apiv1.ServicePort, name string) apiv1.ServicePort {
    44  	for _, p := range ports {
    45  		if p.Name == name {
    46  			return p
    47  		}
    48  	}
    49  	return apiv1.ServicePort{
    50  		Name: name,
    51  	}
    52  }
    53  
    54  type loadBalancerServiceStrategy struct{}
    55  
    56  func (loadBalancerServiceStrategy) randomServiceType() apiv1.ServiceType {
    57  	return apiv1.ServiceTypeNodePort
    58  }
    59  
    60  func (loadBalancerServiceStrategy) publishedServiceType() apiv1.ServiceType {
    61  	return apiv1.ServiceTypeLoadBalancer
    62  }
    63  
    64  func (loadBalancerServiceStrategy) convertServicePort(source latest.ServicePortConfig, originalPublished, originalRandom []apiv1.ServicePort) (port apiv1.ServicePort, published bool) {
    65  	proto := toProtocol(source.Protocol)
    66  	protoLower := strings.ToLower(string(proto))
    67  	if source.Published != 0 {
    68  		p := findPortOrDefault(originalPublished, fmt.Sprintf("%d-%s", source.Published, protoLower))
    69  		p.Port = int32(source.Published)
    70  		p.Protocol = proto
    71  		p.TargetPort = intstr.FromInt(int(source.Target))
    72  		return p, true
    73  	}
    74  
    75  	p := findPortOrDefault(originalRandom, fmt.Sprintf("%d-%s", source.Target, protoLower))
    76  	p.Protocol = proto
    77  	p.Port = int32(source.Target)
    78  	p.TargetPort = intstr.FromInt(int(source.Target))
    79  	return p, false
    80  
    81  }
    82  
    83  type nodePortServiceStrategy struct{}
    84  
    85  func (nodePortServiceStrategy) randomServiceType() apiv1.ServiceType {
    86  	return apiv1.ServiceTypeNodePort
    87  }
    88  
    89  func (nodePortServiceStrategy) publishedServiceType() apiv1.ServiceType {
    90  	return apiv1.ServiceTypeNodePort
    91  }
    92  
    93  func (nodePortServiceStrategy) convertServicePort(source latest.ServicePortConfig, originalPublished, originalRandom []apiv1.ServicePort) (port apiv1.ServicePort, published bool) {
    94  	proto := toProtocol(source.Protocol)
    95  	protoLower := strings.ToLower(string(proto))
    96  	if source.Published != 0 {
    97  		p := findPortOrDefault(originalPublished, fmt.Sprintf("%d-%s", source.Published, protoLower))
    98  		p.NodePort = int32(source.Published)
    99  		p.Protocol = proto
   100  		p.Port = int32(source.Target)
   101  		p.TargetPort = intstr.FromInt(int(source.Target))
   102  		return p, true
   103  	}
   104  	p := findPortOrDefault(originalRandom, fmt.Sprintf("%d-%s", source.Target, protoLower))
   105  	p.Protocol = proto
   106  	p.TargetPort = intstr.FromInt(int(source.Target))
   107  	p.Port = int32(source.Target)
   108  	return p, false
   109  }
   110  
   111  // toServices converts a Compose Service to a Kubernetes headless service as
   112  // well as a normal service if it requires published ports.
   113  func toServices(s latest.ServiceConfig, objectMeta metav1.ObjectMeta, labelSelector map[string]string,
   114  	strategy ServiceStrategy, original *stackresources.StackState) (*apiv1.Service, *apiv1.Service, *apiv1.Service) {
   115  	headlessMeta := objectMeta
   116  	publishedMeta := publishedObjectMeta(objectMeta)
   117  	randomPortsMeta := randomPortsObjectMeta(objectMeta)
   118  
   119  	originalHL := original.Services[stackresources.ObjKey(headlessMeta.Namespace, headlessMeta.Name)]
   120  	hl := toInternalService(headlessMeta, labelSelector, originalHL, s.InternalPorts, s.InternalServiceType)
   121  
   122  	originalRandom := original.Services[stackresources.ObjKey(randomPortsMeta.Namespace, randomPortsMeta.Name)]
   123  	originalPublished := original.Services[stackresources.ObjKey(publishedMeta.Namespace, publishedMeta.Name)]
   124  
   125  	var randomPorts, publishedPorts []apiv1.ServicePort
   126  	for _, p := range s.Ports {
   127  		port, published := strategy.convertServicePort(p, originalPublished.Spec.Ports, originalRandom.Spec.Ports)
   128  		if published {
   129  			publishedPorts = append(publishedPorts, port)
   130  		} else {
   131  			randomPorts = append(randomPorts, port)
   132  		}
   133  	}
   134  	published := toExposedService(
   135  		publishedMeta,
   136  		publishedPorts,
   137  		strategy.publishedServiceType(),
   138  		labelSelector,
   139  		originalPublished,
   140  	)
   141  	random := toExposedService(
   142  		randomPortsMeta,
   143  		randomPorts,
   144  		strategy.randomServiceType(),
   145  		labelSelector,
   146  		originalRandom,
   147  	)
   148  	return hl, published, random
   149  }
   150  
   151  // toInternalService creates a Kubernetes service for intra-stack communication.
   152  func toInternalService(objectMeta metav1.ObjectMeta, labelSelector map[string]string, original apiv1.Service,
   153  	internalPorts []latest.InternalPort, internalServiceType latest.InternalServiceType) *apiv1.Service {
   154  	useHeadless := false
   155  	switch internalServiceType {
   156  	case latest.InternalServiceTypeHeadless:
   157  		useHeadless = true
   158  	case latest.InternalServiceTypeAuto:
   159  		useHeadless = len(internalPorts) == 0
   160  	}
   161  	service := original.DeepCopy()
   162  	service.ObjectMeta = objectMeta
   163  	service.Spec.Selector = labelSelector
   164  	if useHeadless {
   165  		service.Spec.ClusterIP = apiv1.ClusterIPNone
   166  		service.Spec.Ports = []apiv1.ServicePort{{
   167  			Name:       headlessPortName,
   168  			Port:       headlessPort,
   169  			TargetPort: intstr.FromInt(headlessPort),
   170  			Protocol:   apiv1.ProtocolTCP,
   171  		}}
   172  	} else {
   173  		if service.Spec.ClusterIP == apiv1.ClusterIPNone {
   174  			service.Spec.ClusterIP = ""
   175  		}
   176  		service.Spec.Ports = []apiv1.ServicePort{}
   177  		for _, p := range internalPorts {
   178  			service.Spec.Ports = append(service.Spec.Ports,
   179  				apiv1.ServicePort{
   180  					Name:       fmt.Sprintf("%d-%s", p.Port, strings.ToLower(string(p.Protocol))),
   181  					Port:       p.Port,
   182  					TargetPort: intstr.FromInt(int(p.Port)),
   183  					Protocol:   p.Protocol,
   184  				})
   185  		}
   186  	}
   187  	return service
   188  }
   189  
   190  // toExposedService creates a Kubernetes service with exposed ports. The
   191  // service name is suffixed to distinguish it from a headless service.
   192  func toExposedService(objectMeta metav1.ObjectMeta, servicePorts []apiv1.ServicePort, svcType apiv1.ServiceType, labelSelector map[string]string, original apiv1.Service) *apiv1.Service {
   193  	if len(servicePorts) == 0 {
   194  		return nil
   195  	}
   196  	service := original.DeepCopy()
   197  	service.ObjectMeta = objectMeta
   198  	service.Spec.Type = svcType
   199  	service.Spec.Selector = labelSelector
   200  	service.Spec.Ports = servicePorts
   201  	return service
   202  }
   203  
   204  // publishedObjectMeta appends "-published" to the name of a given Kubernetes
   205  // object metadata.
   206  func publishedObjectMeta(objectMeta metav1.ObjectMeta) metav1.ObjectMeta {
   207  	res := objectMeta
   208  	res.Name = fmt.Sprintf("%s%s", objectMeta.Name, publishedServiceSuffix)
   209  	return res
   210  }
   211  
   212  func randomPortsObjectMeta(objectMeta metav1.ObjectMeta) metav1.ObjectMeta {
   213  	res := objectMeta
   214  	res.Name = fmt.Sprintf("%s%s", objectMeta.Name, publishedOnRandomPortSuffix)
   215  	return res
   216  }