istio

Форк
0
468 строк · 16.9 Кб
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

15
package serviceentry
16

17
import (
18
	"net/netip"
19
	"strings"
20
	"time"
21

22
	"istio.io/api/label"
23
	networking "istio.io/api/networking/v1alpha3"
24
	"istio.io/istio/pilot/pkg/features"
25
	"istio.io/istio/pilot/pkg/model"
26
	"istio.io/istio/pilot/pkg/serviceregistry/provider"
27
	labelutil "istio.io/istio/pilot/pkg/serviceregistry/util/label"
28
	"istio.io/istio/pkg/cluster"
29
	"istio.io/istio/pkg/config"
30
	"istio.io/istio/pkg/config/constants"
31
	"istio.io/istio/pkg/config/host"
32
	"istio.io/istio/pkg/config/protocol"
33
	"istio.io/istio/pkg/config/schema/gvk"
34
	"istio.io/istio/pkg/config/visibility"
35
	"istio.io/istio/pkg/kube/labels"
36
	"istio.io/istio/pkg/network"
37
	"istio.io/istio/pkg/spiffe"
38
	netutil "istio.io/istio/pkg/util/net"
39
	"istio.io/istio/pkg/util/sets"
40
)
41

42
func convertPort(port *networking.ServicePort) *model.Port {
43
	return &model.Port{
44
		Name:     port.Name,
45
		Port:     int(port.Number),
46
		Protocol: protocol.Parse(port.Protocol),
47
	}
48
}
49

50
type HostAddress struct {
51
	host    string
52
	address string
53
}
54

55
// ServiceToServiceEntry converts from internal Service representation to ServiceEntry
56
// This does not include endpoints - they'll be represented as EndpointSlice or EDS.
57
//
58
// See convertServices() for the reverse conversion, used by Istio to handle ServiceEntry configs.
59
// See kube.ConvertService for the conversion from K8S to internal Service.
60
func ServiceToServiceEntry(svc *model.Service, proxy *model.Proxy) *config.Config {
61
	gvk := gvk.ServiceEntry
62
	se := &networking.ServiceEntry{
63
		// Host is fully qualified: name, namespace, domainSuffix
64
		Hosts: []string{string(svc.Hostname)},
65

66
		// Internal Service and K8S Service have a single Address.
67
		// ServiceEntry can represent multiple - but we are not using that. SE may be merged.
68
		// Will be 0.0.0.0 if not specified as ClusterIP or ClusterIP==None. In such case resolution is Passthrough.
69
		//
70
		Addresses: svc.GetAddresses(proxy),
71

72
		// Location:             0,
73

74
		// Internal resolution:
75
		//  - Passthrough - for ClusterIP=None and no ExternalName
76
		//  - ClientSideLB - regular ClusterIP clusters (VIP, resolved via EDS)
77
		//  - DNSLB - if ExternalName is specified. Also meshExternal is set.
78

79
		// This is based on alpha.istio.io/canonical-serviceaccounts and
80
		//  alpha.istio.io/kubernetes-serviceaccounts.
81
		SubjectAltNames: svc.ServiceAccounts,
82
	}
83

84
	if len(svc.Attributes.LabelSelectors) > 0 {
85
		se.WorkloadSelector = &networking.WorkloadSelector{Labels: svc.Attributes.LabelSelectors}
86
	}
87

88
	// Based on networking.istio.io/exportTo annotation
89
	for k := range svc.Attributes.ExportTo {
90
		// k is Private or Public
91
		se.ExportTo = append(se.ExportTo, string(k))
92
	}
93

94
	if svc.MeshExternal {
95
		se.Location = networking.ServiceEntry_MESH_EXTERNAL // 0 - default
96
	} else {
97
		se.Location = networking.ServiceEntry_MESH_INTERNAL
98
	}
99

100
	// Reverse in convertServices. Note that enum values are different
101
	// TODO: make the enum match, should be safe (as long as they're used as enum)
102
	var resolution networking.ServiceEntry_Resolution
103
	switch svc.Resolution {
104
	case model.Passthrough: // 2
105
		resolution = networking.ServiceEntry_NONE // 0
106
	case model.DNSLB: // 1
107
		resolution = networking.ServiceEntry_DNS // 2
108
	case model.DNSRoundRobinLB: // 3
109
		resolution = networking.ServiceEntry_DNS_ROUND_ROBIN // 3
110
	case model.ClientSideLB: // 0
111
		resolution = networking.ServiceEntry_STATIC // 1
112
	}
113
	se.Resolution = resolution
114

115
	// Port is mapped from ServicePort
116
	for _, p := range svc.Ports {
117
		se.Ports = append(se.Ports, &networking.ServicePort{
118
			Number: uint32(p.Port),
119
			Name:   p.Name,
120
			// Protocol is converted to protocol.Instance - reverse conversion will use the name.
121
			Protocol: string(p.Protocol),
122
			// TODO: target port
123
		})
124
	}
125

126
	cfg := &config.Config{
127
		Meta: config.Meta{
128
			GroupVersionKind:  gvk,
129
			Name:              "synthetic-" + svc.Attributes.Name,
130
			Namespace:         svc.Attributes.Namespace,
131
			CreationTimestamp: svc.CreationTime,
132
			ResourceVersion:   svc.ResourceVersion,
133
		},
134
		Spec: se,
135
	}
136

137
	// TODO: WorkloadSelector
138

139
	// TODO: preserve ServiceRegistry. The reverse conversion sets it to 'external'
140
	// TODO: preserve UID ? It seems MCP didn't preserve it - but that code path was not used much.
141

142
	// TODO: ClusterExternalPorts map - for NodePort services, with "traffic.istio.io/nodeSelector" ann
143
	// It's a per-cluster map
144

145
	// TODO: ClusterExternalAddresses - for LB types, per cluster. Populated from K8S, missing
146
	// in SE. Used for multi-network support.
147
	return cfg
148
}
149

