istio

Форк
0
415 строк · 10.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 cluster
16

17
import (
18
	"context"
19
	"fmt"
20
	"regexp"
21
	"strings"
22

23
	appsv1 "k8s.io/api/apps/v1"
24
	corev1 "k8s.io/api/core/v1"
25
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26
	"k8s.io/client-go/kubernetes"
27

28
	"istio.io/istio/operator/pkg/name"
29
	"istio.io/istio/pkg/kube/inject"
30
	"istio.io/istio/tools/bug-report/pkg/common"
31
	config2 "istio.io/istio/tools/bug-report/pkg/config"
32
	"istio.io/istio/tools/bug-report/pkg/util/path"
33
)
34

35
var versionRegex = regexp.MustCompile(`.*(\d\.\d\.\d).*`)
36

37
// ParsePath parses path into its components. Input must have the form namespace/deployment/pod/container.
38
func ParsePath(path string) (namespace string, deployment, pod string, container string, err error) {
39
	pv := strings.Split(path, "/")
40
	if len(pv) != 4 {
41
		return "", "", "", "", fmt.Errorf("bad path %s, must be namespace/deployment/pod/container", path)
42
	}
43
	return pv[0], pv[1], pv[2], pv[3], nil
44
}
45

46
// shouldSkip means that current pod should be skip or not based on given --include and --exclude
47
func shouldSkipPod(pod *corev1.Pod, config *config2.BugReportConfig) bool {
48
	for _, eld := range config.Exclude {
49
		if len(eld.Namespaces) > 0 {
50
			if isIncludeOrExcludeEntriesMatched(eld.Namespaces, pod.Namespace) {
51
				return true
52
			}
53
		}
54
		if len(eld.Pods) > 0 {
55
			if isIncludeOrExcludeEntriesMatched(eld.Pods, pod.Name) {
56
				return true
57
			}
58
		}
59
		if len(eld.Containers) > 0 {
60
			for _, c := range pod.Spec.Containers {
61
				if isIncludeOrExcludeEntriesMatched(eld.Containers, c.Name) {
62
					return true
63
				}
64
			}
65
		}
66
		if len(eld.Labels) > 0 {
67
			for key, val := range eld.Labels {
68
				if evLabel, exists := pod.Labels[key]; exists {
69
					if isExactMatchedOrPatternMatched(val, evLabel) {
70
						return true
71
					}
72
				}
73
			}
74
		}
75
		if len(eld.Annotations) > 0 {
76
			for key, val := range eld.Annotations {
77
				if evAnnotation, exists := pod.Annotations[key]; exists {
78
					if isExactMatchedOrPatternMatched(val, evAnnotation) {
79
						return true
80
					}
81
				}
82
			}
83
		}
84
	}
85

86
	for _, ild := range config.Include {
87
		if len(ild.Namespaces) > 0 {
88
			if !isIncludeOrExcludeEntriesMatched(ild.Namespaces, pod.Namespace) {
89
				continue
90
			}
91
		}
92
		if len(ild.Pods) > 0 {
93
			if !isIncludeOrExcludeEntriesMatched(ild.Pods, pod.Name) {
94
				continue
95
			}
96
		}
97

98
		if len(ild.Containers) > 0 {
99
			isContainerMatch := false
100
			for _, c := range pod.Spec.Containers {
101
				if isIncludeOrExcludeEntriesMatched(ild.Containers, c.Name) {
102
					isContainerMatch = true
103
				}
104
			}
105
			if !isContainerMatch {
106
				continue
107
			}
108
		}
109

110
		if len(ild.Labels) > 0 {
111
			isLabelsMatch := false
112
			for key, val := range ild.Labels {
113
				if evLabel, exists := pod.Labels[key]; exists {
114
					if isExactMatchedOrPatternMatched(val, evLabel) {
115
						isLabelsMatch = true
116
						break
117
					}
118
				}
119
			}
120
			if !isLabelsMatch {
121
				continue
122
			}
123
		}
124

125
		if len(ild.Annotations) > 0 {
126
			isAnnotationMatch := false
127
			for key, val := range ild.Annotations {
128
				if evAnnotation, exists := pod.Annotations[key]; exists {
129
					if isExactMatchedOrPatternMatched(val, evAnnotation) {
130
						isAnnotationMatch = true
131
						break
132
					}
133
				}
134
			}
135
			if !isAnnotationMatch {
136
				continue
137
			}
138
		}
139
		// If we reach here, it means that all include entries are matched.
140
		return false
141
	}
142
	// If we reach here, it means that no include entries are matched.
143
	return true
144
}
145

