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 }