diff --git a/deploy/skoopbundle.yaml b/deploy/skoopbundle.yaml index a68f21a5..eaf42aaf 100644 --- a/deploy/skoopbundle.yaml +++ b/deploy/skoopbundle.yaml @@ -135,7 +135,7 @@ data: event_config: port: 19102 loki_enable: true - loki_address: loki-service.kubeskoop.svc.cluster.local + loki_address: loki-service probes: - tcpreset - packetloss @@ -332,7 +332,7 @@ data: "name": "prometheus", "orgId": 1, "type": "prometheus", - "url": "http://prometheus-service.kubeskoop.svc", + "url": "http://prometheus-service", "version": 1 } ] @@ -358,8 +358,11 @@ spec: - name: grafana image: grafana/grafana:latest ports: - - name: grafana - containerPort: 3000 + - name: grafana + containerPort: 3000 + env: + - name: GF_SECURITY_ADMIN_PASSWORD + value: "kubeskoop" resources: limits: memory: "1Gi" diff --git a/pkg/exporter/cmd/diag.go b/pkg/exporter/cmd/diag.go index 4201b421..b8cd18f9 100644 --- a/pkg/exporter/cmd/diag.go +++ b/pkg/exporter/cmd/diag.go @@ -1,23 +1,23 @@ -/* -Copyright © 2022 NAME HERE -*/ package cmd import ( - "fmt" - "github.com/spf13/cobra" ) // diagCmd represents the diag command -var diagCmd = &cobra.Command{ - Use: "diag", - Short: "A brief description of your command", - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("diag called") - }, -} +var ( + diagCmd = &cobra.Command{ + Use: "diag", + Short: "Run command in the command line to probe metrics and events.", + Run: func(cmd *cobra.Command, args []string) { + cmd.Help() // nolint + }, + } + + podname string +) func init() { rootCmd.AddCommand(diagCmd) + diagCmd.PersistentFlags().StringVarP(&podname, "pod", "i", "", "specified pod") } diff --git a/pkg/exporter/cmd/diag_metric.go b/pkg/exporter/cmd/diag_metric.go index 466f1c44..4ae7e028 100644 --- a/pkg/exporter/cmd/diag_metric.go +++ b/pkg/exporter/cmd/diag_metric.go @@ -1,6 +1,3 @@ -/* -Copyright © 2022 NAME HERE -*/ package cmd import ( @@ -20,57 +17,63 @@ import ( var ( metricCmd = &cobra.Command{ Use: "metric", - Short: "A brief description of your command", + Short: "get metric data in cli", Run: func(cmd *cobra.Command, args []string) { - if len(probeName) > 0 { - ctx := slog.NewContext(context.Background(), slog.Default()) - err := nettop.SyncNetTopology() - if err != nil { - slog.Ctx(ctx).Info("sync nettop", "err", err) - return - } - texts := pterm.TableData{ - {"METRIC", "VALUE", "NETNS", "POD", "NAMESPACE", "PROBE"}, + if len(probeName) == 0 { + cmd.Help() // nolint + return + } + ctx := slog.NewContext(context.Background(), slog.Default()) + err := nettop.SyncNetTopology() + if err != nil { + slog.Ctx(ctx).Info("sync nettop", "err", err) + return + } + texts := pterm.TableData{ + {"METRIC", "VALUE", "NETNS", "POD", "NAMESPACE", "PROBE"}, + } + for _, p := range probeName { + data, err := probe.CollectOnce(ctx, p) + if err != nil && data == nil { + slog.Ctx(ctx).Info("collect metric", "err", err) + continue } - for _, p := range probeName { - data, err := probe.CollectOnce(ctx, p) - if err != nil && data == nil { - slog.Ctx(ctx).Info("collect metric", "err", err) + for m, d := range data { + slog.Ctx(ctx).Debug("raw metric msg", "metric", m, "data", d) + // if a probe provide multi subject, only fetch relevant metric data + if !strings.HasPrefix(m, p) { continue } - for m, d := range data { - slog.Ctx(ctx).Debug("raw metric msg", "metric", m, "data", d) - // if a probe provide multi subject, only fetch relevant metric data - if !strings.HasPrefix(m, p) { + for nsinum, v := range d { + et, err := nettop.GetEntityByNetns(int(nsinum)) + if err != nil { + slog.Ctx(ctx).Info("get entity failed", "netns", nsinum, "err", err) continue } - for nsinum, v := range d { - et, err := nettop.GetEntityByNetns(int(nsinum)) - if err != nil { - slog.Ctx(ctx).Info("get entity failed", "netns", nsinum, "err", err) - continue - } - texts = append(texts, []string{ - m, - fmt.Sprintf("%d", v), - fmt.Sprintf("%d", nsinum), - et.GetPodName(), - et.GetPodNamespace(), - p, - }) - } - + texts = append(texts, []string{ + m, + fmt.Sprintf("%d", v), + fmt.Sprintf("%d", nsinum), + et.GetPodName(), + et.GetPodNamespace(), + p, + }) } + } - pterm.DefaultTable.WithHasHeader().WithData(texts).Render() // nolint } + pterm.DefaultTable.WithHasHeader().WithData(texts).Render() // nolint + }, } - probeName []string + probeName []string + metricName []string ) func init() { diagCmd.AddCommand(metricCmd) + metricCmd.PersistentFlags().StringSliceVarP(&probeName, "probe", "p", []string{}, "probe name to diag") + metricCmd.PersistentFlags().StringSliceVarP(&metricName, "metric", "m", []string{}, "metric name to diag") } diff --git a/pkg/exporter/cmd/list.go b/pkg/exporter/cmd/list.go index 587b7b1a..eb7fd112 100644 --- a/pkg/exporter/cmd/list.go +++ b/pkg/exporter/cmd/list.go @@ -13,8 +13,12 @@ var ( cmd.Help() // nolint }, } + + output string ) func init() { rootCmd.AddCommand(listCmd) + + listCmd.PersistentFlags().StringVarP(&output, "output", "o", "text", "output format, support text/json/file") } diff --git a/pkg/exporter/cmd/list_entity.go b/pkg/exporter/cmd/list_entity.go index 3e05be60..60db8bff 100644 --- a/pkg/exporter/cmd/list_entity.go +++ b/pkg/exporter/cmd/list_entity.go @@ -19,7 +19,7 @@ import ( var ( entityCmd = &cobra.Command{ Use: "entity", - Short: "list network entity, aka no-hostNetwork pod", + Short: "List all network entities, including all non-hostnetwork pods and the host itself.", Run: func(cmd *cobra.Command, args []string) { if LabelSelector != "" { slct, err := parseLabelSelector(LabelSelector) diff --git a/pkg/exporter/cmd/list_event.go b/pkg/exporter/cmd/list_event.go index 5c94a72f..29b8f8e8 100644 --- a/pkg/exporter/cmd/list_event.go +++ b/pkg/exporter/cmd/list_event.go @@ -4,12 +4,9 @@ Copyright © 2022 NAME HERE package cmd import ( - "context" - "github.com/alibaba/kubeskoop/pkg/exporter/probe" "github.com/pterm/pterm" "github.com/spf13/cobra" - "golang.org/x/exp/slog" ) // eventCmd represents the event command @@ -17,16 +14,31 @@ var eventCmd = &cobra.Command{ Use: "event", Short: "list all available metrics", Run: func(cmd *cobra.Command, args []string) { - ctx := slog.NewContext(context.Background(), slog.Default()) - if len(listprobe) == 0 { - listprobe = probe.ListMetricProbes(ctx, true) - } - events := probe.ListEvents() - pterm.Print(events) + + sliceMapTextOutput("events", events) }, } func init() { listCmd.AddCommand(eventCmd) } + +func sliceMapTextOutput(title string, data map[string][]string) { + tree := pterm.TreeNode{ + Text: title, + Children: []pterm.TreeNode{}, + } + + for p, unit := range data { + parent := pterm.TreeNode{ + Text: p, + Children: []pterm.TreeNode{}, + } + for i := range unit { + parent.Children = append(parent.Children, pterm.TreeNode{Text: unit[i]}) + } + tree.Children = append(tree.Children, parent) + } + pterm.DefaultTree.WithRoot(tree).Render() // nolint +} diff --git a/pkg/exporter/cmd/list_metric.go b/pkg/exporter/cmd/list_metric.go index 6856634f..4d6cf8b2 100644 --- a/pkg/exporter/cmd/list_metric.go +++ b/pkg/exporter/cmd/list_metric.go @@ -4,33 +4,59 @@ Copyright © 2022 NAME HERE package cmd import ( - "context" + "strings" "github.com/alibaba/kubeskoop/pkg/exporter/probe" "github.com/pterm/pterm" "github.com/spf13/cobra" - "golang.org/x/exp/slog" ) // metricCmd represents the metric command var ( listmetricCmd = &cobra.Command{ Use: "metric", - Short: "list all available metrics", + Short: "list available metrics of probe", Run: func(cmd *cobra.Command, args []string) { - ctx := slog.NewContext(context.Background(), slog.Default()) - if len(listprobe) == 0 { - listprobe = probe.ListMetricProbes(ctx, true) + showprobes := []string{} + allprobes := probe.ListMetricProbes() + for idx := range listprobe { + for _, probe := range allprobes { + if strings.Contains(probe, listprobe[idx]) { + showprobes = append(showprobes, probe) + break + } + } + } + + if len(showprobes) == 0 { + showprobes = allprobes } - metriclist := []string{} + tree := pterm.TreeNode{ + Text: "metrics", + Children: []pterm.TreeNode{}, + } metrics := probe.ListMetrics() - for _, p := range listprobe { + for _, p := range showprobes { if mnames, ok := metrics[p]; ok { - metriclist = append(metriclist, mnames...) + parent := pterm.TreeNode{ + Text: p, + Children: []pterm.TreeNode{}, + } + for i := range mnames { + if !strings.HasPrefix(mnames[i], p) { + continue + } + children := pterm.TreeNode{ + Text: mnames[i], + } + parent.Children = append(parent.Children, children) + } + tree.Children = append(tree.Children, parent) } } - pterm.Print(metriclist) + + pterm.DefaultTree.WithRoot(tree).Render() // nolint }, } diff --git a/pkg/exporter/cmd/list_probe.go b/pkg/exporter/cmd/list_probe.go index bf309686..be5ad66d 100644 --- a/pkg/exporter/cmd/list_probe.go +++ b/pkg/exporter/cmd/list_probe.go @@ -4,10 +4,10 @@ Copyright © 2022 NAME HERE package cmd import ( - "context" + "encoding/json" + "fmt" "github.com/alibaba/kubeskoop/pkg/exporter/probe" - "github.com/pterm/pterm" "github.com/spf13/cobra" "golang.org/x/exp/slog" ) @@ -16,18 +16,34 @@ import ( var ( probeCmd = &cobra.Command{ Use: "probe", - Short: "A brief description of your command", + Short: "list supported probe with metric exporting", Run: func(cmd *cobra.Command, args []string) { - ctx := slog.NewContext(context.Background(), slog.Default()) - pls := probe.ListMetricProbes(ctx, avail) - pterm.Println(pls) + res := map[string][]string{ + "metric": {}, + "event": {}, + } + res["metric"] = probe.ListMetricProbes() + + els := probe.ListEvents() + for ep := range els { + res["event"] = append(res["event"], ep) + } + + switch output { + case "json": + text, err := json.MarshalIndent(res, "", " ") + if err != nil { + slog.Warn("json marshal failed", "err", err) + return + } + fmt.Println(string(text)) + default: + sliceMapTextOutput("probes", res) + } }, } - - avail bool ) func init() { listCmd.AddCommand(probeCmd) - probeCmd.PersistentFlags().BoolVarP(&avail, "avail", "a", false, "list all available probes") } diff --git a/pkg/exporter/cmd/server.go b/pkg/exporter/cmd/server.go index e5527246..a9380f03 100644 --- a/pkg/exporter/cmd/server.go +++ b/pkg/exporter/cmd/server.go @@ -230,7 +230,7 @@ func status(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) res := make(map[string]bool) - for _, pn := range probe.ListMetricProbes(context.Background(), true) { + for _, pn := range probe.ListMetricProbes() { p := probe.GetProbe(pn) res[p.Name()] = p.Ready() } diff --git a/pkg/exporter/probe/metric.go b/pkg/exporter/probe/metric.go index 43537d57..23d09e85 100644 --- a/pkg/exporter/probe/metric.go +++ b/pkg/exporter/probe/metric.go @@ -6,6 +6,7 @@ import ( "github.com/alibaba/kubeskoop/pkg/exporter/probe/nlconntrack" "github.com/alibaba/kubeskoop/pkg/exporter/probe/nlqdisc" + "github.com/alibaba/kubeskoop/pkg/exporter/probe/procfd" "github.com/alibaba/kubeskoop/pkg/exporter/probe/procio" "github.com/alibaba/kubeskoop/pkg/exporter/probe/procipvs" "github.com/alibaba/kubeskoop/pkg/exporter/probe/procnetdev" @@ -48,9 +49,10 @@ func init() { availmprobes["conntrack"] = nlconntrack.GetProbe() availmprobes["ipvs"] = procipvs.GetProbe() availmprobes["qdisc"] = nlqdisc.GetProbe() + availmprobes["fd"] = procfd.GetProbe() } -func ListMetricProbes(_ context.Context, _ bool) (probelist []string) { +func ListMetricProbes() (probelist []string) { for k := range availmprobes { probelist = append(probelist, k) } diff --git a/pkg/exporter/probe/procfd/procfd.go b/pkg/exporter/probe/procfd/procfd.go new file mode 100644 index 00000000..3b1c4ec0 --- /dev/null +++ b/pkg/exporter/probe/procfd/procfd.go @@ -0,0 +1,117 @@ +package procfd + +import ( + "context" + "fmt" + "os" + "strings" + + "github.com/alibaba/kubeskoop/pkg/exporter/nettop" + + "golang.org/x/exp/slog" +) + +const ( + ModuleName = "Procfd" // nolint +) + +var ( + probe = &ProcFD{} + + FDMetrics = []string{"OpenFd", "OpenSocket"} +) + +type ProcFD struct { +} + +func GetProbe() *ProcFD { + return probe +} + +func (s *ProcFD) Close() error { + return nil +} + +func (s *ProcFD) Start(_ context.Context) { +} + +func (s *ProcFD) Ready() bool { + return true +} + +func (s *ProcFD) Name() string { + return ModuleName +} + +func (s *ProcFD) GetMetricNames() []string { + res := []string{} + for _, m := range FDMetrics { + res = append(res, metricUniqueID("fd", m)) + } + return res +} + +func (s *ProcFD) Collect(ctx context.Context) (map[string]map[uint32]uint64, error) { + ets := nettop.GetAllEntity() + if len(ets) == 0 { + slog.Ctx(ctx).Info("collect", "mod", ModuleName, "ignore", "no entity found") + } + return getAllProcessFd(ets) +} + +func metricUniqueID(subject string, m string) string { + return fmt.Sprintf("%s%s", subject, strings.ToLower(m)) +} + +func getAllProcessFd(nslist []*nettop.Entity) (map[string]map[uint32]uint64, error) { + resMap := make(map[string]map[uint32]uint64) + for _, metricname := range FDMetrics { + resMap[metricUniqueID("fd", metricname)] = map[uint32]uint64{} + } + for _, nslogic := range nslist { + nsprocfd := map[string]struct{}{} + nsprocfsock := map[string]struct{}{} + for _, idx := range nslogic.GetPids() { + procfds, err := getProcessFdStat(idx) + if err != nil && !os.IsNotExist(err) { + return resMap, err + } + + for fd := range procfds { + nsprocfd[fd] = struct{}{} + if strings.HasPrefix(fd, "socket:") { + nsprocfsock[fd] = struct{}{} + } + } + } + resMap[metricUniqueID("fd", "OpenFd")][uint32(nslogic.GetNetns())] = uint64(len(nsprocfd)) + resMap[metricUniqueID("fd", "OpenSocket")][uint32(nslogic.GetNetns())] = uint64(len(nsprocfsock)) + } + return resMap, nil +} + +func getProcessFdStat(pid int) (map[string]struct{}, error) { + fdpath := fmt.Sprintf("/proc/%d/fd", pid) + d, err := os.Open(fdpath) + if err != nil { + return nil, err + } + defer d.Close() + + names, err := d.Readdirnames(-1) + if err != nil { + return nil, fmt.Errorf("could not read %q: %w", d.Name(), err) + } + + fds := map[string]struct{}{} + for _, name := range names { + fdlink := fmt.Sprintf("%s/%s", fdpath, name) + info, err := os.Readlink(fdlink) + if os.IsNotExist(err) { + continue + } + fds[info] = struct{}{} + } + + return fds, nil +}