Skip to content
Merged
75 changes: 75 additions & 0 deletions cli/cmd/controller-metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package cmd

import (
"bytes"
"fmt"
"time"

"github.com/linkerd/linkerd2/pkg/k8s"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// ControllerMetricsOptions holds values for command line flags that apply to the controller-metrics
// command.
type ControllerMetricsOptions struct {
wait time.Duration
}

// newControllerMetricsOptions initializes controller-metrics options setting
// the max wait time duration as 30 seconds to fetch controller-metrics
//
// This option may be overridden on the CLI at run-time
func newControllerMetricsOptions() *ControllerMetricsOptions {
return &ControllerMetricsOptions{
wait: 30 * time.Second,
}
}

// newCmdControllerMetrics creates a new cobra command `controller-metrics` which contains commands to fetch control plane container's metrics
func newCmdControllerMetrics() *cobra.Command {
options := newControllerMetricsOptions()

cmd := &cobra.Command{
Use: "controller-metrics",
Aliases: []string{"cp-metrics"},
Short: "Fetch metrics directly from the Linkerd control plane containers",
Long: `Fetch metrics directly from Linkerd control plane containers.

This command initiates port-forward to each control plane process, and
queries the /metrics endpoint on them.`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
k8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)
if err != nil {
return err
}

pods, err := k8sAPI.CoreV1().Pods(controlPlaneNamespace).List(cmd.Context(), metav1.ListOptions{})
if err != nil {
return err
}

results := getMetrics(k8sAPI, pods.Items, adminHTTPPortName, options.wait, verbose)

var buf bytes.Buffer
for i, result := range results {
content := fmt.Sprintf("#\n# POD %s (%d of %d)\n", result.pod, i+1, len(results))
if result.err != nil {
content += fmt.Sprintf("# ERROR %s\n", result.err)
} else {
content += fmt.Sprintf("# CONTAINER %s \n#\n", result.container)
content += string(result.metrics)
}
buf.WriteString(content)
}
fmt.Printf("%s", buf.String())

return nil
},
}

cmd.Flags().DurationVarP(&options.wait, "wait", "w", options.wait, "Time allowed to fetch diagnostics")

return cmd
}
88 changes: 26 additions & 62 deletions cli/cmd/diagnostics.go
Original file line number Diff line number Diff line change
@@ -1,78 +1,42 @@
package cmd

import (
"bytes"
"fmt"
"time"

"github.com/linkerd/linkerd2/pkg/k8s"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
adminHTTPPortName string = "admin-http"
)

// diagnosticsOptions holds values for command line flags that apply to the diagnostics
// command.
type diagnosticsOptions struct {
wait time.Duration
}

// newDiagnosticsOptions initializes diagnostics options setting
// the max wait time duration as 30 seconds to fetch diagnostics
//
// This option may be overridden on the CLI at run-time
func newDiagnosticsOptions() *diagnosticsOptions {
return &diagnosticsOptions{
wait: 30 * time.Second,
}
}

// newCmdDashboard creates a new cobra command `diagnostics` which contains commands to fetch control plane container's metrics
// newCmdDiagnostics creates a new cobra command `diagnostics` which contains commands to fetch Linkerd diagnostics
func newCmdDiagnostics() *cobra.Command {
options := newDiagnosticsOptions()

cmd := &cobra.Command{
Use: "diagnostics",
Short: "Fetch metrics directly from the Linkerd control plane containers",
Long: `Fetch metrics directly from Linkerd control plane containers.

This command initiates port-forward to each control plane process, and
queries the /metrics endpoint on them.`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
k8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)
if err != nil {
return err
}

pods, err := k8sAPI.CoreV1().Pods(controlPlaneNamespace).List(cmd.Context(), metav1.ListOptions{})
if err != nil {
return err
}

results := getMetrics(k8sAPI, pods.Items, adminHTTPPortName, options.wait, verbose)

var buf bytes.Buffer
for i, result := range results {
content := fmt.Sprintf("#\n# POD %s (%d of %d)\n", result.pod, i+1, len(results))
if result.err != nil {
content += fmt.Sprintf("# ERROR %s\n", result.err)
} else {
content += fmt.Sprintf("# CONTAINER %s \n#\n", result.container)
content += string(result.metrics)
}
buf.WriteString(content)
}
fmt.Printf("%s", buf.String())

return nil
},
diagnosticsCmd := &cobra.Command{
Use: "diagnostics [flags]",
Aliases: []string{"dg"},
Args: cobra.NoArgs,
Short: "Commands used to diagnose Linkerd components",
Long: `Commands used to diagnose Linkerd components.

This command provides subcommands to diagnose the functionality of Linkerd.`,
Example: ` # Get control-plane component metrics
linkerd diagnostics controller-metrics

# Get metrics from the web deployment in the emojivoto namespace.
linkerd diagnostics proxy-metrics -n emojivoto deploy/web

# Get the endpoints for authorities in Linkerd's control-plane itself
linkerd diagnostics endpoints linkerd-controller-api.linkerd.svc.cluster.local:8085

# Install service profiles for the control-plane components.
linkerd diagnostics install-sp
`,
}

cmd.Flags().DurationVarP(&options.wait, "wait", "w", options.wait, "Time allowed to fetch diagnostics")
diagnosticsCmd.AddCommand(newCmdControllerMetrics())
diagnosticsCmd.AddCommand(newCmdEndpoints())
diagnosticsCmd.AddCommand(newCmdMetrics())
diagnosticsCmd.AddCommand(newCmdInstallSP())
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we call the install-sp something else? As its a bit confusing as this installs service profiles for the control-plane components and not the service profiles crds itself

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a better suggestion for the name of this.


