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

cmd/kubernetes-static: fixes and improvements #148

Merged
merged 30 commits into from
Jul 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
5ff0519
cmd/kubernetes-static/readme.md: remove trailing whitespace
invidian Jul 8, 2021
ce91b54
cmd/kubernetes-static/readme.md: fix documentation
invidian Jul 9, 2021
31fd1df
go.mod: bump Go version to 1.16
invidian Jul 9, 2021
4f00c39
cmd/kubernetes-static/data: fix location of api-server metrics
invidian Jul 9, 2021
c4c696f
cmd/kubernetes-static/main.go: print newline at the end of execution
invidian Jul 9, 2021
2e4e7b7
src/metric: automatically improve formatting
invidian Jul 9, 2021
55cfd0a
cmd/kubernetes-static: use go:embed for serving static data
invidian Jul 9, 2021
5d61643
cmd/kubernetes-static/main.go: simplify service list initialization
invidian Jul 9, 2021
46d68da
cmd/kubernetes-static/main.go: simplify mock client initialization
invidian Jul 9, 2021
560845f
cmd/kubernetes-static/main.go: simplify returned endpoint address
invidian Jul 9, 2021
442aa2c
cmd/kubernetes-static/main.go: remove unnecessary Sleep
invidian Jul 9, 2021
b4fe304
cmd/kubernetes-static/main.go: improve imports naming
invidian Jul 9, 2021
e17588a
cmd/kubernetes-static: use only single source file
invidian Jul 9, 2021
80c445c
cmd/kubernetes-static/main.go: add missing Service objects
invidian Jul 9, 2021
7640eeb
cmd/kubernetes-static/main.go: fix collecting API server metrics
invidian Jul 12, 2021
1af8c98
cmd/kubernetes-static/main.go: use constants for controlplane components
invidian Jul 12, 2021
1043873
src/definition: improve variable names a bit
invidian Jul 13, 2021
35c4922
src/metric/definition.go: mark some metrics as optional
invidian Jul 13, 2021
496c5dc
src/definition/fetch.go: improve error messages a bit
invidian Jul 13, 2021
69d3765
cmd/kubernetes-static/main.go: mock newer Kubernetes version
invidian Jul 13, 2021
ed5031d
src/definition/populate.go: improve error message formatting
invidian Jul 13, 2021
0c98004
src/ksm/group.go: improve errors a bit
invidian Jul 13, 2021
ab35179
src/ksm/group.go: small styling improvements
invidian Jul 13, 2021
5e9d085
src/kubelet: small styling improvements
invidian Jul 13, 2021
41f1181
src/kubelet/metric/metric.go: simplify loop logic
invidian Jul 13, 2021
f34fb77
src/prometheus: small styling improvements
invidian Jul 13, 2021
9329da1
src/prometheus: improve error messages
invidian Jul 13, 2021
1060f61
src/metric/definition.go: improve error messages in toUtilization()
invidian Jul 13, 2021
5aa03b2
src/definition: small styling and formatting improvements
invidian Jul 13, 2021
6885e7a
src/kubelet/metric: improvements to GetMetricsData method
invidian Jul 14, 2021
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
33 changes: 0 additions & 33 deletions cmd/kubernetes-static/basic_http_client.go

This file was deleted.

Binary file removed cmd/kubernetes-static/config_example.png
Binary file not shown.
144 changes: 119 additions & 25 deletions cmd/kubernetes-static/main.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package main

