...

Source file src/k8s.io/kubectl/pkg/cmd/top/top_pod.go

Documentation: k8s.io/kubectl/pkg/cmd/top

     1  /*
     2  Copyright 2016 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package top
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"time"
    24  
    25  	corev1 "k8s.io/api/core/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/fields"
    28  	"k8s.io/apimachinery/pkg/labels"
    29  	"k8s.io/cli-runtime/pkg/genericiooptions"
    30  	"k8s.io/client-go/discovery"
    31  	corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
    32  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    33  	"k8s.io/kubectl/pkg/metricsutil"
    34  	"k8s.io/kubectl/pkg/util/completion"
    35  	"k8s.io/kubectl/pkg/util/i18n"
    36  	"k8s.io/kubectl/pkg/util/templates"
    37  	metricsapi "k8s.io/metrics/pkg/apis/metrics"
    38  	metricsv1beta1api "k8s.io/metrics/pkg/apis/metrics/v1beta1"
    39  	metricsclientset "k8s.io/metrics/pkg/client/clientset/versioned"
    40  
    41  	"github.com/spf13/cobra"
    42  	"k8s.io/klog/v2"
    43  )
    44  
    45  type TopPodOptions struct {
    46  	ResourceName       string
    47  	Namespace          string
    48  	LabelSelector      string
    49  	FieldSelector      string
    50  	SortBy             string
    51  	AllNamespaces      bool
    52  	PrintContainers    bool
    53  	NoHeaders          bool
    54  	UseProtocolBuffers bool
    55  	Sum                bool
    56  
    57  	PodClient       corev1client.PodsGetter
    58  	Printer         *metricsutil.TopCmdPrinter
    59  	DiscoveryClient discovery.DiscoveryInterface
    60  	MetricsClient   metricsclientset.Interface
    61  
    62  	genericiooptions.IOStreams
    63  }
    64  
    65  const metricsCreationDelay = 2 * time.Minute
    66  
    67  var (
    68  	topPodLong = templates.LongDesc(i18n.T(`
    69  		Display resource (CPU/memory) usage of pods.
    70  
    71  		The 'top pod' command allows you to see the resource consumption of pods.
    72  
    73  		Due to the metrics pipeline delay, they may be unavailable for a few minutes
    74  		since pod creation.`))
    75  
    76  	topPodExample = templates.Examples(i18n.T(`
    77  		# Show metrics for all pods in the default namespace
    78  		kubectl top pod
    79  
    80  		# Show metrics for all pods in the given namespace
    81  		kubectl top pod --namespace=NAMESPACE
    82  
    83  		# Show metrics for a given pod and its containers
    84  		kubectl top pod POD_NAME --containers
    85  
    86  		# Show metrics for the pods defined by label name=myLabel
    87  		kubectl top pod -l name=myLabel`))
    88  )
    89  
    90  func NewCmdTopPod(f cmdutil.Factory, o *TopPodOptions, streams genericiooptions.IOStreams) *cobra.Command {
    91  	if o == nil {
    92  		o = &TopPodOptions{
    93  			IOStreams:          streams,
    94  			UseProtocolBuffers: true,
    95  		}
    96  	}
    97  
    98  	cmd := &cobra.Command{
    99  		Use:                   "pod [NAME | -l label]",
   100  		DisableFlagsInUseLine: true,
   101  		Short:                 i18n.T("Display resource (CPU/memory) usage of pods"),
   102  		Long:                  topPodLong,
   103  		Example:               topPodExample,
   104  		ValidArgsFunction:     completion.ResourceNameCompletionFunc(f, "pod"),
   105  		Run: func(cmd *cobra.Command, args []string) {
   106  			cmdutil.CheckErr(o.Complete(f, cmd, args))
   107  			cmdutil.CheckErr(o.Validate())
   108  			cmdutil.CheckErr(o.RunTopPod())
   109  		},
   110  		Aliases: []string{"pods", "po"},
   111  	}
   112  	cmdutil.AddLabelSelectorFlagVar(cmd, &o.LabelSelector)
   113  	cmd.Flags().StringVar(&o.FieldSelector, "field-selector", o.FieldSelector, "Selector (field query) to filter on, supports '=', '==', and '!='.(e.g. --field-selector key1=value1,key2=value2). The server only supports a limited number of field queries per type.")
   114  	cmd.Flags().StringVar(&o.SortBy, "sort-by", o.SortBy, "If non-empty, sort pods list using specified field. The field can be either 'cpu' or 'memory'.")
   115  	cmd.Flags().BoolVar(&o.PrintContainers, "containers", o.PrintContainers, "If present, print usage of containers within a pod.")
   116  	cmd.Flags().BoolVarP(&o.AllNamespaces, "all-namespaces", "A", o.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.")
   117  	cmd.Flags().BoolVar(&o.NoHeaders, "no-headers", o.NoHeaders, "If present, print output without headers.")
   118  	cmd.Flags().BoolVar(&o.UseProtocolBuffers, "use-protocol-buffers", o.UseProtocolBuffers, "Enables using protocol-buffers to access Metrics API.")
   119  	cmd.Flags().BoolVar(&o.Sum, "sum", o.Sum, "Print the sum of the resource usage")
   120  	return cmd
   121  }
   122  
   123  func (o *TopPodOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
   124  	var err error
   125  	if len(args) == 1 {
   126  		o.ResourceName = args[0]
   127  	} else if len(args) > 1 {
   128  		return cmdutil.UsageErrorf(cmd, "%s", cmd.Use)
   129  	}
   130  
   131  	o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
   132  	if err != nil {
   133  		return err
   134  	}
   135  	clientset, err := f.KubernetesClientSet()
   136  	if err != nil {
   137  		return err
   138  	}
   139  
   140  	o.DiscoveryClient = clientset.DiscoveryClient
   141  	config, err := f.ToRESTConfig()
   142  	if err != nil {
   143  		return err
   144  	}
   145  	if o.UseProtocolBuffers {
   146  		config.ContentType = "application/vnd.kubernetes.protobuf"
   147  	}
   148  	o.MetricsClient, err = metricsclientset.NewForConfig(config)
   149  	if err != nil {
   150  		return err
   151  	}
   152  
   153  	o.PodClient = clientset.CoreV1()
   154  
   155  	o.Printer = metricsutil.NewTopCmdPrinter(o.Out)
   156  	return nil
   157  }
   158  
   159  func (o *TopPodOptions) Validate() error {
   160  	if len(o.SortBy) > 0 {
   161  		if o.SortBy != sortByCPU && o.SortBy != sortByMemory {
   162  			return errors.New("--sort-by accepts only cpu or memory")
   163  		}
   164  	}
   165  	if len(o.ResourceName) > 0 && (len(o.LabelSelector) > 0 || len(o.FieldSelector) > 0) {
   166  		return errors.New("only one of NAME or selector can be provided")
   167  	}
   168  	return nil
   169  }
   170  
   171  func (o TopPodOptions) RunTopPod() error {
   172  	var err error
   173  	labelSelector := labels.Everything()
   174  	if len(o.LabelSelector) > 0 {
   175  		labelSelector, err = labels.Parse(o.LabelSelector)
   176  		if err != nil {
   177  			return err
   178  		}
   179  	}
   180  	fieldSelector := fields.Everything()
   181  	if len(o.FieldSelector) > 0 {
   182  		fieldSelector, err = fields.ParseSelector(o.FieldSelector)
   183  		if err != nil {
   184  			return err
   185  		}
   186  	}
   187  
   188  	apiGroups, err := o.DiscoveryClient.ServerGroups()
   189  	if err != nil {
   190  		return err
   191  	}
   192  
   193  	metricsAPIAvailable := SupportedMetricsAPIVersionAvailable(apiGroups)
   194  
   195  	if !metricsAPIAvailable {
   196  		return errors.New("Metrics API not available")
   197  	}
   198  	metrics, err := getMetricsFromMetricsAPI(o.MetricsClient, o.Namespace, o.ResourceName, o.AllNamespaces, labelSelector, fieldSelector)
   199  	if err != nil {
   200  		return err
   201  	}
   202  
   203  	// First we check why no metrics have been received.
   204  	if len(metrics.Items) == 0 {
   205  		// If the API server query is successful but all the pods are newly created,
   206  		// the metrics are probably not ready yet, so we return the error here in the first place.
   207  		err := verifyEmptyMetrics(o, labelSelector, fieldSelector)
   208  		if err != nil {
   209  			return err
   210  		}
   211  
   212  		// if we had no errors, be sure we output something.
   213  		if o.AllNamespaces {
   214  			fmt.Fprintln(o.ErrOut, "No resources found")
   215  		} else {
   216  			fmt.Fprintf(o.ErrOut, "No resources found in %s namespace.\n", o.Namespace)
   217  		}
   218  	}
   219  
   220  	return o.Printer.PrintPodMetrics(metrics.Items, o.PrintContainers, o.AllNamespaces, o.NoHeaders, o.SortBy, o.Sum)
   221  }
   222  
   223  func getMetricsFromMetricsAPI(metricsClient metricsclientset.Interface, namespace, resourceName string, allNamespaces bool, labelSelector labels.Selector, fieldSelector fields.Selector) (*metricsapi.PodMetricsList, error) {
   224  	var err error
   225  	ns := metav1.NamespaceAll
   226  	if !allNamespaces {
   227  		ns = namespace
   228  	}
   229  	versionedMetrics := &metricsv1beta1api.PodMetricsList{}
   230  	if resourceName != "" {
   231  		m, err := metricsClient.MetricsV1beta1().PodMetricses(ns).Get(context.TODO(), resourceName, metav1.GetOptions{})
   232  		if err != nil {
   233  			return nil, err
   234  		}
   235  		versionedMetrics.Items = []metricsv1beta1api.PodMetrics{*m}
   236  	} else {
   237  		versionedMetrics, err = metricsClient.MetricsV1beta1().PodMetricses(ns).List(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector.String(), FieldSelector: fieldSelector.String()})
   238  		if err != nil {
   239  			return nil, err
   240  		}
   241  	}
   242  	metrics := &metricsapi.PodMetricsList{}
   243  	err = metricsv1beta1api.Convert_v1beta1_PodMetricsList_To_metrics_PodMetricsList(versionedMetrics, metrics, nil)
   244  	if err != nil {
   245  		return nil, err
   246  	}
   247  	return metrics, nil
   248  }
   249  
   250  func verifyEmptyMetrics(o TopPodOptions, labelSelector labels.Selector, fieldSelector fields.Selector) error {
   251  	if len(o.ResourceName) > 0 {
   252  		pod, err := o.PodClient.Pods(o.Namespace).Get(context.TODO(), o.ResourceName, metav1.GetOptions{})
   253  		if err != nil {
   254  			return err
   255  		}
   256  		if err := checkPodAge(pod); err != nil {
   257  			return err
   258  		}
   259  	} else {
   260  		pods, err := o.PodClient.Pods(o.Namespace).List(context.TODO(), metav1.ListOptions{
   261  			LabelSelector: labelSelector.String(),
   262  			FieldSelector: fieldSelector.String(),
   263  		})
   264  		if err != nil {
   265  			return err
   266  		}
   267  		if len(pods.Items) == 0 {
   268  			return nil
   269  		}
   270  		for _, pod := range pods.Items {
   271  			if err := checkPodAge(&pod); err != nil {
   272  				return err
   273  			}
   274  		}
   275  	}
   276  	return errors.New("metrics not available yet")
   277  }
   278  
   279  func checkPodAge(pod *corev1.Pod) error {
   280  	age := time.Since(pod.CreationTimestamp.Time)
   281  	if age > metricsCreationDelay {
   282  		message := fmt.Sprintf("Metrics not available for pod %s/%s, age: %s", pod.Namespace, pod.Name, age.String())
   283  		return errors.New(message)
   284  	} else {
   285  		klog.V(2).Infof("Metrics not yet available for pod %s/%s, age: %s", pod.Namespace, pod.Name, age.String())
   286  		return nil
   287  	}
   288  }
   289  

View as plain text