return cmd
return diagnosticsCmd
}
8 changes: 4 additions & 4 deletions cli/cmd/endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,14 @@ func newCmdEndpoints() *cobra.Command {
options := newEndpointsOptions()

example := ` # get all endpoints for the authorities emoji-svc.emojivoto.svc.cluster.local:8080 and web-svc.emojivoto.svc.cluster.local:80
linkerd endpoints emoji-svc.emojivoto.svc.cluster.local:8080 web-svc.emojivoto.svc.cluster.local:80
linkerd diagnostics endpoints emoji-svc.emojivoto.svc.cluster.local:8080 web-svc.emojivoto.svc.cluster.local:80

# get that same information in json format
linkerd endpoints -o json emoji-svc.emojivoto.svc.cluster.local:8080 web-svc.emojivoto.svc.cluster.local:80
linkerd diagnostics endpoints -o json emoji-svc.emojivoto.svc.cluster.local:8080 web-svc.emojivoto.svc.cluster.local:80

# get the endpoints for authorities in Linkerd's control-plane itself
linkerd endpoints linkerd-controller-api.linkerd.svc.cluster.local:8085
linkerd endpoints linkerd-web.linkerd.svc.cluster.local:8084`
linkerd diagnostics endpoints linkerd-controller-api.linkerd.svc.cluster.local:8085
linkerd diagnostics endpoints linkerd-web.linkerd.svc.cluster.local:8084`

cmd := &cobra.Command{
Use: "endpoints [flags] authorities",
Expand Down
8 changes: 4 additions & 4 deletions cli/cmd/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func newCmdMetrics() *cobra.Command {
options := newMetricsOptions()

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

Expand Down Expand Up @@ -62,13 +62,13 @@ func newCmdMetrics() *cobra.Command {
* replicationcontrollers
* statefulsets`,
Example: ` # Get metrics from pod-foo-bar in the default namespace.
linkerd metrics po/pod-foo-bar
linkerd diagnostics proxy-metrics po/pod-foo-bar

# Get metrics from the web deployment in the emojivoto namespace.
linkerd metrics -n emojivoto deploy/web
linkerd diagnostics proxy-metrics -n emojivoto deploy/web

# Get metrics from the linkerd-controller pod in the linkerd namespace.
linkerd metrics -n linkerd $(
linkerd diagnostics proxy-metrics -n linkerd $(
kubectl --namespace linkerd get pod \
--selector linkerd.io/control-plane-component=controller \
--output name
Expand Down
3 changes: 0 additions & 3 deletions cli/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,10 @@ func init() {
RootCmd.AddCommand(newCmdCompletion())
RootCmd.AddCommand(newCmdDiagnostics())
RootCmd.AddCommand(newCmdDoc())
RootCmd.AddCommand(newCmdEndpoints())
RootCmd.AddCommand(newCmdIdentity())
RootCmd.AddCommand(newCmdInject())
RootCmd.AddCommand(newCmdInstall())
RootCmd.AddCommand(newCmdInstallCNIPlugin())
RootCmd.AddCommand(newCmdInstallSP())
RootCmd.AddCommand(newCmdMetrics())
RootCmd.AddCommand(newCmdProfile())
RootCmd.AddCommand(newCmdRepair())
RootCmd.AddCommand(newCmdUninject())
Expand Down
3 changes: 2 additions & 1 deletion test/integration/endpoints/endpoints_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func TestGoodEndpoints(t *testing.T) {
vizNs := TestHelper.GetVizNamespace()
testDataPath := "testdata"
cmd := []string{
"diagnostics",
"endpoints",
fmt.Sprintf("linkerd-controller-api.%s.svc.cluster.local:8085", ns),
fmt.Sprintf("linkerd-dst.%s.svc.cluster.local:8086", ns),
Expand Down Expand Up @@ -69,7 +70,7 @@ func TestGoodEndpoints(t *testing.T) {

// TODO: when #3004 gets fixed, add a negative test for mismatched ports
func TestBadEndpoints(t *testing.T) {
_, stderr, err := TestHelper.PipeToLinkerdRun("", "endpoints", "foo")
_, stderr, err := TestHelper.PipeToLinkerdRun("", "diagnostics", "endpoints", "foo")
if err == nil {
testutil.AnnotatedFatalf(t, "was expecting an error", "was expecting an error: %v", err)
}
Expand Down
2 changes: 1 addition & 1 deletion test/integration/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -880,7 +880,7 @@ func TestUpgradeTestAppWorksAfterUpgrade(t *testing.T) {
}

func TestInstallSP(t *testing.T) {
cmd := []string{"install-sp"}
cmd := []string{"diagnostics", "install-sp"}

out, err := TestHelper.LinkerdRun(cmd...)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion test/integration/multicluster/target2/target_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func TestMain(m *testing.M) {
// successfully issued requests, then we'll see log messages indicating that the
// web-svc can't reach the voting-svc (because it's not running).
//
// TODO it may be clearer to invoke `linkerd metrics` to check whether we see
// TODO it may be clearer to invoke `linkerd diagnostics proxy-metrics` to check whether we see
// connections from the gateway pod to the web-svc?
func TestTargetTraffic(t *testing.T) {
timeout := time.Minute
Expand Down
2 changes: 1 addition & 1 deletion test/integration/opaqueports/opaque_ports_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func TestOpaquePorts(t *testing.T) {

func getPodMetrics(pod v1.Pod, ns string) (string, error) {
podName := fmt.Sprintf("pod/%s", pod.Name)
cmd := []string{"metrics", "--namespace", ns, podName}
cmd := []string{"diagnostics", "proxy-metrics", "--namespace", ns, podName}
metrics, err := TestHelper.LinkerdRun(cmd...)
if err != nil {
return "", err
Expand Down