Skip to content

Commit

Permalink
feat: add Prometheus/Grafana to Development Tool (#6044)
Browse files Browse the repository at this point in the history
  • Loading branch information
rangoo94 authored Nov 20, 2024
1 parent eb85115 commit 6b5a087
Show file tree
Hide file tree
Showing 3 changed files with 265 additions and 0 deletions.
37 changes: 37 additions & 0 deletions cmd/tcl/kubectl-testkube/devbox/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ func NewDevBoxCommand() *cobra.Command {
binaryStoragePod := namespace.Pod("devbox-binary")
mongoPod := namespace.Pod("devbox-mongodb")
minioPod := namespace.Pod("devbox-minio")
prometheusPod := namespace.Pod("devbox-prometheus")
grafanaPod := namespace.Pod("devbox-grafana")

// Initialize binaries
interceptorBin := devutils.NewBinary(InterceptorMainPath, cluster.OperatingSystem(), cluster.Architecture())
Expand All @@ -143,6 +145,8 @@ func NewDevBoxCommand() *cobra.Command {
binaryStorage := devutils.NewBinaryStorage(binaryStoragePod, binaryStorageBin)
mongo := devutils.NewMongo(mongoPod)
minio := devutils.NewMinio(minioPod)
prometheus := devutils.NewPrometheus(prometheusPod)
grafana := devutils.NewGrafana(grafanaPod)
var env *client.Environment

// Cleanup
Expand Down Expand Up @@ -236,6 +240,34 @@ func NewDevBoxCommand() *cobra.Command {
return nil
})

// Deploying Prometheus
g.Go(func() error {
fmt.Println("[Prometheus] Deploying...")
if err = prometheus.Create(ctx); err != nil {
fail(errors.Wrap(err, "failed to create prometheus instance"))
}
fmt.Println("[Prometheus] Waiting for readiness...")
if err = prometheus.WaitForReady(ctx); err != nil {
fail(errors.Wrap(err, "failed to create prometheus instance"))
}
fmt.Println("[Prometheus] Ready")
return nil
})

// Deploying Grafana
g.Go(func() error {
fmt.Println("[Grafana] Deploying...")
if err = grafana.Create(ctx); err != nil {
fail(errors.Wrap(err, "failed to create grafana instance"))
}
fmt.Println("[Grafana] Waiting for readiness...")
if err = grafana.WaitForReady(ctx); err != nil {
fail(errors.Wrap(err, "failed to create grafana instance"))
}
fmt.Printf("[Grafana] Ready at %s\n", grafana.LocalAddress())
return nil
})

// Deploying binary storage
g.Go(func() error {
fmt.Println("[Binary Storage] Building...")
Expand Down Expand Up @@ -615,6 +647,11 @@ func NewDevBoxCommand() *cobra.Command {

color.Green.Println("Development box is ready. Took", time.Since(startTs).Truncate(time.Millisecond))
fmt.Println("Namespace:", namespace.Name())
if termlink.SupportsHyperlinks() {
fmt.Println("Grafana:", termlink.Link(grafana.LocalAddress(), grafana.LocalAddress()))
} else {
fmt.Println("Grafana:", grafana.LocalAddress())
}
if !oss {
if termlink.SupportsHyperlinks() {
fmt.Println("Dashboard:", termlink.Link(cloud.DashboardUrl(env.Slug, "dashboard/test-workflows"), cloud.DashboardUrl(env.Slug, "dashboard/test-workflows")))
Expand Down
130 changes: 130 additions & 0 deletions cmd/tcl/kubectl-testkube/devbox/devutils/grafana.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Copyright 2024 Testkube.
//
// Licensed as a Testkube Pro file under the Testkube Community
// License (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// https://github.com/kubeshop/testkube/blob/main/licenses/TCL.txt

package devutils

import (
"context"
"fmt"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"

"github.com/kubeshop/testkube/internal/common"
)

type Grafana struct {
pod *PodObject
localPort int
}

func NewGrafana(pod *PodObject) *Grafana {
return &Grafana{
pod: pod,
}
}

const (
grafanaProvisioningPrometheusDataSource = `
apiVersion: 1
deleteDatasources:
- name: Prometheus
orgId: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
orgId: 1
url: http://devbox-prometheus:9090`
)

func (r *Grafana) Create(ctx context.Context) error {
_, err := r.pod.ClientSet().CoreV1().ConfigMaps(r.pod.Namespace()).Create(ctx, &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Name: "devbox-grafana-provisioning-datasources"},
Data: map[string]string{
"prometheus.yml": grafanaProvisioningPrometheusDataSource,
},
}, metav1.CreateOptions{})
if err != nil {
return err
}

err = r.pod.Create(ctx, &corev1.Pod{
Spec: corev1.PodSpec{
TerminationGracePeriodSeconds: common.Ptr(int64(1)),
Volumes: []corev1.Volume{
{Name: "data", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}},
{Name: "provisioning-datasources", VolumeSource: corev1.VolumeSource{ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{Name: "devbox-grafana-provisioning-datasources"},
}}},
},
Containers: []corev1.Container{
{
Name: "prometheus",
Image: "grafana/grafana-oss:11.3.1",
ImagePullPolicy: corev1.PullIfNotPresent,
Env: []corev1.EnvVar{
{Name: "GF_USERS_ALLOW_SIGN_UP", Value: "false"},
{Name: "GF_AUTH_ANONYMOUS_ENABLED", Value: "true"},
{Name: "GF_AUTH_ANONYMOUS_ORG_ROLE", Value: "Admin"},
{Name: "GF_AUTH_DISABLE_LOGIN_FORM", Value: "true"},
},
VolumeMounts: []corev1.VolumeMount{
{Name: "data", MountPath: "/var/lib/grafana"},
{Name: "provisioning-datasources", MountPath: "/etc/grafana/provisioning/datasources"},
},
ReadinessProbe: &corev1.Probe{
ProbeHandler: corev1.ProbeHandler{
TCPSocket: &corev1.TCPSocketAction{
Port: intstr.FromInt32(3000),
},
},
PeriodSeconds: 1,
},
},
},
},
})
if err != nil {
return err
}

err = r.pod.CreateService(ctx, corev1.ServicePort{
Name: "web",
Protocol: "TCP",
Port: 3000,
TargetPort: intstr.FromInt32(3000),
})
if err != nil {
return err
}

err = r.pod.WaitForReady(ctx)
if err != nil {
return err
}

r.localPort = GetFreePort()
err = r.pod.Forward(ctx, 3000, r.localPort, true)
if err != nil {
return err
}

return nil
}

func (r *Grafana) LocalAddress() string {
return fmt.Sprintf("http://localhost:%d", r.localPort)
}

func (r *Grafana) WaitForReady(ctx context.Context) error {
return r.pod.WaitForReady(ctx)
}
98 changes: 98 additions & 0 deletions cmd/tcl/kubectl-testkube/devbox/devutils/prometheus.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright 2024 Testkube.
//
// Licensed as a Testkube Pro file under the Testkube Community
// License (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// https://github.com/kubeshop/testkube/blob/main/licenses/TCL.txt

package devutils

import (
"context"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"

"github.com/kubeshop/testkube/internal/common"
)

type Prometheus struct {
pod *PodObject
}

func NewPrometheus(pod *PodObject) *Prometheus {
return &Prometheus{
pod: pod,
}
}

const (
prometheusConfig = `
global:
scrape_interval: 1s
scrape_timeout: 500ms
evaluation_interval: 1s
scrape_configs:
- job_name: 'Agent'
honor_labels: true
metrics_path: /metrics
static_configs:
- targets: [ 'devbox-agent:8088' ]`
)

func (r *Prometheus) Create(ctx context.Context) error {
_, err := r.pod.ClientSet().CoreV1().ConfigMaps(r.pod.Namespace()).Create(ctx, &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{Name: "devbox-prometheus-config"},
Data: map[string]string{"prometheus.yml": prometheusConfig},
}, metav1.CreateOptions{})
if err != nil {
return err
}

err = r.pod.Create(ctx, &corev1.Pod{
Spec: corev1.PodSpec{
TerminationGracePeriodSeconds: common.Ptr(int64(1)),
Volumes: []corev1.Volume{
{Name: "data", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}},
{Name: "config", VolumeSource: corev1.VolumeSource{ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{Name: "devbox-prometheus-config"},
}}},
},
Containers: []corev1.Container{
{
Name: "prometheus",
Image: "prom/prometheus:v2.53.3",
ImagePullPolicy: corev1.PullIfNotPresent,
VolumeMounts: []corev1.VolumeMount{
{Name: "data", MountPath: "/prometheus"},
{Name: "config", MountPath: "/etc/prometheus/prometheus.yml", SubPath: "prometheus.yml"},
},
ReadinessProbe: &corev1.Probe{
ProbeHandler: corev1.ProbeHandler{
TCPSocket: &corev1.TCPSocketAction{
Port: intstr.FromInt32(9090),
},
},
PeriodSeconds: 1,
},
},
},
},
})
if err != nil {
return err
}
return r.pod.CreateService(ctx, corev1.ServicePort{
Name: "api",
Protocol: "TCP",
Port: 9090,
TargetPort: intstr.FromInt32(9090),
})
}

func (r *Prometheus) WaitForReady(ctx context.Context) error {
return r.pod.WaitForReady(ctx)
}

0 comments on commit 6b5a087

Please # to comment.