Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Feat: Kubernetes Metrics #141

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
12 changes: 7 additions & 5 deletions cmd/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ var containerCmd = &cobra.Command{
Aliases: []string{"containers", "docker"},
RunE: func(cmd *cobra.Command, args []string) error {
// validate args and extract flags.
containerCmd, err := constructContainerCommand(cmd, args)
containerCmd, err := constructKubernetesCommand(cmd, args)
if err != nil {
return err
}
Expand Down Expand Up @@ -79,13 +79,15 @@ var containerCmd = &cobra.Command{
},
}

type containerCommand struct {
type kubernetesCommand struct {
refreshRate uint64
cid string
all bool
}

func constructContainerCommand(cmd *cobra.Command, args []string) (*containerCommand, error) {
func constructKubernetesCommand(cmd *cobra.Command, args []string) (*kubernetesCommand, error) {
// This is the individual command parsing logic
// TODO leave this untouched as of now, since we'll be using defaults
if len(args) > 0 {
return nil, fmt.Errorf("the container command should have no arguments, see grofer container --help for further info")
}
Expand All @@ -108,7 +110,7 @@ func constructContainerCommand(cmd *cobra.Command, args []string) (*containerCom
return nil, errors.New("invalid refresh rate: minimum refresh rate is 1000(ms)")
}

containerCmd := &containerCommand{
containerCmd := &kubernetesCommand{
refreshRate: containerRefreshRate,
cid: cid,
all: allFlag,
Expand All @@ -117,7 +119,7 @@ func constructContainerCommand(cmd *cobra.Command, args []string) (*containerCom
return containerCmd, nil
}

func (cc *containerCommand) isPerContainer() bool {
func (cc *kubernetesCommand) isPerContainer() bool {
return cc.cid != defaultCid
}

Expand Down
134 changes: 134 additions & 0 deletions cmd/kuberenetes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package cmd

import (
"errors"
"fmt"
"log"

"github.com/pesos/grofer/pkg/core"
"github.com/pesos/grofer/pkg/metrics/factory"
"github.com/pesos/grofer/pkg/utils"
"github.com/spf13/cobra"
)

const (
defaultCid = ""
defaultContainerRefreshRate = 1000
)

// k8sCmd represents the kubernetes command
var k8sCmd = &cobra.Command{
Use: "kubernetes",
Short: "kubernetes command is used to get information related to the local kubernetes cluster",
Long: `kubernetes command is used to get information related to the local kubernetes cluster. It provides both overall and per pod metrics.`,
Aliases: []string{"k8s", "kubernetes"},
RunE: func(cmd *cobra.Command, args []string) error {

// validate args and extract flags.
containerCmd, err := constructContainerCommand(cmd, args)
if err != nil {
return err
}

// create a metric scraper factory that will help construct
// a container metric specific MetricScraper.
metricScraperFactory := factory.
NewMetricScraperFactory().
ForCommand(core.KubernetesCommand).
WithScrapeInterval(containerCmd.refreshRate)

/*
if containerCmd.isPerContainer() {
metricScraperFactory = metricScraperFactory.ForSingularEntity(containerCmd.cid)
}
*/

// construct a container specific MetricScraper.
kubeMetricScraper, err := metricScraperFactory.Construct()
if err != nil {
return err
}

if containerCmd.all {
err = kubeMetricScraper.Serve(factory.WithAllAs(containerCmd.all))
} else {
err = kubeMetricScraper.Serve()
}

if err != nil && err != core.ErrCanceledByUser {
if err == core.ErrInvalidContainer {
utils.ErrorMsg("cid")
}
log.Printf("Error: %v\n", err)
}

return nil
},
}

type k8sCommand struct {
refreshRate uint64
cid string
all bool
}

func constructK8sCommand(cmd *cobra.Command, args []string) (*containerCommand, error) {
if len(args) > 0 {
return nil, fmt.Errorf("the container command should have no arguments, see grofer container --help for further info")
}
cid, err := cmd.Flags().GetString("container-id")
if err != nil {
return nil, errors.New("error extracting flag --container-id")
}

allFlag, err := cmd.Flags().GetBool("all")
if err != nil {
return nil, errors.New("error extracting flag --all")
}

containerRefreshRate, err := cmd.Flags().GetUint64("refresh")
if err != nil {
return nil, errors.New("error extracting flag --refresh")
}

if containerRefreshRate < 1000 {
return nil, errors.New("invalid refresh rate: minimum refresh rate is 1000(ms)")
}

containerCmd := &containerCommand{
refreshRate: containerRefreshRate,
cid: cid,
all: allFlag,
}

return containerCmd, nil
}

func (cc *containerCommand) isPerContainer() bool {
return cc.cid != defaultCid
}

func init() {
rootCmd.AddCommand(containerCmd)

containerCmd.Flags().StringP(
"container-id",
"c",
"",
"specify container ID",
)

containerCmd.Flags().Uint64P(
"refresh",
"r",
defaultContainerRefreshRate,
"Container information UI refreshes rate in milliseconds greater than 1000",
)

containerCmd.Flags().BoolP(
"all",
"a",
false,
"Specify to list all containers or only running containers.",
)
}
2 changes: 2 additions & 0 deletions pkg/core/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ const (
ContainerCommand
// ExportCommand is `grofer export` and its variants.
ExportCommand
// KubernetesCommand is `grofer kube` and its variants.
KubernetesCommand
)

// Sink represents any entity that consumes generated metrics.
Expand Down
42 changes: 42 additions & 0 deletions pkg/metrics/factory/metrics_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,15 @@ import (

proc "github.com/shirou/gopsutil/process"

// TODO add client-go import
"github.com/docker/docker/client"
"github.com/pesos/grofer/pkg/core"
"github.com/pesos/grofer/pkg/metrics/container"
kubernetes_metrics "github.com/pesos/grofer/pkg/metrics/kubernetes"
"github.com/pesos/grofer/pkg/metrics/process"

"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)

// MetricScraperFactory constructs a MetricScaper for a command
Expand Down Expand Up @@ -84,10 +89,46 @@ func (msf *MetricScraperFactory) Construct() (MetricScraper, error) {
return msf.constructContainerMetricScraper()
case core.ProcCommand:
return msf.constructProcessMetricScraper()
case core.KubernetesCommand:
return msf.constructKubernetesMetricScraper()
}
return nil, errors.New("command not recognized")
}

func (msf* MetricScraperFactory) constructKubernetesMetricScraper() (MetricScraper, error) {
// TODO Construct a new metrics scraper here
return msf.newKubernetesMetrics()
}

func (msf* MetricScraperFactory) newKubernetesMetrics() (*kubernetes_metrics.KubernetesMetricsScraper, error) {

var kubeconfig string

// Default kubeconfig in ~/.kube/config
// TODO take this in as a CLI argument, and use homedir to find it
kubeconfig = "~/.kube/config"
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
// TODO propagate error
panic(err.Error())
}
// create the clientset
// TODO pass the clientset into the struct here
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err.Error())
}

// construct the actual metrics struct here
kubeMetricsStruct := &kubernetes_metrics.KubernetesMetricsScraper{
Clientset: clientset,
RefreshRate: msf.scrapeIntervalMillisecond,
MetricBus: make(chan kubernetes_metrics.KubernetesMetrics),
}

return kubeMetricsStruct, nil
}

func (msf *MetricScraperFactory) constructSystemWideMetricScraper() (MetricScraper, error) {
return &systemWideMetrics{
refreshRate: msf.scrapeIntervalMillisecond,
Expand Down Expand Up @@ -159,3 +200,4 @@ func (msf *MetricScraperFactory) newSingluarProcessMetrics() (*singularProcessMe

return spm, nil
}

34 changes: 34 additions & 0 deletions pkg/metrics/kubernetes/kubernetes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package kubernetes_metrics

// TODO import client-go
import (
"github.com/pesos/grofer/pkg/core"
)

// TODO define k8s-specific metrics structs
/*
type containerMetrics struct {
client *client.Client
all bool
refreshRate uint64
sink core.Sink // defaults to TUI.
metricBus chan container.OverallMetrics
}
*/

type Pod struct {
name string
}

type KubernetesMetricsScraper struct {
// TODO reference to initialized client-go goes here.
Clientset string
RefreshRate uint64
Sink core.Sink
MetricBus chan KubernetesMetrics
}

// TODO add more data here!
type KubernetesMetrics struct {
pods []string
}