From 7c42edb9125af9e3c3ee43b9509271c6843a7c31 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 2 Mar 2019 17:27:04 +0100 Subject: [PATCH] Use prometheus collector interface (bigger refactoring) --- Dockerfile | 6 +- README.md | 11 +- collector/collector.go | 111 +++++++++++++++ collector/container_resources.go | 197 +++++++++++++++++++++++++++ collector/node_resources.go | 223 +++++++++++++++++++++++++++++++ go.mod | 41 +++--- go.sum | 157 +++++++++++++++++----- kubernetes/client.go | 140 +++++++++++++++++++ main.go | 51 ++++--- options/options.go | 28 ++++ pkg/collectors/container.go | 114 ---------------- pkg/collectors/node.go | 135 ------------------- pkg/log/log.go | 42 ------ pkg/options/options.go | 17 --- pkg/sink/container.go | 84 ------------ pkg/sink/node.go | 68 ---------- pkg/sink/provider.go | 164 ----------------------- 17 files changed, 885 insertions(+), 704 deletions(-) create mode 100644 collector/collector.go create mode 100644 collector/container_resources.go create mode 100644 collector/node_resources.go create mode 100644 kubernetes/client.go create mode 100644 options/options.go delete mode 100644 pkg/collectors/container.go delete mode 100644 pkg/collectors/node.go delete mode 100644 pkg/log/log.go delete mode 100644 pkg/options/options.go delete mode 100644 pkg/sink/container.go delete mode 100644 pkg/sink/node.go delete mode 100644 pkg/sink/provider.go diff --git a/Dockerfile b/Dockerfile index 0c4f030..9798cac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,15 @@ # build image -FROM golang:1.11-alpine as builder +FROM golang:1.12-alpine as builder RUN apk update && apk add git ca-certificates WORKDIR /app COPY . . -RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -installsuffix cgo -o /go/bin/kube-eagle +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -o /go/bin/kube-eagle # executable image FROM scratch COPY --from=builder /go/bin/kube-eagle /go/bin/kube-eagle -ENV VERSION 1.0.3 +ENV VERSION 1.1.0 ENTRYPOINT ["/go/bin/kube-eagle"] diff --git a/README.md b/README.md index e77d4e1..e3abecd 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,13 @@ Make sure the pod has a service account attached that has the required permissio ### Environment variables -| Variable name | Description | Default | -| ------------- | ------------------------------------------------------------------------------------- | ------- | -| PORT | Port to listen on for the prometheus exporter | 8080 | -| IS_IN_CLUSTER | Whether to use in cluster communication or to look for a kubeconfig in home directory | true | +| Variable name | Description | Default | +| ----------------- | ------------------------------------------------------------------------------------- | ------- | +| TELEMETRY_HOST | Host to bind socket on for the prometheus exporter | 0.0.0.0 | +| TELEMETRY_PORT | Port to listen on for the prometheus exporter | 8080 | +| METRICS_NAMESPACE | Prefix of exposed prometheus metrics | eagle | +| IS_IN_CLUSTER | Whether to use in cluster communication or to look for a kubeconfig in home directory | true | +| LOG_LEVEL | Logger's log granularity (debug, info, warn, error, fatal, panic) | info | ### Grafana dashboard diff --git a/collector/collector.go b/collector/collector.go new file mode 100644 index 0000000..b6f52ad --- /dev/null +++ b/collector/collector.go @@ -0,0 +1,111 @@ +package collector + +import ( + "fmt" + "github.com/google-cloud-tools/kube-eagle/kubernetes" + "github.com/google-cloud-tools/kube-eagle/options" + "github.com/prometheus/client_golang/prometheus" + log "github.com/sirupsen/logrus" + "sync" + "time" +) + +type collectorFactoryFunc = func(opts *options.Options) (Collector, error) + +var ( + kubernetesClient *kubernetes.Client + scrapeDurationDesc *prometheus.Desc + scrapeSuccessDesc *prometheus.Desc + factoriesByCollectorName = make(map[string]collectorFactoryFunc) +) + +// registerCollector adds a collector to the registry so that it's Update() method will be called every time +// the metrics endpoint is triggered +func registerCollector(collectorName string, collectorFactory collectorFactoryFunc) { + log.Debugf("Registering collector '%s'", collectorName) + factoriesByCollectorName[collectorName] = collectorFactory +} + +// KubeEagleCollector implements the prometheus collector interface +type KubeEagleCollector struct { + CollectorByName map[string]Collector +} + +// NewKubeEagleCollector creates a new KubeEagle collector which can be considered as manager of multiple collectors +func NewKubeEagleCollector(opts *options.Options) (*KubeEagleCollector, error) { + // Create registered collectors by executing it's collector factory function + collectorByName := make(map[string]Collector) + for collectorName, factory := range factoriesByCollectorName { + log.Debugf("Creating collector '%s'", collectorName) + collector, err := factory(opts) + if err != nil { + return nil, fmt.Errorf("failed to create collector '%s': '%s'", collectorName, err) + } + collectorByName[collectorName] = collector + } + + var err error + kubernetesClient, err = kubernetes.NewClient(opts) + if err != nil { + return nil, fmt.Errorf("failed to initialize kubernetes client: '%v'", err) + } + + scrapeDurationDesc = prometheus.NewDesc( + prometheus.BuildFQName(opts.Namespace, "scrape", "collector_duration_seconds"), + "Kube Eagle: Duration of a collector scrape.", + []string{"collector"}, + nil, + ) + scrapeSuccessDesc = prometheus.NewDesc( + prometheus.BuildFQName(opts.Namespace, "scrape", "collector_success"), + "Kube Eagle: Whether a collector succeeded.", + []string{"collector"}, + nil, + ) + + return &KubeEagleCollector{CollectorByName: collectorByName}, nil +} + +// Describe implements the prometheus.Collector interface +func (k KubeEagleCollector) Describe(ch chan<- *prometheus.Desc) { + ch <- scrapeDurationDesc + ch <- scrapeSuccessDesc +} + +// Collect implements the prometheus.Collector interface +func (k KubeEagleCollector) Collect(ch chan<- prometheus.Metric) { + wg := sync.WaitGroup{} + + // Run all collectors concurrently and add meta information about that (such as request duration and error/success count) + for name, collector := range k.CollectorByName { + wg.Add(1) + go func(wg *sync.WaitGroup, collectorName string, c Collector) { + defer wg.Done() + begin := time.Now() + err := c.updateMetrics(ch) + duration := time.Since(begin) + + var isSuccess float64 + if err != nil { + log.Errorf("Collector '%s' failed after %fs: %s", collectorName, duration.Seconds(), err) + isSuccess = 0 + } else { + log.Debugf("Collector '%s' succeeded after %fs.", collectorName, duration.Seconds()) + isSuccess = 1 + } + ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, duration.Seconds(), collectorName) + ch <- prometheus.MustNewConstMetric(scrapeSuccessDesc, prometheus.GaugeValue, isSuccess, collectorName) + }(&wg, name, collector) + } + wg.Wait() +} + +// IsHealthy returns a bool which indicates whether the collector is working properly or not +func (k KubeEagleCollector) IsHealthy() bool { + return kubernetesClient.IsHealthy() +} + +// Collector is an interface which has to be implemented for each collector which wants to expose metrics +type Collector interface { + updateMetrics(ch chan<- prometheus.Metric) error +} diff --git a/collector/container_resources.go b/collector/container_resources.go new file mode 100644 index 0000000..93573a8 --- /dev/null +++ b/collector/container_resources.go @@ -0,0 +1,197 @@ +package collector + +import ( + "github.com/google-cloud-tools/kube-eagle/options" + "github.com/prometheus/client_golang/prometheus" + log "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + v1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" + "sync" +) + +type containerResourcesCollector struct { + // Resource limits + limitCPUCoresDesc *prometheus.Desc + limitMemoryBytesDesc *prometheus.Desc + + // Resource requests + requestCPUCoresDesc *prometheus.Desc + requestMemoryBytesDesc *prometheus.Desc + + // Resource usage + usageCPUCoresDesc *prometheus.Desc + usageMemoryBytesDesc *prometheus.Desc +} + +func init() { + registerCollector("container_resources", newContainerResourcesCollector) +} + +func newContainerResourcesCollector(opts *options.Options) (Collector, error) { + subsystem := "pod_container_resource" + labels := []string{"pod", "container", "qos", "phase", "namespace"} + + return &containerResourcesCollector{ + // Prometheus metrics + // Resource limits + limitCPUCoresDesc: prometheus.NewDesc( + prometheus.BuildFQName(opts.Namespace, subsystem, "limits_cpu_cores"), + "The container's CPU limit in Kubernetes", + labels, + prometheus.Labels{}, + ), + limitMemoryBytesDesc: prometheus.NewDesc( + prometheus.BuildFQName(opts.Namespace, subsystem, "limits_memory_bytes"), + "The container's RAM limit in Kubernetes", + labels, + prometheus.Labels{}, + ), + // Resource requests + requestCPUCoresDesc: prometheus.NewDesc( + prometheus.BuildFQName(opts.Namespace, subsystem, "requests_cpu_cores"), + "The container's requested CPU resources in Kubernetes", + labels, + prometheus.Labels{}, + ), + requestMemoryBytesDesc: prometheus.NewDesc( + prometheus.BuildFQName(opts.Namespace, subsystem, "requests_memory_bytes"), + "The container's requested RAM resources in Kubernetes", + labels, + prometheus.Labels{}, + ), + // Resource usage + usageCPUCoresDesc: prometheus.NewDesc( + prometheus.BuildFQName(opts.Namespace, subsystem, "usage_cpu_cores"), + "CPU usage in number of cores", + labels, + prometheus.Labels{}, + ), + usageMemoryBytesDesc: prometheus.NewDesc( + prometheus.BuildFQName(opts.Namespace, subsystem, "usage_memory_bytes"), + "RAM usage in bytes", + labels, + prometheus.Labels{}, + ), + }, nil +} + +func (c *containerResourcesCollector) updateMetrics(ch chan<- prometheus.Metric) error { + log.Debug("Collecting container metrics") + + var wg sync.WaitGroup + var podList *corev1.PodList + var podListError error + var podMetricses *v1beta1.PodMetricsList + var podMetricsesError error + + // Get pod list + wg.Add(1) + go func() { + defer wg.Done() + podList, podListError = kubernetesClient.PodList() + }() + + // Get node resource usage metrics + wg.Add(1) + go func() { + defer wg.Done() + podMetricses, podMetricsesError = kubernetesClient.PodMetricses() + }() + + wg.Wait() + if podListError != nil { + log.Warn("Failed to get podList from Kubernetes", podListError) + return podListError + } + if podMetricsesError != nil { + log.Warn("Failed to get podMetricses from Kubernetes", podMetricsesError) + return podMetricsesError + } + + containerMetricses := buildEnrichedContainerMetricses(podList, podMetricses) + + for _, containerMetrics := range containerMetricses { + cm := *containerMetrics + log.Debugf("Test") + labelValues := []string{cm.Pod, cm.Container, cm.Qos, cm.Phase, cm.Namespace} + ch <- prometheus.MustNewConstMetric(c.requestCPUCoresDesc, prometheus.GaugeValue, cm.RequestCPUCores, labelValues...) + ch <- prometheus.MustNewConstMetric(c.requestMemoryBytesDesc, prometheus.GaugeValue, cm.RequestMemoryBytes, labelValues...) + ch <- prometheus.MustNewConstMetric(c.limitCPUCoresDesc, prometheus.GaugeValue, cm.LimitCPUCores, labelValues...) + ch <- prometheus.MustNewConstMetric(c.limitMemoryBytesDesc, prometheus.GaugeValue, cm.LimitMemoryBytes, labelValues...) + ch <- prometheus.MustNewConstMetric(c.usageCPUCoresDesc, prometheus.GaugeValue, cm.UsageCPUCores, labelValues...) + ch <- prometheus.MustNewConstMetric(c.usageMemoryBytesDesc, prometheus.GaugeValue, cm.UsageMemoryBytes, labelValues...) + } + + return nil +} + +type enrichedContainerMetricses struct { + Node string + Pod string + Container string + Qos string + Phase string + Namespace string + RequestCPUCores float64 + RequestMemoryBytes float64 + LimitCPUCores float64 + LimitMemoryBytes float64 + UsageCPUCores float64 + UsageMemoryBytes float64 +} + +// buildEnrichedContainerMetricses merges the container metrics from two requests (podList request and podMetrics request) into +// one, so that we can expose valuable metadata (such as a nodename) as prometheus labels which is just present +// in one of the both responses. +func buildEnrichedContainerMetricses(podList *corev1.PodList, podMetricses *v1beta1.PodMetricsList) []*enrichedContainerMetricses { + // Group container metricses by pod name + containerMetricsesByPod := make(map[string]map[string]v1beta1.ContainerMetrics) + for _, pm := range podMetricses.Items { + containerMetricses := make(map[string]v1beta1.ContainerMetrics) + for _, c := range pm.Containers { + containerMetricses[c.Name] = c + } + containerMetricsesByPod[pm.Name] = containerMetricses + } + + var containerMetricses []*enrichedContainerMetricses + for _, podInfo := range podList.Items { + containers := append(podInfo.Spec.Containers, podInfo.Spec.InitContainers...) + + for _, containerInfo := range containers { + qos := string(podInfo.Status.QOSClass) + + // Resources requested + requestCPUCores := float64(containerInfo.Resources.Requests.Cpu().MilliValue()) / 1000 + requestMemoryBytes := float64(containerInfo.Resources.Requests.Memory().MilliValue()) / 1000 + + // Resources limit + limitCPUCores := float64(containerInfo.Resources.Limits.Cpu().MilliValue()) / 1000 + limitMemoryBytes := float64(containerInfo.Resources.Limits.Memory().MilliValue()) / 1000 + + // Resources usage + containerUsageMetrics := containerMetricsesByPod[podInfo.Name][containerInfo.Name] + usageCPUCores := float64(containerUsageMetrics.Usage.Cpu().MilliValue()) / 1000 + usageMemoryBytes := float64(containerUsageMetrics.Usage.Memory().MilliValue()) / 1000 + + nodeName := podInfo.Spec.NodeName + metric := &enrichedContainerMetricses{ + Node: nodeName, + Container: containerInfo.Name, + Pod: podInfo.Name, + Qos: qos, + Phase: string(podInfo.Status.Phase), + Namespace: podInfo.Namespace, + RequestCPUCores: requestCPUCores, + RequestMemoryBytes: requestMemoryBytes, + LimitCPUCores: limitCPUCores, + LimitMemoryBytes: limitMemoryBytes, + UsageCPUCores: usageCPUCores, + UsageMemoryBytes: usageMemoryBytes, + } + containerMetricses = append(containerMetricses, metric) + } + } + + return containerMetricses +} diff --git a/collector/node_resources.go b/collector/node_resources.go new file mode 100644 index 0000000..130c633 --- /dev/null +++ b/collector/node_resources.go @@ -0,0 +1,223 @@ +package collector + +import ( + "github.com/google-cloud-tools/kube-eagle/kubernetes" + "github.com/google-cloud-tools/kube-eagle/options" + "github.com/prometheus/client_golang/prometheus" + log "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + v1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" + "sync" +) + +type nodeResourcesCollector struct { + kubernetesClient *kubernetes.Client + + // Allocatable + allocatableCPUCoresDesc *prometheus.Desc + allocatableMemoryBytesDesc *prometheus.Desc + + // Resource limits + limitCPUCoresDesc *prometheus.Desc + limitMemoryBytesDesc *prometheus.Desc + + // Resource requests + requestCPUCoresDesc *prometheus.Desc + requestMemoryBytesDesc *prometheus.Desc + + // Resource usage + usageCPUCoresDesc *prometheus.Desc + usageMemoryBytesDesc *prometheus.Desc +} + +func init() { + registerCollector("node_resource", newNodeResourcesCollector) +} + +func newNodeResourcesCollector(opts *options.Options) (Collector, error) { + subsystem := "node_resource" + labels := []string{"node"} + + return &nodeResourcesCollector{ + // Prometheus metrics + // Allocatable + allocatableCPUCoresDesc: prometheus.NewDesc( + prometheus.BuildFQName(opts.Namespace, subsystem, "allocatable_cpu_cores"), + "Allocatable CPU cores on a specific node in Kubernetes", + labels, + prometheus.Labels{}, + ), + allocatableMemoryBytesDesc: prometheus.NewDesc( + prometheus.BuildFQName(opts.Namespace, subsystem, "allocatable_memory_bytes"), + "Allocatable memory bytes on a specific node in Kubernetes", + labels, + prometheus.Labels{}, + ), + // Resource limits + limitCPUCoresDesc: prometheus.NewDesc( + prometheus.BuildFQName(opts.Namespace, subsystem, "limits_cpu_cores"), + "Total limit CPU cores of all specified pod resources on a node", + labels, + prometheus.Labels{}, + ), + limitMemoryBytesDesc: prometheus.NewDesc( + prometheus.BuildFQName(opts.Namespace, subsystem, "limits_memory_bytes"), + "Total limit of RAM bytes of all specified pod resources on a node", + labels, + prometheus.Labels{}, + ), + // Resource requests + requestCPUCoresDesc: prometheus.NewDesc( + prometheus.BuildFQName(opts.Namespace, subsystem, "requests_cpu_cores"), + "Total request of CPU cores of all specified pod resources on a node", + labels, + prometheus.Labels{}, + ), + requestMemoryBytesDesc: prometheus.NewDesc( + prometheus.BuildFQName(opts.Namespace, subsystem, "requests_memory_bytes"), + "Total request of RAM bytes of all specified pod resources on a node", + labels, + prometheus.Labels{}, + ), + // Resource usage + usageCPUCoresDesc: prometheus.NewDesc( + prometheus.BuildFQName(opts.Namespace, subsystem, "usage_cpu_cores"), + "Total number of used CPU cores on a node", + labels, + prometheus.Labels{}, + ), + usageMemoryBytesDesc: prometheus.NewDesc( + prometheus.BuildFQName(opts.Namespace, subsystem, "usage_memory_bytes"), + "Total number of RAM bytes used on a node", + labels, + prometheus.Labels{}, + ), + }, nil +} + +func (c *nodeResourcesCollector) updateMetrics(ch chan<- prometheus.Metric) error { + log.Debug("Collecting node metrics") + + var wg sync.WaitGroup + var nodeList *corev1.NodeList + var nodeListError error + var podList *corev1.PodList + var podListError error + var nodeMetricsList *v1beta1.NodeMetricsList + var nodeMetricsListError error + + // Get pod list + wg.Add(1) + go func() { + defer wg.Done() + podList, podListError = kubernetesClient.PodList() + }() + + // Get node list + wg.Add(1) + go func() { + defer wg.Done() + nodeList, nodeListError = kubernetesClient.NodeList() + }() + + // Get node resource usage metrics + wg.Add(1) + go func() { + defer wg.Done() + nodeMetricsList, nodeMetricsListError = kubernetesClient.NodeMetricses() + }() + + wg.Wait() + if podListError != nil { + log.Warn("Failed to get podList from Kubernetes", podListError) + return podListError + } + if nodeListError != nil { + log.Warn("Failed to get nodeList from Kubernetes", nodeListError) + return nodeListError + } + if nodeMetricsListError != nil { + log.Warn("Failed to get podList from Kubernetes", nodeMetricsListError) + return nodeMetricsListError + } + nodeMetricsByNodeName := getNodeMetricsByNodeName(nodeMetricsList) + podMetricsByNodeName := getAggregatedPodMetricsByNodeName(podList) + + for _, n := range nodeList.Items { + // allocatable + allocatableCPU := n.Status.Allocatable.Cpu().Value() + allocatableMemoryBytes := float64(n.Status.Allocatable.Memory().MilliValue()) / 1000 + ch <- prometheus.MustNewConstMetric(c.allocatableCPUCoresDesc, prometheus.GaugeValue, float64(allocatableCPU), n.Name) + ch <- prometheus.MustNewConstMetric(c.allocatableMemoryBytesDesc, prometheus.GaugeValue, float64(allocatableMemoryBytes), n.Name) + + // resource usage + usageMetrics := nodeMetricsByNodeName[n.Name] + usageCPU := float64(usageMetrics.Usage.Cpu().MilliValue()) / 1000 + usageMemoryBytes := float64(usageMetrics.Usage.Memory().MilliValue()) / 1000 + ch <- prometheus.MustNewConstMetric(c.usageCPUCoresDesc, prometheus.GaugeValue, float64(usageCPU), n.Name) + ch <- prometheus.MustNewConstMetric(c.usageMemoryBytesDesc, prometheus.GaugeValue, float64(usageMemoryBytes), n.Name) + + // aggregated pod metrics (e. g. resource requests by node) + podMetrics := podMetricsByNodeName[n.Name] + ch <- prometheus.MustNewConstMetric(c.requestCPUCoresDesc, prometheus.GaugeValue, podMetrics.requestedCPUCores, n.Name) + ch <- prometheus.MustNewConstMetric(c.requestMemoryBytesDesc, prometheus.GaugeValue, float64(podMetrics.requestedMemoryBytes), n.Name) + ch <- prometheus.MustNewConstMetric(c.limitCPUCoresDesc, prometheus.GaugeValue, podMetrics.limitCPUCores, n.Name) + ch <- prometheus.MustNewConstMetric(c.limitMemoryBytesDesc, prometheus.GaugeValue, float64(podMetrics.limitMemoryBytes), n.Name) + } + + return nil +} + +// getNodeMetricsByNodeName returns a map of node metrics where the keys are the particular node names +func getNodeMetricsByNodeName(nodeMetricsList *v1beta1.NodeMetricsList) map[string]v1beta1.NodeMetrics { + nodeMetricsByName := make(map[string]v1beta1.NodeMetrics) + for _, metrics := range nodeMetricsList.Items { + nodeMetricsByName[metrics.Name] = metrics + } + + return nodeMetricsByName +} + +type aggregatedPodMetrics struct { + podCount uint16 + containerCount uint16 + requestedMemoryBytes int64 + requestedCPUCores float64 + limitMemoryBytes int64 + limitCPUCores float64 +} + +// getAggregatedPodMetricsByNodeName returns a map of aggregated pod metrics grouped by node name. +func getAggregatedPodMetricsByNodeName(pods *corev1.PodList) map[string]aggregatedPodMetrics { + podMetrics := make(map[string]aggregatedPodMetrics) + + // Iterate through all pod definitions to sum and group pods' resource requests and limits by node name + for _, podInfo := range pods.Items { + nodeName := podInfo.Spec.NodeName + podCount := podMetrics[nodeName].podCount + 1 + + // skip not running pods (e. g. failed/succeeded jobs, evicted pods etc.) + podPhase := podInfo.Status.Phase + if podPhase == corev1.PodFailed || podPhase == corev1.PodSucceeded { + continue + } + + for _, c := range podInfo.Spec.Containers { + requestedCPUCores := float64(c.Resources.Requests.Cpu().MilliValue()) / 1000 + requestedMemoryBytes := c.Resources.Requests.Memory().MilliValue() / 1000 + limitCPUCores := float64(c.Resources.Limits.Cpu().MilliValue()) / 1000 + limitMemoryBytes := c.Resources.Limits.Memory().MilliValue() / 1000 + + podMetrics[nodeName] = aggregatedPodMetrics{ + podCount: podCount, + containerCount: podMetrics[nodeName].containerCount + 1, + requestedCPUCores: podMetrics[nodeName].requestedCPUCores + requestedCPUCores, + requestedMemoryBytes: podMetrics[nodeName].requestedMemoryBytes + requestedMemoryBytes, + limitCPUCores: podMetrics[nodeName].limitCPUCores + limitCPUCores, + limitMemoryBytes: podMetrics[nodeName].limitMemoryBytes + limitMemoryBytes, + } + } + } + + return podMetrics +} diff --git a/go.mod b/go.mod index 92ff450..5d97f0d 100644 --- a/go.mod +++ b/go.mod @@ -1,38 +1,37 @@ module github.com/google-cloud-tools/kube-eagle require ( - cloud.google.com/go v0.33.1 // indirect - github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect - github.com/gogo/protobuf v1.1.1 // indirect - github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect - github.com/golang/protobuf v1.2.0 // indirect + contrib.go.opencensus.io/exporter/ocagent v0.4.5 // indirect + github.com/Azure/go-autorest v11.4.0+incompatible // indirect + github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect + github.com/evanphx/json-patch v4.1.0+incompatible // indirect + github.com/gogo/protobuf v1.2.0 // indirect github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c // indirect github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect github.com/googleapis/gnostic v0.2.0 // indirect - github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f // indirect - github.com/imdario/mergo v0.3.6 // indirect + github.com/gophercloud/gophercloud v0.0.0-20190216224116-dcc6e84aef1b // indirect + github.com/gregjones/httpcache v0.0.0-20190203031600-7a902570cb17 // indirect + github.com/hashicorp/golang-lru v0.5.0 // indirect + github.com/imdario/mergo v0.3.7 // indirect github.com/json-iterator/go v1.1.5 // indirect github.com/kelseyhightower/envconfig v1.3.0 - github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect - github.com/prometheus/client_golang v0.9.1 - github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 // indirect - github.com/prometheus/common v0.0.0-20181116084131-1f2c4f3cd6db // indirect - github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d // indirect - github.com/sirupsen/logrus v1.2.0 + github.com/prometheus/client_golang v0.9.2 + github.com/sirupsen/logrus v1.3.0 github.com/spf13/pflag v1.0.3 // indirect - golang.org/x/net v0.0.0-20181114220301-adae6a3d119a // indirect - golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288 // indirect - golang.org/x/text v0.3.0 // indirect + go.opencensus.io v0.19.0 // indirect + golang.org/x/oauth2 v0.0.0-20190130055435-99b60b757ec1 // indirect + golang.org/x/sys v0.0.0-20190219203350-90b0e4468f99 // indirect golang.org/x/time v0.0.0-20181108054448-85acf8d2951c // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v2 v2.2.1 // indirect - k8s.io/api v0.0.0-20181117111259-46ad728b8d13 - k8s.io/apimachinery v0.0.0-20181116115711-1b0702fe2927 - k8s.io/client-go v9.0.0+incompatible + k8s.io/api v0.0.0-20190202010724-74b699b93c15 + k8s.io/apimachinery v0.0.0-20190117220443-572dfc7bdfcb + k8s.io/client-go v10.0.0+incompatible k8s.io/klog v0.1.0 // indirect - k8s.io/metrics v0.0.0-20181116232942-4773e9d50c6b + k8s.io/kube-openapi v0.0.0-20190215190454-ea82251f3668 // indirect + k8s.io/metrics v0.0.0-20190208173709-1733093e277b + sigs.k8s.io/structured-merge-diff v0.0.0-20190130003954-e5e029740eb8 // indirect sigs.k8s.io/yaml v1.1.0 // indirect ) diff --git a/go.sum b/go.sum index c62dd48..9eae0fd 100644 --- a/go.sum +++ b/go.sum @@ -1,28 +1,56 @@ -cloud.google.com/go v0.33.1 h1:fmJQWZ1w9PGkHR1YL/P7HloDvqlmKQ4Vpb7PC2e+aCk= -cloud.google.com/go v0.33.1/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +contrib.go.opencensus.io/exporter/ocagent v0.4.5 h1:LYJWmgJ7I5t2aO6DkHQK9EEs0PGqNyrADaGAS+Rudmw= +contrib.go.opencensus.io/exporter/ocagent v0.4.5/go.mod h1:YuG83h+XWwqWjvCqn7vK4KSyLKhThY3+gNGQ37iS2V0= +git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +git.apache.org/thrift.git v0.0.0-20181218151757-9b75e4fe745a/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +github.com/Azure/go-autorest v11.4.0+incompatible h1:z3Yr6KYqs0nhSNwqGXEBpWK977hxVqsLv2n9PVYcixY= +github.com/Azure/go-autorest v11.4.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/census-instrumentation/opencensus-proto v0.1.0-0.20181214143942-ba49f56771b8 h1:gUqsFVdUKoRHNg8fkFd8gB5OOEa/g5EwlAHznb4zjbI= +github.com/census-instrumentation/opencensus-proto v0.1.0-0.20181214143942-ba49f56771b8/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/evanphx/json-patch v4.1.0+incompatible h1:K1MDoo4AZ4wU0GIU/fPmtZg7VpzLjCxu+UwBD1FvwOc= +github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g= github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f h1:ShTPMJQes6tubcjzGMODIVG5hlrCeImaBnZzKF2N8SM= -github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= -github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/gophercloud/gophercloud v0.0.0-20190216224116-dcc6e84aef1b h1:bmCatXrA2OlbroyOV5gfpvtrgFV9H3lqr75lomgnusA= +github.com/gophercloud/gophercloud v0.0.0-20190216224116-dcc6e84aef1b/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gregjones/httpcache v0.0.0-20190203031600-7a902570cb17 h1:prg2TTpTOcJF1jRWL2zSU1FQNgB0STAFNux8GK82y8k= +github.com/gregjones/httpcache v0.0.0-20190203031600-7a902570cb17/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/grpc-ecosystem/grpc-gateway v1.6.2 h1:8KyC64BiO8ndiGHY5DlFWWdangUPC9QHPakFRre/Ud0= +github.com/grpc-ecosystem/grpc-gateway v1.6.2/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= +github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/kelseyhightower/envconfig v1.3.0 h1:IvRS4f2VcIQy6j4ORGIf9145T/AsUB+oY8LyvN8BXNM= github.com/kelseyhightower/envconfig v1.3.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -30,49 +58,118 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/openzipkin/zipkin-go v0.1.3/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1 h1:K47Rk0v/fkEfwfQet2KWhscE0cJzjgCCDBG2KHZoVno= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/common v0.0.0-20181116084131-1f2c4f3cd6db h1:ckMAAQJ96ZKwKyiGamJdsinLn3D9+daeRlvvmYo9tkI= -github.com/prometheus/common v0.0.0-20181116084131-1f2c4f3cd6db/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d h1:GoAlyOgbOEIFdaDqxJVlbOQ1DtGmZWs/Qau0hIlk+WQ= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181218105931-67670fe90761 h1:z6tvbDJ5OLJ48FFmnksv04a78maSTRBUIhkdHYV5Y98= +github.com/prometheus/common v0.0.0-20181218105931-67670fe90761/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME= +github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +go.opencensus.io v0.18.1-0.20181204023538-aab39bd6a98b/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= +go.opencensus.io v0.19.0 h1:+jrnNy8MR4GZXvwF9PEuSyHxA4NaTf6601oNRwCSXq0= +go.opencensus.io v0.19.0/go.mod h1:AYeH0+ZxYyghG8diqaaIq/9P3VgCCt5GF2ldCY4dkFg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288 h1:JIqe8uIcRBHXDQVvZtHwp80ai3Lw3IJAeJEs55Dc1W0= -golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 h1:ng3VDlRp5/DHpSWl02R4rM9I+8M2rhmsuLwAMmkLQWE= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181217023233-e147a9138326/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190130055435-99b60b757ec1 h1:VeAkjQVzKLmu+JnFcK96TPbkuaTIqwGGAzQ9hgwPjVg= +golang.org/x/oauth2 v0.0.0-20190130055435-99b60b757ec1/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181218192612-074acd46bca6 h1:MXtOG7w2ND9qNCUZSDBGll/SpVIq7ftozR9I8/JGBHY= +golang.org/x/sys v0.0.0-20181218192612-074acd46bca6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190209173611-3b5209105503 h1:5SvYFrOM3W8Mexn9/oA44Ji7vhXAZQ9hiP+1Q/DMrWg= +golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190219203350-90b0e4468f99 h1:mlL4HvR5ojTCLdWRydhoj7jto5SXLsxLc0b1r/3DNlE= +golang.org/x/sys v0.0.0-20190219203350-90b0e4468f99/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181219222714-6e267b5cc78e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20181220000619-583d854617af h1:iQMS7JKv/0w/iiWf1M49Cg3dmOkBoBZT5KheqPDpaac= +google.golang.org/api v0.0.0-20181220000619-583d854617af/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181219182458-5a97ab628bfb h1:dQshZyyJ5W/Xk8myF4GKBak1pZW6EywJuQ8+44EQhGA= +google.golang.org/genproto v0.0.0-20181219182458-5a97ab628bfb/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.15.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.17.0 h1:TRJYBgMclJvGYn2rIMjj+h9KtMt5r1Ij7ODVRIZkwhk= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -k8s.io/api v0.0.0-20181117111259-46ad728b8d13 h1:kScMdtyRni4/487ib8PTPnHNcgWWiRRH94iyicChmS0= -k8s.io/api v0.0.0-20181117111259-46ad728b8d13/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= -k8s.io/apimachinery v0.0.0-20181116115711-1b0702fe2927 h1:RkGqNDA3mKVqAQbCHoB+QeHshksgEzhAlFqY4HhlSu0= -k8s.io/apimachinery v0.0.0-20181116115711-1b0702fe2927/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= -k8s.io/client-go v9.0.0+incompatible h1:2kqW3X2xQ9SbFvWZjGEHBLlWc1LG9JIJNXWkuqwdZ3A= -k8s.io/client-go v9.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +k8s.io/api v0.0.0-20190202010724-74b699b93c15 h1:AoUGjnJ3PJMFz+Rkp4lx3X+6mPUnY1MESJhbUSGX+pc= +k8s.io/api v0.0.0-20190202010724-74b699b93c15/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= +k8s.io/api v0.0.0-20190208171344-de494049e22a h1:0XPknT3d+Jjc0SoctPzbPtudwAGI+Sx7MWxF8VRc4dw= +k8s.io/api v0.0.0-20190208171344-de494049e22a/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= +k8s.io/apimachinery v0.0.0-20190117220443-572dfc7bdfcb h1:+mNFBkhBgd0wFJ1K18cOYw3LVW7aMIM/pazb4i44aS0= +k8s.io/apimachinery v0.0.0-20190117220443-572dfc7bdfcb/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= +k8s.io/apimachinery v0.0.0-20190208171202-bf20fabcb825 h1:R1zWS5d8s8sKOtptDgydbiUJ7xifO+rH8kN0G0Qj+xc= +k8s.io/apimachinery v0.0.0-20190208171202-bf20fabcb825/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= +k8s.io/client-go v2.0.0-alpha.0.0.20190202011228-6e4752048fde+incompatible h1:xkYgpj1zwmLkSh7QASe/GUMTyjPI3hwHy7k/RcQSp2A= +k8s.io/client-go v2.0.0-alpha.0.0.20190202011228-6e4752048fde+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= +k8s.io/client-go v10.0.0+incompatible h1:F1IqCqw7oMBzDkqlcBymRq1450wD0eNqLE9jzUrIi34= +k8s.io/client-go v10.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= k8s.io/klog v0.1.0 h1:I5HMfc/DtuVaGR1KPwUrTc476K8NCqNBldC7H4dYEzk= k8s.io/klog v0.1.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/metrics v0.0.0-20181116232942-4773e9d50c6b h1:rCqo83EJX/vGZJwzXpr2KowGCIBuBoruGg+sb5sDzQQ= -k8s.io/metrics v0.0.0-20181116232942-4773e9d50c6b/go.mod h1:a25VAbm3QT3xiVl1jtoF1ueAKQM149UdZ+L93ePfV3M= +k8s.io/kube-openapi v0.0.0-20190215190454-ea82251f3668 h1:M80qeWaBNOX2Uc4plRHcb6k+3YE5VWMaJXKZo+tX9aU= +k8s.io/kube-openapi v0.0.0-20190215190454-ea82251f3668/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= +k8s.io/metrics v0.0.0-20190208173709-1733093e277b h1:+mzK6vcDbfoXdFHBbzL0irtQeSnim+fiZGrKJyNYia0= +k8s.io/metrics v0.0.0-20190208173709-1733093e277b/go.mod h1:a25VAbm3QT3xiVl1jtoF1ueAKQM149UdZ+L93ePfV3M= +sigs.k8s.io/structured-merge-diff v0.0.0-20190130003954-e5e029740eb8 h1:UBkrbecoQliUCGP3Izc0NRKu877BV6VLT3lUykRJURM= +sigs.k8s.io/structured-merge-diff v0.0.0-20190130003954-e5e029740eb8/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/kubernetes/client.go b/kubernetes/client.go new file mode 100644 index 0000000..6a7dda3 --- /dev/null +++ b/kubernetes/client.go @@ -0,0 +1,140 @@ +package kubernetes + +import ( + "fmt" + "github.com/google-cloud-tools/kube-eagle/options" + log "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + _ "k8s.io/client-go/plugin/pkg/client/auth" // Auth required for out of cluster connections + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + v1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" + metrics "k8s.io/metrics/pkg/client/clientset/versioned" + "os" + "path/filepath" +) + +// Client provides methods to get all required metrics from Kubernetes +type Client struct { + apiClient *kubernetes.Clientset + metricsClient *metrics.Clientset +} + +// NewClient creates a new client to get data from kubernetes masters +func NewClient(opts *options.Options) (*Client, error) { + // Get right config to connect to kubernetes + var config *rest.Config + if opts.IsInCluster { + log.Info("Creating InCluster config to communicate with Kubernetes master") + var err error + config, err = rest.InClusterConfig() + if err != nil { + return nil, err + } + } else { + // Try to read currently set kubernetes config from your local kube config + log.Info("Looking for Kubernetes config to communicate with Kubernetes master") + kubeConfigPath, err := getKubeConfigPath() + if err != nil { + return nil, err + } + // use the current context in kubeconfig + config, err = clientcmd.BuildConfigFromFlags("", kubeConfigPath) + if err != nil { + return nil, fmt.Errorf("read kubeconfig: %v", err) + } + } + + // We got two clients, one for the common API and one explicitly for metrics + client, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, fmt.Errorf("error creating kubernetes main client: '%v'", err) + } + + metricsClient, err := metrics.NewForConfig(config) + if err != nil { + return nil, fmt.Errorf("error creating kubernetes metrics client: '%v'", err) + } + + return &Client{ + apiClient: client, + metricsClient: metricsClient, + }, nil +} + +// NodeList returns a list of all known nodes in a kubernetes cluster +func (c *Client) NodeList() (*corev1.NodeList, error) { + nodeList, err := c.apiClient.CoreV1().Nodes().List(metav1.ListOptions{}) + if err != nil { + return nil, err + } + + return nodeList, nil +} + +// PodList returns a list of all known pods in a kubernetes cluster +func (c *Client) PodList() (*corev1.PodList, error) { + podList, err := c.apiClient.CoreV1().Pods(metav1.NamespaceAll).List(metav1.ListOptions{}) + if err != nil { + return nil, err + } + + return podList, nil +} + +// PodMetricses returns all pods' usage metrics +func (c *Client) PodMetricses() (*v1beta1.PodMetricsList, error) { + podMetricses, err := c.metricsClient.MetricsV1beta1().PodMetricses(metav1.NamespaceAll).List(metav1.ListOptions{}) + if err != nil { + return nil, err + } + + return podMetricses, nil +} + +// NodeMetricses returns all nodes' usage metrics +func (c *Client) NodeMetricses() (*v1beta1.NodeMetricsList, error) { + nodeMetricses, err := c.metricsClient.MetricsV1beta1().NodeMetricses().List(metav1.ListOptions{}) + if err != nil { + return nil, err + } + + return nodeMetricses, nil +} + +// IsHealthy returns whether the kubernetes client is able to get a list of all pods +func (c *Client) IsHealthy() bool { + _, err := c.apiClient.CoreV1().Pods(metav1.NamespaceAll).List(metav1.ListOptions{}) + if err != nil { + return false + } + + return true +} + +// getKubeConfigPath returns the filepath to the local kubeConfig file or fails if it couldn't find it +func getKubeConfigPath() (string, error) { + home := os.Getenv("HOME") + + // Mac OS + if home != "" { + configPath := filepath.Join(home, ".kubeconfig") + if _, err := os.Stat(configPath); os.IsExist(err) { + return configPath, nil + } + } + + // Windows + home = os.Getenv("USERPROFILE") + if home != "" { + configPath := filepath.Join(home, ".kube", "config") + _, err := os.Stat(configPath) + if err == nil { + return configPath, nil + } + } + + return "", fmt.Errorf("couldn't find home directory to look for the kube config") +} diff --git a/main.go b/main.go index babb572..27e930d 100644 --- a/main.go +++ b/main.go @@ -2,23 +2,22 @@ package main import ( "fmt" + "github.com/google-cloud-tools/kube-eagle/collector" + "github.com/prometheus/client_golang/prometheus" "net/http" + "os" "strconv" - "time" - "github.com/google-cloud-tools/kube-eagle/pkg/collectors" - "github.com/google-cloud-tools/kube-eagle/pkg/log" - "github.com/google-cloud-tools/kube-eagle/pkg/options" - "github.com/google-cloud-tools/kube-eagle/pkg/sink" + "github.com/google-cloud-tools/kube-eagle/options" "github.com/kelseyhightower/envconfig" "github.com/prometheus/client_golang/prometheus/promhttp" + log "github.com/sirupsen/logrus" ) -func healthcheck() http.HandlerFunc { +func healthcheck(collector *collector.KubeEagleCollector) http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Debug("Healthcheck has been called") - isKubernetesClientHealthy := sink.IsClientHealthy() - if isKubernetesClientHealthy == true { + if collector.IsHealthy() == true { w.Write([]byte("Ok")) } else { http.Error(w, "Healthcheck failed", http.StatusServiceUnavailable) @@ -27,27 +26,35 @@ func healthcheck() http.HandlerFunc { } func main() { + // Initialize logrus settings + log.SetOutput(os.Stdout) + log.SetFormatter(&log.JSONFormatter{}) + + // Parse & validate environment variable opts := options.NewOptions() err := envconfig.Process("", opts) if err != nil { - log.Fatal(err, "error parsing env vars into opts") + log.Fatal("Error parsing env vars into opts", err) } - log.Infof("Starting kube eagle v%v", opts.Version) - go func() { - sink.InitKuberneterClient(opts) - // Collect stats every 10s - for { - sink.Collect() - collectors.UpdateContainerMetrics() - collectors.UpdateNodeMetrics() - time.Sleep(10 * time.Second) - } - }() + // Set log level from environment variable + level, err := log.ParseLevel(opts.LogLevel) + if err != nil { + log.Panicf("Loglevel could not be parsed as one of the known loglevels. See logrus documentation for valid log level inputs. Given input was: '%s'", opts.LogLevel) + } + log.SetLevel(level) + + // Start kube eagle exporter + log.Infof("Starting kube eagle v%v", opts.Version) + collector, err := collector.NewKubeEagleCollector(opts) + if err != nil { + log.Fatalf("could not start kube eagle collector: '%v'", err) + } + prometheus.MustRegister(collector) http.Handle("/metrics", promhttp.Handler()) - http.Handle("/health", healthcheck()) - address := fmt.Sprintf("0.0.0.0:%s", strconv.Itoa(opts.Port)) + http.Handle("/health", healthcheck(collector)) + address := fmt.Sprintf("%v:%s", opts.Host, strconv.Itoa(opts.Port)) log.Info("Listening on ", address) log.Fatal(http.ListenAndServe(address, nil)) } diff --git a/options/options.go b/options/options.go new file mode 100644 index 0000000..4d963d2 --- /dev/null +++ b/options/options.go @@ -0,0 +1,28 @@ +package options + +// Options are configuration options that can be set by Environment Variables +type Options struct { + // General + Version string `envconfig:"VERSION" required:"true"` + + // Kubernetes + // IsInCluster - Whether to use in cluster communication (if deployed inside of Kubernetes) or to look for a kubeconfig in home directory + IsInCluster bool `envconfig:"IS_IN_CLUSTER" default:"true"` + + // Prometheus + // Host - Host to bind socket on for the prometheus exporter + // Port - Port to listen on for the prometheus exporter + // Namespace - Prefix of exposed prometheus metrics + Host string `envconfig:"TELEMETRY_HOST" default:"0.0.0.0"` + Port int `envconfig:"TELEMETRY_PORT" default:"8080"` + Namespace string `envconfig:"METRICS_NAMESPACE" default:"eagle"` + + // Logger + // LogLevel - Logger's log granularity (debug, info, warn, error, fatal, panic) + LogLevel string `envconfig:"LOG_LEVEL" default:"info"` +} + +// NewOptions provides Application Options +func NewOptions() *Options { + return &Options{} +} diff --git a/pkg/collectors/container.go b/pkg/collectors/container.go deleted file mode 100644 index a46ce7c..0000000 --- a/pkg/collectors/container.go +++ /dev/null @@ -1,114 +0,0 @@ -package collectors - -import ( - "github.com/google-cloud-tools/kube-eagle/pkg/sink" - "github.com/prometheus/client_golang/prometheus" -) - -var ( - labelsContainers = []string{"node", "container", "pod", "qos", "namespace", "phase"} - namespace = "eagle" - - // resources requested - requestedContainerCPUCoresGauge = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Namespace: namespace, - Subsystem: "pod_container_resource_requests", - Name: "cpu_cores", - Help: "Requested CPU cores in Kubernetes configuration", - }, - labelsContainers, - ) - requestedContainerRAMBytesGauge = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Namespace: namespace, - Subsystem: "pod_container_resource_requests", - Name: "memory_bytes", - Help: "Requested memory bytes in Kubernetes configuration", - }, - labelsContainers, - ) - - // resources limit - limitContainerRAMBytesGauge = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Namespace: namespace, - Subsystem: "pod_container_resource_limits", - Name: "memory_bytes", - Help: "Memory bytes limit in Kubernetes configuration", - }, - labelsContainers, - ) - limitContainerCPUCoresGauge = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Namespace: namespace, - Subsystem: "pod_container_resource_limits", - Name: "cpu_cores", - Help: "CPU cores limit in Kubernetes configuration", - }, - labelsContainers, - ) - - // resources usage - usageContainerRAMBytesGauge = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Namespace: namespace, - Subsystem: "pod_container_resource_usage", - Name: "memory_bytes", - Help: "Memory bytes usage", - }, - labelsContainers, - ) - usageContainerCPUCoresGauge = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Namespace: namespace, - Subsystem: "pod_container_resource_usage", - Name: "cpu_cores", - Help: "CPU cores usage", - }, - labelsContainers, - ) -) - -func init() { - prometheus.MustRegister(requestedContainerCPUCoresGauge) - prometheus.MustRegister(requestedContainerRAMBytesGauge) - - prometheus.MustRegister(limitContainerCPUCoresGauge) - prometheus.MustRegister(limitContainerRAMBytesGauge) - - prometheus.MustRegister(usageContainerCPUCoresGauge) - prometheus.MustRegister(usageContainerRAMBytesGauge) -} - -// UpdateContainerMetrics updates exposed container metrics in prometheus client -func UpdateContainerMetrics() { - containerMetrics := sink.BuildContainerMetrics() - - requestedContainerCPUCoresGauge.Reset() - requestedContainerRAMBytesGauge.Reset() - limitContainerCPUCoresGauge.Reset() - limitContainerRAMBytesGauge.Reset() - usageContainerCPUCoresGauge.Reset() - usageContainerRAMBytesGauge.Reset() - - for _, containerMetric := range containerMetrics { - containerLabels := prometheus.Labels{ - "node": containerMetric.Node, - "container": containerMetric.Container, - "qos": containerMetric.Qos, - "pod": containerMetric.Pod, - "namespace": containerMetric.Namespace, - "phase": string(containerMetric.Phase), - } - - requestedContainerCPUCoresGauge.With(containerLabels).Set(containerMetric.RequestedCPUCores) - requestedContainerRAMBytesGauge.With(containerLabels).Set(containerMetric.RequestedMemoryBytes) - - limitContainerCPUCoresGauge.With(containerLabels).Set(containerMetric.LimitCPUCores) - limitContainerRAMBytesGauge.With(containerLabels).Set(containerMetric.LimitMemoryBytes) - - usageContainerCPUCoresGauge.With(containerLabels).Set(containerMetric.UsageCPUCores) - usageContainerRAMBytesGauge.With(containerLabels).Set(containerMetric.UsageMemoryBytes) - } -} diff --git a/pkg/collectors/node.go b/pkg/collectors/node.go deleted file mode 100644 index da099b4..0000000 --- a/pkg/collectors/node.go +++ /dev/null @@ -1,135 +0,0 @@ -package collectors - -import ( - "github.com/google-cloud-tools/kube-eagle/pkg/sink" - "github.com/prometheus/client_golang/prometheus" -) - -var ( - labelsNodes = []string{"node"} - - // resources allocatable - allocatableNodeCPUCoresGauge = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Namespace: namespace, - Subsystem: "node_resource_allocatable", - Name: "cpu_cores", - Help: "Allocatable CPU cores", - }, - labelsNodes, - ) - allocatableNodeRAMBytesGauge = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Namespace: namespace, - Subsystem: "node_resource_allocatable", - Name: "memory_bytes", - Help: "Allocatable memory bytes", - }, - labelsNodes, - ) - - // resources requested - requestedNodeCPUCoresGauge = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Namespace: namespace, - Subsystem: "node_resource_requests", - Name: "cpu_cores", - Help: "Requested CPU cores in Kubernetes configuration", - }, - labelsNodes, - ) - requestedNodeRAMBytesGauge = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Namespace: namespace, - Subsystem: "node_resource_requests", - Name: "memory_bytes", - Help: "Requested memory bytes in Kubernetes configuration", - }, - labelsNodes, - ) - - // resources limit - limitNodeRAMBytesGauge = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Namespace: namespace, - Subsystem: "node_resource_limits", - Name: "memory_bytes", - Help: "Memory bytes limit in Kubernetes configuration", - }, - labelsNodes, - ) - limitNodeCPUCoresGauge = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Namespace: namespace, - Subsystem: "node_resource_limits", - Name: "cpu_cores", - Help: "CPU cores limit in Kubernetes configuration", - }, - labelsNodes, - ) - - // resources usage - usageNodeRAMBytesGauge = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Namespace: namespace, - Subsystem: "node_resource_usage", - Name: "memory_bytes", - Help: "Memory bytes usage", - }, - labelsNodes, - ) - usageNodeCPUCoresGauge = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Namespace: namespace, - Subsystem: "node_resource_usage", - Name: "cpu_cores", - Help: "CPU cores usage", - }, - labelsNodes, - ) -) - -func init() { - prometheus.MustRegister(allocatableNodeCPUCoresGauge) - prometheus.MustRegister(allocatableNodeRAMBytesGauge) - - prometheus.MustRegister(requestedNodeCPUCoresGauge) - prometheus.MustRegister(requestedNodeRAMBytesGauge) - - prometheus.MustRegister(limitNodeRAMBytesGauge) - prometheus.MustRegister(limitNodeCPUCoresGauge) - - prometheus.MustRegister(usageNodeRAMBytesGauge) - prometheus.MustRegister(usageNodeCPUCoresGauge) -} - -// UpdateNodeMetrics updates exposed node metrics in prometheus client -func UpdateNodeMetrics() { - nodeMetrics := sink.BuildNodeMetrics() - // reset all prometheus metrics so that old metrics don't appear anymore - allocatableNodeCPUCoresGauge.Reset() - allocatableNodeRAMBytesGauge.Reset() - requestedNodeCPUCoresGauge.Reset() - requestedNodeRAMBytesGauge.Reset() - limitNodeCPUCoresGauge.Reset() - limitNodeRAMBytesGauge.Reset() - usageNodeCPUCoresGauge.Reset() - usageNodeRAMBytesGauge.Reset() - - for _, nodeMetric := range nodeMetrics { - nodeLabels := prometheus.Labels{ - "node": nodeMetric.Node, - } - allocatableNodeCPUCoresGauge.With(nodeLabels).Set(nodeMetric.AllocatableCPUCores) - allocatableNodeRAMBytesGauge.With(nodeLabels).Set(nodeMetric.AllocatableMemoryBytes) - - requestedNodeCPUCoresGauge.With(nodeLabels).Set(nodeMetric.RequestedCPUCores) - requestedNodeRAMBytesGauge.With(nodeLabels).Set(nodeMetric.RequestedMemoryBytes) - - limitNodeCPUCoresGauge.With(nodeLabels).Set(nodeMetric.LimitCPUCores) - limitNodeRAMBytesGauge.With(nodeLabels).Set(nodeMetric.LimitMemoryBytes) - - usageNodeCPUCoresGauge.With(nodeLabels).Set(nodeMetric.UsageCPUCores) - usageNodeRAMBytesGauge.With(nodeLabels).Set(nodeMetric.UsageMemoryBytes) - } -} diff --git a/pkg/log/log.go b/pkg/log/log.go deleted file mode 100644 index cd16b46..0000000 --- a/pkg/log/log.go +++ /dev/null @@ -1,42 +0,0 @@ -package log - -import ( - log "github.com/sirupsen/logrus" - "os" -) - -func init() { - log.SetFormatter(&log.JSONFormatter{}) - log.SetOutput(os.Stdout) - log.SetLevel(log.InfoLevel) -} - -// Fatal logs a message at level Fatal on the standard logger. -func Fatal(args ...interface{}) { - log.Fatal(args...) -} - -// Debug logs a message at level Debug on the standard logger. -func Debug(args ...interface{}) { - log.Debug(args...) -} - -// Info logs a message at level Info on the standard logger. -func Info(args ...interface{}) { - log.Info(args...) -} - -// Infof logs a message at level Info on the standard logger. -func Infof(format string, args ...interface{}) { - log.Infof(format, args...) -} - -// Warn logs a message at level Warn on the standard logger. -func Warn(args ...interface{}) { - log.Warn(args...) -} - -// Warnf logs a message at level Warn on the standard logger. -func Warnf(format string, args ...interface{}) { - log.Warnf(format, args...) -} diff --git a/pkg/options/options.go b/pkg/options/options.go deleted file mode 100644 index 6bb9b84..0000000 --- a/pkg/options/options.go +++ /dev/null @@ -1,17 +0,0 @@ -package options - -// Options are configuration options that can be set by Environment Variables -// Port - Port to listen on for the prometheus exporter -// IsInCluster - Whether to use in cluster communication (if deployed inside of Kubernetes) or to look for a kubeconfig in home directory -// Namespace - Prefix of exposed prometheus metrics -type Options struct { - Port int `envconfig:"PORT" default:"8080"` - IsInCluster bool `envconfig:"IS_IN_CLUSTER" default:"true"` - Version string `envconfig:"VERSION" default:"unknown version"` - Namespace string `envconfig:"METRICS_NAME" default:"eagle"` -} - -// NewOptions provides Application Options -func NewOptions() *Options { - return &Options{} -} diff --git a/pkg/sink/container.go b/pkg/sink/container.go deleted file mode 100644 index 4e557d9..0000000 --- a/pkg/sink/container.go +++ /dev/null @@ -1,84 +0,0 @@ -package sink - -import "k8s.io/metrics/pkg/apis/metrics/v1beta1" -import corev1 "k8s.io/api/core/v1" - -// ContainerMetrics defines the labels and values we expose with prometheus -type ContainerMetrics struct { - Node string - Container string - Pod string - Qos string - Phase corev1.PodPhase - AppLabel string - Namespace string - RequestedCPUCores float64 - RequestedMemoryBytes float64 - LimitCPUCores float64 - LimitMemoryBytes float64 - UsageCPUCores float64 - UsageMemoryBytes float64 -} - -// containerUsageMap returns a map of of maps -// Where the outer map key is the podname which contains the map of -// ContainerMetrics (where container name is the key) -// It basically as a map of ContainerMetrics grouped by (podName, containerName) -func containerUsageMap() map[string]map[string]v1beta1.ContainerMetrics { - containerUsageByName := make(map[string]map[string]v1beta1.ContainerMetrics) - - for _, podMetrics := range podUsageList.Items { - podMetricsMap := make(map[string]v1beta1.ContainerMetrics) - for _, containerMetrics := range podMetrics.Containers { - podMetricsMap[containerMetrics.Name] = containerMetrics - } - containerUsageByName[podMetrics.Name] = podMetricsMap - } - - return containerUsageByName -} - -// BuildContainerMetrics returns all container relevant exported prometheus metrics -func BuildContainerMetrics() []ContainerMetrics { - var containerMetrics []ContainerMetrics - containerUsageByName := containerUsageMap() - - for _, podInfo := range podList.Items { - containers := append(podInfo.Spec.Containers, podInfo.Spec.InitContainers...) - for _, containerInfo := range containers { - qos := string(podInfo.Status.QOSClass) - - // Resources requested - requestedCPUCores := float64(containerInfo.Resources.Requests.Cpu().MilliValue()) / 1000 - requestedMemoryBytes := float64(containerInfo.Resources.Requests.Memory().MilliValue()) / 1000 - - // Resources limit - limitCPUCores := float64(containerInfo.Resources.Limits.Cpu().MilliValue()) / 1000 - limitMemoryBytes := float64(containerInfo.Resources.Limits.Memory().MilliValue()) / 1000 - - // Resources usage - containerUsageMetrics := containerUsageByName[podInfo.Name][containerInfo.Name] - usageCPUCores := float64(containerUsageMetrics.Usage.Cpu().MilliValue()) / 1000 - usageMemoryBytes := float64(containerUsageMetrics.Usage.Memory().MilliValue()) / 1000 - - nodeName := podInfo.Spec.NodeName - metric := ContainerMetrics{ - Node: nodeName, - Container: containerInfo.Name, - Pod: podInfo.Name, - Qos: qos, - Phase: podInfo.Status.Phase, - Namespace: podInfo.Namespace, - RequestedCPUCores: requestedCPUCores, - RequestedMemoryBytes: requestedMemoryBytes, - LimitCPUCores: limitCPUCores, - LimitMemoryBytes: limitMemoryBytes, - UsageCPUCores: usageCPUCores, - UsageMemoryBytes: usageMemoryBytes, - } - containerMetrics = append(containerMetrics, metric) - } - } - - return containerMetrics -} diff --git a/pkg/sink/node.go b/pkg/sink/node.go deleted file mode 100644 index fde82db..0000000 --- a/pkg/sink/node.go +++ /dev/null @@ -1,68 +0,0 @@ -package sink - -import "k8s.io/metrics/pkg/apis/metrics/v1beta1" -import corev1 "k8s.io/api/core/v1" - -// NodeMetrics defines the labels and values we expose with prometheus -type NodeMetrics struct { - Node string - AllocatableCPUCores float64 - AllocatableMemoryBytes float64 - RequestedCPUCores float64 - RequestedMemoryBytes float64 - LimitCPUCores float64 - LimitMemoryBytes float64 - UsageCPUCores float64 - UsageMemoryBytes float64 -} - -type nodeResourceDetails struct { - cpuCoresRequested float64 - memoryBytesRequested float64 - cpuCoresLimit float64 - memoryBytesLimit float64 -} - -func getResourceDetailsByNode() map[string]nodeResourceDetails { - nodeResourcesByNodeName := make(map[string]nodeResourceDetails) - - // iterate through all pod definitions to aggregate pods' resource requests and limits by node - for _, podInfo := range podList.Items { - nodeName := podInfo.Spec.NodeName - - // skip not running pods (e. g. failed/succeeded jobs, evicted pods etc.) - podPhase := podInfo.Status.Phase - if podPhase == corev1.PodFailed || podPhase == corev1.PodSucceeded { - continue - } - - for _, containerInfo := range podInfo.Spec.Containers { - requestedCPUCores := float64(containerInfo.Resources.Requests.Cpu().MilliValue()) / 1000 - requestedMemoryBytes := float64(containerInfo.Resources.Requests.Memory().MilliValue()) / 1000 - limitCPUCores := float64(containerInfo.Resources.Limits.Cpu().MilliValue()) / 1000 - limitMemoryBytes := float64(containerInfo.Resources.Limits.Memory().MilliValue()) / 1000 - - newCPURequest := nodeResourcesByNodeName[nodeName].cpuCoresRequested + requestedCPUCores - newMemoryBytesRequest := nodeResourcesByNodeName[nodeName].memoryBytesRequested + requestedMemoryBytes - newCPULimit := nodeResourcesByNodeName[nodeName].cpuCoresLimit + limitCPUCores - newMemoryBytesLimit := nodeResourcesByNodeName[nodeName].memoryBytesLimit + limitMemoryBytes - nodeResourcesByNodeName[nodeName] = nodeResourceDetails{ - cpuCoresRequested: newCPURequest, - cpuCoresLimit: newCPULimit, - memoryBytesRequested: newMemoryBytesRequest, - memoryBytesLimit: newMemoryBytesLimit, - } - } - } - - return nodeResourcesByNodeName -} - -func getNodeMetricsByNode() map[string]v1beta1.NodeMetrics { - resourceUsageMap := make(map[string]v1beta1.NodeMetrics) - for _, localNodeMetrics := range nodeUsageList.Items { - resourceUsageMap[localNodeMetrics.Name] = localNodeMetrics - } - - return resourceUsageMap -} diff --git a/pkg/sink/provider.go b/pkg/sink/provider.go deleted file mode 100644 index 5646b0b..0000000 --- a/pkg/sink/provider.go +++ /dev/null @@ -1,164 +0,0 @@ -package sink - -import ( - "github.com/google-cloud-tools/kube-eagle/pkg/log" - "os" - "path/filepath" - "time" - - "k8s.io/client-go/rest" - - "github.com/google-cloud-tools/kube-eagle/pkg/options" - "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/metrics/pkg/apis/metrics/v1beta1" - metrics "k8s.io/metrics/pkg/client/clientset/versioned" - - // Needed for GCP auth - only relevant for out of cluster communications (developers) - _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" -) - -var ( - config *rest.Config - clientset *kubernetes.Clientset - metricsClientset *metrics.Clientset - - nodeList *v1.NodeList - podList *v1.PodList - podUsageList *v1beta1.PodMetricsList - nodeUsageList *v1beta1.NodeMetricsList -) - -// Collect gathers all needed metrics and actually fires requests against the kubernetes master -func Collect() { - var err error - var errorCount int8 - - start := time.Now() - // get kubernetes' node metrics - nodeList, err = clientset.CoreV1().Nodes().List(metav1.ListOptions{}) - if err != nil { - errorCount++ - log.Warn("Couldn't get nodeList from Kubernetes master", err.Error()) - } - - // get kubernetes' pod metrics - podList, err = clientset.CoreV1().Pods(metav1.NamespaceAll).List(metav1.ListOptions{}) - if err != nil { - errorCount++ - log.Warn("Couldn't get podList from Kubernetes master", err.Error()) - } - - // get pods' resource usage metrics - podUsageList, err = metricsClientset.MetricsV1beta1().PodMetricses(metav1.NamespaceAll).List(metav1.ListOptions{}) - if err != nil { - errorCount++ - log.Warn("Couldn't get podUsageList from Kubernetes master", err.Error()) - } - - // get nodes' resource usage metrics - nodeUsageList, err = metricsClientset.MetricsV1beta1().NodeMetricses().List(metav1.ListOptions{}) - if err != nil { - errorCount++ - log.Warn("Couldn't get nodeUsageList from Kubernetes master", err.Error()) - } - elapsed := time.Since(start) - elapsedMs := int64(elapsed / time.Millisecond) - log.Infof("Collected metrics with %v errors from Kubernetes cluster within %vms", errorCount, elapsedMs) -} - -// BuildNodeMetrics returns all node relevant exposed prometheus metrics -func BuildNodeMetrics() []NodeMetrics { - var nodeMetricsPrepared []NodeMetrics - nodeResourceConfig := getResourceDetailsByNode() - nodeMetricsByName := getNodeMetricsByNode() - - for _, nodeInfo := range nodeList.Items { - // resources allocatable - allocatableCPUCores := float64(nodeInfo.Status.Allocatable.Cpu().MilliValue()) / 1000 - allocatableMemoryBytes := float64(nodeInfo.Status.Allocatable.Memory().MilliValue()) / 1000 - - // resource usage - nodeUsageMetrics := nodeMetricsByName[nodeInfo.Name] - nodeCPUUsage := float64(nodeUsageMetrics.Usage.Cpu().MilliValue()) / 1000 - nodeMemoryBytesUsage := float64(nodeUsageMetrics.Usage.Memory().MilliValue()) / 1000 - - // resources requested - resourceConfig := nodeResourceConfig[nodeInfo.Name] - - nodeMetric := NodeMetrics{ - Node: nodeInfo.Name, - AllocatableCPUCores: allocatableCPUCores, - AllocatableMemoryBytes: allocatableMemoryBytes, - RequestedCPUCores: resourceConfig.cpuCoresRequested, - RequestedMemoryBytes: resourceConfig.memoryBytesRequested, - LimitCPUCores: resourceConfig.cpuCoresLimit, - LimitMemoryBytes: resourceConfig.memoryBytesLimit, - UsageCPUCores: nodeCPUUsage, - UsageMemoryBytes: nodeMemoryBytesUsage, - } - nodeMetricsPrepared = append(nodeMetricsPrepared, nodeMetric) - } - - return nodeMetricsPrepared -} - -// InitKuberneterClient parses kubeconfig and creates kubernetes clientset -func InitKuberneterClient(opts *options.Options) { - var err error - if opts.IsInCluster { - log.Info("Creating InCluster config to communicate with Kubernetes master") - config, err = rest.InClusterConfig() - if err != nil { - panic(err.Error()) - } - } else { - log.Info("Looking for Kubernetes config to communicate with Kubernetes master") - home := homeDir() - kubeconfigPath := filepath.Join(home, ".kube", "config") - - // use the current context in kubeconfig - config, err = clientcmd.BuildConfigFromFlags("", kubeconfigPath) - if err != nil { - panic(err.Error()) - } - } - - // create the clientset - clientset, err = kubernetes.NewForConfig(config) - if err != nil { - log.Fatal("Error while creating kubernetes clientSet", err.Error()) - } - - metricsClientset, err = metrics.NewForConfig(config) - if err != nil { - log.Fatal("Error while creating metrics clientSet", err.Error()) - } -} - -// IsClientHealthy tries to get PodList. If this is successful the client is considered healthy -func IsClientHealthy() bool { - _, err := clientset.CoreV1().Pods(metav1.NamespaceDefault).List(metav1.ListOptions{}) - if err != nil { - log.Warn("Kubernetes client is not healthy. Couldn't list pods in the default namespace.", err.Error()) - return false - } - - return true -} - -// returns os homedir -func homeDir() string { - home := os.Getenv("HOME") - if home != "" { - return home - } - home = os.Getenv("USERPROFILE") // windows - if home != "" { - return home - } - log.Fatal("Couldn't find home directory to look for the kube config.") - return "" -}