top_pod.go 8.3 KB

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