Skip to content

Commit 38e96e9

Browse files
committed
kube-state-metrics: support for exposing single cluster metrics
Signed-off-by: Iceber Gu <[email protected]>
1 parent 2e33b49 commit 38e96e9

File tree

17 files changed

+3170
-68
lines changed

17 files changed

+3170
-68
lines changed

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/DATA-DOG/go-sqlmock v1.5.0
77
github.com/clusterpedia-io/api v0.0.0
88
github.com/go-sql-driver/mysql v1.6.0
9+
github.com/gorilla/mux v1.8.0
910
github.com/jackc/pgconn v1.13.0
1011
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa
1112
github.com/jackc/pgx/v4 v4.17.2

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
270270
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
271271
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
272272
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
273+
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
274+
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
273275
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
274276
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
275277
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
body {
2+
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;
3+
margin: 0;
4+
}
5+
header {
6+
background-color: {{.HeaderColor}};
7+
color: #fff;
8+
font-size: 1rem;
9+
padding: 1rem;
10+
}
11+
main {
12+
padding: 1rem;
13+
}
14+
label {
15+
display: inline-block;
16+
width: {{.Form.Width}}em;
17+
}
18+
{{.ExtraCSS}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<html lang="en">
2+
<head>
3+
<meta charset="UTF-8">
4+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
5+
<title>{{.Name}}</title>
6+
<style>{{.CSS}}</style>
7+
</head>
8+
<body>
9+
<header>
10+
<h1>{{.Name}}</h1>
11+
</header>
12+
<main>
13+
{{if .Description}}<h2>{{.Description}}</h2>{{end}}
14+
{{if .Version}}<div>Version: {{.Version}}</div>{{end}}
15+
<div>
16+
<ul>
17+
{{ range .Links }}
18+
<li><a href="{{ .Address }}">{{.Text}}</a>{{if .Description}}: {{.Description}}{{end}}</li>
19+
{{ end }}
20+
</ul>
21+
</div>
22+
{{ if .Form.Action }}
23+
<div>
24+
<form action="{{ .Form.Action}}">
25+
{{ range .Form.Inputs }}
26+
<label>{{ .Label }}:</label>&nbsp;<input type="{{ .Type }}" name="{{ .Name }}" placeholder="{{ .Placeholder }}" value="{{ .Value }}"><br>
27+
{{ end }}
28+
<input type="submit" value="Submit">
29+
</form>
30+
</div>
31+
{{ end }}
32+
{{ .ExtraHTML }}
33+
</main>
34+
</body>
35+
</html>

pkg/kube_state_metrics/metrics_handler.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"net/http"
77
"strings"
88

9+
"github.com/gorilla/mux"
910
"github.com/prometheus/common/expfmt"
1011
"k8s.io/klog/v2"
1112
metricsstore "k8s.io/kube-state-metrics/v2/pkg/metrics_store"
@@ -34,8 +35,14 @@ func NewMetricsHandler(getter ClusterMetricsWriterListGetter, diableGZIPEncoding
3435
// metrics to the response body.
3536
func (m *MetricsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
3637
var writers metricsstore.MetricsWriterList
37-
for _, wl := range m.getter.GetMetricsWriterList() {
38-
writers = append(writers, wl...)
38+
39+
clusterWriters := m.getter.GetMetricsWriterList()
40+
if cluster := mux.Vars(r)["cluster"]; cluster != "" {
41+
writers = clusterWriters[cluster]
42+
} else {
43+
for _, wl := range clusterWriters {
44+
writers = append(writers, wl...)
45+
}
3946
}
4047

4148
resHeader := w.Header()

pkg/kube_state_metrics/server.go

+108-14
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
package kubestatemetrics
22

33
import (
4+
"bytes"
5+
_ "embed"
6+
"fmt"
7+
"net/http"
8+
"text/template"
9+
"time"
10+
11+
"github.com/gorilla/mux"
412
"github.com/prometheus/client_golang/prometheus"
513
"github.com/prometheus/client_golang/prometheus/promauto"
614
"github.com/prometheus/client_golang/prometheus/promhttp"
715
"github.com/prometheus/exporter-toolkit/web"
816
"k8s.io/klog/v2"
917

1018
"github.com/clusterpedia-io/clusterpedia/pkg/metrics"
19+
"github.com/clusterpedia-io/clusterpedia/pkg/version"
1120
)
1221

1322
type ServerConfig struct {
@@ -26,24 +35,109 @@ func RunServer(config ServerConfig, getter ClusterMetricsWriterListGetter) {
2635
}, []string{"method"},
2736
)
2837

29-
handlers := []metrics.Handler{
30-
{
31-
LandingName: "Metrics",
32-
Path: "/metrics",
33-
Handler: promhttp.InstrumentHandlerDuration(durationVec,
34-
NewMetricsHandler(getter, config.DisableGZIPEncoding),
35-
),
38+
handler := promhttp.InstrumentHandlerDuration(durationVec,
39+
NewMetricsHandler(getter, config.DisableGZIPEncoding),
40+
)
41+
mux := mux.NewRouter()
42+
mux.Handle("/metrics", handler)
43+
mux.Handle("/clusters/{cluster}/metrics", handler)
44+
45+
// Add index
46+
landingConfig := web.LandingConfig{
47+
Name: "Clusterpedia kube-state-metrics",
48+
Description: "Metrics for Kubernetes' state managed by Clusterpedia",
49+
Version: version.Get().String(),
50+
Links: []web.LandingLinks{
51+
{
52+
Text: "Metrics",
53+
Address: "/metrics",
54+
},
3655
},
3756
}
38-
server, flags := metrics.BuildMetricsServer(
39-
config.Endpoint,
40-
config.TLSConfig,
41-
"Clusterpedia kube-state-metrics",
42-
"Metrics for Kubernetes' state managed by Clusterpedia",
43-
handlers,
44-
)
57+
landingPage, err := NewLandingPage(landingConfig, getter)
58+
if err != nil {
59+
klog.ErrorS(err, "failed to create landing page")
60+
}
61+
mux.Handle("/", landingPage)
62+
63+
server := &http.Server{
64+
Handler: mux,
65+
ReadHeaderTimeout: 5 * time.Second,
66+
}
67+
68+
flags := &web.FlagConfig{
69+
WebListenAddresses: &[]string{config.Endpoint},
70+
WebSystemdSocket: new(bool),
71+
WebConfigFile: &config.TLSConfig,
72+
}
4573

4674
klog.Info("Kube State Metrics Server is running...")
4775
// TODO(iceber): handle error
4876
_ = web.ListenAndServe(server, flags, metrics.Logger)
4977
}
78+
79+
var (
80+
//go:embed landing_page/landing_page.html
81+
landingPagehtmlContent string
82+
//go:embed landing_page/landing_page.css
83+
landingPagecssContent string
84+
)
85+
86+
func NewLandingPage(c web.LandingConfig, getter ClusterMetricsWriterListGetter) (*LandingPageHandler, error) {
87+
length := 0
88+
for _, input := range c.Form.Inputs {
89+
inputLength := len(input.Label)
90+
if inputLength > length {
91+
length = inputLength
92+
}
93+
}
94+
c.Form.Width = (float64(length) + 1) / 2
95+
if c.CSS == "" {
96+
if c.HeaderColor == "" {
97+
// Default to Prometheus orange.
98+
c.HeaderColor = "#e6522c"
99+
}
100+
cssTemplate := template.Must(template.New("landing css").Parse(landingPagecssContent))
101+
var buf bytes.Buffer
102+
if err := cssTemplate.Execute(&buf, c); err != nil {
103+
return nil, err
104+
}
105+
c.CSS = buf.String()
106+
}
107+
return &LandingPageHandler{
108+
config: c,
109+
template: template.Must(template.New("landing page").Parse(landingPagehtmlContent)),
110+
getter: getter,
111+
}, nil
112+
}
113+
114+
type LandingPageHandler struct {
115+
config web.LandingConfig
116+
template *template.Template
117+
118+
getter ClusterMetricsWriterListGetter
119+
}
120+
121+
func (h *LandingPageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
122+
clusters := h.getter.GetMetricsWriterList()
123+
124+
config := h.config
125+
config.Links = append(make([]web.LandingLinks, 0, len(config.Links)+len(clusters)),
126+
h.config.Links...,
127+
)
128+
for cluster := range clusters {
129+
config.Links = append(config.Links, web.LandingLinks{
130+
Text: cluster + " Metrics",
131+
Address: fmt.Sprintf("/clusters/%s/metrics", cluster),
132+
})
133+
}
134+
135+
var buf bytes.Buffer
136+
if err := h.template.Execute(&buf, config); err != nil {
137+
w.Write([]byte(err.Error()))
138+
return
139+
}
140+
141+
w.Write(buf.Bytes())
142+
w.Header().Add("Content-Type", "text/html; charset=UTF-8")
143+
}

pkg/metrics/server.go

+25-52
Original file line numberDiff line numberDiff line change
@@ -19,67 +19,40 @@ type Config struct {
1919
}
2020

2121
func RunServer(config Config) {
22-
handlers := []Handler{
23-
{
24-
LandingName: "Metrics",
25-
Path: "metrics",
26-
Handler: promhttp.HandlerFor(registry, promhttp.HandlerOpts{
27-
ErrorLog: Logger,
28-
DisableCompression: config.DisableGZIPEncoding,
29-
}),
30-
},
31-
}
32-
server, flags := BuildMetricsServer(
33-
config.Endpoint,
34-
config.TLSConfig,
35-
"clusterpedia clustersynchro manager",
36-
"Self-metrics for clusterpedia clustersynchro manager",
37-
handlers,
38-
)
39-
40-
klog.Info("Metrics Server is running...")
41-
_ = web.ListenAndServe(server, flags, Logger)
42-
}
43-
44-
type Handler struct {
45-
LandingName string
46-
Path string
47-
Handler http.Handler
48-
}
49-
50-
func BuildMetricsServer(endpoint, tlsConfigFile, name, desc string, handlers []Handler) (*http.Server, *web.FlagConfig) {
51-
var links []web.LandingLinks
5222
mux := http.NewServeMux()
53-
for _, handler := range handlers {
54-
mux.Handle(handler.Path, handler.Handler)
55-
if handler.LandingName != "" {
56-
links = append(links, web.LandingLinks{
57-
Address: handler.Path,
58-
Text: handler.LandingName,
59-
})
60-
}
61-
}
23+
mux.Handle("/metrics", promhttp.HandlerFor(registry, promhttp.HandlerOpts{
24+
ErrorLog: Logger,
25+
DisableCompression: config.DisableGZIPEncoding,
26+
}))
6227

63-
// Add index
6428
landingConfig := web.LandingConfig{
65-
Name: name,
66-
Description: desc,
29+
Name: "clusterpedia clustersynchro manager",
30+
Description: "Self-metrics for clusterpedia clustersynchro manager",
6731
Version: version.Get().String(),
68-
Links: links,
32+
Links: []web.LandingLinks{
33+
{
34+
Text: "Metrics",
35+
Address: "/metrics",
36+
},
37+
},
6938
}
7039
landingPage, err := web.NewLandingPage(landingConfig)
7140
if err != nil {
7241
klog.ErrorS(err, "failed to create landing page")
7342
}
7443
mux.Handle("/", landingPage)
7544

76-
return &http.Server{
77-
Handler: mux,
78-
ReadHeaderTimeout: 5 * time.Second,
79-
},
80-
&web.FlagConfig{
81-
WebListenAddresses: &[]string{endpoint},
82-
WebSystemdSocket: new(bool),
83-
WebConfigFile: &tlsConfigFile,
84-
}
45+
server := &http.Server{
46+
Handler: mux,
47+
ReadHeaderTimeout: 5 * time.Second,
48+
}
49+
50+
flags := &web.FlagConfig{
51+
WebListenAddresses: &[]string{config.Endpoint},
52+
WebSystemdSocket: new(bool),
53+
WebConfigFile: &config.TLSConfig,
54+
}
55+
56+
klog.Info("Metrics Server is running...")
57+
_ = web.ListenAndServe(server, flags, Logger)
8558
}

vendor/github.com/gorilla/mux/AUTHORS

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/github.com/gorilla/mux/LICENSE

+27
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)