146
func shouldSkipDeployment(deployment string, config *config2.BugReportConfig) bool {
147
	for _, eld := range config.Exclude {
148
		if len(eld.Deployments) > 0 {
149
			if isIncludeOrExcludeEntriesMatched(eld.Deployments, deployment) {
150
				return true
151
			}
152
		}
153
	}
154

155
	for _, ild := range config.Include {
156
		if len(ild.Deployments) > 0 {
157
			if !isIncludeOrExcludeEntriesMatched(ild.Deployments, deployment) {
158
				return true
159
			}
160
		}
161
	}
162

163
	return false
164
}
165

166
func shouldSkipDaemonSet(daemonSet string, config *config2.BugReportConfig) bool {
167
	for _, eld := range config.Exclude {
168
		if len(eld.Daemonsets) > 0 {
169
			if isIncludeOrExcludeEntriesMatched(eld.Daemonsets, daemonSet) {
170
				return true
171
			}
172
		}
173
	}
174

175
	for _, ild := range config.Include {
176
		if len(ild.Daemonsets) > 0 {
177
			if !isIncludeOrExcludeEntriesMatched(ild.Daemonsets, daemonSet) {
178
				return true
179
			}
180
		}
181
	}
182
	return false
183
}
184

185
func isExactMatchedOrPatternMatched(pattern string, term string) bool {
186
	result, _ := regexp.MatchString(entryPatternToRegexp(pattern), term)
187
	return result
188
}
189

190
func isIncludeOrExcludeEntriesMatched(entries []string, term string) bool {
191
	for _, entry := range entries {
192
		if isExactMatchedOrPatternMatched(entry, term) {
193
			return true
194
		}
195
	}
196
	return false
197
}
198

199
func entryPatternToRegexp(pattern string) string {
200
	var reg string
201
	for i, literal := range strings.Split(pattern, "*") {
202
		if i > 0 {
203
			reg += ".*"
204
		}
205
		reg += regexp.QuoteMeta(literal)
206
	}
207
	return reg
208
}
209

210
// GetClusterResources returns cluster resources for the given REST config and k8s Clientset.
211
func GetClusterResources(ctx context.Context, clientset *kubernetes.Clientset, config *config2.BugReportConfig) (*Resources, error) {
212
	out := &Resources{
213
		Labels:      make(map[string]map[string]string),
214
		Annotations: make(map[string]map[string]string),
215
		Pod:         make(map[string]*corev1.Pod),
216
		CniPod:      make(map[string]*corev1.Pod),
217
	}
218

219
	pods, err := clientset.CoreV1().Pods("").List(ctx, metav1.ListOptions{})
220
	if err != nil {
221
		return nil, err
222
	}
223

224
	replicasets, err := clientset.AppsV1().ReplicaSets("").List(ctx, metav1.ListOptions{})
225
	if err != nil {
226
		return nil, err
227
	}
228

229
	daemonsets, err := clientset.AppsV1().DaemonSets("").List(ctx, metav1.ListOptions{})
230
	if err != nil {
231
		return nil, err
232
	}
233

234
	for i, p := range pods.Items {
235
		if p.Labels["k8s-app"] == "istio-cni-node" {
236
			out.CniPod[PodKey(p.Namespace, p.Name)] = &pods.Items[i]
237
		}
238

239
		if inject.IgnoredNamespaces.Contains(p.Namespace) {
240
			continue
241
		}
242
		if skip := shouldSkipPod(&p, config); skip {
243
			continue
244
		}
245

246
		deployment := getOwnerDeployment(&p, replicasets.Items)
247
		if skip := shouldSkipDeployment(deployment, config); skip {
248
			continue
249
		}
250
		daemonset := getOwnerDaemonSet(&p, daemonsets.Items)
251
		if skip := shouldSkipDaemonSet(daemonset, config); skip {
252
			continue
253
		}
254

255
		if deployment != "" {
256
			for _, c := range p.Spec.Containers {
257
				out.insertContainer(p.Namespace, deployment, p.Name, c.Name)
258
			}
259
			for _, c := range p.Spec.InitContainers {
260
				if c.Name == inject.ProxyContainerName {
261
					out.insertContainer(p.Namespace, deployment, p.Name, c.Name)
262
				}
263
			}
264
		} else if daemonset != "" {
265
			for _, c := range p.Spec.Containers {
266
				out.insertContainer(p.Namespace, daemonset, p.Name, c.Name)
267
			}
268
			for _, c := range p.Spec.InitContainers {
269
				if c.Name == inject.ProxyContainerName {
270
					out.insertContainer(p.Namespace, deployment, p.Name, c.Name)
271
				}
272
			}
273
		}
274

275
		out.Labels[PodKey(p.Namespace, p.Name)] = p.Labels
276
		out.Annotations[PodKey(p.Namespace, p.Name)] = p.Annotations
277
		out.Pod[PodKey(p.Namespace, p.Name)] = &pods.Items[i]
278
	}
279

280
	return out, nil
281
}
282

283
// Resources defines a tree of cluster resource names.
284
type Resources struct {
285
	// Root is the first level in the cluster resource hierarchy.
286
	// Each level in the hierarchy is a map[string]interface{} to the next level.
287
	// The levels are: namespaces/deployments/pods/containers.
288
	Root map[string]any
289
	// Labels maps a pod name to a map of labels key-values.
290
	Labels map[string]map[string]string
291
	// Annotations maps a pod name to a map of annotation key-values.
292
	Annotations map[string]map[string]string
293
	// Pod maps a pod name to its Pod info. The key is namespace/pod-name.
294
	Pod map[string]*corev1.Pod
295
	// CniPod
296
	CniPod map[string]*corev1.Pod
297
}
298

299
func (r *Resources) insertContainer(namespace, deployment, pod, container string) {
300
	if r.Root == nil {
301
		r.Root = make(map[string]any)
302
	}
303
	if r.Root[namespace] == nil {
304
		r.Root[namespace] = make(map[string]any)
305
	}
306
	d := r.Root[namespace].(map[string]any)
307
	if d[deployment] == nil {
308
		d[deployment] = make(map[string]any)
309
	}
310
	p := d[deployment].(map[string]any)
311
	if p[pod] == nil {
312
		p[pod] = make(map[string]any)
313
	}
314
	c := p[pod].(map[string]any)
315
	c[container] = nil
316
}
317

318
// ContainerRestarts returns the number of container restarts for the given container.
319
func (r *Resources) ContainerRestarts(namespace, pod, container string, isCniPod bool) int {
320
	var podItem *corev1.Pod
321
	if isCniPod {
322
		podItem = r.CniPod[PodKey(namespace, pod)]
323
	} else {
324
		podItem = r.Pod[PodKey(namespace, pod)]
325
	}
326
	for _, cs := range podItem.Status.ContainerStatuses {
327
		if cs.Name == container {
328
			return int(cs.RestartCount)
329
		}
330
	}
331
	return 0
332
}
333

334
// IsDiscoveryContainer reports whether the given container is the Istio discovery container.
335
func (r *Resources) IsDiscoveryContainer(clusterVersion, namespace, pod, container string) bool {
336
	return common.IsDiscoveryContainer(clusterVersion, container, r.Labels[PodKey(namespace, pod)])
337
}
338

339
// PodIstioVersion returns the Istio version for the given pod, if either the proxy or discovery are one of its
340
// containers and the tag is in a parseable format.
341
func (r *Resources) PodIstioVersion(namespace, pod string) string {
342
	p := r.Pod[PodKey(namespace, pod)]
343
	if p == nil {
344
		return ""
345
	}
346

347
	for _, c := range p.Spec.Containers {
348
		if c.Name == common.ProxyContainerName || c.Name == common.DiscoveryContainerName {
349
			return imageToVersion(c.Image)
350
		}
351
	}
352
	return ""
353
}
354

355
// String implements the Stringer interface.
356
func (r *Resources) String() string {
357
	return resourcesStringImpl(r.Root, "")
358
}
359

360
func resourcesStringImpl(node any, prefix string) string {
361
	out := ""
362
	if node == nil {
363
		return ""
364
	}
365
	nv := node.(map[string]any)
366
	for k, n := range nv {
367
		out += prefix + k + "\n"
368
		out += resourcesStringImpl(n, prefix+"  ")
369
	}
370

371
	return out
372
}
373

374
// PodKey returns a unique key based on the namespace and pod name.
375
func PodKey(namespace, pod string) string {
376
	return path.Path{namespace, pod}.String()
377
}
378

379
func getOwnerDeployment(pod *corev1.Pod, replicasets []appsv1.ReplicaSet) string {
380
	for _, o := range pod.OwnerReferences {
381
		if o.Kind == name.ReplicaSetStr {
382
			for _, rs := range replicasets {
383
				if rs.Name == o.Name {
384
					for _, oo := range rs.OwnerReferences {
385
						if oo.Kind == name.DeploymentStr {
386
							return oo.Name
387
						}
388
					}
389
				}
390
			}
391
		}
392
	}
393
	return ""
394
}
395

396
func getOwnerDaemonSet(pod *corev1.Pod, daemonsets []appsv1.DaemonSet) string {
397
	for _, o := range pod.OwnerReferences {
398
		if o.Kind == name.DaemonSetStr {
399
			for _, ds := range daemonsets {
400
				if ds.Name == o.Name {
401
					return ds.Name
402
				}
403
			}
404
		}
405
	}
406
	return ""
407
}
408

409
func imageToVersion(imageStr string) string {
410
	vs := versionRegex.FindStringSubmatch(imageStr)
411
	if len(vs) != 2 {
412
		return ""
413
	}
414
	return vs[0]
415
}
416

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

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

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

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