150
// convertServices transforms a ServiceEntry config to a list of internal Service objects.
151
func convertServices(cfg config.Config) []*model.Service {
152
	serviceEntry := cfg.Spec.(*networking.ServiceEntry)
153
	creationTime := cfg.CreationTimestamp
154

155
	var resolution model.Resolution
156
	switch serviceEntry.Resolution {
157
	case networking.ServiceEntry_NONE:
158
		resolution = model.Passthrough
159
	case networking.ServiceEntry_DNS:
160
		resolution = model.DNSLB
161
	case networking.ServiceEntry_DNS_ROUND_ROBIN:
162
		resolution = model.DNSRoundRobinLB
163
	case networking.ServiceEntry_STATIC:
164
		resolution = model.ClientSideLB
165
	}
166

167
	svcPorts := make(model.PortList, 0, len(serviceEntry.Ports))
168
	var portOverrides map[uint32]uint32
169
	for _, port := range serviceEntry.Ports {
170
		svcPorts = append(svcPorts, convertPort(port))
171
		if resolution == model.Passthrough && port.TargetPort != 0 {
172
			if portOverrides == nil {
173
				portOverrides = map[uint32]uint32{}
174
			}
175
			portOverrides[port.Number] = port.TargetPort
176
		}
177
	}
178

179
	var exportTo sets.Set[visibility.Instance]
180
	if len(serviceEntry.ExportTo) > 0 {
181
		exportTo = sets.NewWithLength[visibility.Instance](len(serviceEntry.ExportTo))
182
		for _, e := range serviceEntry.ExportTo {
183
			exportTo.Insert(visibility.Instance(e))
184
		}
185
	}
186

187
	var labelSelectors map[string]string
188
	if serviceEntry.WorkloadSelector != nil {
189
		labelSelectors = serviceEntry.WorkloadSelector.Labels
190
	}
191
	hostAddresses := []*HostAddress{}
192
	for _, hostname := range serviceEntry.Hosts {
193
		if len(serviceEntry.Addresses) > 0 {
194
			for _, address := range serviceEntry.Addresses {
195
				// Check if address is an IP first because that is the most common case.
196
				if netutil.IsValidIPAddress(address) {
197
					hostAddresses = append(hostAddresses, &HostAddress{hostname, address})
198
				} else if cidr, cidrErr := netip.ParsePrefix(address); cidrErr == nil {
199
					newAddress := address
200
					if cidr.Bits() == cidr.Addr().BitLen() {
201
						// /32 mask. Remove the /32 and make it a normal IP address
202
						newAddress = cidr.Addr().String()
203
					}
204
					hostAddresses = append(hostAddresses, &HostAddress{hostname, newAddress})
205
				}
206
			}
207
		} else {
208
			hostAddresses = append(hostAddresses, &HostAddress{hostname, constants.UnspecifiedIP})
209
		}
210
	}
211

212
	return buildServices(hostAddresses, cfg.Name, cfg.Namespace, svcPorts, serviceEntry.Location, resolution,
213
		exportTo, labelSelectors, serviceEntry.SubjectAltNames, creationTime, cfg.Labels, portOverrides)
214
}
215

