istio
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
15package cluster
16
17import (
18"context"
19"fmt"
20"regexp"
21"strings"
22
23appsv1 "k8s.io/api/apps/v1"
24corev1 "k8s.io/api/core/v1"
25metav1 "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"
31config2 "istio.io/istio/tools/bug-report/pkg/config"
32"istio.io/istio/tools/bug-report/pkg/util/path"
33)
34
35var versionRegex = regexp.MustCompile(`.*(\d\.\d\.\d).*`)
36
37// ParsePath parses path into its components. Input must have the form namespace/deployment/pod/container.
38func ParsePath(path string) (namespace string, deployment, pod string, container string, err error) {
39pv := strings.Split(path, "/")
40if len(pv) != 4 {
41return "", "", "", "", fmt.Errorf("bad path %s, must be namespace/deployment/pod/container", path)
42}
43return 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
47func shouldSkipPod(pod *corev1.Pod, config *config2.BugReportConfig) bool {
48for _, eld := range config.Exclude {
49if len(eld.Namespaces) > 0 {
50if isIncludeOrExcludeEntriesMatched(eld.Namespaces, pod.Namespace) {
51return true
52}
53}
54if len(eld.Pods) > 0 {
55if isIncludeOrExcludeEntriesMatched(eld.Pods, pod.Name) {
56return true
57}
58}
59if len(eld.Containers) > 0 {
60for _, c := range pod.Spec.Containers {
61if isIncludeOrExcludeEntriesMatched(eld.Containers, c.Name) {
62return true
63}
64}
65}
66if len(eld.Labels) > 0 {
67for key, val := range eld.Labels {
68if evLabel, exists := pod.Labels[key]; exists {
69if isExactMatchedOrPatternMatched(val, evLabel) {
70return true
71}
72}
73}
74}
75if len(eld.Annotations) > 0 {
76for key, val := range eld.Annotations {
77if evAnnotation, exists := pod.Annotations[key]; exists {
78if isExactMatchedOrPatternMatched(val, evAnnotation) {
79return true
80}
81}
82}
83}
84}
85
86for _, ild := range config.Include {
87if len(ild.Namespaces) > 0 {
88if !isIncludeOrExcludeEntriesMatched(ild.Namespaces, pod.Namespace) {
89continue
90}
91}
92if len(ild.Pods) > 0 {
93if !isIncludeOrExcludeEntriesMatched(ild.Pods, pod.Name) {
94continue
95}
96}
97
98if len(ild.Containers) > 0 {
99isContainerMatch := false
100for _, c := range pod.Spec.Containers {
101if isIncludeOrExcludeEntriesMatched(ild.Containers, c.Name) {
102isContainerMatch = true
103}
104}
105if !isContainerMatch {
106continue
107}
108}
109
110if len(ild.Labels) > 0 {
111isLabelsMatch := false
112for key, val := range ild.Labels {
113if evLabel, exists := pod.Labels[key]; exists {
114if isExactMatchedOrPatternMatched(val, evLabel) {
115isLabelsMatch = true
116break
117}
118}
119}
120if !isLabelsMatch {
121continue
122}
123}
124
125if len(ild.Annotations) > 0 {
126isAnnotationMatch := false
127for key, val := range ild.Annotations {
128if evAnnotation, exists := pod.Annotations[key]; exists {
129if isExactMatchedOrPatternMatched(val, evAnnotation) {
130isAnnotationMatch = true
131break
132}
133}
134}
135if !isAnnotationMatch {
136continue
137}
138}
139// If we reach here, it means that all include entries are matched.
140return false
141}
142// If we reach here, it means that no include entries are matched.
143return true
144}
145
146func shouldSkipDeployment(deployment string, config *config2.BugReportConfig) bool {
147for _, eld := range config.Exclude {
148if len(eld.Deployments) > 0 {
149if isIncludeOrExcludeEntriesMatched(eld.Deployments, deployment) {
150return true
151}
152}
153}
154
155for _, ild := range config.Include {
156if len(ild.Deployments) > 0 {
157if !isIncludeOrExcludeEntriesMatched(ild.Deployments, deployment) {
158return true
159}
160}
161}
162
163return false
164}
165
166func shouldSkipDaemonSet(daemonSet string, config *config2.BugReportConfig) bool {
167for _, eld := range config.Exclude {
168if len(eld.Daemonsets) > 0 {
169if isIncludeOrExcludeEntriesMatched(eld.Daemonsets, daemonSet) {
170return true
171}
172}
173}
174
175for _, ild := range config.Include {
176if len(ild.Daemonsets) > 0 {
177if !isIncludeOrExcludeEntriesMatched(ild.Daemonsets, daemonSet) {
178return true
179}
180}
181}
182return false
183}
184
185func isExactMatchedOrPatternMatched(pattern string, term string) bool {
186result, _ := regexp.MatchString(entryPatternToRegexp(pattern), term)
187return result
188}
189
190func isIncludeOrExcludeEntriesMatched(entries []string, term string) bool {
191for _, entry := range entries {
192if isExactMatchedOrPatternMatched(entry, term) {
193return true
194}
195}
196return false
197}
198
199func entryPatternToRegexp(pattern string) string {
200var reg string
201for i, literal := range strings.Split(pattern, "*") {
202if i > 0 {
203reg += ".*"
204}
205reg += regexp.QuoteMeta(literal)
206}
207return reg
208}
209
210// GetClusterResources returns cluster resources for the given REST config and k8s Clientset.
211func GetClusterResources(ctx context.Context, clientset *kubernetes.Clientset, config *config2.BugReportConfig) (*Resources, error) {
212out := &Resources{
213Labels: make(map[string]map[string]string),
214Annotations: make(map[string]map[string]string),
215Pod: make(map[string]*corev1.Pod),
216CniPod: make(map[string]*corev1.Pod),
217}
218
219pods, err := clientset.CoreV1().Pods("").List(ctx, metav1.ListOptions{})
220if err != nil {
221return nil, err
222}
223
224replicasets, err := clientset.AppsV1().ReplicaSets("").List(ctx, metav1.ListOptions{})
225if err != nil {
226return nil, err
227}
228
229daemonsets, err := clientset.AppsV1().DaemonSets("").List(ctx, metav1.ListOptions{})
230if err != nil {
231return nil, err
232}
233
234for i, p := range pods.Items {
235if p.Labels["k8s-app"] == "istio-cni-node" {
236out.CniPod[PodKey(p.Namespace, p.Name)] = &pods.Items[i]
237}
238
239if inject.IgnoredNamespaces.Contains(p.Namespace) {
240continue
241}
242if skip := shouldSkipPod(&p, config); skip {
243continue
244}
245
246deployment := getOwnerDeployment(&p, replicasets.Items)
247if skip := shouldSkipDeployment(deployment, config); skip {
248continue
249}
250daemonset := getOwnerDaemonSet(&p, daemonsets.Items)
251if skip := shouldSkipDaemonSet(daemonset, config); skip {
252continue
253}
254
255if deployment != "" {
256for _, c := range p.Spec.Containers {
257out.insertContainer(p.Namespace, deployment, p.Name, c.Name)
258}
259for _, c := range p.Spec.InitContainers {
260if c.Name == inject.ProxyContainerName {
261out.insertContainer(p.Namespace, deployment, p.Name, c.Name)
262}
263}
264} else if daemonset != "" {
265for _, c := range p.Spec.Containers {
266out.insertContainer(p.Namespace, daemonset, p.Name, c.Name)
267}
268for _, c := range p.Spec.InitContainers {
269if c.Name == inject.ProxyContainerName {
270out.insertContainer(p.Namespace, deployment, p.Name, c.Name)
271}
272}
273}
274
275out.Labels[PodKey(p.Namespace, p.Name)] = p.Labels
276out.Annotations[PodKey(p.Namespace, p.Name)] = p.Annotations
277out.Pod[PodKey(p.Namespace, p.Name)] = &pods.Items[i]
278}
279
280return out, nil
281}
282
283// Resources defines a tree of cluster resource names.
284type 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.
288Root map[string]any
289// Labels maps a pod name to a map of labels key-values.
290Labels map[string]map[string]string
291// Annotations maps a pod name to a map of annotation key-values.
292Annotations map[string]map[string]string
293// Pod maps a pod name to its Pod info. The key is namespace/pod-name.
294Pod map[string]*corev1.Pod
295// CniPod
296CniPod map[string]*corev1.Pod
297}
298
299func (r *Resources) insertContainer(namespace, deployment, pod, container string) {
300if r.Root == nil {
301r.Root = make(map[string]any)
302}
303if r.Root[namespace] == nil {
304r.Root[namespace] = make(map[string]any)
305}
306d := r.Root[namespace].(map[string]any)
307if d[deployment] == nil {
308d[deployment] = make(map[string]any)
309}
310p := d[deployment].(map[string]any)
311if p[pod] == nil {
312p[pod] = make(map[string]any)
313}
314c := p[pod].(map[string]any)
315c[container] = nil
316}
317
318// ContainerRestarts returns the number of container restarts for the given container.
319func (r *Resources) ContainerRestarts(namespace, pod, container string, isCniPod bool) int {
320var podItem *corev1.Pod
321if isCniPod {
322podItem = r.CniPod[PodKey(namespace, pod)]
323} else {
324podItem = r.Pod[PodKey(namespace, pod)]
325}
326for _, cs := range podItem.Status.ContainerStatuses {
327if cs.Name == container {
328return int(cs.RestartCount)
329}
330}
331return 0
332}
333
334// IsDiscoveryContainer reports whether the given container is the Istio discovery container.
335func (r *Resources) IsDiscoveryContainer(clusterVersion, namespace, pod, container string) bool {
336return 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.
341func (r *Resources) PodIstioVersion(namespace, pod string) string {
342p := r.Pod[PodKey(namespace, pod)]
343if p == nil {
344return ""
345}
346
347for _, c := range p.Spec.Containers {
348if c.Name == common.ProxyContainerName || c.Name == common.DiscoveryContainerName {
349return imageToVersion(c.Image)
350}
351}
352return ""
353}
354
355// String implements the Stringer interface.
356func (r *Resources) String() string {
357return resourcesStringImpl(r.Root, "")
358}
359
360func resourcesStringImpl(node any, prefix string) string {
361out := ""
362if node == nil {
363return ""
364}
365nv := node.(map[string]any)
366for k, n := range nv {
367out += prefix + k + "\n"
368out += resourcesStringImpl(n, prefix+" ")
369}
370
371return out
372}
373
374// PodKey returns a unique key based on the namespace and pod name.
375func PodKey(namespace, pod string) string {
376return path.Path{namespace, pod}.String()
377}
378
379func getOwnerDeployment(pod *corev1.Pod, replicasets []appsv1.ReplicaSet) string {
380for _, o := range pod.OwnerReferences {
381if o.Kind == name.ReplicaSetStr {
382for _, rs := range replicasets {
383if rs.Name == o.Name {
384for _, oo := range rs.OwnerReferences {
385if oo.Kind == name.DeploymentStr {
386return oo.Name
387}
388}
389}
390}
391}
392}
393return ""
394}
395
396func getOwnerDaemonSet(pod *corev1.Pod, daemonsets []appsv1.DaemonSet) string {
397for _, o := range pod.OwnerReferences {
398if o.Kind == name.DaemonSetStr {
399for _, ds := range daemonsets {
400if ds.Name == o.Name {
401return ds.Name
402}
403}
404}
405}
406return ""
407}
408
409func imageToVersion(imageStr string) string {
410vs := versionRegex.FindStringSubmatch(imageStr)
411if len(vs) != 2 {
412return ""
413}
414return vs[0]
415}
416