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

kube-state-metrics: support for exposing single cluster metrics #553

Merged
merged 1 commit into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/clusterpedia-io/api v0.0.0
github.com/go-sql-driver/mysql v1.6.0
github.com/gorilla/mux v1.8.0
github.com/jackc/pgconn v1.13.0
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa
github.com/jackc/pgx/v4 v4.17.2
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
Expand Down
18 changes: 18 additions & 0 deletions pkg/kube_state_metrics/landing_page/landing_page.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
body {
font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,Liberation Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;
margin: 0;
}
header {
background-color: {{.HeaderColor}};
color: #fff;
font-size: 1rem;
padding: 1rem;
}
main {
padding: 1rem;
}
label {
display: inline-block;
width: {{.Form.Width}}em;
}
{{.ExtraCSS}}
35 changes: 35 additions & 0 deletions pkg/kube_state_metrics/landing_page/landing_page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Name}}</title>
<style>{{.CSS}}</style>
</head>
<body>
<header>
<h1>{{.Name}}</h1>
</header>
<main>
{{if .Description}}<h2>{{.Description}}</h2>{{end}}
{{if .Version}}<div>Version: {{.Version}}</div>{{end}}
<div>
<ul>
{{ range .Links }}
<li><a href="{{ .Address }}">{{.Text}}</a>{{if .Description}}: {{.Description}}{{end}}</li>
{{ end }}
</ul>
</div>
{{ if .Form.Action }}
<div>
<form action="{{ .Form.Action}}">
{{ range .Form.Inputs }}
<label>{{ .Label }}:</label>&nbsp;<input type="{{ .Type }}" name="{{ .Name }}" placeholder="{{ .Placeholder }}" value="{{ .Value }}"><br>
{{ end }}
<input type="submit" value="Submit">
</form>
</div>
{{ end }}
{{ .ExtraHTML }}
</main>
</body>
</html>
11 changes: 9 additions & 2 deletions pkg/kube_state_metrics/metrics_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/http"
"strings"