216
func buildServices(hostAddresses []*HostAddress, name, namespace string, ports model.PortList, location networking.ServiceEntry_Location,
217
	resolution model.Resolution, exportTo sets.Set[visibility.Instance], selectors map[string]string, saccounts []string,
218
	ctime time.Time, labels map[string]string, overrides map[uint32]uint32,
219
) []*model.Service {
220
	out := make([]*model.Service, 0, len(hostAddresses))
221
	lbls := labels
222
	if features.CanonicalServiceForMeshExternalServiceEntry && location == networking.ServiceEntry_MESH_EXTERNAL {
223
		lbls = ensureCanonicalServiceLabels(name, labels)
224
	}
225
	for _, ha := range hostAddresses {
226
		out = append(out, &model.Service{
227
			CreationTime:   ctime,
228
			MeshExternal:   location == networking.ServiceEntry_MESH_EXTERNAL,
229
			Hostname:       host.Name(ha.host),
230
			DefaultAddress: ha.address,
231
			Ports:          ports,
232
			Resolution:     resolution,
233
			Attributes: model.ServiceAttributes{
234
				ServiceRegistry:        provider.External,
235
				PassthroughTargetPorts: overrides,
236
				Name:                   ha.host,
237
				Namespace:              namespace,
238
				Labels:                 lbls,
239
				ExportTo:               exportTo,
240
				LabelSelectors:         selectors,
241
			},
242
			ServiceAccounts: saccounts,
243
		})
244
	}
245
	return out
246
}
247

248
func ensureCanonicalServiceLabels(name string, srcLabels map[string]string) map[string]string {
249
	if srcLabels == nil {
250
		srcLabels = make(map[string]string)
251
	}
252
	_, svcLabelFound := srcLabels[model.IstioCanonicalServiceLabelName]
253
	_, revLabelFound := srcLabels[model.IstioCanonicalServiceRevisionLabelName]
254
	if svcLabelFound && revLabelFound {
255
		return srcLabels
256
	}
257

258
	srcLabels[model.IstioCanonicalServiceLabelName], srcLabels[model.IstioCanonicalServiceRevisionLabelName] = labels.CanonicalService(srcLabels, name)
259
	return srcLabels
260
}
261

262
func (s *Controller) convertEndpoint(service *model.Service, servicePort *networking.ServicePort,
263
	wle *networking.WorkloadEntry, configKey *configKey, clusterID cluster.ID,
264
) *model.ServiceInstance {
265
	var instancePort uint32
266
	addr := wle.GetAddress()
267
	// priority level: unixAddress > we.ports > se.port.targetPort > se.port.number
268
	if strings.HasPrefix(addr, model.UnixAddressPrefix) {
269
		instancePort = 0
270
		addr = strings.TrimPrefix(addr, model.UnixAddressPrefix)
271
	} else if port, ok := wle.Ports[servicePort.Name]; ok && port > 0 {
272
		instancePort = port
273
	} else if servicePort.TargetPort > 0 {
274
		instancePort = servicePort.TargetPort
275
	} else {
276
		// final fallback is to the service port value
277
		instancePort = servicePort.Number
278
	}
279

280
	tlsMode := getTLSModeFromWorkloadEntry(wle)
281
	sa := ""
282
	if wle.ServiceAccount != "" {
283
		sa = spiffe.MustGenSpiffeURI(service.Attributes.Namespace, wle.ServiceAccount)
284
	}
285
	networkID := s.workloadEntryNetwork(wle)
286
	locality := wle.Locality
287
	if locality == "" && len(wle.Labels[model.LocalityLabel]) > 0 {
288
		locality = model.GetLocalityLabel(wle.Labels[model.LocalityLabel])
289
	}
290
	labels := labelutil.AugmentLabels(wle.Labels, clusterID, locality, "", networkID)
291
	return &model.ServiceInstance{
292
		Endpoint: &model.IstioEndpoint{
293
			Address:         addr,
294
			EndpointPort:    instancePort,
295
			ServicePortName: servicePort.Name,
296
			Network:         network.ID(wle.Network),
297
			Locality: model.Locality{
298
				Label:     locality,
299
				ClusterID: clusterID,
300
			},
301
			LbWeight:       wle.Weight,
302
			Labels:         labels,
303
			TLSMode:        tlsMode,
304
			ServiceAccount: sa,
305
			// Workload entry config name is used as workload name, which will appear in metric label.
306
			// After VM auto registry is introduced, workload group annotation should be used for workload name.
307
			WorkloadName: configKey.name,
308
			Namespace:    configKey.namespace,
309
		},
310
		Service:     service,
311
		ServicePort: convertPort(servicePort),
312
	}
313
}
314

315
// convertWorkloadEntryToServiceInstances translates a WorkloadEntry into ServiceEndpoints. This logic is largely the
316
// same as the ServiceEntry convertServiceEntryToInstances.
317
func (s *Controller) convertWorkloadEntryToServiceInstances(wle *networking.WorkloadEntry, services []*model.Service,
318
	se *networking.ServiceEntry, configKey *configKey, clusterID cluster.ID,
319
) []*model.ServiceInstance {
320
	out := make([]*model.ServiceInstance, 0)
321
	for _, service := range services {
322
		for _, port := range se.Ports {
323
			out = append(out, s.convertEndpoint(service, port, wle, configKey, clusterID))
324
		}
325
	}
326
	return out
327
}
328

329
func (s *Controller) convertServiceEntryToInstances(cfg config.Config, services []*model.Service) []*model.ServiceInstance {
330
	out := make([]*model.ServiceInstance, 0)
331
	serviceEntry := cfg.Spec.(*networking.ServiceEntry)
332
	if serviceEntry == nil {
333
		return nil
334
	}
335
	if services == nil {
336
		services = convertServices(cfg)
337
	}
338
	for _, service := range services {
339
		for _, serviceEntryPort := range serviceEntry.Ports {
340
			if len(serviceEntry.Endpoints) == 0 && serviceEntry.WorkloadSelector == nil &&
341
				(serviceEntry.Resolution == networking.ServiceEntry_DNS || serviceEntry.Resolution == networking.ServiceEntry_DNS_ROUND_ROBIN) {
342
				// Note: only convert the hostname to service instance if WorkloadSelector is not set
343
				// when service entry has discovery type DNS and no endpoints
344
				// we create endpoints from service's host
345
				// Do not use serviceentry.hosts as a service entry is converted into
346
				// multiple services (one for each host)
347
				endpointPort := serviceEntryPort.Number
348
				if serviceEntryPort.TargetPort > 0 {
349
					endpointPort = serviceEntryPort.TargetPort
350
				}
351
				out = append(out, &model.ServiceInstance{
352
					Endpoint: &model.IstioEndpoint{
353
						Address:         string(service.Hostname),
354
						EndpointPort:    endpointPort,
355
						ServicePortName: serviceEntryPort.Name,
356
						Labels:          nil,
357
						TLSMode:         model.DisabledTLSModeLabel,
358
					},
359
					Service:     service,
360
					ServicePort: convertPort(serviceEntryPort),
361
				})
362
			} else {
363
				for _, endpoint := range serviceEntry.Endpoints {
364
					out = append(out, s.convertEndpoint(service, serviceEntryPort, endpoint, &configKey{}, s.clusterID))
365
				}
366
			}
367
		}
368
	}
369
	return out
370
}
371

372
func getTLSModeFromWorkloadEntry(wle *networking.WorkloadEntry) string {
373
	// * Use security.istio.io/tlsMode if its present
374
	// * If not, set TLS mode if ServiceAccount is specified
375
	tlsMode := model.DisabledTLSModeLabel
376
	if val, exists := wle.Labels[label.SecurityTlsMode.Name]; exists {
377
		tlsMode = val
378
	} else if wle.ServiceAccount != "" {
379
		tlsMode = model.IstioMutualTLSModeLabel
380
	}
381

382
	return tlsMode
383
}
384

