istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/analysis/analyzers/deployment/services.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 package deployment 15 16 import ( 17 "fmt" 18 "sort" 19 "strconv" 20 21 appsv1 "k8s.io/api/apps/v1" 22 core_v1 "k8s.io/api/core/v1" 23 klabels "k8s.io/apimachinery/pkg/labels" 24 25 "istio.io/istio/pkg/config" 26 "istio.io/istio/pkg/config/analysis" 27 "istio.io/istio/pkg/config/analysis/analyzers/util" 28 "istio.io/istio/pkg/config/analysis/msg" 29 "istio.io/istio/pkg/config/constants" 30 "istio.io/istio/pkg/config/resource" 31 "istio.io/istio/pkg/config/schema/gvk" 32 ) 33 34 type ServiceAssociationAnalyzer struct{} 35 36 var _ analysis.Analyzer = &ServiceAssociationAnalyzer{} 37 38 type ( 39 PortMap map[int32]ProtocolMap 40 ProtocolMap map[core_v1.Protocol]ServiceNames 41 ServiceNames []string 42 ServiceSpecWithName struct { 43 Name string 44 Spec *core_v1.ServiceSpec 45 } 46 ) 47 48 // targetPort port serviceName 49 type targetPortMap map[string]map[int32]string 50 51 func (s *ServiceAssociationAnalyzer) Metadata() analysis.Metadata { 52 return analysis.Metadata{ 53 Name: "deployment.MultiServiceAnalyzer", 54 Description: "Checks association between services and pods", 55 Inputs: []config.GroupVersionKind{ 56 gvk.Service, 57 gvk.Deployment, 58 gvk.Namespace, 59 }, 60 } 61 } 62 63 func (s *ServiceAssociationAnalyzer) Analyze(c analysis.Context) { 64 c.ForEach(gvk.Deployment, func(r *resource.Instance) bool { 65 if !isWaypointDeployment(r) && util.DeploymentInMesh(r, c) { 66 s.analyzeDeploymentPortProtocol(r, c) 67 s.analyzeDeploymentTargetPorts(r, c) 68 } 69 return true 70 }) 71 } 72 73 func isWaypointDeployment(r *resource.Instance) bool { 74 return r.Metadata.Labels[constants.ManagedGatewayLabel] == constants.ManagedGatewayMeshControllerLabel 75 } 76 77 // analyzeDeploymentPortProtocol analyzes the specific service mesh deployment 78 func (s *ServiceAssociationAnalyzer) analyzeDeploymentPortProtocol(r *resource.Instance, c analysis.Context) { 79 // Find matching services with resulting pod from deployment 80 matchingSvcs := s.findMatchingServices(r, c) 81 82 // Generate a port map from the matching services. 83 // It creates a structure that will allow us to detect 84 // if there are different protocols for the same port. 85 portMap := servicePortMap(matchingSvcs) 86 87 // Determining which ports use more than one protocol. 88 for port := range portMap { 89 // In case there are two protocols using same port number, generate a message 90 protMap := portMap[port] 91 if len(protMap) > 1 { 92 // Collect names from both protocols 93 svcNames := make(ServiceNames, 0) 94 for protocol := range protMap { 95 svcNames = append(svcNames, protMap[protocol]...) 96 } 97 sort.Strings(svcNames) 98 m := msg.NewDeploymentAssociatedToMultipleServices(r, r.Metadata.FullName.Name.String(), port, svcNames) 99 100 if line, ok := util.ErrorLine(r, fmt.Sprintf(util.MetadataName)); ok { 101 m.Line = line 102 } 103 104 // Reporting the message for the deployment, port and conflicting services. 105 c.Report(gvk.Deployment, m) 106 } 107 } 108 } 109 110 // analyzeDeploymentPortProtocol analyzes the targetPorts conflicting 111 func (s *ServiceAssociationAnalyzer) analyzeDeploymentTargetPorts(r *resource.Instance, c analysis.Context) { 112 // Find matching services with resulting pod from deployment 113 matchingSvcs := s.findMatchingServices(r, c) 114 115 tpm := serviceTargetPortsMap(matchingSvcs) 116 117 // Determining which ports use more than one protocol. 118 for targetPort, portServices := range tpm { 119 if len(portServices) > 1 { 120 // Collect names from both protocols 121 svcNames := make(ServiceNames, 0, len(portServices)) 122 ports := make([]int32, 0, len(portServices)) 123 for p, s := range portServices { 124 svcNames = append(svcNames, s) 125 ports = append(ports, p) 126 } 127 128 sort.Strings(svcNames) 129 sort.Slice(ports, func(i, j int) bool { 130 return ports[i] < ports[j] 131 }) 132 m := msg.NewDeploymentConflictingPorts(r, r.Metadata.FullName.Name.String(), svcNames, targetPort, ports) 133 134 if line, ok := util.ErrorLine(r, fmt.Sprintf(util.MetadataName)); ok { 135 m.Line = line 136 } 137 138 // Reporting the message for the deployment, port and conflicting services. 139 c.Report(gvk.Deployment, m) 140 } 141 } 142 } 143 144 // findMatchingServices returns an slice of Services that matches with deployment's pods. 145 func (s *ServiceAssociationAnalyzer) findMatchingServices(r *resource.Instance, c analysis.Context) []ServiceSpecWithName { 146 matchingSvcs := make([]ServiceSpecWithName, 0) 147 d := r.Message.(*appsv1.DeploymentSpec) 148 deploymentNS := r.Metadata.FullName.Namespace.String() 149 150 c.ForEach(gvk.Service, func(r *resource.Instance) bool { 151 s := r.Message.(*core_v1.ServiceSpec) 152 153 sSelector := klabels.SelectorFromSet(s.Selector) 154 pLabels := klabels.Set(d.Template.Labels) 155 if !sSelector.Empty() && sSelector.Matches(pLabels) && r.Metadata.FullName.Namespace.String() == deploymentNS { 156 matchingSvcs = append(matchingSvcs, ServiceSpecWithName{r.Metadata.FullName.String(), s}) 157 } 158 159 return true 160 }) 161 162 return matchingSvcs 163 } 164 165 // servicePortMap build a map of ports and protocols for each Service. e.g. m[80]["TCP"] -> svcA, svcB, svcC 166 func servicePortMap(svcs []ServiceSpecWithName) PortMap { 167 portMap := PortMap{} 168 169 for _, swn := range svcs { 170 svc := swn.Spec 171 for _, sPort := range svc.Ports { 172 // If it is the first occurrence of this port, create a ProtocolMap 173 if _, ok := portMap[sPort.Port]; !ok { 174 portMap[sPort.Port] = ProtocolMap{} 175 } 176 177 // Default protocol is TCP 178 protocol := sPort.Protocol 179 if protocol == "" { 180 protocol = core_v1.ProtocolTCP 181 } 182 183 // Appending the service information for the Port/Protocol combination 184 portMap[sPort.Port][protocol] = append(portMap[sPort.Port][protocol], swn.Name) 185 } 186 } 187 188 return portMap 189 } 190 191 // serviceTargetPortsMap build a map of targetPort and ports for each Service. e.g. m["80"][80] -> svc 192 func serviceTargetPortsMap(svcs []ServiceSpecWithName) targetPortMap { 193 pm := targetPortMap{} 194 for _, swn := range svcs { 195 svc := swn.Spec 196 for _, sPort := range svc.Ports { 197 p := sPort.TargetPort.String() 198 if p == "0" || p == "" { 199 // By default and for convenience, the targetPort is set to the same value as the port field. 200 p = strconv.Itoa(int(sPort.Port)) 201 } 202 if _, ok := pm[p]; !ok { 203 pm[p] = map[int32]string{} 204 } 205 pm[p][sPort.Port] = swn.Name 206 } 207 } 208 return pm 209 }