Skip to content

Commit 5f5b5c6

Browse files
cli: add --selector flag to proxy-metrics and multi-resource to routes
Closes #2734 Two additions toward full kubectl-style resource selector support: 1. viz routes — multi-resource support Change Args from ExactArgs(1) to MinimumNArgs(1) so that multiple resources can be specified: linkerd viz routes svc/webapp svc/api -n test The API client is constructed once before the loop; each resource produces one TopRoutesRequest and the outputs are concatenated. 2. diagnostics proxy-metrics — --selector / -l flag Add a labelSelector field to metricsOptions and register the --selector/-l flag. When no positional argument is given but a selector is provided, GetPodsBySelector (new helper in pkg/k8s) is called to list matching pods directly: linkerd diagnostics proxy-metrics -n emojivoto -l app=web GetPodsBySelector wraps a plain CoreV1().Pods().List() call with the label-selector string, consistent with the existing getPods() helper in cli/cmd/identity.go. A positional argument is still accepted and takes precedence when both are given; omitting both is an error. Signed-off-by: Sudheer Obbu <mail2sudheerobbu@gmail.com>
1 parent bdab82f commit 5f5b5c6

3 files changed

Lines changed: 72 additions & 36 deletions

File tree

cli/cmd/metrics.go

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,45 @@ package cmd
22

33
import (
44
"bytes"
5+
"errors"
56
"fmt"
67
"time"
78

89
pkgcmd "github.com/linkerd/linkerd2/pkg/cmd"
910
"github.com/linkerd/linkerd2/pkg/k8s"
1011
"github.com/spf13/cobra"
12+
corev1 "k8s.io/api/core/v1"
1113
)
1214

1315
type metricsOptions struct {
14-
namespace string
15-
pod string
16-
obfuscate bool
16+
namespace string
17+
pod string
18+
obfuscate bool
19+
labelSelector string
1720
}
1821

1922
func newMetricsOptions() *metricsOptions {
2023
return &metricsOptions{
21-
pod: "",
22-
obfuscate: false,
24+
pod: "",
25+
obfuscate: false,
26+
labelSelector: "",
2327
}
2428
}
2529

