2019-01-12 04:58:27 +00:00
/ *
Copyright 2016 The Kubernetes Authors .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package top
import (
2020-03-26 21:07:15 +00:00
"context"
2019-01-12 04:58:27 +00:00
"errors"
"fmt"
"time"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2021-07-02 08:43:15 +00:00
"k8s.io/apimachinery/pkg/fields"
2019-01-12 04:58:27 +00:00
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/discovery"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
2019-09-27 21:51:53 +00:00
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/metricsutil"
2021-07-02 08:43:15 +00:00
"k8s.io/kubectl/pkg/util"
2019-09-27 21:51:53 +00:00
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
2019-01-12 04:58:27 +00:00
metricsapi "k8s.io/metrics/pkg/apis/metrics"
metricsv1beta1api "k8s.io/metrics/pkg/apis/metrics/v1beta1"
metricsclientset "k8s.io/metrics/pkg/client/clientset/versioned"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
2020-08-10 17:43:49 +00:00
"k8s.io/klog/v2"
2019-01-12 04:58:27 +00:00
)
type TopPodOptions struct {
2021-03-18 22:40:29 +00:00
ResourceName string
Namespace string
2021-07-02 08:43:15 +00:00
LabelSelector string
FieldSelector string
2021-03-18 22:40:29 +00:00
SortBy string
AllNamespaces bool
PrintContainers bool
NoHeaders bool
UseProtocolBuffers bool
2019-01-12 04:58:27 +00:00
PodClient corev1client . PodsGetter
Printer * metricsutil . TopCmdPrinter
DiscoveryClient discovery . DiscoveryInterface
MetricsClient metricsclientset . Interface
genericclioptions . IOStreams
}
const metricsCreationDelay = 2 * time . Minute
var (
topPodLong = templates . LongDesc ( i18n . T ( `
2021-07-02 08:43:15 +00:00
Display resource ( CPU / memory ) usage of pods .
2019-01-12 04:58:27 +00:00
The ' top pod ' command allows you to see the resource consumption of pods .
Due to the metrics pipeline delay , they may be unavailable for a few minutes
since pod creation . ` ) )
topPodExample = templates . Examples ( i18n . T ( `
# Show metrics for all pods in the default namespace
kubectl top pod
# Show metrics for all pods in the given namespace
kubectl top pod -- namespace = NAMESPACE
# Show metrics for a given pod and its containers
kubectl top pod POD_NAME -- containers
# Show metrics for the pods defined by label name = myLabel
kubectl top pod - l name = myLabel ` ) )
)
func NewCmdTopPod ( f cmdutil . Factory , o * TopPodOptions , streams genericclioptions . IOStreams ) * cobra . Command {
if o == nil {
o = & TopPodOptions {
2021-07-02 08:43:15 +00:00
IOStreams : streams ,
UseProtocolBuffers : true ,
2019-01-12 04:58:27 +00:00
}
}
cmd := & cobra . Command {
Use : "pod [NAME | -l label]" ,
DisableFlagsInUseLine : true ,
2021-07-02 08:43:15 +00:00
Short : i18n . T ( "Display resource (CPU/memory) usage of pods" ) ,
2019-01-12 04:58:27 +00:00
Long : topPodLong ,
Example : topPodExample ,
2021-07-02 08:43:15 +00:00
ValidArgsFunction : util . ResourceNameCompletionFunc ( f , "pod" ) ,
2019-01-12 04:58:27 +00:00
Run : func ( cmd * cobra . Command , args [ ] string ) {
cmdutil . CheckErr ( o . Complete ( f , cmd , args ) )
cmdutil . CheckErr ( o . Validate ( ) )
cmdutil . CheckErr ( o . RunTopPod ( ) )
} ,
Aliases : [ ] string { "pods" , "po" } ,
}
2021-07-02 08:43:15 +00:00
cmd . Flags ( ) . StringVarP ( & o . LabelSelector , "selector" , "l" , o . LabelSelector , "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)" )
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." )
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'." )
2019-01-12 04:58:27 +00:00
cmd . Flags ( ) . BoolVar ( & o . PrintContainers , "containers" , o . PrintContainers , "If present, print usage of containers within a pod." )
2019-04-07 17:07:55 +00:00
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." )
2019-01-12 04:58:27 +00:00
cmd . Flags ( ) . BoolVar ( & o . NoHeaders , "no-headers" , o . NoHeaders , "If present, print output without headers." )
2021-07-02 08:43:15 +00:00
cmd . Flags ( ) . BoolVar ( & o . UseProtocolBuffers , "use-protocol-buffers" , o . UseProtocolBuffers , "Enables using protocol-buffers to access Metrics API." )
2019-01-12 04:58:27 +00:00
return cmd
}
func ( o * TopPodOptions ) Complete ( f cmdutil . Factory , cmd * cobra . Command , args [ ] string ) error {
var err error
if len ( args ) == 1 {
o . ResourceName = args [ 0 ]
} else if len ( args ) > 1 {
return cmdutil . UsageErrorf ( cmd , "%s" , cmd . Use )
}
o . Namespace , _ , err = f . ToRawKubeConfigLoader ( ) . Namespace ( )
if err != nil {
return err
}
clientset , err := f . KubernetesClientSet ( )
if err != nil {
return err
}
o . DiscoveryClient = clientset . DiscoveryClient
config , err := f . ToRESTConfig ( )
if err != nil {
return err
}
2021-03-18 22:40:29 +00:00
if o . UseProtocolBuffers {
config . ContentType = "application/vnd.kubernetes.protobuf"
}
2019-01-12 04:58:27 +00:00
o . MetricsClient , err = metricsclientset . NewForConfig ( config )
if err != nil {
return err
}
o . PodClient = clientset . CoreV1 ( )
o . Printer = metricsutil . NewTopCmdPrinter ( o . Out )
return nil
}
func ( o * TopPodOptions ) Validate ( ) error {
2019-08-30 18:33:25 +00:00
if len ( o . SortBy ) > 0 {
if o . SortBy != sortByCPU && o . SortBy != sortByMemory {
return errors . New ( "--sort-by accepts only cpu or memory" )
}
}
2021-07-02 08:43:15 +00:00
if len ( o . ResourceName ) > 0 && ( len ( o . LabelSelector ) > 0 || len ( o . FieldSelector ) > 0 ) {
return errors . New ( "only one of NAME or selector can be provided" )
2019-01-12 04:58:27 +00:00
}
return nil
}
func ( o TopPodOptions ) RunTopPod ( ) error {
var err error
2021-07-02 08:43:15 +00:00
labelSelector := labels . Everything ( )
if len ( o . LabelSelector ) > 0 {
labelSelector , err = labels . Parse ( o . LabelSelector )
if err != nil {
return err
}
}
fieldSelector := fields . Everything ( )
if len ( o . FieldSelector ) > 0 {
fieldSelector , err = fields . ParseSelector ( o . FieldSelector )
2019-01-12 04:58:27 +00:00
if err != nil {
return err
}
}
apiGroups , err := o . DiscoveryClient . ServerGroups ( )
if err != nil {
return err
}
metricsAPIAvailable := SupportedMetricsAPIVersionAvailable ( apiGroups )
2020-08-10 17:43:49 +00:00
if ! metricsAPIAvailable {
return errors . New ( "Metrics API not available" )
}
2021-07-02 08:43:15 +00:00
metrics , err := getMetricsFromMetricsAPI ( o . MetricsClient , o . Namespace , o . ResourceName , o . AllNamespaces , labelSelector , fieldSelector )
2020-08-10 17:43:49 +00:00
if err != nil {
return err
2019-01-12 04:58:27 +00:00
}
// First we check why no metrics have been received.
if len ( metrics . Items ) == 0 {
// If the API server query is successful but all the pods are newly created,
// the metrics are probably not ready yet, so we return the error here in the first place.
2021-07-02 08:43:15 +00:00
err := verifyEmptyMetrics ( o , labelSelector , fieldSelector )
2021-03-18 22:40:29 +00:00
if err != nil {
return err
2019-01-12 04:58:27 +00:00
}
2020-03-26 21:07:15 +00:00
// if we had no errors, be sure we output something.
if o . AllNamespaces {
fmt . Fprintln ( o . ErrOut , "No resources found" )
} else {
fmt . Fprintf ( o . ErrOut , "No resources found in %s namespace.\n" , o . Namespace )
}
2019-01-12 04:58:27 +00:00
}
2019-08-30 18:33:25 +00:00
return o . Printer . PrintPodMetrics ( metrics . Items , o . PrintContainers , o . AllNamespaces , o . NoHeaders , o . SortBy )
2019-01-12 04:58:27 +00:00
}
2021-07-02 08:43:15 +00:00
func getMetricsFromMetricsAPI ( metricsClient metricsclientset . Interface , namespace , resourceName string , allNamespaces bool , labelSelector labels . Selector , fieldSelector fields . Selector ) ( * metricsapi . PodMetricsList , error ) {
2019-01-12 04:58:27 +00:00
var err error
ns := metav1 . NamespaceAll
if ! allNamespaces {
ns = namespace
}
versionedMetrics := & metricsv1beta1api . PodMetricsList { }
if resourceName != "" {
2020-03-26 21:07:15 +00:00
m , err := metricsClient . MetricsV1beta1 ( ) . PodMetricses ( ns ) . Get ( context . TODO ( ) , resourceName , metav1 . GetOptions { } )
2019-01-12 04:58:27 +00:00
if err != nil {
return nil , err
}
versionedMetrics . Items = [ ] metricsv1beta1api . PodMetrics { * m }
} else {
2021-07-02 08:43:15 +00:00
versionedMetrics , err = metricsClient . MetricsV1beta1 ( ) . PodMetricses ( ns ) . List ( context . TODO ( ) , metav1 . ListOptions { LabelSelector : labelSelector . String ( ) , FieldSelector : fieldSelector . String ( ) } )
2019-01-12 04:58:27 +00:00
if err != nil {
return nil , err
}
}
metrics := & metricsapi . PodMetricsList { }
err = metricsv1beta1api . Convert_v1beta1_PodMetricsList_To_metrics_PodMetricsList ( versionedMetrics , metrics , nil )
if err != nil {
return nil , err
}
return metrics , nil
}
2021-07-02 08:43:15 +00:00
func verifyEmptyMetrics ( o TopPodOptions , labelSelector labels . Selector , fieldSelector fields . Selector ) error {
2019-01-12 04:58:27 +00:00
if len ( o . ResourceName ) > 0 {
2020-03-26 21:07:15 +00:00
pod , err := o . PodClient . Pods ( o . Namespace ) . Get ( context . TODO ( ) , o . ResourceName , metav1 . GetOptions { } )
2019-01-12 04:58:27 +00:00
if err != nil {
return err
}
if err := checkPodAge ( pod ) ; err != nil {
return err
}
} else {
2020-03-26 21:07:15 +00:00
pods , err := o . PodClient . Pods ( o . Namespace ) . List ( context . TODO ( ) , metav1 . ListOptions {
2021-07-02 08:43:15 +00:00
LabelSelector : labelSelector . String ( ) ,
FieldSelector : fieldSelector . String ( ) ,
2019-01-12 04:58:27 +00:00
} )
if err != nil {
return err
}
if len ( pods . Items ) == 0 {
return nil
}
for _ , pod := range pods . Items {
if err := checkPodAge ( & pod ) ; err != nil {
return err
}
}
}
return errors . New ( "metrics not available yet" )
}
func checkPodAge ( pod * v1 . Pod ) error {
age := time . Since ( pod . CreationTimestamp . Time )
if age > metricsCreationDelay {
message := fmt . Sprintf ( "Metrics not available for pod %s/%s, age: %s" , pod . Namespace , pod . Name , age . String ( ) )
return errors . New ( message )
} else {
klog . V ( 2 ) . Infof ( "Metrics not yet available for pod %s/%s, age: %s" , pod . Namespace , pod . Name , age . String ( ) )
return nil
}
}