Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 28 additions & 9 deletions cli/cmd/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,45 @@ package cmd

import (
"bytes"
"errors"
"fmt"
"time"

pkgcmd "github.com/linkerd/linkerd2/pkg/cmd"
"github.com/linkerd/linkerd2/pkg/k8s"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
)

type metricsOptions struct {
namespace string
pod string
obfuscate bool
namespace string
pod string
obfuscate bool
labelSelector string
}

func newMetricsOptions() *metricsOptions {
return &metricsOptions{
pod: "",
obfuscate: false,
pod: "",
obfuscate: false,
labelSelector: "",
}
}

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

cmd := &cobra.Command{
Use: "proxy-metrics [flags] (RESOURCE)",
Use: "proxy-metrics [flags] [(RESOURCE)]",
Short: "Fetch metrics directly from Linkerd proxies",
Long: `Fetch metrics directly from Linkerd proxies.

This command initiates a port-forward to a given pod or set of pods, and
queries the /metrics endpoint on the Linkerd proxies.

The RESOURCE argument specifies the target resource to query metrics for:
(TYPE/NAME)
(TYPE/NAME). Alternatively, use --selector (-l) to select pods by label
without specifying a resource name.

Examples:
* cronjob/my-cronjob
Expand All @@ -61,14 +66,21 @@ func newCmdMetrics() *cobra.Command {
# Get metrics from the web deployment in the emojivoto namespace.
linkerd diagnostics proxy-metrics -n emojivoto deploy/web

# Get metrics from all pods with a given label in the emojivoto namespace.
linkerd diagnostics proxy-metrics -n emojivoto -l app=web

# Get metrics from the linkerd-destination pod in the linkerd namespace.
linkerd diagnostics proxy-metrics -n linkerd $(
kubectl --namespace linkerd get pod \
--selector linkerd.io/control-plane-component=destination \
--output name
)`,
Args: cobra.ExactArgs(1),
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 && options.labelSelector == "" {
return errors.New("must specify a resource or --selector")
}

if options.namespace == "" {
options.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)
}
Expand All @@ -77,7 +89,13 @@ func newCmdMetrics() *cobra.Command {
return err
}

pods, err := k8s.GetPodsFor(cmd.Context(), k8sAPI, options.namespace, args[0])
var pods []corev1.Pod
if len(args) == 0 {
// selector-only mode: list pods matching the label selector
pods, err = k8s.GetPodsBySelector(cmd.Context(), k8sAPI, options.namespace, options.labelSelector)
} else {
pods, err = k8s.GetPodsFor(cmd.Context(), k8sAPI, options.namespace, args[0])
}
if err != nil {
return err
}
Expand Down Expand Up @@ -111,6 +129,7 @@ func newCmdMetrics() *cobra.Command {

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

pkgcmd.ConfigureNamespaceFlagCompletion(cmd, []string{"namespace"},
kubeconfigPath, impersonate, impersonateGroup, kubeContext)
Expand Down
12 changes: 12 additions & 0 deletions pkg/k8s/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,18 @@ func GetPodsFor(ctx context.Context, clientset kubernetes.Interface, namespace s

return pods, nil
}
// GetPodsBySelector queries the Kubernetes API and returns all pods in the
// given namespace matching the provided label selector string. If selector is
// empty, all pods in the namespace are returned.
func GetPodsBySelector(ctx context.Context, clientset kubernetes.Interface, namespace string, selector string) ([]corev1.Pod, error) {
podList, err := clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
LabelSelector: selector,
})
if err != nil {
return nil, err
}
return podList.Items, nil
}

func isOwner(u types.UID, ownerRefs []metav1.OwnerReference) bool {
for _, or := range ownerRefs {
Expand Down
59 changes: 32 additions & 27 deletions viz/cmd/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,50 +54,55 @@ func NewCmdRoutes() *cobra.Command {
options := newRoutesOptions()

cmd := &cobra.Command{
Use: "routes [flags] (RESOURCES)",
Use: "routes [flags] (RESOURCE) [RESOURCE...]",
Short: "Display route stats",
Long: `Display route stats.

This command will only display traffic which is sent to a service that has a Service Profile defined.`,
Example: ` # Routes for the webapp service in the test namespace.
linkerd viz routes service/webapp -n test

# Routes for multiple services in the test namespace.
linkerd viz routes service/webapp service/api -n test

# Routes for calls from the traffic deployment to the webapp service in the test namespace.
linkerd viz routes deploy/traffic -n test --to svc/webapp`,
Args: cobra.ExactArgs(1),
Args: cobra.MinimumNArgs(1),
ValidArgs: pkgUtil.ValidTargets,
RunE: func(cmd *cobra.Command, args []string) error {
if options.namespace == "" {
options.namespace = pkgcmd.GetDefaultNamespace(kubeconfigPath, kubeContext)
}
req, err := buildTopRoutesRequest(args[0], options)
if err != nil {
return fmt.Errorf("error creating metrics request while making routes request: %w", err)
}

output, err := requestRouteStatsFromAPI(
api.CheckClientOrExit(hc.VizOptions{
Options: &healthcheck.Options{
ControlPlaneNamespace: controlPlaneNamespace,
KubeConfig: kubeconfigPath,
Impersonate: impersonate,
ImpersonateGroup: impersonateGroup,
KubeContext: kubeContext,
APIAddr: apiAddr,
},
VizNamespaceOverride: vizNamespace,
}),
req,
options,
)
if err != nil {
fmt.Fprint(os.Stderr, err.Error())
os.Exit(1)
client := api.CheckClientOrExit(hc.VizOptions{
Options: &healthcheck.Options{
ControlPlaneNamespace: controlPlaneNamespace,
KubeConfig: kubeconfigPath,
Impersonate: impersonate,
ImpersonateGroup: impersonateGroup,
KubeContext: kubeContext,
APIAddr: apiAddr,
},
VizNamespaceOverride: vizNamespace,
})

var buf bytes.Buffer
for _, arg := range args {
req, err := buildTopRoutesRequest(arg, options)
if err != nil {
return fmt.Errorf("error creating metrics request while making routes request: %w", err)
}

output, err := requestRouteStatsFromAPI(client, req, options)
if err != nil {
fmt.Fprint(os.Stderr, err.Error())
os.Exit(1)
}
buf.WriteString(output)
}

_, err = fmt.Print(output)
_, printErr := fmt.Print(buf.String())

return err
return printErr
},
}

Expand Down