1
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
204 if len(metrics.Items) == 0 {
205
206
207 err := verifyEmptyMetrics(o, labelSelector, fieldSelector)
208 if err != nil {
209 return err
210 }
211
212
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