| Serge Bazanski | 54e212a | 2023-06-14 13:45:11 +0200 | [diff] [blame] | 1 | package metrics |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "io" |
| Serge Bazanski | 54e212a | 2023-06-14 13:45:11 +0200 | [diff] [blame] | 6 | "net/http" |
| Tim Windelschmidt | fd49f22 | 2023-07-20 14:27:50 +0200 | [diff] [blame] | 7 | |
| Serge Bazanski | 54e212a | 2023-06-14 13:45:11 +0200 | [diff] [blame] | 8 | "source.monogon.dev/metropolis/node" |
| Tim Windelschmidt | f64f197 | 2023-07-28 00:00:50 +0000 | [diff] [blame] | 9 | "source.monogon.dev/metropolis/pkg/supervisor" |
| Serge Bazanski | 54e212a | 2023-06-14 13:45:11 +0200 | [diff] [blame] | 10 | ) |
| 11 | |
| 12 | // An Exporter is a Prometheus binary running under the Metrics service which |
| 13 | // collects some metrics and exposes them on a locally bound TCP port. |
| 14 | // |
| 15 | // The Metrics Service will forward requests from /metrics/<name> to the |
| 16 | // exporter. |
| 17 | type Exporter struct { |
| 18 | // Name of the exporter, which becomes part of the metrics URL for this exporter. |
| 19 | Name string |
| 20 | // Port on which this exporter will be running. |
| 21 | Port node.Port |
| 22 | // Executable to run to start the exporter. |
| 23 | Executable string |
| 24 | // Arguments to start the exporter. The exporter should listen at 127.0.0.1 and |
| 25 | // the port specified by Port, and serve its metrics on /metrics. |
| 26 | Arguments []string |
| 27 | } |
| 28 | |
| 29 | // DefaultExporters are the exporters which we run by default in Metropolis. |
| Tim Windelschmidt | f64f197 | 2023-07-28 00:00:50 +0000 | [diff] [blame] | 30 | var DefaultExporters = []*Exporter{ |
| Serge Bazanski | 54e212a | 2023-06-14 13:45:11 +0200 | [diff] [blame] | 31 | { |
| 32 | Name: "node", |
| 33 | Port: node.MetricsNodeListenerPort, |
| 34 | Executable: "/metrics/bin/node_exporter", |
| 35 | Arguments: []string{ |
| 36 | "--web.listen-address=127.0.0.1:" + node.MetricsNodeListenerPort.PortString(), |
| Tim Windelschmidt | 3df66eb | 2023-07-17 15:58:07 +0200 | [diff] [blame] | 37 | "--collector.buddyinfo", |
| 38 | "--collector.zoneinfo", |
| 39 | "--collector.tcpstat", |
| Tim Windelschmidt | 79d0b0d | 2023-11-18 23:12:13 +0100 | [diff] [blame] | 40 | "--collector.cpu.info", |
| Tim Windelschmidt | 9907d19 | 2023-11-18 23:11:56 +0100 | [diff] [blame] | 41 | "--collector.netclass.ignored-devices=^(veth.*)$", |
| 42 | "--collector.netdev.device-exclude=^(veth.*)$", |
| Tim Windelschmidt | 3df66eb | 2023-07-17 15:58:07 +0200 | [diff] [blame] | 43 | "--collector.filesystem.mount-points-exclude=^/(dev|proc|sys|data/kubernetes/kubelet/pods/.+|tmp/.+|ephermal/containerd/.+)($|/)", |
| Serge Bazanski | 54e212a | 2023-06-14 13:45:11 +0200 | [diff] [blame] | 44 | }, |
| 45 | }, |
| Tim Windelschmidt | c37a886 | 2023-07-19 16:33:21 +0200 | [diff] [blame] | 46 | { |
| 47 | Name: "etcd", |
| 48 | Port: node.MetricsEtcdListenerPort, |
| 49 | }, |
| Tim Windelschmidt | fd49f22 | 2023-07-20 14:27:50 +0200 | [diff] [blame] | 50 | { |
| Tim Windelschmidt | f64f197 | 2023-07-28 00:00:50 +0000 | [diff] [blame] | 51 | Name: "kubernetes-scheduler", |
| 52 | Port: node.MetricsKubeSchedulerListenerPort, |
| Tim Windelschmidt | fd49f22 | 2023-07-20 14:27:50 +0200 | [diff] [blame] | 53 | }, |
| 54 | { |
| Tim Windelschmidt | f64f197 | 2023-07-28 00:00:50 +0000 | [diff] [blame] | 55 | Name: "kubernetes-controller-manager", |
| 56 | Port: node.MetricsKubeControllerManagerListenerPort, |
| Tim Windelschmidt | fd49f22 | 2023-07-20 14:27:50 +0200 | [diff] [blame] | 57 | }, |
| Lorenz Brun | 4b42c8a | 2023-11-19 07:02:51 +0100 | [diff] [blame^] | 58 | { |
| 59 | Name: "kubernetes-apiserver", |
| 60 | Port: node.MetricsKubeAPIServerListenerPort, |
| 61 | }, |
| Tim Windelschmidt | fd49f22 | 2023-07-20 14:27:50 +0200 | [diff] [blame] | 62 | } |
| 63 | |
| Tim Windelschmidt | f64f197 | 2023-07-28 00:00:50 +0000 | [diff] [blame] | 64 | func (e *Exporter) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
| 65 | if r.Method != http.MethodGet { |
| 66 | http.Error(w, fmt.Sprintf("method %q not allowed", r.Method), http.StatusMethodNotAllowed) |
| Serge Bazanski | 54e212a | 2023-06-14 13:45:11 +0200 | [diff] [blame] | 67 | return |
| 68 | } |
| 69 | |
| Tim Windelschmidt | f64f197 | 2023-07-28 00:00:50 +0000 | [diff] [blame] | 70 | ctx := r.Context() |
| 71 | |
| 72 | // We are supplying the http.Server with a BaseContext that contains the |
| 73 | // context from our runnable which contains the logger. |
| 74 | logger := supervisor.Logger(ctx) |
| 75 | |
| 76 | url := "http://127.0.0.1:" + e.Port.PortString() + "/metrics" |
| 77 | outReq, err := http.NewRequestWithContext(ctx, "GET", url, nil) |
| 78 | if err != nil { |
| 79 | logger.Errorf("%s: forwarding to %q failed: %v", r.RemoteAddr, e.Name, err) |
| 80 | http.Error(w, "internal server error", http.StatusInternalServerError) |
| 81 | return |
| 82 | } |
| 83 | |
| 84 | res, err := http.DefaultTransport.RoundTrip(outReq) |
| 85 | if err != nil { |
| 86 | logger.Errorf("%s: forwarding to %q failed: %v", r.RemoteAddr, e.Name, err) |
| 87 | http.Error(w, "could not reach exporter", http.StatusBadGateway) |
| 88 | return |
| 89 | } |
| 90 | defer res.Body.Close() |
| 91 | |
| Serge Bazanski | 54e212a | 2023-06-14 13:45:11 +0200 | [diff] [blame] | 92 | copyHeader(w.Header(), res.Header) |
| 93 | w.WriteHeader(res.StatusCode) |
| 94 | |
| 95 | if _, err := io.Copy(w, res.Body); err != nil { |
| 96 | logger.Errorf("%s: copying response from %q failed: %v", r.RemoteAddr, e.Name, err) |
| 97 | return |
| 98 | } |
| Serge Bazanski | 54e212a | 2023-06-14 13:45:11 +0200 | [diff] [blame] | 99 | } |
| 100 | |
| 101 | func copyHeader(dst, src http.Header) { |
| 102 | for k, vv := range src { |
| 103 | for _, v := range vv { |
| 104 | dst.Add(k, v) |
| 105 | } |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | func (e *Exporter) externalPath() string { |
| 110 | return "/metrics/" + e.Name |
| 111 | } |