import (
"errors"
"embed"
"fmt"
"io/fs"
"net"
"net/http"
"os"
Expand All @@ -14,14 +15,15 @@ import (
"github.com/newrelic/infra-integrations-sdk/sdk"
"github.com/sirupsen/logrus"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/version"

"github.com/newrelic/nri-kubernetes/v2/src/apiserver"
"github.com/newrelic/nri-kubernetes/v2/src/client"
"github.com/newrelic/nri-kubernetes/v2/src/controlplane"
"github.com/newrelic/nri-kubernetes/v2/src/ksm"
"github.com/newrelic/nri-kubernetes/v2/src/kubelet"
metric2 "github.com/newrelic/nri-kubernetes/v2/src/kubelet/metric"
kubeletmetric "github.com/newrelic/nri-kubernetes/v2/src/kubelet/metric"
"github.com/newrelic/nri-kubernetes/v2/src/metric"
"github.com/newrelic/nri-kubernetes/v2/src/scrape"
)
Expand All @@ -37,18 +39,19 @@ type argumentList struct {

var args argumentList

func main() {
// Embed static metrics into binary.
//go:embed data
var content embed.FS
invidian marked this conversation as resolved.
Show resolved Hide resolved

func main() {
// Determines which subdirectory of cmd/kubernetes-static/ to use
// for serving the static metrics
k8sMetricsVersion := os.Getenv("K8S_METRICS_VERSION")
if k8sMetricsVersion == "" {
k8sMetricsVersion = "1_18"
}
endpoint := startStaticMetricsServer(k8sMetricsVersion)

// let the http server start...
time.Sleep(time.Millisecond * 100)
endpoint := startStaticMetricsServer(content, k8sMetricsVersion)

integration, err := sdk.NewIntegrationProtocol2(integrationName, integrationVersion, &args)
if err != nil {
Expand All @@ -70,20 +73,85 @@ func main() {

// Kubelet
kubeletClient := newBasicHTTPClient(endpoint + "/kubelet")
podsFetcher := metric2.NewPodsFetcher(logger, kubeletClient, true)
podsFetcher := kubeletmetric.NewPodsFetcher(logger, kubeletClient, true)
kubeletGrouper := kubelet.NewGrouper(
kubeletClient,
logger,
apiServerClient,
"ens5",
podsFetcher.FetchFuncWithCache(),
metric2.CadvisorFetchFunc(kubeletClient, metric.CadvisorQueries))
kubeletmetric.CadvisorFetchFunc(kubeletClient, metric.CadvisorQueries))
// KSM
ksmClient := newBasicHTTPClient(endpoint + "/ksm")
k8sClient := new(client.MockedKubernetes)
k8sClient := &client.MockedKubernetes{}

serviceList := &v1.ServiceList{
Items: []v1.Service{
{
ObjectMeta: metav1.ObjectMeta{
Name: "kube-state-metrics",
Namespace: "kube-system",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"l1": "v1",
"l2": "v2",
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "cockroachdb",
Namespace: "default",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"l1": "v1",
"l2": "v2",
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "metrics-server",
Namespace: "kube-system",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"l1": "v1",
"l2": "v2",
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "kubernetes",
Namespace: "default",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"l1": "v1",
"l2": "v2",
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "kube-dns",
Namespace: "kube-system",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"l1": "v1",
"l2": "v2",
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "cockroachdb-public",
Namespace: "default",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"l1": "v1",
Expand All @@ -93,8 +161,7 @@ func main() {
},
},
}
serviceList.Items[0].Namespace = "kube-system"
serviceList.Items[0].Name = "kube-state-metrics"

k8sClient.On("ListServices").Return(serviceList, nil)
ksmGrouper := ksm.NewGrouper(ksmClient, metric.KSMQueries, logger, k8sClient)

Expand All @@ -106,10 +173,10 @@ func main() {
// controlPlaneComponentPods maps component.Name to the pod name
// found in the file `cmd/kubernetes-static/data/kubelet/pods`
controlPlaneComponentPods := map[controlplane.ComponentName]string{
"scheduler": "kube-scheduler-minikube",
"etcd": "etcd-minikube",
"controller-manager": "kube-controller-manager-minikube",
"apiserver": "kube-apiserver-minikube",
controlplane.Scheduler: "kube-scheduler-minikube",
controlplane.Etcd: "etcd-minikube",
controlplane.ControllerManager: "kube-controller-manager-minikube",
controlplane.APIServer: "kube-apiserver-minikube",
}

for _, component := range controlplane.BuildComponentList() {
Expand All @@ -125,7 +192,7 @@ func main() {
)
}

k8sVersion := &version.Info{GitVersion: "v1.15.42"}
k8sVersion := &version.Info{GitVersion: "v1.18.19"}

for _, job := range jobs {

Expand All @@ -145,31 +212,58 @@ func main() {
if err := integration.Publish(); err != nil {
logrus.Fatalf("Error while publishing: %v", err)
}

fmt.Println()
}

func startStaticMetricsServer(k8sMetricsVersion string) string {
func startStaticMetricsServer(content embed.FS, k8sMetricsVersion string) string {
listenAddress := "127.0.0.1:0"
// This will allocate a random port
listener, err := net.Listen("tcp", "127.0.0.1:0")
listener, err := net.Listen("tcp", listenAddress)
if err != nil {
panic(err)
logrus.Fatalf("Error listening on %q: %v", listenAddress, err)
}

endpoint := fmt.Sprintf("http://localhost:%d", listener.Addr().(*net.TCPAddr).Port)
endpoint := fmt.Sprintf("http://%s", listener.Addr())
fmt.Println("Hosting Mock Metrics data on:", endpoint)

mux := http.NewServeMux()

dataDir := fmt.Sprintf("./cmd/kubernetes-static/data/%s", k8sMetricsVersion)

path, err := filepath.Abs(dataDir)
path := filepath.Join("data", k8sMetricsVersion)
k8sContent, err := fs.Sub(content, path)
if err != nil {
log.Fatal(errors.New("cannot start server"))
logrus.Fatalf("Error taking a %q subtree of embedded data: %v", path, err)
}

mux.Handle("/", http.FileServer(http.Dir(path)))
mux.Handle("/", http.FileServer(http.FS(k8sContent)))
go func() {
logrus.Fatal(http.Serve(listener, mux))
}()

return endpoint
}

func newBasicHTTPClient(url string) *basicHTTPClient {
return &basicHTTPClient{
url: url,
httpClient: http.Client{
Timeout: time.Minute * 10, // high for debugging purposes
},
}
}

type basicHTTPClient struct {
url string
httpClient http.Client
}

func (b basicHTTPClient) Do(method, path string) (*http.Response, error) {
endpoint := fmt.Sprintf("%s%s", b.url, path)
log.Info("Getting: %s", endpoint)

return b.httpClient.Get(endpoint)
}

func (b basicHTTPClient) NodeIP() string {
return "localhost"
}
19 changes: 5 additions & 14 deletions cmd/kubernetes-static/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,14 @@ Kubernetes Static is a project used to run the Kubernetes Integration locally on

## How it works

The files in the `./data` folder are saved outputs from KSM and various kubelet endpoints, all required to run the full integration.
The program wil start a temporary HTTP server and serve these files. The groupers are configured to use these endpoints instead of discovering them.
The files in the `./data` folder are saved outputs from KSM and various kubelet endpoints, which will be embedded at build-time and then served from a temporary HTTP server.
The groupers are configured to use these endpoints instead of discovering them.

## Running kubernetes-static

From within this directory, run the following command in your terminal
From within root of this repository, run the following command in your terminal
```shell script
go run main.go basic_http_client.go
go run cmd/kubernetes-static/main.go
```

This is not sending any data to an agent, but outputs the JSON to stdout.

## Configuring your IDE

It's import that the working directory is set to `cmd/kubernetes-static`, because it expects that the `data` folder is in the process's working directory.
Some IDE's/editor use a temporary folder as the working directory.

Example configuration for GoLand:
![Goland configuration example](./config_example.png)

This is not sending any data to an agent, but outputs the JSON to stdout.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/newrelic/nri-kubernetes/v2

go 1.14
go 1.16

require (
github.com/golang/protobuf v1.5.2
Expand Down
14 changes: 7 additions & 7 deletions src/definition/fetch.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package definition

import (
"errors"
"fmt"
)

// RawValue is just any value from a raw metric.
Expand Down Expand Up @@ -32,19 +32,19 @@ type TransformFunc func(FetchedValue) (FetchedValue, error)
// FromRaw fetches metrics from raw metrics. Is the most simple use case.
func FromRaw(metricKey string) FetchFunc {
return func(groupLabel, entityID string, groups RawGroups) (FetchedValue, error) {
g, ok := groups[groupLabel]
group, ok := groups[groupLabel]
if !ok {
return nil, errors.New("group not found")
return nil, fmt.Errorf("group %q not found", groupLabel)
}

e, ok := g[entityID]
entity, ok := group[entityID]
if !ok {
return nil, errors.New("entity not found")
return nil, fmt.Errorf("entity %q not found", entityID)
}

value, ok := e[metricKey]
value, ok := entity[metricKey]
if !ok {
return nil, errors.New("metric not found")
return nil, fmt.Errorf("metric %q not found", metricKey)
}

return value, nil
Expand Down
15 changes: 7 additions & 8 deletions src/definition/fetch_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package definition

import (
"testing"

"strings"
"testing"

"github.com/stretchr/testify/assert"
)
Expand Down Expand Up @@ -44,15 +43,15 @@ func TestFromRawErrorsOnNotFound(t *testing.T) {
}

v, err := FromRaw("metric_name_3")("nonExistingGroup", "entity2", raw)
assert.EqualError(t, err, "group not found")
assert.EqualError(t, err, "group \"nonExistingGroup\" not found")
assert.Nil(t, v)

v, err = FromRaw("metric_name_3")("group1", "nonExistingEntity", raw)
assert.EqualError(t, err, "entity not found")
assert.EqualError(t, err, "entity \"nonExistingEntity\" not found")
assert.Nil(t, v)

v, err = FromRaw("non_existing_metric")("group1", "entity2", raw)
assert.EqualError(t, err, "metric not found")
assert.EqualError(t, err, "metric \"non_existing_metric\" not found")
assert.Nil(t, v)
invidian marked this conversation as resolved.
Show resolved Hide resolved
}

Expand Down Expand Up @@ -99,14 +98,14 @@ func TestTransformBypassesError(t *testing.T) {
}

v, err := Transform(FromRaw("metric_name_3"), transformFunc)("nonExistingGroup", "entity2", raw)
assert.EqualError(t, err, "group not found")
assert.EqualError(t, err, "group \"nonExistingGroup\" not found")
assert.Nil(t, v)

v, err = Transform(FromRaw("metric_name_3"), transformFunc)("group1", "nonExistingEntity", raw)
assert.EqualError(t, err, "entity not found")
assert.EqualError(t, err, "entity \"nonExistingEntity\" not found")
assert.Nil(t, v)

v, err = Transform(FromRaw("non_existing_metric"), transformFunc)("group1", "entity2", raw)
assert.EqualError(t, err, "metric not found")
assert.EqualError(t, err, "metric \"non_existing_metric\" not found")
assert.Nil(t, v)
}
roobre marked this conversation as resolved.
Show resolved Hide resolved
Loading