"github.com/gorilla/mux"
"github.com/prometheus/common/expfmt"
"k8s.io/klog/v2"
metricsstore "k8s.io/kube-state-metrics/v2/pkg/metrics_store"
Expand Down Expand Up @@ -34,8 +35,14 @@ func NewMetricsHandler(getter ClusterMetricsWriterListGetter, diableGZIPEncoding
// metrics to the response body.
func (m *MetricsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var writers metricsstore.MetricsWriterList
for _, wl := range m.getter.GetMetricsWriterList() {
writers = append(writers, wl...)

clusterWriters := m.getter.GetMetricsWriterList()
if cluster := mux.Vars(r)["cluster"]; cluster != "" {
writers = clusterWriters[cluster]
} else {
for _, wl := range clusterWriters {
writers = append(writers, wl...)
}
}

resHeader := w.Header()
Expand Down
127 changes: 112 additions & 15 deletions pkg/kube_state_metrics/server.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
package kubestatemetrics

import (
"bytes"
_ "embed"
"fmt"
"net/http"
"text/template"
"time"

"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/prometheus/exporter-toolkit/web"
"k8s.io/klog/v2"

"github.com/clusterpedia-io/clusterpedia/pkg/metrics"
"github.com/clusterpedia-io/clusterpedia/pkg/version"
)

type ServerConfig struct {
Expand All @@ -26,24 +35,112 @@ func RunServer(config ServerConfig, getter ClusterMetricsWriterListGetter) {
}, []string{"method"},
)

handlers := []metrics.Handler{
{
LandingName: "Metrics",
Path: "/metrics",
Handler: promhttp.InstrumentHandlerDuration(durationVec,
NewMetricsHandler(getter, config.DisableGZIPEncoding),
),
},
server := &http.Server{
Handler: buildMetricsServer(config, getter, durationVec),
ReadHeaderTimeout: 5 * time.Second,
}

flags := &web.FlagConfig{
WebListenAddresses: &[]string{config.Endpoint},
WebSystemdSocket: new(bool),
WebConfigFile: &config.TLSConfig,
}
server, flags := metrics.BuildMetricsServer(
config.Endpoint,
config.TLSConfig,
"Clusterpedia kube-state-metrics",
"Metrics for Kubernetes' state managed by Clusterpedia",
handlers,
)

klog.Info("Kube State Metrics Server is running...")
// TODO(iceber): handle error
_ = web.ListenAndServe(server, flags, metrics.Logger)
}

func buildMetricsServer(config ServerConfig, getter ClusterMetricsWriterListGetter, durationObserver prometheus.ObserverVec) *mux.Router {
handler := promhttp.InstrumentHandlerDuration(durationObserver,
NewMetricsHandler(getter, config.DisableGZIPEncoding),
)
mux := mux.NewRouter()
mux.Handle("/metrics", handler)
mux.Handle("/clusters/{cluster}/metrics", handler)

// Add index
landingConfig := web.LandingConfig{
Name: "Clusterpedia kube-state-metrics",
Description: "Metrics for Kubernetes' state managed by Clusterpedia",
Version: version.Get().String(),
Links: []web.LandingLinks{
{
Text: "Metrics",
Address: "/metrics",
},
},
}
landingPage, err := NewLandingPage(landingConfig, getter)
if err != nil {
klog.ErrorS(err, "failed to create landing page")
}
mux.Handle("/", landingPage)
return mux
}

var (
//go:embed landing_page/landing_page.html
landingPagehtmlContent string
//go:embed landing_page/landing_page.css
landingPagecssContent string
)

func NewLandingPage(c web.LandingConfig, getter ClusterMetricsWriterListGetter) (*LandingPageHandler, error) {
length := 0
for _, input := range c.Form.Inputs {
inputLength := len(input.Label)
if inputLength > length {
length = inputLength
}
}
c.Form.Width = (float64(length) + 1) / 2
if c.CSS == "" {
if c.HeaderColor == "" {
// Default to Prometheus orange.
c.HeaderColor = "#e6522c"
}
cssTemplate := template.Must(template.New("landing css").Parse(landingPagecssContent))
var buf bytes.Buffer
if err := cssTemplate.Execute(&buf, c); err != nil {
return nil, err
}
c.CSS = buf.String()
}
return &LandingPageHandler{
config: c,
template: template.Must(template.New("landing page").Parse(landingPagehtmlContent)),
getter: getter,
}, nil
}

type LandingPageHandler struct {
config web.LandingConfig
template *template.Template

getter ClusterMetricsWriterListGetter
}

func (h *LandingPageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
clusters := h.getter.GetMetricsWriterList()

config := h.config
config.Links = append(make([]web.LandingLinks, 0, len(config.Links)+len(clusters)),
h.config.Links...,
)
for cluster := range clusters {
config.Links = append(config.Links, web.LandingLinks{
Text: cluster + " Metrics",
Address: fmt.Sprintf("/clusters/%s/metrics", cluster),
})
}

var buf bytes.Buffer
if err := h.template.Execute(&buf, config); err != nil {
_, _ = w.Write([]byte(err.Error()))
return
}

_, _ = w.Write(buf.Bytes())
w.Header().Add("Content-Type", "text/html; charset=UTF-8")
}
69 changes: 23 additions & 46 deletions pkg/metrics/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,67 +19,44 @@ type Config struct {
}

func RunServer(config Config) {
handlers := []Handler{
{
LandingName: "Metrics",
Path: "metrics",
Handler: promhttp.HandlerFor(registry, promhttp.HandlerOpts{
ErrorLog: Logger,
DisableCompression: config.DisableGZIPEncoding,
}),
},
server := &http.Server{
Handler: buildMetricsServer(config),
ReadHeaderTimeout: 6 * time.Second,
}

flags := &web.FlagConfig{
WebListenAddresses: &[]string{config.Endpoint},
WebSystemdSocket: new(bool),
WebConfigFile: &config.TLSConfig,
}
server, flags := BuildMetricsServer(
config.Endpoint,
config.TLSConfig,
"clusterpedia clustersynchro manager",
"Self-metrics for clusterpedia clustersynchro manager",
handlers,
)

klog.Info("Metrics Server is running...")
_ = web.ListenAndServe(server, flags, Logger)
}

type Handler struct {
LandingName string
Path string
Handler http.Handler
}

func BuildMetricsServer(endpoint, tlsConfigFile, name, desc string, handlers []Handler) (*http.Server, *web.FlagConfig) {
var links []web.LandingLinks
func buildMetricsServer(config Config) *http.ServeMux {
mux := http.NewServeMux()
for _, handler := range handlers {
mux.Handle(handler.Path, handler.Handler)
if handler.LandingName != "" {
links = append(links, web.LandingLinks{
Address: handler.Path,
Text: handler.LandingName,
})
}
}
mux.Handle("/metrics", promhttp.HandlerFor(registry, promhttp.HandlerOpts{
ErrorLog: Logger,
DisableCompression: config.DisableGZIPEncoding,
}))

// Add index
landingConfig := web.LandingConfig{
Name: name,
Description: desc,
Name: "clusterpedia clustersynchro manager",
Description: "Self-metrics for clusterpedia clustersynchro manager",
Version: version.Get().String(),
Links: links,
Links: []web.LandingLinks{
{
Text: "Metrics",
Address: "/metrics",
},
},
}
landingPage, err := web.NewLandingPage(landingConfig)
if err != nil {
klog.ErrorS(err, "failed to create landing page")
}
mux.Handle("/", landingPage)

return &http.Server{
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
},
&web.FlagConfig{
WebListenAddresses: &[]string{endpoint},
WebSystemdSocket: new(bool),
WebConfigFile: &tlsConfigFile,
}
return mux
}
8 changes: 8 additions & 0 deletions vendor/github.com/gorilla/mux/AUTHORS

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions vendor/github.com/gorilla/mux/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading