1
16
17 package top
18
19 import (
20 "context"
21 "errors"
22
23 "github.com/spf13/cobra"
24 v1 "k8s.io/api/core/v1"
25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26 "k8s.io/apimachinery/pkg/labels"
27 "k8s.io/cli-runtime/pkg/genericiooptions"
28 "k8s.io/client-go/discovery"
29 corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
30 cmdutil "k8s.io/kubectl/pkg/cmd/util"
31 "k8s.io/kubectl/pkg/metricsutil"
32 "k8s.io/kubectl/pkg/util/completion"
33 "k8s.io/kubectl/pkg/util/i18n"
34 "k8s.io/kubectl/pkg/util/templates"
35 metricsapi "k8s.io/metrics/pkg/apis/metrics"
36 metricsV1beta1api "k8s.io/metrics/pkg/apis/metrics/v1beta1"
37 metricsclientset "k8s.io/metrics/pkg/client/clientset/versioned"
38 )
39
40
41 type TopNodeOptions struct {
42 ResourceName string
43 Selector string
44 SortBy string
45 NoHeaders bool
46 UseProtocolBuffers bool
47 ShowCapacity bool
48
49 NodeClient corev1client.CoreV1Interface
50 Printer *metricsutil.TopCmdPrinter
51 DiscoveryClient discovery.DiscoveryInterface
52 MetricsClient metricsclientset.Interface
53
54 genericiooptions.IOStreams
55 }
56
57 var (
58 topNodeLong = templates.LongDesc(i18n.T(`
59 Display resource (CPU/memory) usage of nodes.
60
61 The top-node command allows you to see the resource consumption of nodes.`))
62
63 topNodeExample = templates.Examples(i18n.T(`
64 # Show metrics for all nodes
65 kubectl top node
66
67 # Show metrics for a given node
68 kubectl top node NODE_NAME`))
69 )
70
71 func NewCmdTopNode(f cmdutil.Factory, o *TopNodeOptions, streams genericiooptions.IOStreams) *cobra.Command {
72 if o == nil {
73 o = &TopNodeOptions{
74 IOStreams: streams,
75 UseProtocolBuffers: true,
76 }
77 }
78
79 cmd := &cobra.Command{
80 Use: "node [NAME | -l label]",
81 DisableFlagsInUseLine: true,
82 Short: i18n.T("Display resource (CPU/memory) usage of nodes"),
83 Long: topNodeLong,
84 Example: topNodeExample,
85 ValidArgsFunction: completion.ResourceNameCompletionFunc(f, "node"),
86 Run: func(cmd *cobra.Command, args []string) {
87 cmdutil.CheckErr(o.Complete(f, cmd, args))
88 cmdutil.CheckErr(o.Validate())
89 cmdutil.CheckErr(o.RunTopNode())
90 },
91 Aliases: []string{"nodes", "no"},
92 }
93 cmdutil.AddLabelSelectorFlagVar(cmd, &o.Selector)
94 cmd.Flags().StringVar(&o.SortBy, "sort-by", o.SortBy, "If non-empty, sort nodes list using specified field. The field can be either 'cpu' or 'memory'.")
95 cmd.Flags().BoolVar(&o.NoHeaders, "no-headers", o.NoHeaders, "If present, print output without headers")
96 cmd.Flags().BoolVar(&o.UseProtocolBuffers, "use-protocol-buffers", o.UseProtocolBuffers, "Enables using protocol-buffers to access Metrics API.")
97 cmd.Flags().BoolVar(&o.ShowCapacity, "show-capacity", o.ShowCapacity, "Print node resources based on Capacity instead of Allocatable(default) of the nodes.")
98
99 return cmd
100 }
101
102 func (o *TopNodeOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
103 if len(args) == 1 {
104 o.ResourceName = args[0]
105 } else if len(args) > 1 {
106 return cmdutil.UsageErrorf(cmd, "%s", cmd.Use)
107 }
108
109 clientset, err := f.KubernetesClientSet()
110 if err != nil {
111 return err
112 }
113
114 o.DiscoveryClient = clientset.DiscoveryClient
115
116 config, err := f.ToRESTConfig()
117 if err != nil {
118 return err
119 }
120 if o.UseProtocolBuffers {
121 config.ContentType = "application/vnd.kubernetes.protobuf"
122 }
123 o.MetricsClient, err = metricsclientset.NewForConfig(config)
124 if err != nil {
125 return err
126 }
127
128 o.NodeClient = clientset.CoreV1()
129
130 o.Printer = metricsutil.NewTopCmdPrinter(o.Out)
131 return nil
132 }
133
134 func (o *TopNodeOptions) Validate() error {
135 if len(o.SortBy) > 0 {
136 if o.SortBy != sortByCPU && o.SortBy != sortByMemory {
137 return errors.New("--sort-by accepts only cpu or memory")
138 }
139 }
140 if len(o.ResourceName) > 0 && len(o.Selector) > 0 {
141 return errors.New("only one of NAME or --selector can be provided")
142 }
143 return nil
144 }
145
146 func (o TopNodeOptions) RunTopNode() error {
147 var err error
148 selector := labels.Everything()
149 if len(o.Selector) > 0 {
150 selector, err = labels.Parse(o.Selector)
151 if err != nil {
152 return err
153 }
154 }
155
156 apiGroups, err := o.DiscoveryClient.ServerGroups()
157 if err != nil {
158 return err
159 }
160
161 metricsAPIAvailable := SupportedMetricsAPIVersionAvailable(apiGroups)
162
163 if !metricsAPIAvailable {
164 return errors.New("Metrics API not available")
165 }
166
167 metrics, err := getNodeMetricsFromMetricsAPI(o.MetricsClient, o.ResourceName, selector)
168 if err != nil {
169 return err
170 }
171
172 if len(metrics.Items) == 0 {
173 return errors.New("metrics not available yet")
174 }
175
176 var nodes []v1.Node
177 if len(o.ResourceName) > 0 {
178 node, err := o.NodeClient.Nodes().Get(context.TODO(), o.ResourceName, metav1.GetOptions{})
179 if err != nil {
180 return err
181 }
182 nodes = append(nodes, *node)
183 } else {
184 nodeList, err := o.NodeClient.Nodes().List(context.TODO(), metav1.ListOptions{
185 LabelSelector: selector.String(),
186 })
187 if err != nil {
188 return err
189 }
190 nodes = append(nodes, nodeList.Items...)
191 }
192
193 availableResources := make(map[string]v1.ResourceList)
194
195 for _, n := range nodes {
196 if !o.ShowCapacity {
197 availableResources[n.Name] = n.Status.Allocatable
198 } else {
199 availableResources[n.Name] = n.Status.Capacity
200 }
201 }
202
203 return o.Printer.PrintNodeMetrics(metrics.Items, availableResources, o.NoHeaders, o.SortBy)
204 }
205
206 func getNodeMetricsFromMetricsAPI(metricsClient metricsclientset.Interface, resourceName string, selector labels.Selector) (*metricsapi.NodeMetricsList, error) {
207 var err error
208 versionedMetrics := &metricsV1beta1api.NodeMetricsList{}
209 mc := metricsClient.MetricsV1beta1()
210 nm := mc.NodeMetricses()
211 if resourceName != "" {
212 m, err := nm.Get(context.TODO(), resourceName, metav1.GetOptions{})
213 if err != nil {
214 return nil, err
215 }
216 versionedMetrics.Items = []metricsV1beta1api.NodeMetrics{*m}
217 } else {
218 versionedMetrics, err = nm.List(context.TODO(), metav1.ListOptions{LabelSelector: selector.String()})
219 if err != nil {
220 return nil, err
221 }
222 }
223 metrics := &metricsapi.NodeMetricsList{}
224 err = metricsV1beta1api.Convert_v1beta1_NodeMetricsList_To_metrics_NodeMetricsList(versionedMetrics, metrics, nil)
225 if err != nil {
226 return nil, err
227 }
228 return metrics, nil
229 }
230
View as plain text