sigs.k8s.io/controller-runtime@v0.18.2/pkg/metrics/server/server.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package server 18 19 import ( 20 "context" 21 "crypto/tls" 22 "fmt" 23 "net" 24 "net/http" 25 "os" 26 "path/filepath" 27 "sync" 28 "time" 29 30 "github.com/go-logr/logr" 31 "github.com/prometheus/client_golang/prometheus/promhttp" 32 "k8s.io/client-go/rest" 33 certutil "k8s.io/client-go/util/cert" 34 35 "sigs.k8s.io/controller-runtime/pkg/certwatcher" 36 "sigs.k8s.io/controller-runtime/pkg/internal/httpserver" 37 "sigs.k8s.io/controller-runtime/pkg/metrics" 38 ) 39 40 const ( 41 defaultMetricsEndpoint = "/metrics" 42 ) 43 44 // DefaultBindAddress is the default bind address for the metrics server. 45 var DefaultBindAddress = ":8080" 46 47 // Server is a server that serves metrics. 48 type Server interface { 49 // AddExtraHandler adds extra handler served on path to the http server that serves metrics. 50 AddExtraHandler(path string, handler http.Handler) error 51 52 // NeedLeaderElection implements the LeaderElectionRunnable interface, which indicates 53 // the metrics server doesn't need leader election. 54 NeedLeaderElection() bool 55 56 // Start runs the server. 57 // It will install the metrics related resources depending on the server configuration. 58 Start(ctx context.Context) error 59 } 60 61 // Options are all available options for the metrics.Server 62 type Options struct { 63 // SecureServing enables serving metrics via https. 64 // Per default metrics will be served via http. 65 SecureServing bool 66 67 // BindAddress is the bind address for the metrics server. 68 // It will be defaulted to ":8080" if unspecified. 69 // Set this to "0" to disable the metrics server. 70 BindAddress string 71 72 // ExtraHandlers contains a map of handlers (by path) which will be added to the metrics server. 73 // This might be useful to register diagnostic endpoints e.g. pprof. 74 // Note that pprof endpoints are meant to be sensitive and shouldn't be exposed publicly. 75 // If the simple path -> handler mapping offered here is not enough, a new http 76 // server/listener should be added as Runnable to the manager via the Add method. 77 ExtraHandlers map[string]http.Handler 78 79 // FilterProvider provides a filter which is a func that is added around 80 // the metrics and the extra handlers on the metrics server. 81 // This can be e.g. used to enforce authentication and authorization on the handlers 82 // endpoint by setting this field to filters.WithAuthenticationAndAuthorization. 83 FilterProvider func(c *rest.Config, httpClient *http.Client) (Filter, error) 84 85 // CertDir is the directory that contains the server key and certificate. Defaults to 86 // <temp-dir>/k8s-metrics-server/serving-certs. 87 // 88 // Note: This option is only used when TLSOpts does not set GetCertificate. 89 // Note: If certificate or key doesn't exist a self-signed certificate will be used. 90 CertDir string 91 92 // CertName is the server certificate name. Defaults to tls.crt. 93 // 94 // Note: This option is only used when TLSOpts does not set GetCertificate. 95 // Note: If certificate or key doesn't exist a self-signed certificate will be used. 96 CertName string 97 98 // KeyName is the server key name. Defaults to tls.key. 99 // 100 // Note: This option is only used when TLSOpts does not set GetCertificate. 101 // Note: If certificate or key doesn't exist a self-signed certificate will be used. 102 KeyName string 103 104 // TLSOpts is used to allow configuring the TLS config used for the server. 105 // This also allows providing a certificate via GetCertificate. 106 TLSOpts []func(*tls.Config) 107 108 // ListenConfig contains options for listening to an address on the metric server. 109 ListenConfig net.ListenConfig 110 } 111 112 // Filter is a func that is added around metrics and extra handlers on the metrics server. 113 type Filter func(log logr.Logger, handler http.Handler) (http.Handler, error) 114 115 // NewServer constructs a new metrics.Server from the provided options. 116 func NewServer(o Options, config *rest.Config, httpClient *http.Client) (Server, error) { 117 o.setDefaults() 118 119 // Skip server creation if metrics are disabled. 120 if o.BindAddress == "0" { 121 return nil, nil 122 } 123 124 // Validate that ExtraHandlers is not overwriting the default /metrics endpoint. 125 if o.ExtraHandlers != nil { 126 if _, ok := o.ExtraHandlers[defaultMetricsEndpoint]; ok { 127 return nil, fmt.Errorf("overriding builtin %s endpoint is not allowed", defaultMetricsEndpoint) 128 } 129 } 130 131 // Create the metrics filter if a FilterProvider is set. 132 var metricsFilter Filter 133 if o.FilterProvider != nil { 134 var err error 135 metricsFilter, err = o.FilterProvider(config, httpClient) 136 if err != nil { 137 return nil, fmt.Errorf("filter provider failed to create filter for the metrics server: %w", err) 138 } 139 } 140 141 return &defaultServer{ 142 metricsFilter: metricsFilter, 143 options: o, 144 }, nil 145 } 146 147 // defaultServer is the default implementation used for Server. 148 type defaultServer struct { 149 options Options 150 151 // metricsFilter is a filter which is added around 152 // the metrics and the extra handlers on the metrics server. 153 metricsFilter Filter 154 155 // mu protects access to the bindAddr field. 156 mu sync.RWMutex 157 158 // bindAddr is used to store the bindAddr after the listener has been created. 159 // This is used during testing to figure out the port that has been chosen randomly. 160 bindAddr string 161 } 162 163 // setDefaults does defaulting for the Server. 164 func (o *Options) setDefaults() { 165 if o.BindAddress == "" { 166 o.BindAddress = DefaultBindAddress 167 } 168 169 if len(o.CertDir) == 0 { 170 o.CertDir = filepath.Join(os.TempDir(), "k8s-metrics-server", "serving-certs") 171 } 172 173 if len(o.CertName) == 0 { 174 o.CertName = "tls.crt" 175 } 176 177 if len(o.KeyName) == 0 { 178 o.KeyName = "tls.key" 179 } 180 } 181 182 // NeedLeaderElection implements the LeaderElectionRunnable interface, which indicates 183 // the metrics server doesn't need leader election. 184 func (*defaultServer) NeedLeaderElection() bool { 185 return false 186 } 187 188 // AddExtraHandler adds extra handler served on path to the http server that serves metrics. 189 func (s *defaultServer) AddExtraHandler(path string, handler http.Handler) error { 190 s.mu.Lock() 191 defer s.mu.Unlock() 192 if s.options.ExtraHandlers == nil { 193 s.options.ExtraHandlers = make(map[string]http.Handler) 194 } 195 if path == defaultMetricsEndpoint { 196 return fmt.Errorf("overriding builtin %s endpoint is not allowed", defaultMetricsEndpoint) 197 } 198 if _, found := s.options.ExtraHandlers[path]; found { 199 return fmt.Errorf("can't register extra handler by duplicate path %q on metrics http server", path) 200 } 201 s.options.ExtraHandlers[path] = handler 202 return nil 203 } 204 205 // Start runs the server. 206 // It will install the metrics related resources depend on the server configuration. 207 func (s *defaultServer) Start(ctx context.Context) error { 208 log.Info("Starting metrics server") 209 210 listener, err := s.createListener(ctx, log) 211 if err != nil { 212 return fmt.Errorf("failed to start metrics server: failed to create listener: %w", err) 213 } 214 // Storing bindAddr here so we can retrieve it during testing via GetBindAddr. 215 s.mu.Lock() 216 s.bindAddr = listener.Addr().String() 217 s.mu.Unlock() 218 219 mux := http.NewServeMux() 220 221 handler := promhttp.HandlerFor(metrics.Registry, promhttp.HandlerOpts{ 222 ErrorHandling: promhttp.HTTPErrorOnError, 223 }) 224 if s.metricsFilter != nil { 225 log := log.WithValues("path", defaultMetricsEndpoint) 226 var err error 227 handler, err = s.metricsFilter(log, handler) 228 if err != nil { 229 return fmt.Errorf("failed to start metrics server: failed to add metrics filter: %w", err) 230 } 231 } 232 // TODO(JoelSpeed): Use existing Kubernetes machinery for serving metrics 233 mux.Handle(defaultMetricsEndpoint, handler) 234 235 for path, extraHandler := range s.options.ExtraHandlers { 236 if s.metricsFilter != nil { 237 log := log.WithValues("path", path) 238 var err error 239 extraHandler, err = s.metricsFilter(log, extraHandler) 240 if err != nil { 241 return fmt.Errorf("failed to start metrics server: failed to add metrics filter to extra handler for path %s: %w", path, err) 242 } 243 } 244 mux.Handle(path, extraHandler) 245 } 246 247 log.Info("Serving metrics server", "bindAddress", s.options.BindAddress, "secure", s.options.SecureServing) 248 249 srv := httpserver.New(mux) 250 251 idleConnsClosed := make(chan struct{}) 252 go func() { 253 <-ctx.Done() 254 log.Info("Shutting down metrics server with timeout of 1 minute") 255 256 ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) 257 defer cancel() 258 if err := srv.Shutdown(ctx); err != nil { 259 // Error from closing listeners, or context timeout 260 log.Error(err, "error shutting down the HTTP server") 261 } 262 close(idleConnsClosed) 263 }() 264 265 if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed { 266 return err 267 } 268 269 <-idleConnsClosed 270 return nil 271 } 272 273 func (s *defaultServer) createListener(ctx context.Context, log logr.Logger) (net.Listener, error) { 274 if !s.options.SecureServing { 275 return s.options.ListenConfig.Listen(ctx, "tcp", s.options.BindAddress) 276 } 277 278 cfg := &tls.Config{ //nolint:gosec 279 NextProtos: []string{"h2"}, 280 } 281 // fallback TLS config ready, will now mutate if passer wants full control over it 282 for _, op := range s.options.TLSOpts { 283 op(cfg) 284 } 285 286 if cfg.GetCertificate == nil { 287 certPath := filepath.Join(s.options.CertDir, s.options.CertName) 288 keyPath := filepath.Join(s.options.CertDir, s.options.KeyName) 289 290 _, certErr := os.Stat(certPath) 291 certExists := !os.IsNotExist(certErr) 292 _, keyErr := os.Stat(keyPath) 293 keyExists := !os.IsNotExist(keyErr) 294 if certExists && keyExists { 295 // Create the certificate watcher and 296 // set the config's GetCertificate on the TLSConfig 297 certWatcher, err := certwatcher.New(certPath, keyPath) 298 if err != nil { 299 return nil, err 300 } 301 cfg.GetCertificate = certWatcher.GetCertificate 302 303 go func() { 304 if err := certWatcher.Start(ctx); err != nil { 305 log.Error(err, "certificate watcher error") 306 } 307 }() 308 } 309 } 310 311 // If cfg.GetCertificate is still nil, i.e. we didn't configure a cert watcher, fallback to a self-signed certificate. 312 if cfg.GetCertificate == nil { 313 // Note: Using self-signed certificates here should be good enough. It's just important that we 314 // encrypt the communication. For example kube-controller-manager also uses a self-signed certificate 315 // for the metrics endpoint per default. 316 cert, key, err := certutil.GenerateSelfSignedCertKeyWithFixtures("localhost", []net.IP{{127, 0, 0, 1}}, nil, "") 317 if err != nil { 318 return nil, fmt.Errorf("failed to generate self-signed certificate for metrics server: %w", err) 319 } 320 321 keyPair, err := tls.X509KeyPair(cert, key) 322 if err != nil { 323 return nil, fmt.Errorf("failed to create self-signed key pair for metrics server: %w", err) 324 } 325 cfg.Certificates = []tls.Certificate{keyPair} 326 } 327 328 l, err := s.options.ListenConfig.Listen(ctx, "tcp", s.options.BindAddress) 329 if err != nil { 330 return nil, err 331 } 332 333 return tls.NewListener(l, cfg), nil 334 } 335 336 func (s *defaultServer) GetBindAddr() string { 337 s.mu.RLock() 338 defer s.mu.RUnlock() 339 return s.bindAddr 340 }