2630
func newCmdMetrics() *cobra.Command {
2731
options := newMetricsOptions()
2832

2933
cmd := &cobra.Command{
30-
Use: "proxy-metrics [flags] (RESOURCE)",
34+
Use: "proxy-metrics [flags] [(RESOURCE)]",
3135
Short: "Fetch metrics directly from Linkerd proxies",
3236
Long: `Fetch metrics directly from Linkerd proxies.
3337
3438
This command initiates a port-forward to a given pod or set of pods, and
3539
queries the /metrics endpoint on the Linkerd proxies.
3640
3741
The RESOURCE argument specifies the target resource to query metrics for:
38-
(TYPE/NAME)
42+
(TYPE/NAME). Alternatively, use --selector (-l) to select pods by label
43+
without specifying a resource name.
3944
4045
Examples:
4146
* cronjob/my-cronjob
@@ -61,14 +66,21 @@ func newCmdMetrics() *cobra.Command {
6166
# Get metrics from the web deployment in the emojivoto namespace.
6267
linkerd diagnostics proxy-metrics -n emojivoto deploy/web
6368
69+
# Get metrics from all pods with a given label in the emojivoto namespace.
70+
linkerd diagnostics proxy-metrics -n emojivoto -l app=web
71+
6472
# Get metrics from the linkerd-destination pod in the linkerd namespace.
6573
linkerd diagnostics proxy-metrics -n linkerd $(
6674
kubectl --namespace linkerd get pod \
6775
--selector linkerd.io/control-plane-component=destination \
6876
--output name
6977
)`,
70-
Args: cobra.ExactArgs(1),
78+
Args: cobra.MaximumNArgs(1),
7179
RunE: func(cmd *cobra.Command, args []string) error {
80+
if len(args) == 0 && options.labelSelector == "" {
81+
return errors.New("must specify a resource or --selector")
82+
}
83+
7284
if options.namespace == "" {
7385
options.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)
7486
}
@@ -77,7 +89,13 @@ func newCmdMetrics() *cobra.Command {
7789
return err
7890
}
7991

80-
pods, err := k8s.GetPodsFor(cmd.Context(), k8sAPI, options.namespace, args[0])
92+
var pods []corev1.Pod
93+
if len(args) == 0 {
94+
// selector-only mode: list pods matching the label selector
95+
pods, err = k8s.GetPodsBySelector(cmd.Context(), k8sAPI, options.namespace, options.labelSelector)
96+
} else {
97+
pods, err = k8s.GetPodsFor(cmd.Context(), k8sAPI, options.namespace, args[0])
98+
}
8199
if err != nil {
82100
return err
83101
}
@@ -111,6 +129,7 @@ func newCmdMetrics() *cobra.Command {
111129

112130
cmd.PersistentFlags().StringVarP(&options.namespace, "namespace", "n", options.namespace, "Namespace of resource")
113131
cmd.PersistentFlags().BoolVar(&options.obfuscate, "obfuscate", options.obfuscate, "Obfuscate sensitive information")
132+
cmd.PersistentFlags().StringVarP(&options.labelSelector, "selector", "l", options.labelSelector, "Selector (label query) to filter on, supports '=', '==', and '!=")
114133

115134
pkgcmd.ConfigureNamespaceFlagCompletion(cmd, []string{"namespace"},
116135
kubeconfigPath, impersonate, impersonateGroup, kubeContext)

pkg/k8s/api.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,18 @@ func GetPodsFor(ctx context.Context, clientset kubernetes.Interface, namespace s
490490

491491
return pods, nil
492492
}
493+
// GetPodsBySelector queries the Kubernetes API and returns all pods in the
494+
// given namespace matching the provided label selector string. If selector is
495+
// empty, all pods in the namespace are returned.
496+
func GetPodsBySelector(ctx context.Context, clientset kubernetes.Interface, namespace string, selector string) ([]corev1.Pod, error) {
497+
podList, err := clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
498+
LabelSelector: selector,
499+
})
500+
if err != nil {
501+
return nil, err
502+
}
503+
return podList.Items, nil
504+
}
493505

494506
func isOwner(u types.UID, ownerRefs []metav1.OwnerReference) bool {
495507
for _, or := range ownerRefs {

viz/cmd/routes.go

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -54,50 +54,55 @@ func NewCmdRoutes() *cobra.Command {
5454
options := newRoutesOptions()
5555

5656
cmd := &cobra.Command{
57-
Use: "routes [flags] (RESOURCES)",
57+
Use: "routes [flags] (RESOURCE) [RESOURCE...]",
5858
Short: "Display route stats",
5959
Long: `Display route stats.
6060
6161
This command will only display traffic which is sent to a service that has a Service Profile defined.`,
6262
Example: ` # Routes for the webapp service in the test namespace.
6363
linkerd viz routes service/webapp -n test
6464
65+
# Routes for multiple services in the test namespace.
66+
linkerd viz routes service/webapp service/api -n test
67+
6568
# Routes for calls from the traffic deployment to the webapp service in the test namespace.
6669
linkerd viz routes deploy/traffic -n test --to svc/webapp`,
67-
Args: cobra.ExactArgs(1),
70+
Args: cobra.MinimumNArgs(1),
6871
ValidArgs: pkgUtil.ValidTargets,
6972
RunE: func(cmd *cobra.Command, args []string) error {
7073
if options.namespace == "" {
7174
options.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)
7275
}
73-
req, err := buildTopRoutesRequest(args[0], options)
74-
if err != nil {
75-
return fmt.Errorf("error creating metrics request while making routes request: %w", err)
76-
}
77-
78-
output, err := requestRouteStatsFromAPI(
79-
api.CheckClientOrExit(hc.VizOptions{
80-
Options: &healthcheck.Options{
81-
ControlPlaneNamespace: controlPlaneNamespace,
82-
KubeConfig: kubeconfigPath,
83-
Impersonate: impersonate,
84-
ImpersonateGroup: impersonateGroup,
85-
KubeContext: kubeContext,
86-
APIAddr: apiAddr,
87-
},
88-
VizNamespaceOverride: vizNamespace,
89-
}),
90-
req,
91-
options,
92-
)
93-
if err != nil {
94-
fmt.Fprint(os.Stderr, err.Error())
95-
os.Exit(1)
76+
client := api.CheckClientOrExit(hc.VizOptions{
77+
Options: &healthcheck.Options{
78+
ControlPlaneNamespace: controlPlaneNamespace,
79+
KubeConfig: kubeconfigPath,
80+
Impersonate: impersonate,
81+
ImpersonateGroup: impersonateGroup,
82+
KubeContext: kubeContext,
83+
APIAddr: apiAddr,
84+
},
85+
VizNamespaceOverride: vizNamespace,
86+
})
87+
88+
var buf bytes.Buffer
89+
for _, arg := range args {
90+
req, err := buildTopRoutesRequest(arg, options)
91+
if err != nil {
92+
return fmt.Errorf("error creating metrics request while making routes request: %w", err)
93+
}
94+
95+
output, err := requestRouteStatsFromAPI(client, req, options)
96+
if err != nil {
97+
fmt.Fprint(os.Stderr, err.Error())
98+
os.Exit(1)
99+
}
100+
buf.WriteString(output)
96101
}
97102

98-
_, err = fmt.Print(output)
103+
_, printErr := fmt.Print(buf.String())
99104

100-
return err
105+
return printErr
101106
},
102107
}
103108

0 commit comments

Comments
 (0)