385
// The workload instance has pointer to the service and its service port.
386
// We need to create our own but we can retain the endpoint already created.
387
func convertWorkloadInstanceToServiceInstance(workloadInstance *model.WorkloadInstance, serviceEntryServices []*model.Service,
388
	serviceEntry *networking.ServiceEntry,
389
) []*model.ServiceInstance {
390
	out := make([]*model.ServiceInstance, 0)
391
	for _, service := range serviceEntryServices {
392
		for _, serviceEntryPort := range serviceEntry.Ports {
393
			// note: this is same as workloadentry handler
394
			// endpoint port will first use the port defined in wle with same port name,
395
			// if not port name not match, use the targetPort specified in ServiceEntry
396
			// if both not matched, fallback to ServiceEntry port number.
397
			var targetPort uint32
398
			if port, ok := workloadInstance.PortMap[serviceEntryPort.Name]; ok && port > 0 {
399
				targetPort = port
400
			} else if serviceEntryPort.TargetPort > 0 {
401
				targetPort = serviceEntryPort.TargetPort
402
			} else {
403
				targetPort = serviceEntryPort.Number
404
			}
405
			ep := workloadInstance.Endpoint.ShallowCopy()
406
			ep.ServicePortName = serviceEntryPort.Name
407
			ep.EndpointPort = targetPort
408
			ep.ComputeEnvoyEndpoint(nil)
409
			out = append(out, &model.ServiceInstance{
410
				Endpoint:    ep,
411
				Service:     service,
412
				ServicePort: convertPort(serviceEntryPort),
413
			})
414
		}
415
	}
416
	return out
417
}
418

419
// Convenience function to convert a workloadEntry into a WorkloadInstance object encoding the endpoint (without service
420
// port names) and the namespace - k8s will consume this workload instance when selecting workload entries
421
func (s *Controller) convertWorkloadEntryToWorkloadInstance(cfg config.Config, clusterID cluster.ID) *model.WorkloadInstance {
422
	we := ConvertWorkloadEntry(cfg)
423
	addr := we.GetAddress()
424
	dnsServiceEntryOnly := false
425
	if strings.HasPrefix(addr, model.UnixAddressPrefix) {
426
		// k8s can't use uds for service objects
427
		dnsServiceEntryOnly = true
428
	}
429
	if addr != "" && !netutil.IsValidIPAddress(addr) {
430
		// k8s can't use workloads with hostnames in the address field.
431
		dnsServiceEntryOnly = true
432
	}
433
	tlsMode := getTLSModeFromWorkloadEntry(we)
434
	sa := ""
435
	if we.ServiceAccount != "" {
436
		sa = spiffe.MustGenSpiffeURI(cfg.Namespace, we.ServiceAccount)
437
	}
438
	networkID := s.workloadEntryNetwork(we)
439
	locality := we.Locality
440
	if locality == "" && len(we.Labels[model.LocalityLabel]) > 0 {
441
		locality = model.GetLocalityLabel(we.Labels[model.LocalityLabel])
442
	}
443
	labels := labelutil.AugmentLabels(we.Labels, clusterID, locality, "", networkID)
444
	return &model.WorkloadInstance{
445
		Endpoint: &model.IstioEndpoint{
446
			Address: addr,
447
			// Not setting ports here as its done by k8s controller
448
			Network: network.ID(we.Network),
449
			Locality: model.Locality{
450
				Label:     locality,
451
				ClusterID: clusterID,
452
			},
453
			LbWeight:  we.Weight,
454
			Namespace: cfg.Namespace,
455
			// Workload entry config name is used as workload name, which will appear in metric label.
456
			// After VM auto registry is introduced, workload group annotation should be used for workload name.
457
			WorkloadName:   cfg.Name,
458
			Labels:         labels,
459
			TLSMode:        tlsMode,
460
			ServiceAccount: sa,
461
		},
462
		PortMap:             we.Ports,
463
		Namespace:           cfg.Namespace,
464
		Name:                cfg.Name,
465
		Kind:                model.WorkloadEntryKind,
466
		DNSServiceEntryOnly: dnsServiceEntryOnly,
467
	}
468
}
469

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.